Giter VIP home page Giter VIP logo

its-fine's Introduction

its-fine

Size Version Downloads Twitter Discord

It's gonna be alright

A collection of escape hatches for React.

As such, you can go beyond React's component abstraction; components are self-aware and can tap into the React Fiber tree. This enables powerful abstractions that can modify or extend React behavior without explicitly taking reconciliation into your own hands.

Table of Contents

Components

FiberProvider

A react-internal Fiber provider. This component binds React children to the React Fiber tree. Call its-fine hooks within this.

Note: pmndrs renderers like react-three-fiber implement this internally to make use of useContextBridge, so you would only need this when using hooks inside of react-dom or react-native.

import * as ReactDOM from 'react-dom/client'
import { FiberProvider, useFiber } from 'its-fine'

function App() {
  const fiber = useFiber()
}

ReactDOM.createRoot(document.getElementById('root')!).render(
  <FiberProvider>
    <App />
  </FiberProvider>,
)

Hooks

Useful React hook abstractions for manipulating and querying from a component. These must be called within a FiberProvider component.

useFiber

Returns the current react-internal Fiber. This is an implementation detail of react-reconciler.

import * as React from 'react'
import { type Fiber, useFiber } from 'its-fine'

function Component() {
  // Returns the current component's react-internal Fiber
  const fiber: Fiber<null> | undefined = useFiber()

  // function Component() {}
  if (fiber) console.log(fiber.type)
}

useContainer

Returns the current react-reconciler container info passed to Reconciler.createContainer.

In react-dom, a container will point to the root DOM element; in react-three-fiber, it will point to the root Zustand store.

import * as React from 'react'
import { useContainer } from 'its-fine'

function Component() {
  // Returns the current renderer's root container
  const container: HTMLDivElement | undefined = useContainer<HTMLDivElement>()

  // <div> (e.g. react-dom)
  if (container) console.log(container)
}

useNearestChild

Returns the nearest react-reconciler child instance or the node created from Reconciler.createInstance.

In react-dom, this would be a DOM element; in react-three-fiber this would be an Instance descriptor.

import * as React from 'react'
import { useNearestChild } from 'its-fine'

function Component() {
  // Returns a React Ref which points to the nearest child <div /> element.
  // Omit the element type to match the nearest element of any kind
  const childRef: React.MutableRefObject<HTMLDivElement | undefined> = useNearestChild<HTMLDivElement>('div')

  // Access child Ref on mount
  React.useEffect(() => {
    // <div> (e.g. react-dom)
    const child = childRef.current
    if (child) console.log(child)
  }, [])

  // A child element, can live deep down another component
  return <div />
}

useNearestParent

Returns the nearest react-reconciler parent instance or the node created from Reconciler.createInstance.

In react-dom, this would be a DOM element; in react-three-fiber this would be an instance descriptor.

import * as React from 'react'
import { useNearestParent } from 'its-fine'

function Component() {
  // Returns a React Ref which points to the nearest parent <div /> element.
  // Omit the element type to match the nearest element of any kind
  const parentRef: React.MutableRefObject<HTMLDivElement | undefined> = useNearestParent<HTMLDivElement>('div')

  // Access parent Ref on mount
  React.useEffect(() => {
    // <div> (e.g. react-dom)
    const parent = parentRef.current
    if (parent) console.log(parent)
  }, [])
}

// A parent element wrapping Component, can live deep up another component
;<div>
  <Component />
</div>

useContextMap

Returns a map of all contexts and their values.

import * as React from 'react'
import { useContextMap } from 'its-fine'

const SomeContext = React.createContext<string>(null!)

function Component() {
  const contextMap = useContextMap()
  return contextMap.get(SomeContext)
}

useContextBridge

