question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

clunky combine store usage

See original GitHub issue

I’ve followed the recommended way to combine stores in #228. It’s clunky to work with as inside the stores you have to set state as if you’re setting from the root of the state, this makes everything overly verbose. I imagine in many cases sub-stores won’t touch other stores state and having one root store would mainly be for simplifying co-dependent state queries outside of the stores.

Take a root store made up of sub-stores like this:

const useStore = create((set, get, api) => ({
  users: { ...createUsersStore(set, get, api) },
  restrictedUsers: { ...createRestrictedUsersStore(set, get, api) }
}));

Inside the sub-stores, it’d be intuitive to set state like this:

const createUsersStore = (set, get, api) => ({
  data: [],
  setUsers: users => set({data: users})
})

Example:

// expected state after calling useStore.getState().users.setUsers(['user1', 'user2']) :
{
  users: {
    data: ['user1', 'user2']
  },
  restrictedUsers: {...}
}

// instead, it looks like this:
{
  data: ['user1', 'user2'],
  users: {
    data: []
  },
  restrictedUsers: {...}
}

To fix this issue, you need to prepend all set object params with the sub-store key, and spread the current values:

const createUsersStore = (set, get, api) => ({
  data: [],
  setUsers: users => set({users: {...get().users, data: users}})
})

This feels like unnecessary ceremony. Immer could be a solution, but I like to have my async logic contained in store actions; Immer doesn’t like async. The solution I’ve come to is a helper like this:

const _setIn = (set, get, propName) => setObjArg => set({ [propName]: { ...get()[propName], ...setObjArg } });

This helper can then be passed down into the stores

const useStore = create((set, get, api) => {
  const setIn = curry(_setIn)(set, get);
  return {
    users: { ...createUsersStore(set, get, api, setIn("users")) },
    restrictedUsers: {...createRestrictedUsersStore(set, get, api, setIn("restrictedUsers"))}
  };
});

How setting now looks, and works intuitively:

const createUsersStore = (set, get, api, setIn) => ({
  data: [],
  setUsers: users => setIn({data: users})
})

This works well, but only supports set(obj); no support for set(state => ...). Is there an alternative solution (or modification to mine) that matches this, and ideally doesn’t limit the api of set. I feel a solution to this should be documented as in real world apps it’s quite common to have co-dependent stores, and having one global store simplifies the application logic for these co-dependencies (imo).

Zustand is fantastic, I want to harness the full power of it without introducing ridiculous ceremony like Redux.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:6 (3 by maintainers)

github_iconTop GitHub Comments

2reactions
jgibocommented, Jan 30, 2021

I will take a look through those other issues, cheers.

Immer doesn't like async: Bit of a bad take on my part. While it does support async, I think it isn’t possible to set state incrementally inside the producer - new state is created when producer exits, for async this is when the promise resolves. I may be wrong here, I’ll need to double check…

Assuming that’s right ^, if you had a async fetch action in the store, and wanted to set isFetching before and after the network call, and for store listeners to pickup on the incremental updates, this wouldn’t be possible in the same action.

By no means an expert on state management patterns, but happy to help keep the conversation going and contribute where I can.

1reaction
jgibocommented, Jan 31, 2021

There’s some good solutions in those issues you mentioned above. I’ve settled on using your scopedSet solution from Issue 161, for anyone else looking to use this solution, I had to wrap and spread the ternary expression to get it to work nicely:

const scopedSet = (ns, set) => (update) => {
    set(prev => ({
      [ns]: { ...prev[ns], ...(typeof update === 'function' ? update(prev[ns]) : update) }
    }))
}

I’m using it by passing the scoped setter as an extra arg into the stores:

const useStore = create(devtools((set, get, api) => ({
    users: createUsersStore(set, get, api, scopedSet('users', set)),
    restrictedUsers: createRestrictedUsersStore(set, get, api, scopedSet('restrictedUsers', set))
})));

export const createUsersStore = (set, get, api, scopedSet) => ({
    isFetching: false,
    lastFetched: 0,
    data: [],

    fetch: async () => {
        scopedSet(state => ({isFetching: !state.isFetching})); // state updater fn scopes as well
        const data = await usersApi.getUsers();
        scopedSet({data, isFetching: false, lastFetched: Date.now()});
        return data;
    }
});

#161 has alternative patterns for using scopedSet.

Not sure I can contribute much else than has already been discussed in the other issues. The integrated solution in #178 looks promising, I would use it if it was a part of Zustand, but the simple solution above solves my problem just fine.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Is there a Combine-y way to stop multiple requests being ...
It works but feels clunky. I feel like there should be a more "Combine-y" way of doing this. Is there a more elegant/Combine-y...
Read more >
What's the best way to combine and edit Xbox clips ... - Reddit
... to combine and edit Xbox clips? The Xbox video editor is clunky and painful to use. ... Upload Studio free download app...
Read more >
Multi-Store Accounts - Omnisend
Manage multiple channels at once. Running separate tools for email, SMS and push for your multiple stores can get clunky. With Omnisend, you...
Read more >
VipRiser PDF Ops on the Mac App Store
It is collection of Automator workflow steps useful for working with PDFs. This makes it incredibly powerful because you can combine it with...
Read more >
How to Survive Slow Months in Retail - Shopify
Here's how to use your time wisely, prepare for busy seasons, ... Customers flock to retail stores to pick up gifts for themselves...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found