Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RFC: useIsolation #257

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
Open

RFC: useIsolation #257

wants to merge 20 commits into from

Conversation

Ayc0
Copy link

@Ayc0 Ayc0 commented Dec 13, 2023

This RFC related to:

This is about performance & memoization

>>> View rendered text <<<

@EECOLOR
Copy link

EECOLOR commented Dec 15, 2023

If I understand correctly you want the equivalent of the following:

Usage:

import React from 'react'

export function App() {
  console.log('render App')
  return (
    <IsolationProvider>
        <Test />
    </IsolationProvider>
  );
}

function Test() {
  console.log('render test')

  const x = useIsolation(() => {
    const [x, setX] = React.useState(0)
    React.useEffect(
      () => {
        let interval = setInterval(() => setX(x => x + 1), 1000)
        return () => { clearInterval(interval) }
      },
      []
    )
    return React.useMemo(() => x - (x % 2), [x])
  })

  return <p>{x}</p>
}

Non-optimized user space implementation:

const isolationContext = React.createContext(null)

function useIsolation(unsafeHook) {
  const hook = useEvent(unsafeHook)

  const [result, setResult] = React.useState(null)

  const registerHook = React.useContext(isolationContext)

  React.useEffect(
    () => registerHook({ hook, callback: (...args) => setTimeout(() => setResult(...args), 0) }),
    []
  )

  return result
}

function IsolationProvider({ children }) {
  console.log('render isolation provider')

  const [hookInfo, setHookInfo] = React.useState([])

  const registerHook = React.useCallback(
    (hookInfoToBeIsolated) => {
      setHookInfo(existing => existing.concat(hookInfoToBeIsolated))
      return function cleanup() {
        setHookInfo(existing => existing.filter(info => info !== hookInfoToBeIsolated))
      }
    },
    []
  )

  return (
    <isolationContext.Provider value={registerHook}>
      {children}
      {hookInfo.map((info, i) => 
        // key should be handled differently
        <Isolation key={i} {...{ info }} />
      )}
    </isolationContext.Provider>
  )
}

function Isolation({ info }) {
  const { callback, hook } = info
  const result = hook()

  console.log('hook executed', result)

  useCallOnChange({ ifChanged: result, callback })

  return null
}

function useCallOnChange({ ifChanged, callback }) {
  const changeRef = React.useRef(null)
  if (changeRef.current !== ifChanged) callback(ifChanged)
  changeRef.current = ifChanged
}

function useEvent(f) {
  const fRef = React.useRef(null)
  fRef.current = f

  return React.useCallback((...args) => fRef.current(...args), [])
}

@Ayc0
Copy link
Author

Ayc0 commented Dec 15, 2023

If I understand correctly you want the equivalent of the following:

Yes, the idea is here with 2 differences:

  • useIsolation could run synchronously during the 1st render (like a useMemo)
  • you could also provide dependencies to useIsolation(hook, [deps]) to conditionally re-call the hook when its parent scope changes (but not if hooks within it would trigger a re-render)

@gaearon
Copy link
Member

gaearon commented Jan 3, 2024

Thank you for the RFC. I wanted to note that we’ve had a very similar proposal planned except that we wanted to roll this behavior into the existing useMemo Hook.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants