4

I have several actions which use the same reducer, and instead of having a dom operation in each of those actions, I want to just add it once inside my shared reducer. I know reducers are to be pure (which the returned data still is), but is this some kind of anti-pattern or an acceptable strategy?

 case APPEND_POSTS:
      !payload.length &&
        document.getElementById('posts-cont').classList.add('no-more-posts'); // this 
      const total = state.posts.length + payload.length;
      const limit = total > posts_to_keep_limit ? 50 : 0;
      return {
        ...state,
        posts: [...state.posts.slice(limit), ...payload],
        loading: false,
      };
    ```
Jim G.
  • 15,141
  • 22
  • 103
  • 166
ram
  • 680
  • 5
  • 15
  • 1
    it looks bad... you should change the class with state – kyun Aug 19 '20 at 05:13
  • 2
    DOM operations should (98% of the cases) be avoided even in components, let alone in reducers. If some effect must be run alongside actions, put them inside action creators or as middlewares. – Rodrigo Amaral Aug 19 '20 at 05:42

3 Answers3

4

Redux Action

 case APPEND_POSTS:
      // you don't need to use below code.
      // !payload.length && document.getElementById('posts-cont').classList.add('no-more-posts'); // this 
      const total = state.posts.length + payload.length;
      const limit = total > posts_to_keep_limit ? 50 : 0;
      return {
        ...state,
        posts: [...state.posts.slice(limit), ...payload],
        nomore: true,
        loading: false,
      };

Your component.

function YourComp(props){
  const state = useSelector(...); 

  return ( <div id="posts-cont" className={state.nomore ? 'no-more-posts' : ''} > {...}</div>

 
} 
kyun
  • 9,710
  • 9
  • 31
  • 66
3

I know reducers are to be pure (which the returned data still is), but is this some kind of anti-pattern or an acceptable strategy?

The returned data is pure, but you've introduced a side-effect in the form of a DOM mutation. Therefore, this reducer is not pure.

This is indeed an anti-pattern because now, the component(s) that render posts-cont items have an invisible coupling to this reducer. It makes your codebase more difficult to read and debug.

Jim G.
  • 15,141
  • 22
  • 103
  • 166
1

jinongun's advice is good: let the className of the component derive its value from the store's state using a selector. AS for the general question

I have several actions which use the same reducer, and instead of having a dom operation in each of those actions, I want to just add it once inside my shared reducer.

DON'T EVER make DOM operations inside a reducer. Don't ever make any operation that is not a pure computation.

But you can create an action creator that always calls a side effect (with Redux-Thunk):

function appendPosts(payload) {
  return dispatch => {
    mySideEffect()

    dispatch({
      type: APPEND_POSTS,
      payload
    })
  }
}

function action1(params) {
  return dispatch => {
    dispatch({
      type: ACTION1,
      payload: params
    })    

    dispatch(appendPosts(params))
  }
}

function action2(params) {
  return dispatch => {
    dispatch({
      type: ACTION2,
      payload: params
    })    

    dispatch(appendPosts(params))
  }
}

// etc
Rodrigo Amaral
  • 1,324
  • 1
  • 13
  • 17