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

Difficulty Managing Dynamic atomFamily Instances in ScopeProvider #50

Closed
Yarden-Ankonina opened this issue Jul 24, 2024 · 1 comment · Fixed by pmndrs/jotai#2685 or #51
Closed

Comments

@Yarden-Ankonina
Copy link

I'm encountering challenges while managing dynamic atomFamily instances within the ScopeProvider.

Currently, I'm unable to directly pass the entire atomFamily into the atom array of ScopeProvider. Instead, I'm forced to extract each individual atom and pass them separately. This approach becomes cumbersome in my application, where I heavily rely on atomFamily for its functionality. Manually tracking and passing every single atom instance is quite tedious and doesn't seem an option currently.

// Current workaround (tedious and error-prone):

const keys = [...]; // Array containing dynamic keys
const atomsFromFooFamily = keys.map(key => fooFamily(key));

<ScopeProvider atoms={[...normalAtoms, ...atomsFromFooFamily]}>

Ideally, I'd like to pass an entire atomFamily group into the ScopeProvider.

// Ideally, we would like to do this:
<ScopeProvider atoms={[fooFamily]}>  // Not currently supported

Is there a recommended approach to address this scenario? Any suggestions or workarounds would be greatly appreciated.

@dmaskasky
Copy link
Member

dmaskasky commented Jul 24, 2024

I don't have any suggestions outside of the current tedious approach you mentioned above.

Brainstorming: AtomFamily is a just a function but we can assume it returns atoms since we want to pass it in to atoms prop. Perhaps jotai-scope could expose a utility to scope functions.

import { scopableFn, ScopeProvider } from 'jotai-scope'

const scopableAtomFamily = scopableFn(atomFamily((id) => atom(id)))

<ScopeProvider atoms={[scopableAtomFamily]}>{children}</ScopeProvider>

Impl might look something like this:

type AtomFn = <T = unknown>(...args:T[]) => Atom<T>
const SCOPABLE = Symbol()
function ScopeFnAtom(fn: AtomFn, sub: Store['sub']) {
  return Object.assign(
    Atom(null),
    fn,
    { type: SCOPABLE, sub }
  )
}

export function scopableFn<T extends AtomFn>(fn: T): T & Atom<ReturnType<T>> {
  const store = createStore()
  const i = function interceptor(...args: unknown[]) {
    const r = fn(...args)
    store.set(a, r)
    return r;
  }
  cons sub = store.sub.bind(a)
  const a = ScopeFnAtom(i, sub)
  return a
}
export function ScopeProvider({ atoms }) {
  const [rAtoms, setRAtoms] = useState(new Set())
  const scopedAtoms = new Set([...atoms, ...rAtoms])
  function initialize(scopedAtoms) {
    return { scopedAtoms, store: createScopedStore() }
  }
  const [state, setState] = useState(initialize())
  if (!isSameSet(state.scopedAtoms, scopedAtoms) {
    setState(initialize(scopedAtoms))
  }
  // subscribe to new rAtoms
  useEffect(() => {
    const unsubs = Array.from(state.scopedAtoms)
    .filter((a) => SCOPABLE in a)
    .map((a) => {
      a.sub((r) => {
        setRAtoms((rSet) => {
          new Set([...rSet, r])
        })
      })
    })
    return () => unsubs.forEach(unsub => unsub())
  }, [state])
  const memoizedChildren = useMemo(() => children, [state])
  return <Provider store={state.store}>{children}</Provider>
}

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