ctrlplusb / easy-peasy

Vegetarian friendly state for React

Home Page:https://easy-peasy.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Differences between `computed` and a hook

KevinMusgrave opened this issue · comments

What are the differences between using computed:

const model = {
  user: null,
  isLoggedIn: computed((state) => state.user !== null),
};

// In component
const isLoggedIn = useStoreState(state => state.model.isLoggedIn)

And a hook like this:

function useIsLoggedIn(){
  const user = useStoreState(state => state.model.user)
  return user !== null
}

// In component
const isLoggedIn = useIsLoggedIn()

Another question:

The documentation says that computed values might not be up-to-date when accessed from actions. Is this also true when the values are accessed from thunks?

Hi @KevinMusgrave !

What are the differences between using computed and a hook

In your specific example, the difference is nothing - other than using store state directly (via the hook) or using a computed property.

However, there is a caveat to computed properties when this is based solely on data from an external store. But in your example, this is not an issue 👍

The documentation says that computed values might not be up-to-date when accessed from actions. Is this also true when the values are accessed from thunks?

I am not 100% certain, but I would assume this is also the case for thunks. If your thunk changes any state (via actions), computed props based on the changed state might not be updated within the thunk.

Hope that answers your questions 😄

Thanks for the fast reply!

I guess one advantage of computed is that it can be accessed within actions and thunks, whereas the hook cannot be, because the hook can only be used within components or other hooks.

This brings me to a broader question: what is the best way to re-use complex logic within actions and thunks, without the caching limitation of computed?

For example:

function useComplicatedValue(id){
  const x = useStoreState(state => state.a.foo[id])
  const y = useStoreState(state => state.b.bar[x])
  return useStoreState(state => state.c.baz[y])
}

This is a hook, so it can't be used inside actions and thunks. On the other hand, if I make it a computed property, then I can use it within actions and thunks. But only the cached value will be available, and I feel like that could introduce hard-to-detect bugs.

Is there another approach?

This is a hook, so it can't be used inside actions and thunks. On the other hand, if I make it a computed property, then I can use it within actions and thunks. But only the cached value will be available, and I feel like that could introduce hard-to-detect bugs.

Well it might be in some cases - and it all depends on your store. I would suggest you to test out if computed props are working in your case. If it works - great, if it doesn't, you can get around it via the solution below.

Is there another approach?

Computed props are just derived of state. If you are worried that you might get cached computed props, you could extrapolate the "compute" generator and reuse it within your action/thunk to regenerate the data based on the current state.

const model = {
  comptutedProp: computed((state) => generateComplexData(state))
  myAction: action((state) => {
    const data = generateComplexData(state)
    // etc..
  })
}

const generateComplexData = (state) => { /* extract from state */ }

Ok that seems like a reasonable approach.

I'm also wondering about re-renders using computed. The documentation says "Any updates to our computed property, i.e. the input state to our computed property changed, will automatically re-render your component."

How does it determine if the input state has changed? If the input state is an object, will the input always be considered "changed" and cause excessive re-renders?

I'm able to pass a custom equality function to useStoreState, but not to computed.

How does it determine if the input state has changed? If the input state is an object, will the input always be considered "changed" and cause excessive re-renders?

The input is deep-diffed based on the previous previous input.

The computed property is also checked and only updated if it has changed, to prevent excessive renders.

see #764 for more details

I'm able to pass a custom equality function to useStoreState, but not to computed.

State resolvers provides the input to the computed props. Equality checks are handled internally.

Read about state resolvers for computed props here.