Search code examples
reactjsreduxredux-thunk

Redux State Change: UI only updates if i'm on rootPath


I have a Reddit alike application. Where I'm trying to build out a voting function. I thought I had solved it because it workes great when on /

However, if I'm entering a different path /:category :/category/:id I can see a dispatch being sent on click but here I'll have to "force update" (f5) to see a UI change.

API file

export const submitPostVote = (option, id) => {
    return axios.post(`${API_URL}/posts/${id}`, {option}, { headers })
}

Action Creator (using redux-thunk)

export function postPostVote(option, id) {
    const request = API.submitPostVote(option, id)

    return (dispatch) => {
        request.then(({data}) => {
            dispatch({type: SUBMIT_POST_VOTE, payload: data})
        });
    };
}

Reducer

export default function(state = {}, action) {
  const {payload} = action
  switch (action.type){
        case SUBMIT_POST_VOTE:
            return {...state, [payload.id]: payload}

Component that use it

import { postPostVote } from '../actions';

<span
 className='fa fa-angle-up voteArrow'
 onClick={() => this.props.postPostVote('upVote', id)}
></span>

export default connect(null, {postPostVote})(PostComponent);

Imported as following in other components

import PostComponent from './Post_PostComponent';
<div>
                <Container>
                    <PostComponent
                        key={id}
                        id={id}
                        title={title}
                        author={author}
                        voteScore={voteScore}
                        category={category}
                        timestamp={timestamp}
                        redirect={false}
                    />
                </Container>
            </div>

Repo

Readable Repo


Solution

  • I think i see your problem, you are using the same reducer to both of the pages.

    1. The page that holds a list of items, in this case the reducer shape is an object that each key is an id of item and it's an object as well that holds all the data of this item.
      { '6ni6ok3ym7mf1p33lnez': { author: "thingone", body: "Just kidding. It takes more than 10 minutes to learn technology.", category: "redux", deleted: false, id: "6ni6ok3ym7mf1p33lnez", timestamp: 1468479767190, title: "Learn Redux in 10 minutes!", voteScore: 6 } }

    2. The page that holds a single item, in this case the very same reducer needs to deal with a different shape of object where all of the item's properties are spread.

    { author: "thingone", body: "Just kidding. It takes more than 10 minutes to learn technology.", category: "redux", deleted: false, id: "6ni6ok3ym7mf1p33lnez", timestamp: 1468479767190, title: "Learn Redux in 10 minutes!", voteScore: 6 }

    Just for example, if you will change the shape of the object that your reducer_posts.js returns:
    From this:

    case SUBMIT_POST_VOTE:
                return {...state, [payload.id]: payload}
    

    To this:

    case SUBMIT_POST_VOTE:
                return {...state, ...payload}  
    

    You will notice that now the first page with the list not working well but the second page that shows the single item is working as expected.

    So you should re-think the shape of your reducers or split this reducer into two.

    EDIT
    I was curious on what will be the best structure to handle this scenario so i took the liberty of changing some stuff for you.

    So I've decided to split your reducer_posts.js into 2 reducers:
    posts and post (plural and singular). I've added another reducer reducer_post.js.
    and this is the code:

    import { POST_GET_POST, SUBMIT_POST_VOTE } from '../actions/action_constants';
    
    export default function (state = {}, action) {
        const { payload } = action
        switch (action.type) {
            case SUBMIT_POST_VOTE:
                return { ...state, ...payload }
            case POST_GET_POST:
                return payload;
            default:
                return state;
        }
    }
    

    And the old reducer reducer_posts.js now looks like this:

    import _ from 'lodash';
    import { POST_GET_POSTS, SUBMIT_POST_VOTE } from '../actions/action_constants';
    
    export default function(state = {}, action) {
      const {payload} = action
      switch (action.type){
            case SUBMIT_POST_VOTE:
                return {...state, [payload.id]: payload}
            case POST_GET_POSTS:
          return _.mapKeys(payload, 'id');
        default:
          return state;
      }
    }
    

    And of course don't forget to add it to the rootReducer:

    export const rootReducer = combineReducers({
      routing: routerReducer,
      posts: PostsReducer,
      post: PostReducer, // our new reducer
        comments: CommentReducer,
      categories: CategoriesReducer,
    });
    

    Basically one will handle multiple posts object shape and one will handle a single post object shape.

    Now, the only thing you should change is the mapStateToProps in the Post_DetailedPost.js component.
    Instead of using the posts reducer it will use the post reducer:

    function mapStateToProps(state) {
        return {post: state.post}
    }  
    

    This should fix your problem.