Skip to content

RFC: useIsolation #257

New issue

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

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

Already on GitHub? # to your account

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.

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

Successfully merging this pull request may close these issues.

4 participants