facebookarchive / redux-react-hook

React Hook for accessing state and dispatch from a Redux store
MIT License
2.16k stars 103 forks source link

How to pass values from redux store to useCallback as dependency arguments #36

Closed AlexanderLoshkarov closed 5 years ago

AlexanderLoshkarov commented 5 years ago

Hi Lads, Sorry, if this is wrong place to ask the question, please point me to the correct one. In my case I end up with situation when I have to update my property depend on the changes happened to one of the store values. Was looking through the tutorial and didn't find any solutions for this kind of cases. After digging a little bit deeper into original redux api I found that I can subscribe to the store directly and getState() outside of useCallback, read the necessary value and provide it as a useCallback dependency argument. But in that case, seems like it makes the useMappedState() redundant... Could you please help me to find the right way to sort this?

ianobermiller commented 5 years ago

when I have to update my property

What do you mean by this? Are you updating some piece of UI? Or another variable in your component?

AlexanderLoshkarov commented 5 years ago

@ianobermiller yes, I'm updating UI.

AlexanderLoshkarov commented 5 years ago

@ianobermiller here is the simplified code which might clarify my intents

const mapState = useCallback( state => { //...code to read node from the store const value = getValue(node.path)(state); return { value: value, } } ,[ getValue(node.path)(store.getState()) ] );

AlexanderLoshkarov commented 5 years ago

@ianobermiller
in the application I have different inputs as custom components who constantly speaking to the server via persistent connection. So they send their input values and server processes them and respond with validation or calculation results as a commands which then dispatched to the store and updates those values matching their path/key. So I need to react on those values updates coming from the server and dispatched to redux, but I can't because useCallback is limited to the arguments from outside...

In other words I kinda don't understand the situation we limiting to read store updates by the arguments which might not belong to the store at all. I was thinking about the store like an observable collection to which we subscribe for updates receiving, but seems like now it is not.

ianobermiller commented 5 years ago

Can you create a simple codesandbox demonstrating the issue? Another way to explain this that might help is showing how you would solve this with react-redux.

AlexanderLoshkarov commented 5 years ago

@ianobermiller Thanks for your response Ian. I eventually found the work around. But it still looks a little bit weird/awkward to use useState to sort that issue. Please see updated example. Mostly (Input.tsx) Edit alex-example

ianobermiller commented 5 years ago

I'm still not really sure what you want to accomplish. Copying from Input.tsx:

  const [localStateValue, setLocalStateValue] = useState("");

  useMappedState(
    useCallback(
      reduxStoreState => {
        const reduxStoreValue = reduxStoreState.processedValue;
        if (localStateValue.value !== reduxStoreValue.value) {
          setLocalStateValue(reduxStoreValue);
        }
      },
      [localStateValue.value] // I wish I can have an access to reduxStoreState.processedValue here
      //to pass it's value as a dependency
    )
  );

useMappedState returns the value you should be using directly, and takes care of not re-rendering if that value hasn't changed. Your code could be as simple as:

  const localStateValue = useMappedState(useCallback(
      reduxStoreState => reduxStoreState.processedValue,
      [],
  ));
ianobermiller commented 5 years ago

How to pass values from redux store to useCallback as dependency arguments

It doesn't really make sense to do this -- the entire point of the callback is to select something from the store, so how could the callback itself be based on the store values? You can certainly select things from the store with useMappedState and then put them in the dependency array of another hook.

I'm going to close right now, but I will reopen if our discussion turns up something actionable.

AlexanderLoshkarov commented 5 years ago

I'm still not really sure what you want to accomplish. Copying from Input.tsx:

  const [localStateValue, setLocalStateValue] = useState("");

  useMappedState(
    useCallback(
      reduxStoreState => {
        const reduxStoreValue = reduxStoreState.processedValue;
        if (localStateValue.value !== reduxStoreValue.value) {
          setLocalStateValue(reduxStoreValue);
        }
      },
      [localStateValue.value] // I wish I can have an access to reduxStoreState.processedValue here
      //to pass it's value as a dependency
    )
  );

useMappedState returns the value you should be using directly, and takes care of not re-rendering if that value hasn't changed. Your code could be as simple as:

  const localStateValue = useMappedState(useCallback(
      reduxStoreState => reduxStoreState.processedValue,
      [],
  ));

Hi Ian, thanks! Your example is working well, I tried similar thing before, but seems like there was a bug in my sendbox, the input value was not changing, and I mistakenly thought that this happens because we supply an empty array as a dependency argument to the useCallback.

AlexanderLoshkarov commented 5 years ago

@ianobermiller Just want to clarify one last thing, that "connect HOC" from "react-redux" works more precise, because it composes in right direction. It happens that in our app we remove the parent node from the store to notify the application that edit form must be closed. So in JSX we check if the node does not exist we render null. That makes "react" to remove components tree from the DOM, and because "connect HOC" is a part of that tree it also safely unsubscribes from the store (I hope) , and does not react on store updates coused by parent node removal. This works differently for hooks. Because hooks do not participate in the components composition (not composed/nested into components tree, like "connect HOC") they're reacting to the store update earlier than components removal happened. That leads to one more redundant cycle and extra problem, like in our case we need to check if the parent exists or make a safer code, just not to get null pointer exception.

I have updated, the previous example, just to clarify that situation. Please feel free to share your opinion on that! Is there another way how to eliminate this redundant callback?

ianobermiller commented 5 years ago

Sounds like you may be referring to top-down data flow, which this hook does not implement: https://github.com/facebookincubator/redux-react-hook#how-does-this-compare-to-react-redux