Closed mutewinter closed 2 years ago
The pattern that we've used to solve this is to put those values into scope, here's an example:
In this case, we use the values from this molecule to create derived atoms, but you can do the same thing with atom initial values.
import { atom, Atom, PrimitiveAtom } from 'jotai';
import { molecule, createScope, ScopeProvider } from 'jotai-molecules';
import React from 'react';
import type { AttributeConfig } from '../attributes/AttributeConfig';
import type { CanvasConfig } from '../canvas/CanvasConfig';
import type { Module } from '../component-metamodel/types';
export type RaisinConfig = Partial<CanvasConfig> &
Partial<AttributeConfig> & {
/**
* Atom for the primitive string value that will be read
*/
HTMLAtom: PrimitiveAtom<string>;
/**
* Atom for the set of NPM packages
*/
PackagesAtom: PrimitiveAtom<Module[]>;
/**
* Atom for the set of UI Widgets that can be use for editing attributes
*
* Read-only
*/
uiWidgetsAtom: Atom<Record<string, React.FC>>;
/**
* When an NPM package is just `@local` then it is loaded from this URL
*
* Read-only
*/
LocalURLAtom: Atom<string | undefined>;
};
type Molecule<T> = ReturnType<typeof molecule>;
/**
* The core thing that needs to be provided to raisins for editing to be possible.
*
* Everything in raisins is in some way *derived state* from this molecule
*/
export type RaisinConfigMolecule = Molecule<Partial<RaisinConfig>>;
const configScope = createScope<RaisinConfigMolecule | undefined>(undefined);
configScope.displayName = 'ConfigScope';
export const ConfigMolecule = molecule<RaisinConfig>((getMol, getScope) => {
/**
* This will create a new set of atoms for every {@link configScope}
*/
const config = getScope(configScope);
if (!config)
throw new Error(
'Must use this molecule in a wrapping <RaisinsProvider> element'
);
const LocalURLAtom = atom<string | undefined>(undefined);
LocalURLAtom.debugLabel = 'LocalURLAtom';
const HTMLAtom = atom('');
HTMLAtom.debugLabel = 'HTMLAtom';
const provided = getMol(config) as Partial<RaisinConfig>;
return {
...provided,
LocalURLAtom: provided.LocalURLAtom ?? atom(undefined),
PackagesAtom: provided.PackagesAtom ?? atom<Module[]>([]),
uiWidgetsAtom: provided.uiWidgetsAtom ?? atom({}),
HTMLAtom: provided.HTMLAtom ?? HTMLAtom,
};
});
/**
* Provides a scope for editing a {@link RaisinConfigMolecule}
*/
export const ConfigScopeProvider = ({
molecule,
children,
}: {
molecule: RaisinConfigMolecule;
children: React.ReactNode;
}) => {
return (
<ScopeProvider scope={configScope} value={molecule}>
{children}
</ScopeProvider>
);
};
export const RaisinsProvider = ConfigScopeProvider;
Ah didn't realize the value could be a non-string. I see the example of using an atom for the scope value in ScopeProvider.test.tsx
now. It's a little tedious due to needing to memoize the entire object for shallow equality, but perhaps that's the only way to ensure initialization re-runs if values change.
I am wanting to initialize the values for my scoped atoms from props of a parent component. Right now, I'm doing it with a
useSetAtom
on every render in myScopeProvider
wrapper. It'd be nice ifScopeProvider
could takeinitialValues
like https://jotai.org/docs/api/core#provider.