Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to handle tree-shaped entities in Redux reducers?

Tags:

I'm a bit stuck thinking on how to implement a reducer where its entities can have children of the same type.

Let's take reddit comments as an example: each comment can have child comments that can have comments themselves etc. For simplification reason, a comment is a record of type {id, pageId, value, children}, with pageId being the reddit page.

How would one model the reducer around that? I was thinking of having the reducer be a map -> id of the comments where you can filter by page using the pageId.

The issue is that for example when we want to add a comment to a nested one: we need to create the record on the root of the map and then add its id in the parent children property. To display all the comments we'd need to get all of them, filter those that we have at the top (that would be kept in the page reducers as an orderedList for example) and then iterate on them, fetching from the comments objects when we encounter children using recursion.

Is there a better approach than that or is it flawed?

like image 947
Vincent P Avatar asked Sep 26 '15 14:09

Vincent P


People also ask

What is normalized state in Redux?

The basic concepts of normalizing data are: Each type of data gets its own "table" in the state. Each "data table" should store the individual items in an object, with the IDs of the items as keys and the items themselves as the values. Any references to individual items should be done by storing the item's ID.

Can we have multiple reducers in Redux?

It turns out that Redux lets us combine multiple reducers into one that can be passed into createStore by using a helper function named combineReducers . The way we combine reducers is simple, we create one file per reducer in the reducers directory.

Should you keep all component states in the Redux store?

Some users prefer to keep every single piece of data in Redux, to maintain a fully serializable and controlled version of their application at all times. Others prefer to keep non-critical or UI state, such as “is this dropdown currently open”, inside a component's internal state. Using local component state is fine.

How do I change my global state in Redux?

The way to set global state is by passing the value to the action creator, which returns it in the action. The reducer gets it in the action object and saves it. There are many simple examples available.


1 Answers

The official solution to this is to use normalizr to keep your state like this:

{   comments: {     1: {       id: 1,       children: [2, 3]     },     2: {       id: 2,       children: []     },     3: {       id: 3,       children: [42]     },     ...   } } 

You're right that you'd need to connect() the Comment component so each can recursively query the children it's interested in from the Redux store:

class Comment extends Component {   static propTypes = {     comment: PropTypes.object.isRequired,     childComments: PropTypes.arrayOf(PropTypes.object.isRequired).isRequired   },    render() {     return (       <div>         {this.props.comment.text}         {this.props.childComments.map(child => <Comment key={child.id} comment={child} />)}       </div>      );   } }  function mapStateToProps(state, ownProps) {   return {     childComments: ownProps.comment.children.map(id => state.comments[id])   }; }  Comment = connect(mapStateToProps)(Comment); export default Comment; 

We think this is a good compromise. You pass comment as a prop, but component retrieves childrenComments from the store.

like image 141
Dan Abramov Avatar answered Nov 30 '22 07:11

Dan Abramov