Skip to content
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

What's the best approach when creating/composing nested stores? #163

Closed
strawberrysunset opened this issue Aug 22, 2020 · 4 comments
Closed

Comments

@strawberrysunset
Copy link

strawberrysunset commented Aug 22, 2020

My main problem is that even if I nest one store in another, the basic store methods (getState, setState) are exposed as opposed to its state and methods that would be returned from a hook call. Since I am unable to initialize a hook in a store, how do I properly tackle composing stores within other stores?

As an example, I would like to create a store containing an array of users. Each user would also be an individual store with state and methods. Then, in a component I could access the users and call their methods independently. For example in pseudo-code:

const {users} = useUserDatabase();
const buttons = users.map(user => <button onClick={user.setStatus('Happy')}>Make User Happy</button>)

I've thought about passing the set method from the parent store but that seems to create too much coupling between stores. I've thought about creating one monolithic store but that would be quite messy. Perhaps I'm missing something obvious but I'm not sure how to tackle this problem.

@dai-shi
Copy link
Member

dai-shi commented Aug 22, 2020

I can think of a) creating a store in React lifecycle but I'd not recommend because it's easy to violate rules of hooks, b) creating multiple vanilla stores and composing them but that can be messy with subscribes, so c) creating one monolithic store which seems like a zustand way.

Does something like this work for you?

const useUsers = create((set, get) => ({
  users: [],
  createUser: (userProps) => {
    const userId = createUniqueId()
    const user = {
      ...userProps,
      userId,
      setStatus: (status) => {
        set({
          users: get().users.map((u) => u.userId === userId ? { ...u, status } : u),
        })
      },
    }
    set({
      users: [...get().users, user],
    })
  },
})

@strawberrysunset
Copy link
Author

@dai-shi Hi there, thank you very much for your reply. Yes, I think keeping it to one store is the most suitable approach for the time being. Your solution is also very helpful. Thanks 👍

@strawberrysunset
Copy link
Author

@dai-shi I came up with a solution which means that child objects can remain unaware of the structure of a parent store. The implementation passes a custom set method which passes the child's state instead of the parent store state. As a sidenote, I'm using immer for state updates and Maps instead of arrays to avoid having id props on children.

Here's a code sandbox demo: https://codesandbox.io/s/zustand-reference-passing-6r09r?file=/src/App.js:110-829

const createUser = (props, set, name) => ({
  name,
  ...props,
  setName: (name) => {
    set((state) => {
      state.name = name;
    });
  }
});

const useDatabase = create((set, get) => ({
  users: new Map(),
  addUser: (name) => {
    const id = createID();
    // Set method passed to child passes the user itself as state.
    const childSet = (fn) => {
      set((state) => {
        fn(state.users.get(id));
      });
    };
    const remove = () => {
      set((state) => {
        state.users.delete(id);
      });
    };
    const newUser = createUser({ remove }, childSet, name);
    set((state) => {
      state.users.set(id, newUser);
    });
  },
  getUsers: () => Array.from(get().users.values())
}));

@Maxwellnft50
Copy link

Hi

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants