Skip to content

Instantly share code, notes, and snippets.

@onurkerimov
Last active March 2, 2023 10:52
Show Gist options
  • Save onurkerimov/42c8ece38290648d595e2ac4df0f5926 to your computer and use it in GitHub Desktop.
Save onurkerimov/42c8ece38290648d595e2ac4df0f5926 to your computer and use it in GitHub Desktop.
My take on an ideal mental model for React (A React without hooks)
import create from 'xoid'
import React from 'react'

const App = ($props, { effect, read }) => {
  const $count = create(0)
  const increment = () => $count.update(state => state + 1)

  // conditionally callable context reader
  const $background = $props.map(({ themeEnabled }) => themeEnabled ? read(ThemeContext) : 'white')

  // (also conditionally callable)
  effect(() => {
    console.log('mount occured')
    return () => console.log('unmount occured')
  })

  // only the following closure rerenders. Outside is static.
  // This means there's absolutely no need for `useMemo`, `useCallback`, `useEvent`, even `useRef`.
  return (get) => (
    <div 
      style={{ background: get($background) }} 
      onClick={increment}
    >
        {get($count)}
    </div>
  )
}

export default App

High-level summary of the above code:

const App = ($props, { effect, read }) => {
  ...REMAINS STATIC THROUGHOUT THE COMPONENT LIFECYCLE...
  return (get) => {
    ...RENDER FUNCTION...
  }
}

Today, with xoid, a similar thing is achievable with a few extra steps:

(See: https://xoid.dev/docs/api-react/use-setup)

import { useSetup, useAtom } from '@xoid/react'

const App = (props) => {
  const setup = useSetup(($props, { effect, read }) => {
    ...REMAINS STATIC THROUGHOUT THE COMPONENT LIFECYCLE...
  }, props)
  return useAtom(() => create((get) => {
    ...RENDER FUNCTION...
  }))
}

In the future I'm planning to add a default export to @xoid/react package, or create a completely different package that abstracts out the above code to achieve the following. It will have automatic forwardRef capabilities, and resulting components will be wrapped inside React.memo by default, with a custom equality checker that doesn't check very long strings (Based on my research, this is the only bottleneck of using React.memo ALWAYS).

import component from '@xoid/react'
const App = component(($props, { effect, read, ref* }) => {
  ...REMAINS STATIC THROUGHOUT THE COMPONENT LIFECYCLE...
  return (get) => {
    // You can use React hooks here! This means:
    // - You can gradually refactor your components if you decide to
    // - You can continue using the hooks of third-party React libraries.
    ...RENDER FUNCTION...
  }
})

*: use a proxy, if ref is destructed, wrap it in React.forwardRef, if not destructed, don't.

Lastly, even useRef is not need to be imported:

const RefExample = (_, { effect }) => {
  const ref = create<HTMLDivElement>()

  // do something with ref
  effect(() => {
    const element = ref.value
    ...
  })

  // do something with dynamically changing ref! (no more [ref, setRef] = useState())
  ref.subscribe((element) => {
    ...
  })

  return () => <div ref={ref.set} />
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment