Chris Chris - 1 month ago 12
React JSX Question

Strategy for data management in React

Context / Background



In a few projects now, I seem to face a common problem, and I still don't feel like I have created a solid way of tackling it.

I am using React, Redux, and React Router.

Current state



In it's simplest, I have a page, with a series of saveable forms all of which can exist in both a view mode and an edit mode:

+--------------------------+
| EDIT MODE |
+--------------------------+
| |
| <input name="A"/> |
| <input name="B"/> |
| |
| <button label="save"/> |
+--------------------------+
| |
| <input name="C"/> |
| <input name="D"/> |
| |
| <button label="save"/> |
+--------------------------+

+--------------------------+
| VIEW MODE [edit] |
+--------------------------+
| readonly A |
| readonly B |
| |
| readonly A |
| readonly B |
+--------------------------+


All the forms are wrapped up in a Request component, which takes a endpoint prop, and on mount will fetch the data. Once the request is complete it will render its children.

Sample page: url:
owner/1/pets


const ownerId = props.params.id;

<Request endpoint={`api/owner/{ownerId}/pets`}>
<OwnerDetails id={ownerId} />
<Pets ownerId={ownerId} />
}}
</Request>


Reducer after request succeeds (noting already normalised):

{
"tables": {
"pets": {
"1": { "id":"1", "name":"max", "owner": 1 },
"2": { "id":"2", "name":"yella", "owner": 1 }
},
"owner": {
"1": { "id":"1", "name": "jim" }
}
}
}


Then each Form is a connected component, and knows the slice of state it needs based on a simple passed in id.

Each Form also has an onChange action, that gets passed to each form input. onChange updates a "draft" slice of state.

So when the user types in the following input while in edit mode of pet 1:



The full state looks like:

{
"tables": {
"pets": {
"1": { "id":"1", "name":"max", "owner": 1 },
"2": { "id":"2", "name":"yella", "owner": 1 }
},
"owner": {
"1": { "id":"1", "name": "jim" }
}
},
"draft": {
"pets": {
"1": { "name": "new name" },
}
}
}


Actions / Reducers



onChange => # updates the draft slice of state
save => # sends all data in draft to the server to be saved, upon being saved, the draft slice for that particular form is emptied, and table slice is updated with new data - in other words, the table slice is always the latest reflection of the server.


Connected forms will grab and merge table state with draft state to "get the full picture" when in edit mode. So if we're at the current reducer state above, and looking at pet 1, the connected form will grab all of tables.pets.1 data, then merge over the top draft.pets.1 data. In view mode, it only needs to grab the tables data (draft data is ignored in view mode).

The problem



This feels somewhat overcooked. It feels awkward to extract and merge draft and table data, even when using selectors. The problems of updating and pulling data from store slices of state are exacerbated when forms can have nested elements.

The question



What is a more concrete strategy of dealing with complex, multiple forms that can exist in a draft and state?

Answer

That seems like a fairly solid approach overall. It's actually pretty close to what I'm doing in my own app, although I don't have to "merge" data per se - all the data related to the item being edited gets copied to the "draft" slice when I start editing.

But yes, that approach looks good to me.

edit

Update: I've published a couple posts on Redux-ORM as the first two parts of a series on "Practical Redux", discussing techniques I've developed from my own experience with Redux. I plan to discuss the "draft editing" concept in a later post.