Can I consume react context from atomFamily ? #1855
-
(first time using jotai, sorry if the question sounds obvious) I'm working in a 3rd party application that accepts extensibility using widgets. Basically, the SDK of the application provides a placeholder div and an opaque 'context' object that contains, along a lot of objects, a custom HTTP client that contains everything required to query the API. Basically, I use react to build the widget and at the very top of my widget component tree, I set a react context that provide the 3rd party context across the whole widget. Something very similar to: import * as React from 'react';
import * as ReactDom from 'react-dom';
export class MyWidget extends BaseWidget {
public render() {
const appContext = this.context; // opaque context
const placeholder = this.domElement; // placeholder div
const root = (
<React.StrictMode>
<MyCustomContext.Provider value={appContext}
<MyWidgetUI />
</MyCustomContext.Provider>
</React.StrictMode >
);
ReactDom.render(root, placeholder);
}
} Now I'd like to add jotai to my project. I can write atoms and atomFamilty to construct a state tree. However, when I need to query the host application's API, I need to use the provided context. What is the best/proper way to consume my appcontext within async atoms ? For example: const userList = atom(async (context:AppContext)=>await context.httpClient.fetch('_api/userlist'));
const manager = atomFamily( ({context, userId} : {context:AppContext, userId : number}) =>async (get) => atom (await get(userList(context))).find(u=>u.id === userId)) I guess my code is wrong because I don't feel passing the context for every single atomFamily is correct. And what I understand from the doc is that it will lead to a memory leak because every single call to the atom family won't be cached since the param object is never the same. Is there a way to avoid this parameter propagation? I wonder if there's a way to extend the component to pass arbitrary data across the whole tree Thanks in advance for clarification |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 1 reply
-
(would anyone help to answer?) |
Beta Was this translation helpful? Give feedback.
-
Th current state of my researches/attempts is:
const rootServicesAtom = atom<{
someService: SomeService,
otherService: OtherService;
} | undefined>(undefined);
const servicesAtom = atom(get => {
const context = get(rootServicesAtom); // type is possibly undefined
if (!context) throw new Error('Required services not provided');
return context; // type is no more possibly undefined
}); When I set the import { createStore, Provider as JotaiProvider } from 'jotai';
// ... in my "render" function :
const jotaiStore = createStore();
jotaiStore.set(rootServicesAtom, {
someService : getSomeService(context),
otherService: getOtherService(context),
});
const element = (
<React.StrictMode>
<JotaiProvider store={jotaiStore}>
<MyWidgetUI />
</JotaiProvider>
</React.StrictMode>
); This way I have a "per widget" dedicated store. Then, instead of passing my custom services/context object everytime I need them, I can rely on derived atom or atomFamily. const customerListAtom = atom(get => get(servicesAtom).SomeService.getCustomerList(); This is valid (I guess), because my services are unique per instance of the widget. I use two atoms. One nullable, used to inject the services. The other one, non nullable and readonly to get the services instance. I check the presence in the getter to respect typescript strict typings. I also feel I actually don't need atomFamily in my case. If I work on a master/details scenario, I don't need to have multiple user shown simultaneously. I can set a currentSelectedCustomerAtom to hold the one which is clicked and consume this atom in the details view. atomFamily would help only if I want to display multiple customers details at once or if I want to "cache" every single customer data. But the actual use case is to get fresh data everytime a user details is opened. Do you think I'm on the right track? |
Beta Was this translation helpful? Give feedback.
-
Years later, my architecture has changed a lot. Today, I use a mix of tanstack react query for data fetching (server side state), and jotai or zustand for client side state (filters, selection, etc.) |
Beta Was this translation helpful? Give feedback.
Years later, my architecture has changed a lot.
What I learned is that jotai is not the good candidate for a centralized store which mixes client and server state (my services are essentially a facade to query apis).
Today, I use a mix of tanstack react query for data fetching (server side state), and jotai or zustand for client side state (filters, selection, etc.)
When some data requires to assemble both worlds (processing complex rules against server data and user input), I create some custom hook that consume server state, client state and return a derived state ready to be used.