Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React redux normalizr: how to deal with nested entities?

Let's say I have a structure like this, which is the result of one fetch from an API and using "normalizr" on my entities:

entities: {
    users:{
        1: {
            name: 'John',
            posts: [ 1, 4 ]
        }
    },
    posts: {
        1: {
            name: 'First Post',
        },
        4: {
            name: 'Second Post',
        }
    }
}

Now I have a method that filters the posts by user, which would do basically this:

let filteredPosts = {};
entities.users.posts.forEach(key => {
    if(posts.hasOwnProperty(key))
        filteredPosts[key] = posts[key]
});

And a page that shows the posts from that user, for example:

render() {
    return(
        <div>
            {Object.keys(filteredPosts).map(key => {
                return (
                    <div>{filteredPosts[key].name}</div>
                );
            })}
        </div>
    )
}

My entities reducer is very simple:

import { merge } from 'lodash';
...
function entities(state = { users: {}, posts: {} }, action) {
    if (action.response && action.response.entities) {
        return merge({}, state, action.response.entities);
    }
    return state;
}

Now if I make a request to the API to add a post for that user, returning the newly created post record, that record will be automatically added to the posts on my entities.

How would I deal with updating the user to reflect that change, so that the user now has 3 posts, with the new post id in the array?

Should I create a reducer and listen to the post creation action, and update the state.entities.users.posts in there? Refetching the entities doesn't seem like an option. What would be the best way to go about that?

Thanks

UPDATE:

This is the solution I'm having to use right now to keep the data consistent. I modify my response to account for the created post id. Iknow This could be decomposed into multiple reducers, but I'm still wondering if there's a better and more direct approach where I wouldn't have to do this for every nested entity.

function entities(state = {}, action) { 
    ...

    if(action.type === 'POST_ADD_SUCCESS') {
        // Get the user id from the created post
        let userId = response.entities.posts[response.result].userId;
        // Add the user with all his posts to the response
        response.entities.users = {
            [userId]: {
                posts: [...state.users[userId].posts, response.result]
            }
        }
    }
    ...
    // Merge normally
    return merge({}, state, response.entities);
}
like image 801
Thomas Monte Avatar asked Nov 09 '22 21:11

Thomas Monte


1 Answers

Your updated snippet looks mostly along the right path, although I'm not sure why you're still referencing "response" in there. Your action creator function should probably be grabbing the user ID and post ID, and when the AJAX call succeeds, create an action that looks like {type : POST_ADD_SUCCESS, userId : userId : postId}. In other words, the reducer shouldn't know anything about a "response" at all, just that it should add post ID 'x' to the list for user ID 'y'.

like image 149
markerikson Avatar answered Nov 15 '22 07:11

markerikson