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.

Selectors defined outside of a render function do not always return the associated store value

See original GitHub issue

I’m having a problem where store values output via a custom hook do not all have the values from the current store. Some have values from the previous store. Here’s a brief explanation, I’ve also pasted more complete code at the end. Appreciate your help!

The crux of the issue is that if I have my selectors in an object useStore(selectors.blah), values from the store are incorrect. If I do this useStore(state => state.blah) there’s no problem. More info:

I’ve grouped my selectors like this (living in a module with the store):

export const selectors = {
  status: (state) => state.status,
  profile: (state) => state.profile,
};

And use them like this in a custom hook in a separate module

  const status = useStore(selectors.status);
  const profile = useStore(selectors.profile);

A login and a logout button change store status and profile together from

{ status: statusType.LOGGED_OUT, groups: [], profile: null }

to

{
      status: statusType.LOGGED_IN,
      profile: {
        familyName: 'bob',
      },
    }

The values output in the custom hook are not what I was expecting. status has the current value of the store (statusType.LOGGED_IN), profile has the previous value of the store (null). Odd!

If instead I do this in the custom hook

  const status = useStore((state) => state.status);
  const profile = useStore(state => state.profile);

All is well. status and profile values have the current values of the store on every render

Here’s some more complete code:


Custom hook

import { useStore, selectors, mutators, statusType } from './store';

export function useCustom() {
  const status = useStore(selectors.status);
  const profile = useStore(selectors.profile);

  const login = useStore(mutators.login);
  const logout = useStore(mutators.logout);

  // these do not match the current version of the store on every render
  console.log('profile', profile); // null
  console.log('status', status); // statusType.LOGGED_IN

  switch (status) {
    case statusType.LOGGED_IN:
      return {
        status: statusType.LOGGED_IN,
        isLoggedIn: true,
        profile,
        logout,
      };
    case statusType.LOGGED_OUT:
      return {
        status: statusType.LOGGED_OUT,
        isLoggedIn: false,
        profile,
        login,
      };
    default:
      return {};
  }
}

store

import create from 'zustand';

export const statusType = {
  LOADING: 'loading',
  LOGGED_IN: 'logged_in',
  LOGGED_OUT: 'logged_out',
};

export const [useStore, store] = create((set) => ({
  status: statusType.LOADING,
  profile: null,
  login: async () => {
    set({
      status: statusType.LOGGED_IN,
      profile: {
        familyName: 'bob',
      },
    });
  },
  logout: async () => {
    set({ status: statusType.LOGGED_OUT, groups: [], profile: null });
  },
}));

if (typeof window !== 'undefined') {
  init();
}

async function init() {
  store.setState({
    status: statusType.LOGGED_IN,
    profile: {
      familyName: 'bob',
    },
  });
}

export const selectors = {
  status: (state) => state.status,
  profile: (state) => state.profile,
};

export const mutators = {
  login: (state) => state.login,
  logout: (state) => state.logout,
};

Issue Analytics

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

github_iconTop GitHub Comments

2reactions
TkDodocommented, Sep 8, 2020

Thanks for fixing this - coincidentally, we had an issue filed similar to this one today, where a useEffect updates the state while an async request is in-flight, and then the async request updates the store with outdated data (or so). We also extract selectors to stable functions.

Updating from 3.1.0 to 3.1.1 fixed the issue for us as well, so thank you for taking the time to address this issue @dai-shi 👍

1reaction
andrew-mecommented, Jun 29, 2020

Hi @acd02, Our actual codebase is more complex, this is an example of how we were considering using Zustand. It doesn’t seem unusual to use custom hooks to hide a store away. We can of course re-architect, and will do for now. I wanted to share the issue I have in case others come across it.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Selectors defined outside of a render function do not ... - GitHub
I'm having a problem where store values output via a custom hook do not all have the values from the current store. Some...
Read more >
Getting Data Out of the Redux Store with Selectors
A selector is a pure function that takes a state object from the Redux store and returns some information extracted from that state...
Read more >
Recipes - Zustand Documentation - Pmndrs.docs
If a selector doesn't depend on scope, you can define it outside the render function to obtain a fixed reference without useCallback.
Read more >
What is the best way to access redux store outside a react ...
Export the store from the module you called createStore with. Then you are assured it will both be created and will not pollute...
Read more >
How to Render Components Outside the Main ReactJS App
The appendElementContainer should reference an element that is outside of the element used to render the main React application (since the main ...
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