-
Notifications
You must be signed in to change notification settings - Fork 145
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
[Question] The best practice to combine containers to have it as "global" state #55
Comments
Something like this?
// i'm using ink so this is regular node not ES6 imports
const React = require("react");
const Bank = require("./Bank");
const Counter = require("./Counter");
const compose = (...containers) => {
const nest = (containers, children) => {
if (containers.length > 0) {
const [first, ...rest] = containers;
return <first.Provider>{nest(rest, children)}</first.Provider>;
}
return children;
};
return {
Provider: ({ children }) => {
return nest(containers, children);
}
};
};
module.exports = compose(Bank, Counter);
const Store = require('./store')
// ...
<Store.Provider><YourComponent /></Store.Provider> |
Maybe you can just create a store container? const useA = () => {//...}
const useB = () => {//...}
const composeHooks = (...hooks) => () => hooks.reduce((acc, hook) => ({ ...acc, ...hook() }), {})
const Store = createContainer(composeHooks(useA, useB))
const App = () => (
<Store.Provider>
//...
</Store.Provider>
) |
WHY?! |
In my case I want to create a Container that composes and extends other Containers const useA = () => {//...}
const useB = () => {//...}
const ContainerA = createContainer(useA)
const ContainerB = createContainer(useB)
const useC = () => {
cont { somethingFromA } = ContainerA.useContainer()
cont { somethingFromB } = ContainerB.useContainer()
const somethingFromC = somethingFromA + somethingFromC
return { somethingFromC }
}
const ContainerC = createContainer(useC) So to use ContainerC you need the providers of A and B <ContainerA.Provider>
<ContainerB.Provider>
<ContainerC.Provider>
<App />
</ContainerC.Provider>
</ContainerB.Provider>
</ContainerA.Provider> So I use this function to combine the deps: import React from 'react'
export const addContainerDeps = (container, ...deps) => {
if (!deps) return container
const CurrentProvider = container.Provider
container.Provider = props =>
deps.reduce(
(combinedDeps, Dep) => <Dep.Provider>{combinedDeps}</Dep.Provider>,
<CurrentProvider>{props.children}</CurrentProvider>
)
return container
} In this way: const ContainerC = addContainerDeps(
createContainer(useC),
ContainerA,
ContainerB
) <ContainerC.Provider>
<App />
</ContainerC.Provider> |
Hmm this is just confusing in my opinion, its not very declarative getting the values from A + B passed into C via a render would be much cleaner Does C actually do anything special? could you not just write a custom hook wrapping useMemo if its intensive ? function useComputedValuesFromAAndB() {
const { value: aValue } = ContainerA.useContainer()
const { value: bValue } = ContainerB.useContainer()
return aValue + bValue
}
function MyComponent() {
const computedValue = useComputedValuesFromAAndB();
return <div>{computedValue}</div>
} |
It is great @tancc And I add some code to avoid key conflict. const useA = () => {//...}
const useB = () => {//...}
const composeHooks = (...hooks) => () => hooks.reduce(
(acc, hook) => {
const hookObj = hook();
if(Object.keys(acc).every(key => hookObj[key] === undefined)) {
return {...acc, ...hookObj}
} else {
throw new Error('there exist same key in multiple hooks');
}
}, {}
)
const Store = createContainer(composeHooks(useA, useB))
const App = () => (
<Store.Provider>
//...
</Store.Provider>
) |
@helongbin @tancc |
You can checkout my idea: react-hook-svs. Highlights:
|
You know, I've come back to this problem several times over the last year or so. And my conclusion is that the most maintainable method is to just ignore the "ugliness" of a deeply nested tree and just roll with it: <ContainerA.Provider>
<ContainerB.Provider>
<ContainerC.Provider>
<ContainerD.Provider>
<ContainerE.Provider>
<ContainerF.Provider>
<App />
</ContainerF.Provider>
</ContainerE.Provider>
</ContainerD.Provider>
</ContainerC.Provider>
</ContainerB.Provider>
</ContainerA.Provider> Once you get past the ugliness, it's actually quite liberating. It clearly shows the hierarchy of the different providers and doesn't require writing any logic with reduce (or any logic at all!). It's my opinion that this "ugly" way of doing things might actually be the most maintainable after all. What's the point of making something look "nice" and "elegant" if it becomes a huge pain to maintain? Are we engineers or are we artists first? I welcome all downvotes 🤗 |
@adrianmcli I think Dan Abramov had an article on his blog here about this. I agree with this. I don't like it one single bit though. But I agree. |
Currently I have thrown out redux and working with unstated.
and it works |
@hck1205 em...there is a problem with unstated, if you use unstated which return multi states, every setstate will trigger the whole page rerender... even you do not change other state... |
Would that be a problem if app is set with react routers? If so, probably i should subscribe stores in separate component. But is it okay to access stores directly in component by using Import? |
if your app has some complex state, like component A use stateA, component B use stateB, but all state come from the same unstated, you will need to seprate into multi state, if you do not want the unnecessary rerender |
no worries, i have separated root store into multiple stores and distributed them to components where requires to access to the stores. and have tested rendering issues. I think it works fine so far. |
it seems you must subscribe the whole context while you only want to subscribe useA . and the comonent will rerender when useB hook is trigger but the comonent may not use the data in useB hook |
I think above there is antipattern. How to do it in the best manner?
The text was updated successfully, but these errors were encountered: