Search code examples
javascriptreactjsreduxredux-thunkredux-observable

Callbacks using redux-thunk / redux-observable with redux


I am learning how redux works but its a lot of code to do simple things. For example, I want to load some data from the server before displaying. For editing reasons, I can't simply just use incoming props but I have to copy props data into the local state.

As far as I've learned, I have to send a Fetch_request action. If successful, a fetch_success action will update the store with new item. Then updated item will cause my component's render function to update.

In component

componentWillMount() {
  this.props.FETCH_REQUEST(this.props.match.params.id);
}
...

In actions

export function FETCH_REQUEST(id) {
  api.get(...)
    .then(d => FETCH_SUCCESS(d))
    .catch(e => FETCH_FAILURE(e));
}
...

In reducer

export function FETCH_REDUCER(state = {}, action ={}) {
  switch (action.type) {
    case 'FETCH_SUCCESS':
      return { ...state, [action.payload.id]: ...action.payload }
  ...
}

Back in component

this.props.FETCH_REDUCER
// extra code for state, getting desired item from...

Instead, can I call a react-thunk function and pass some callback functions? The react-thunk can update the store and callbacks can change the component's local state.

In component

componentWillMount() {
  this.props.FETCH_REQUEST(this.props.match.params.id, this.cbSuccess, this.cbFailure);
}
cbSuccess(data) {
  // do something
}
cbFailure(error) {
  // do something
}
...

In action

export function FETCH_REQUEST(id, cbSuccess, cbFailure) {
  api.get(...)
    .then(d => {
      cbSuccess(d);
      FETCH_SUCCESS(d);
    }).catch(e => {
      cbFailure(d);
      FETCH_FAILURE(e);
    });
}
...

Is this improper? Can I do the same thing with redux-observable?


UPDATE 1

I moved nearly everything to the redux store, even for edits (ie replaced this.setState with this.props.setState). It eases state management. However, every time any input's onChange fires, a new state is popping up. Can someone confirm whether this is okay? I'm worried about the app's memory management due to redux saving a ref to each state.


Solution

  • First of all, you should call your API in componentDidMount instead of componentWillMount. More on this at : what is right way to do API call in react js?

    When you use a redux store, your components subscribe to state changes using the mapStateToProps function and they change state using the actions added a props through the mapDispatchToProps function (assuming you are using these functions in your connect call). So you already are subscribing to state changes using your props. Using a callback would be similar to having the callback tell you of a change which your component already knows about because of a change in its props. And the change in props would trigger a re-render of the component to show the new state.

    UPDATE: The case you refer to, of an input field firing an onChange event at the change of every character, can cause a lot of updates to the store. As mentioned in my comments, you can use an api like _.debounce to throttle the updates to the store to reduce the number of state changes in such cases. More on handling this at Perform debounce in React.js.

    The issue of memory management is a real issue in real world applications when using Redux. The way to reduce the effect of repeated updates to the state is to

    1. Normalize the shape of state : http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
    2. Create memoized selectors using Reselect (https://github.com/reactjs/reselect)
    3. Follow the advice provided in the articles regarding performance in Redux github pages (https://github.com/reactjs/redux/blob/master/docs/faq/Performance.md)

    Also remember that although the whole state should be copied to prevent mutating, only the slice of state that changes needs to be updated. For example, if your state holds 10 objects and only one of them changes, you need to update the reference of the new object in the state, but the remaining 9 unchanged objects still point to the old references and the total number of objects in your memory is 11 and not 20 (excluding the encompassing state object.)