Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

best practise using jotai in big project? #683

Closed
chj-damon opened this issue Aug 31, 2021 · 9 comments
Closed

best practise using jotai in big project? #683

chj-damon opened this issue Aug 31, 2021 · 9 comments
Labels
documentation Improvements or additions to documentation

Comments

@chj-damon
Copy link

Now I just treat jotai as state management tool, but I think jotai can do more than that.
I read the docs, and noticed that maybe I can write my logic in the derived atom and I don't need to put them in custom hooks anymore.

simply like this:

const countAtom = atom(1)
const asyncIncrementAtom = atom(null, async (get, set) => {
  // await something
  set(countAtom, get(countAtom) + 1)
})

const Component = () => {
  const [, increment] = useAtom(asyncIncrementAtom)
  // it will suspend while `increment` is pending.
}

but then my question is, how should I organize the atom structure? should I put theme together or split them into serval files?
when should I use atom, and when shouldn't I use it?

all the examples in the doc is too simple. Is there any best practices to demonstate the best way of using jotai?

@dai-shi
Copy link
Member

dai-shi commented Aug 31, 2021

noticed that maybe I can write my logic in the derived atom and I don't need to put them in custom hooks anymore.

Yeah, I noticed that too. Now, I would write all logic in atoms and I wouldn't use custom hooks. (Especially, that would allow me to run the app in my experimental jotai-jsx.)

how should I organize the atom structure? should I put theme together or split them into serval files?

It's still an open question. I think both are valid. I'd prefer splitting atom files and only exporting atoms that are necessary.

Is there any best practices to demonstate the best way of using jotai?

I think we are still at the stage of building best practices. Help wanted.

@dai-shi dai-shi added the documentation Improvements or additions to documentation label Sep 9, 2021
@tarngerine
Copy link
Contributor

++ to putting as much mutation logic into writable atoms! it gives a nice separation similar to MVC where jotai atoms handle Model and Controller, and React FC handles View.

these are some incomplete thoughts, but some best practices i've observed in my own work (two spatial tools: https://sprout.place & https://felt.com)

  • start your project off putting all your atoms and writable atoms into one file, i.e. atoms.ts. This is similar to how a lot of typescript projects start off using a single types.ts. i tried prematurely splitting atoms into different concerns but it always came back to bite me. unless tsc times get really long, it's not really worth splitting up. i put all my 'Model' state atoms up top, and all the 'Controller' WritableAtoms below.
  • atoms that are only relevant to a single component/subsystem can colocate with the component. a good example i often run into is:
// atoms.ts
// ID of the selected element, used in a lot of places
const selectedElementId = atom<string>("");

// Element.tsx
// Only Element.tsx cares if its selected, and we can reduce rerenders with an atom family
const isSelectedFamily = atomFamily((id) => atom((get) => get(selectedElementId) === id)));

function Element(id) {
  const isSelected = useAtomValue(isSelectedFamily(id))
}
  • one thing i do often is write a custom hook to quickly make using a frequently used atom/writableatom faster (so I don't have to import both the atom and the useAtom/useUpdateAtom/useAtomValue). another benefit to this is that you can group multiple related writable atoms/atom states into one shared hook
// writable atoms, don't export these
const selectElementAtom = atom(null, ()=> ...)
const toggleElementAtom = atom(null, () => ...)
// export the hook, so you only have to import one thing
export function useSelectElement() {
  return {
    select: useUpdateAtom(selectElementAtom),
    toggle: useUpdateAtom(toggleElementAtom)
  }
}

some other best practices im still figuring out, curious to hear other peoples patterns!

@tarngerine
Copy link
Contributor

tarngerine commented Sep 30, 2021

on the question of when to use atom/when not to, this is something i've discussed often with my friends — in particular, one pattern i see a lot is how to capture some state from one component and pass it to a newly created component

this specifically comes up in drawing tools type interaction — select a Text Tool, then create a new Text Element. This new Text Element should autofocus — but how does the newly created Text Element know if it's actually just created, or if it's only being remounted (i.e. on refresh)

one way would be to use an atom, i.e. const justCreatedElementIdAtom = atom<string>("")

and in TextElement, we just look at if the ID matches

const isJustCreatedFamily = atomFamily((id) => atom(get => get(justCreatedElementIdAtom) === id))
function TextElement(id) {
  const shouldFocus = useAtomValue(isJustCreatedFamily(id));
  return <textarea autoFocus={shouldFocus} />
}

however, it might feel overkill, because it doesn't REALLY need to be reactive, as it'll only ever happen once to an element. the alternative here would be to make this justCreatedElementId not an atom, but a simple module variable let justCreatedElementId = ""

and then we just check it on mount to focus the element imperatively, or we could use a shouldFocus useState locally

function TextElement(id) {
  const textareaRef = useRef(null);
  useEffect(() => {
    if (justCreatedElementId === id) textareaRef.current?.focus();
  }, []);
  return <textarea ref={textareaRef} />
}

this is def one of those situations where I'm like "is an atom appropriate?" and it always comes back to "does it need to be reactive? or can it simply be checked imperatively"

@xadh00m
Copy link

xadh00m commented Dec 9, 2021

I´m wondering how to model the lifecycle of atoms.

Using React contexts all contained state (e.g. in useState hooks) is removed as soon as we stop rendering the context provider. This comes in handy if you have an app with two pages where one page e.g. needs a websocket connection to the backend (and some additional state) which can be wrapped up in a Page1Context and a completely different context for the second page. When switching between both pages the page state (including the websocket connection etc.) is created/removed accordingly.

Atoms on the other hand look like global entites which are never GC´ed. Do I miss something?

@dai-shi
Copy link
Member

dai-shi commented Dec 10, 2021

Even if atom configs are defined at module level, they don't hold values. It's <Provider> that holds values. Essentially, it's just based on React Context. Please try using Jotai's Provider where you would use React Context Provider.

@xadh00m
Copy link

xadh00m commented Dec 16, 2021

@dai-shi Thank you for the tip. I wasn´t aware of the tight coupling with React Context. This makes sense especially for bigger projects.

Does Jotai require a context bridge for react-three-fiber ?

@dai-shi
Copy link
Member

dai-shi commented Dec 16, 2021

If we use a default store (called Provider-less mode), then we don't need the context bridge (and we don't get much benefit from Context either).

If we use a Provider, it doesn't work with r3f. (we once provided a context bridge function, but removed before v1.)
For the future, hopefully #861 will be a solution in such use cases.

@chj-damon
Copy link
Author

I didn't get a good idea about dealing with other hooks from third-party libraries, such as react-navigation.
for example, I need to do some logic and then navigate to other screens. I can use const navigation = useNavigation() which useNavigation is a custom hook from react-navigation library. I can do this in custom hooks, but as we discussed above, If we write all these logic in derived atoms, how can I do that? because Hooks can only be used in hooks.

@chj-damon
Copy link
Author

@dai-shi maybe it's better to transform this issue to discuss.

@pmndrs pmndrs locked and limited conversation to collaborators Dec 17, 2021
@dai-shi dai-shi converted this issue into discussion #896 Dec 17, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

4 participants