-
I'm trying to seperate my project into several independent modules, and each module should be wrapped under the // atom1/atom2/atom3 would only be used in SubTreeA // atom4/atom5/atom6 would only be used in SubTreeB // atom7 can be used in both SubTreeA and SubTreeB the code is like this: import { Provider, atom, useAtom } from 'jotai';
const atom1 = atom('');
const atom2 = atom('');
const atom3 = atom('');
const atom4 = atom('');
const atom5 = atom('');
const atom6 = atom('');
const atom7 = atom('');
export default function App() {
return (
<Provider>
<ModuleA />
<ModuleB />
</Provider>
);
}
function ModuleA() {
return (
<Provider>
<SubTreeA />
</Provider>
);
}
function ModuleB() {
return (
<Provider>
<SubTreeB />
</Provider>
);
}
function SubTreeA() {
const [value1, setValue1] = useAtom(atom1);
const [value2, setValue2] = useAtom(atom2);
const [value3, setValue3] = useAtom(atom3);
const [value7, setValue7] = useAtom(atom7); // this should be allowed
const [value6, setValue6] = useAtom(atom6);// this should be forbidden
return (
<div>
<div>value1: {value1}</div>
<div>value2: {value2}</div>
<div>value3: {value3}</div>
<div>value7: {value7}</div>
</div>
);
}
function SubTreeB() {
const [value4, setValue4] = useAtom(atom4);
const [value5, setValue5] = useAtom(atom5);
const [value6, setValue6] = useAtom(atom6);
const [value7, setValue7] = useAtom(atom7); // this should be allowed
const [value1, setValue1] = useAtom(atom1); // this should be forbidden
return (
<div>
<div>value4: {value4}</div>
<div>value5: {value5}</div>
<div>value6: {value6}</div>
<div>value7: {value7}</div>
</div>
);
} I tried to give the different Provider different scope, but it doesn't work at all. Is there any way that I can achieve my goal? my main target is to make each module unrelyable. ModuleA shouldn't care about anything in MouduleB, and vice versa. Those atoms that can be used in all the modules must be passed by the Provider above all the modules. in my very first idea, I think scope can do this, which is what <Provider scope={scope1}>
<Component />
</Provider>
const Component = () => {
const [value, setValue] = useAtom(atom1, scope2); // notice the scope is different
} |
Beta Was this translation helpful? Give feedback.
Replies: 16 comments 6 replies
-
@chj-damon. First of all, thanks for a good description of your problem.
To solve your case, I'd do the following: import { Provider, atom, useAtom } from 'jotai';
const atom1 = atom('');
const atom2 = atom('');
const atom3 = atom('');
const atom4 = atom('');
const atom5 = atom('');
const atom6 = atom('');
const atom7 = atom('');
const scopeA = Symbol("ModuleA");
const scopeB = Symbol("ModuleB");
export default function App() {
return (
<Provider> // Stays here for debugging purposes/inspection, but there's no functional need for it
<ModuleA />
<ModuleB />
</Provider>
);
}
function ModuleA() {
return (
<SubTreeA />
);
}
function ModuleB() {
return (
<SubTreeB />
);
}
function SubTreeA() {
const [value1, setValue1] = useAtom(atom1, scopeA);
const [value2, setValue2] = useAtom(atom2, scopeA);
const [value3, setValue3] = useAtom(atom3, scopeA);
const [value7, setValue7] = useAtom(atom7, scopeA);
return (
<div>
<div>value1: {value1}</div>
<div>value2: {value2}</div>
<div>value3: {value3}</div>
<div>value7: {value7}</div>
</div>
);
}
function SubTreeB() {
const [value4, setValue4] = useAtom(atom4, scopeB);
const [value5, setValue5] = useAtom(atom5, scopeB);
const [value6, setValue6] = useAtom(atom6, scopeB);
const [value7, setValue7] = useAtom(atom7, scopeB);
return (
<div>
<div>value4: {value4}</div>
<div>value5: {value5}</div>
<div>value6: {value6}</div>
<div>value7: {value7}</div>
</div>
);
} A couple of things to note:
I hope this helps. |
Beta Was this translation helpful? Give feedback.
-
@Thisen thanks for your reply. I know I can do this without problem, but still, using atoms like this way has to command developers to be selfconscious that not to import atoms out of one module. @dai-shi what is your suggestion? |
Beta Was this translation helpful? Give feedback.
-
const [value1, setValue1] = useAtom(atom1); // this should be forbidden When you say "forbidden", what should happen? Runtime error, compile error, or even not possible as ideas? |
Beta Was this translation helpful? Give feedback.
-
runtime error would be ok. it should report errors like: 'you can not access atom1 because you're not in the same scope'. atoms can be used anywhere without constraints, It's convinient, but I don't thinks it's good for big projects, big projects always contain many modules which are developed by different groups, there should be some boundaries using atom. |
Beta Was this translation helpful? Give feedback.
-
I tried to practise DDD paradigm in projects, the main difference is that DDD projects are divided to different modules, each module has its own context, the communication between modules is not using something inside the module directly, but by interfaces or apis. moduleA should definitely a black box to moduleB, and vise versa. |
Beta Was this translation helpful? Give feedback.
-
While it would be possible to raise a runtime error with a custom hook and a custom Provider, think it more like the atom config is an identifier or even a (global) variable. Let's suppose you have a global variable in a big project and want to avoid directly accessing it, how would you? You wouldn't export the variable from the module. Likewise, you shouldn't export the atom config. That said, there would be several (hacky) patterns to approach your needs. What I think is canonical for your use case is this: const AtomNotInitialized = new Error() // or Symbol() or new Proxy(...) or just null
const atom1 = atom(AtomNotInitialized) // for module1
const atom2 = atom(AtomNotInitialized) // for module2
const Module1Provider = ({ children }) => (
<Provider initialValues={[[atom1, '']]}>
{children}
</Provider>
)
const Module2Provider = ({ children }) => (
<Provider initialValues={[[atom2, '']]}>
{children}
</Provider>
)
const useAtomSafely = (anAtom) => {
const [value, setValue] = useAtom(anAtom)
if (value === AtomNotInitialized) {
throw AtomNotInitialized
}
return [value, setValue]
} Think it like |
Beta Was this translation helpful? Give feedback.
-
Isn't |
Beta Was this translation helpful? Give feedback.
-
It's implemented from such requests, but it's a general feature not limited to specific use cases. Same applies to |
Beta Was this translation helpful? Give feedback.
-
nice. I tried the So now, all I need to do is put all the atoms I need in |
Beta Was this translation helpful? Give feedback.
-
I'll give it a try and if it works, I'll close this issue. |
Beta Was this translation helpful? Give feedback.
-
it works!! the full example is here: https://codesandbox.io/s/jotai-module-example-ls1c7 I give it a @dai-shi any good suggestions that I can improve the develop experience? |
Beta Was this translation helpful? Give feedback.
-
@chj-damon There's a different option aswell: const ScopeGuardContext = createContext<Scope | null>(null)
function createScopedAtomHook(scope: Scope) {
return (atom: Parameters<typeof useAtom>[0]) => {
const currentScope = useContext(ScopeGuardContext)
if (scope !== currentScope) {
throw Error('Wrong scope')
}
return useAtom(atom)
}
}
// ...
const useScopedAtom = createScopedAtomHook(myScope)
function SubTreeA(props) {
return (
<ScopeGuardContext.Provider value={myScope}>
<MyComponent />
</ScopeGuardContext.Provider>
);
}
function MyComponent() {
const [value] = useScopedAtom(atom1)
// ...
}
|
Beta Was this translation helpful? Give feedback.
-
@Thisen but what about global atoms? |
Beta Was this translation helpful? Give feedback.
-
@chj-damon Then you can just use |
Beta Was this translation helpful? Give feedback.
-
@Thisen I would not suggest that, because if so, developers can also use |
Beta Was this translation helpful? Give feedback.
-
Im not sure I understand global atoms then. |
Beta Was this translation helpful? Give feedback.
While it would be possible to raise a runtime error with a custom hook and a custom Provider, think it more like the atom config is an identifier or even a (global) variable.
Let's suppose you have a global variable in a big project and want to avoid directly accessing it, how would you? You wouldn't export the variable from the module. Likewise, you shouldn't export the atom config.
That said, there would be several (hacky) patterns to approach your needs. What I think is canonical for your use case is this: