impact
impact copied to clipboard
Are complex nested stores possible?
This is a brilliant idea, I've read through the codebase a couple of times but need to learn/understand much more about how it's working fully but I'm loving your implementation so far!
I've achieved something much simpler recently to achieve the native app feel using context api with a state manager class instance, and observers on mount/unmount in the provider to ensure the provider store/state object has the latest copy of a standard js 'container' class store/state object (within the state manager instance) which could be a complex nested object of primitive types. I.e when store/state vals change and the provider re-renders after the notification, and then returning the store/state as part of the provider value object. It's blazing fast and handles very complex stores nicely. The state manager provides the functionality for manipulating all of internal store state, via both external calls from the UI components and private functions operating on the data, basically in an attempt to separate concerns and keep as much logic related to processing/manipulating the state data away from any UI components (which might only need simple data for presentation and a function call which triggers some complex internal logic) and ultimately out of the react component render cycle or any dependency on useState etc.
The provider contains something like:
const managerRef = useRef<StateManager | null>(null);
if (!managerRef.current) {
managerRef.current = useQueryStringManager
? new QueryStringStateManager({ ...mergedInitialState })
: new StateManager({ ...mergedInitialState, triggerFetch: true, triggerRefetch: true });
}
const [state, setState] = useState<StateType>(() =>
managerRef.current ? managerRef.current.getState() : mergedInitialState
);
useEffect(() => {
if (managerRef.current) {
const unsubscribe = managerRef.current.subscribe(() => {
setState(managerRef.current ? managerRef.current.getState() : mergedInitialState);
});
return () => unsubscribe();
}
}, []);
useEffect(() => {
if (useQueryStringManager && managerRef.current && state.searchParams !== searchParams) {
setSearchParams({ ...state.searchParams });
}
}, [state.searchParams]);
return (
<Context.Provider value={{ state, manager: managerRef.current }}>{children}</Context.Provider>
);
The StateManager has the following functionality:
...
getState(): StateType {
Logger.log("getState");
return this.state;
}
notifySubscribers(): void {
Logger.log("notifySubscribers");
this.listeners.forEach((listener) => listener());
}
subscribe(listener: () => void): () => void {
Logger.log("subscribe");
this.listeners.push(listener);
return () => {
this.listeners = this.listeners.filter((l) => l !== listener);
};
}
updateState(partialState: Partial<StateType>) {
Logger.log("updateState", partialState);
// Deep clone the incoming partial state to ensure immutability in nested objects
const clonedPartialState = cloneDeep(partialState);
this.state = { ...this.state, ...clonedPartialState };
this.notifySubscribers();
}
// example trivial fn which updates the internal state.name prop
setName(name: string): void {
Logger.log("setName", name);
this.updateState({ name });
// call to another private fn which updates other parts of state further after a name change
}
Something tells me impact-react is a good fit for what I'm working on but I need to thoroughly read the docs and understand the codebase some more to grok the concepts fully.
Questions:
- Can stores contain stores recursively in order to create a nested tree of complex state objects?
- The AppStoreProvider in the docs is a little confusing, I want to query apis/db/do other stuff server side to build up an initial state which is passed into the provider to then hydrate the store's initial state client side which is subsequently updated client side, is that possible?
It's late and I'm tired, I've just reread the docs and may have answered my own questions, at a glance it seem too good to be true, I need to understand consuming the observable promises too, will review again tomorrow.