c99
c99

Reputation: 33

React Hooks & React-Redux connect: What is the best practice to make them work together?

I would like to ask to the community what is the best practice to combine existing code using react-redux connect() and dependency management in hooks like useEffect().

Let's look at the following example:

/* ... */

const mapStateToProps = (state) => ({
  todos: getTodos(state),
  currentUserId: getCurrentUserId(state)
})

const mapDispatchToProps = (dispatch) => ({dispatch})

const mapMergeProps = (stateProps, {dispatch}, ownProps) => {
  return {
    ...stateProps,
    ...dispatchProps,
    ...ownProps,
    fetchTodos: () => dispatch(fetchTodos(stateProps.currentUserId))
  }
}

const TodosListContainer = connect(mapStateToProps, mapDispatchToProps, mapMergeProps)

const TodosList = ({todos, fetchTodos, ...props}) => {

  const _fetchTodos = useCallback(fetchTodos, [])

  useEffect(() => {
    _fetchTodos()
  }, [_fetchTodos])

  return (
    <ul>
      {todos && todos.map((todo) => <li key={todo.id}>{todo.name}</li>)}
    </ul>
  )
}

In the code above useEffect has all the dependencies, and useCallback ensures that useEffect is triggered only once.

It seems however like an extra layer of boilerplate: Without useCallback, the functions passed down as props that are coming from connect's mapMergeToProps will be recreated on every change of state, and will trigger useEffect.

My question is whether the code above is correct, and there's a better way to handle useEffect in the context described.

Upvotes: 3

Views: 1043

Answers (1)

Jap Mul
Jap Mul

Reputation: 18739

I would change it to the following

// Leave this one like it is
const mapStateToProps = (state) => ({
  todos: getTodos(state),
  currentUserId: getCurrentUserId(state)
})

// Map the fetchTodos here
const mapDispatchToProps = {
  fetchTodos,
}

// Completely remove the mergeProps
const TodosListContainer = connect(mapStateToProps, mapDispatchToProps)

// Then use the hook like this
const TodosList = ({ todos, fetchTodos, currentUserId, ...props }) => {

  useEffect(() => {
    fetchTodos(currentUserId)
  }, [fetchTodos, currentUserId])

  return (
    <ul>
      {todos && todos.map((todo) => <li key={todo.id}>{todo.name}</li>)}
    </ul>
  )
}

This will not trigger useEffect on each render because the props stay the same. It removes (some of the) boilerplate code. I also think it makes it easier to understand what is going and it will run the effect when the currentUserId changes, which is probably what is meant to happen.

Upvotes: 3

Related Questions