"Mid-level" state atoms? #826
-
Imagine an image editor component. It's a complex component that encapsulates lots of other components, and ideally presents a simple interface. Inside the import { currentImageAtom } from 'cool-app/atoms';
// ⛔️ Don't want to do this, because it tightly couples this generic component to my global state
function ImageEditor() {
let image = useAtom(currentImageAtom) So I'll take the image I want to edit as a prop: // ✅ Nice! Reusable, decoupled, looks good
function ImageEditor(props: { image: Image }) { But now all my other internal components need to reference this What's the best way to approach this? My first take: "sync" the prop to a "local" atom: const imageBeingEdited = atom<Image | null>(null);
function ImageEditor(props) {
let [image, setImage] = useAtom(imageBeingEdited);
useEffect(() => {
setImage(props.image)
}, [props.image]); But now all my other derived atoms need to have null checks everywhere, despite the fact that they will never be used in the real-world without an image present: function ImageCropper() {
let [image] = useAtom(imageBeingEdited);
// ⛔️ image is now typed as `Image | null` despite it not really being possible to be null Basically, I want to have "mid-level" atoms. I.e. atoms that are not really global state, but also not purely local to a single component. Curious if anyone has any good patterns for this. |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 4 replies
-
Jotai is designed to cover such use cases. const ParentComponent = ({ foo }) => {
const fooAtom = useMemo(() => atom(foo), [foo]))
// ...
} Now, the question is how to pass Option 1: propsconst ChildComponent = ({ fooAtom }) => {
const [foo, setFoo] = useAtom(fooAtom)
// ...
}
const ParentComponent = ({ foo }) => {
const fooAtom = useMemo(() => atom(foo), [foo]))
return <ChildComponent fooAtom={fooAtom} />
} Option 2: contextconst FooAtomContext = createContext()
const ChildComponent = () => {
const fooAtom = useContext(FooAtomContext)
const [foo, setFoo] = useAtom(fooAtom)
// ...
}
const ParentComponent = ({ foo, children }) => {
const fooAtom = useMemo(() => atom(foo), [foo]))
return (
<FooAtomContext.Provider value={fooAtom}>
{children}
< /FooAtomContext>
)
} Option 3: atom-in-atom with scoped providerThis is an alternative pattern to option2 only with jotai. Provider scope is almost equivalent to creating a new context. const FooScope = Symbol()
const fooAtomAtom = atom(null)
const ChildComponent = () => {
const [fooAtom] = useAtom(fooAtomAtom, FooScope)
const [foo, setFoo] = useAtom(fooAtom)
// ...
}
const ParentComponent = ({ foo, children }) => {
return (
<Provider initialValues={[fooAtomAtom, atom(foo)]} scope={FooScope}>
{children}
< /Provider>
)
} Another pattern still to be explored: provider with globally defined atomsThis is still a new pattern to explore, but sometimes we want to define atoms outside so that defining derived atoms is easier. const fooAtom = atom(0)
const doubledFooAtom = atom((get) => get(fooAtom) * 2)
const ChildComponent = () => {
const [foo, setFoo] = useAtom(fooAtom)
const [doubledFooAtom] = useAtom(doubledFooAtom)
// ...
}
const ParentComponent = ({ foo, children }) => {
return (
<Provider initialValues={[fooAtom, foo]}>
{children}
< /Provider>
)
} Provider will isolate atom values from other providers (or non-provider). So, this is limited to some use cases. Hm, now I re-read your description, and I actually wonder what I described is relevant. Let's see..
|
Beta Was this translation helpful? Give feedback.
Jotai is designed to cover such use cases.
Basically, you don't need to define atoms globally.
Now, the question is how to pass
fooAtom
to child components.Option 1: props
Option 2: context