React Context currently cannot be shared across React renderers but explicitly forwarded between providers (see react#17275). This hook returns a ContextBridge of live context providers to pierce Context across renderers.

Pass ContextBridge as a component to a secondary renderer to enable context-sharing within its children.

import * as React from 'react'
// react-nil is a secondary renderer that is usually used for testing.
// This also includes Fabric, react-three-fiber, etc
import * as ReactNil from 'react-nil'
// react-dom is a primary renderer that works on top of a secondary renderer.
// This also includes react-native, react-pixi, etc.
import * as ReactDOM from 'react-dom/client'
import { type ContextBridge, useContextBridge, FiberProvider } from 'its-fine'

function Canvas(props: { children: React.ReactNode }) {
  // Returns a bridged context provider that forwards context
  const Bridge: ContextBridge = useContextBridge()
  // Renders children with bridged context into a secondary renderer
  ReactNil.render(<Bridge>{props.children}</Bridge>)
}

// A React Context whose provider lives in react-dom
const DOMContext = React.createContext<string>(null!)

// A component that reads from DOMContext
function Component() {
  // "Hello from react-dom"
  console.log(React.useContext(DOMContext))
}

// Renders into a primary renderer like react-dom or react-native,
// DOMContext wraps Canvas and is bridged into Component
ReactDOM.createRoot(document.getElementById('root')!).render(
  <FiberProvider>
    <DOMContext.Provider value="Hello from react-dom">
      <Canvas>
        <Component />
      </Canvas>
    </DOMContext.Provider>
  </FiberProvider>,
)

Utils

Additional exported utility functions for raw handling of Fibers.

traverseFiber

Traverses up or down a Fiber, return true to stop and select a node.

import { type Fiber, traverseFiber } from 'its-fine'

// Traverses through the Fiber tree, returns the current node when `true` is passed via selector
const parentDiv: Fiber<HTMLDivElement> | undefined = traverseFiber<HTMLDivElement>(
  // Input Fiber to traverse
  fiber as Fiber,
  // Whether to ascend and walk up the tree. Will walk down if `false`
  true,
  // A Fiber node selector, returns the first match when `true` is passed
  (node: Fiber<HTMLDivElement | null>) => node.type === 'div',
)

its-fine's People

Contributors

adventful avatar codyjasonbennett avatar dcousens avatar dennemark avatar drcmda avatar hansottowirtz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

its-fine's Issues

Problem with using react three fiber in grafana

Hi, I'm using grafana.
And I'm trying to create grafana plugin, witch is basically react component with custom props.

When adding <Canvas/> into JSX I get its-fine: useFiber must be called within a <FiberProvider />!

import { PanelProps } from '@grafana/data';
import { Canvas } from '@react-three/fiber';
import React, { useRef, useState } from 'react';


export const SimplePanel: React.FC<PanelProps> = ({ options, data, width, height }) => {

  return (
    <Canvas>
      <h1>epic</h1>
    </Canvas>
  )
};

I really don't know what's wrong, nether can I find anything useful online, and I'm not sure if this issue belongs in this ripo or not I'm just looking for a step in right direction, cuz I've ran out of options.
I think that maybe webpack config is at fault.

So here is grafana default webpack config:

/*
 * ⚠️⚠️⚠️ THIS FILE WAS SCAFFOLDED BY `@grafana/create-plugin`. DO NOT EDIT THIS FILE DIRECTLY. ⚠️⚠️⚠️
 *
 * In order to extend the configuration follow the steps in .config/README.md
 */

import CopyWebpackPlugin from 'copy-webpack-plugin';
import ESLintPlugin from 'eslint-webpack-plugin';
import ForkTsCheckerWebpackPlugin from 'fork-ts-checker-webpack-plugin';
import LiveReloadPlugin from 'webpack-livereload-plugin';
import path from 'path';
import ReplaceInFileWebpackPlugin from 'replace-in-file-webpack-plugin';
import { Configuration } from 'webpack';

import { getPackageJson, getPluginId, hasReadme, getEntries } from './utils';
import { SOURCE_DIR, DIST_DIR } from './constants';

const config = async (env): Promise<Configuration> => ({
  cache: {
    type: 'filesystem',
    buildDependencies: {
      config: [__filename],
    },
  },

  context: path.join(process.cwd(), SOURCE_DIR),

  devtool: env.production ? 'source-map' : 'eval-source-map',

  entry: await getEntries(),

  externals: [
    'lodash',
    'jquery',
    'moment',
    'slate',
    'emotion',
    '@emotion/react',
    '@emotion/css',
    'prismjs',
    'slate-plain-serializer',
    '@grafana/slate-react',
    'react',
    'react-dom',
    'react-redux',
    'redux',
    'rxjs',
    'react-router',
    'react-router-dom',
    'd3',
    'angular',
    '@grafana/ui',
    '@grafana/runtime',
    '@grafana/data',

    // Mark legacy SDK imports as external if their name starts with the "grafana/" prefix
    ({ request }, callback) => {
      const prefix = 'grafana/';
      const hasPrefix = (request) => request.indexOf(prefix) === 0;
      const stripPrefix = (request) => request.substr(prefix.length);

      if (hasPrefix(request)) {
        return callback(undefined, stripPrefix(request));
      }

      callback();
    },
  ],

  mode: env.production ? 'production' : 'development',

  module: {
    rules: [
      {
        exclude: /(node_modules)/,
        test: /\.[tj]sx?$/,
        use: {
          loader: 'swc-loader',
          options: {
            jsc: {
              baseUrl: './src',
              target: 'es2015',
              loose: false,
              parser: {
                syntax: 'typescript',
                tsx: true,
                decorators: false,
                dynamicImport: true,
              },
            },
          },
        },
      },
      {
        test: /\.css$/,
        use: ["style-loader", "css-loader"]
      },
      {
        exclude: /(node_modules)/,
        test: /\.s[ac]ss$/,
        use: ['style-loader', 'css-loader', 'sass-loader'],
      },
      {
        test: /\.(png|jpe?g|gif|svg)$/,
        type: 'asset/resource',
        generator: {
          // Keep publicPath relative for host.com/grafana/ deployments
          publicPath: `public/plugins/${getPluginId()}/img/`,
          outputPath: 'img/',
          filename: Boolean(env.production) ? '[hash][ext]' : '[name][ext]',
        },
      },
      {
        test: /\.(woff|woff2|eot|ttf|otf)(\?v=\d+\.\d+\.\d+)?$/,
        type: 'asset/resource',
        generator: {
          // Keep publicPath relative for host.com/grafana/ deployments
          publicPath: `public/plugins/${getPluginId()}/fonts`,
          outputPath: 'fonts/',
          filename: Boolean(env.production) ? '[hash][ext]' : '[name][ext]',
        },
      },
    ],
  },

  output: {
    clean: {
      keep: /gpx_.*/,
    },
    filename: '[name].js',
    library: {
      type: 'amd',
    },
    path: path.resolve(process.cwd(), DIST_DIR),
    publicPath: '/',
  },

  plugins: [
    new CopyWebpackPlugin({
      patterns: [
        // If src/README.md exists use it; otherwise the root README
        // To `compiler.options.output`
        { from: hasReadme() ? 'README.md' : '../README.md', to: '.', force: true },
        { from: 'plugin.json', to: '.' },
        { from: '../LICENSE', to: '.' },
        { from: '../CHANGELOG.md', to: '.', force: true },
        { from: '**/*.json', to: '.' }, // TODO<Add an error for checking the basic structure of the repo>
        { from: '**/*.svg', to: '.', noErrorOnMissing: true }, // Optional
        { from: '**/*.png', to: '.', noErrorOnMissing: true }, // Optional
        { from: '**/*.html', to: '.', noErrorOnMissing: true }, // Optional
        { from: 'img/**/*', to: '.', noErrorOnMissing: true }, // Optional
        { from: 'libs/**/*', to: '.', noErrorOnMissing: true }, // Optional
        { from: 'static/**/*', to: '.', noErrorOnMissing: true }, // Optional
      ],
    }),
    // Replace certain template-variables in the README and plugin.json
    new ReplaceInFileWebpackPlugin([
      {
        dir: DIST_DIR,
        files: ['plugin.json', 'README.md'],
        rules: [
          {
            search: /\%VERSION\%/g,
            replace: getPackageJson().version,
          },
          {
            search: /\%TODAY\%/g,
            replace: new Date().toISOString().substring(0, 10),
          },
          {
            search: /\%PLUGIN_ID\%/g,
            replace: getPluginId(),
          },
        ],
      },
    ]),
    new ForkTsCheckerWebpackPlugin({
      async: Boolean(env.development),
      issue: {
        include: [{ file: '**/*.{ts,tsx}' }],
      },
      typescript: { configFile: path.join(process.cwd(), 'tsconfig.json') },
    }),
    new ESLintPlugin({
      extensions: ['.ts', '.tsx'],
      lintDirtyModulesOnly: Boolean(env.development), // don't lint on start, only lint changed files
    }),
    ...(env.development ? [new LiveReloadPlugin()] : []),
  ],

  resolve: {
    extensions: ['.js', '.jsx', '.ts', '.tsx'],
    // handle resolving "rootDir" paths
    modules: [path.resolve(process.cwd(), 'src'), 'node_modules'],
    unsafeCache: true,
  },
});

export default config;

Multiple Contexts

Hi,
I tried to use its-fine together with tunnel-rats. Each tunnel having a different context provider.
However, its-fine spreads only one context provider to all tunnels, namely the last. The reason seems to be here:

const contexts: React.Context<any>[] = []

If we move these two variables within the useContextBridge, the issue is resolved.

You can have a look into this codesandbox: https://codesandbox.io/s/compassionate-varahamihira-yxqqu7?file=/src/App.tsx
Check the console. CompDOM and CompNIL are the same except that NIL consumes context via its-fine. To try the correct version, just comment out import ... 'its-fine' and uncomment import ... './itsFine'
Check the video on how to do it.
It also shows where contexts and variables are placed within useContextBridge.

useContextBridge.webm

Error following React Native setup guide

Hi,

When following the setup guide for React Native here: https://docs.pmnd.rs/react-three-fiber/getting-started/installation

The following error occurs, running inside Expo Go on iOS:

While trying to resolve module its-fine from file /Users/admin/React Native/three/node_modules/@react-three/fiber/native/dist/react-three-fiber-native.cjs.prod.js, the package /Users/admin/React Native/three/node_modules/its-fine/package.json was successfully found. However, this package itself specifies a main module field that could not be resolved (/Users/admin/React Native/three/node_modules/its-fine/dist/index.cjs. Indeed, none of these files exist:

  • /Users/admin/React Native/three/node_modules/its-fine/dist/index.cjs(.native|.ios.ts|.native.ts|.ts|.ios.tsx|.native.tsx|.tsx|.ios.js|.native.js|.js|.ios.jsx|.native.jsx|.jsx|.ios.json|.native.json|.json)
  • /Users/admin/React Native/three/node_modules/its-fine/dist/index.cjs/index(.native|.ios.ts|.native.ts|.ts|.ios.tsx|.native.tsx|.tsx|.ios.js|.native.js|.js|.ios.jsx|.native.jsx|.jsx|.ios.json|.native.json|.json)

Dependencies

"dependencies": {
"@react-three/fiber": "^8.8.6",
"expo": "~46.0.13",
"expo-gl": "~11.4.0",
"expo-status-bar": "~1.4.0",
"react": "18.0.0",
"react-native": "0.69.6",
"three": "^0.144.0"
},
"devDependencies": {
"@babel/core": "^7.12.9"
},

Component import problem

Last day i updated my npm packages and got this error:

./node_modules/its-fine/dist/index.mjs
Can't import the named export 'Component' from non EcmaScript module (only default export is available)

'useId' (imported as 'React') was not found in 'react

Hi ,

I am using react and react-dom 17 version in my app and I can't migrate to it version 18 because many npm package that I have installed are not fully compatible with react 18

Hence here I am getting this error here

ERROR in ./node_modules/its-fine/dist/index.js 67:13-24

export 'useId' (imported as 'React') was not found in 'react' (possible exports: Children, Component, Fragment, Profiler, PureComponent, StrictMode, Suspense, __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED, cloneElement, createContext, createElement, createFactory, createRef, forwardRef, isValidElement, lazy, memo, useCallback, useContext, useDebugValue, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, version)

So How to resolve this , I have no idea that this package is a dependency package of some other package or what but I have not installed this package in my app

Fiber is undefined when using concurrent features

I'm still investigating, but it seems like root.memoizedState is null and also ReactCurrentOwner.current is null when replayFunctionComponent is called. This causes useFiber to return null, which also breaks the ContextBridge.

I think this is because the root FiberProvider is rendered at another time than the element itself.

This is happening in Next 13.2, but only sometimes (I think if the page is lagging React switches to use the concurrent features).

I will edit the issue when I know more.

Screenshot 2023-04-02 at 17 29 57

ContextBridge not updating without calling root.render()

Without calling root.render(), the ContextBridge doesn't update.

In the example below, the context in the Canvas does not update, the number stays 42.

Do you know any workaround for this?

const NumberContext = createContext<number | undefined>(undefined);

function Number() {
  const number = useContext(NumberContext);

  return <p>number: {number ?? "none"}</p>;
}

function Canvas({ Component }: { Component: any }) {
  const [el, setEl] = useState<HTMLDivElement>();
  const [root, setRoot] = useState<Root>();
  const ContextBridge = useContextBridge();

  useEffect(() => {
    if (!el) return;
    const root = createRoot(el);
    setRoot(root);
    return () => root.unmount();
  }, [el]);

  useEffect(() => {
    if (!root) return;
    root.render(<ContextBridge><Component/></ContextBridge>);
  }, [root, Component]);

  return <div ref={(el) => el && setEl(el)}></div>;
}

function App() {
  const [number, setNumber] = useState(42);
  return (
    <NumberContext.Provider value={number}>
      <button onClick={() => setNumber((n) => n + 1)}>increment</button>
      <Number />
      <Canvas Component={Number}></Canvas>
    </NumberContext.Provider>
  );
}

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.