How deep do I need to clone my state when only updating parts of the state? - reactjs

Until recently, I've always just used lodash's cloneDeep to make a copy of my state, then change values and return the cloned state. For example like this:
This would be my initial state:
{
"id": 1213,
"title": "Some title...",
"pages": {
"page1": {
"id": 459,
"title": "Some Page title...",
"fields": {
"field_1": {
"title": "My field",
"type": "text",
"value": "my text value..."
},
"field_2": {
"title": "My field 2",
"type": "text",
"value": "my text two value..."
},
"field_3": {
"title": "My field 3",
"type": "text",
"value": "my text value..."
}
}
}
}
}
Now, I want to update the value of field_2.
My redux reducer would look like this:
import cloneDeep from 'lodash/fp/cloneDeep';
export default function reducer(state, action) {
const {type, payload} = action;
switch (type) {
case 'UPDATE_FIELD_VALUE': {
const { pageIdent, fieldIdent, newValue } = payload;
// This is what I'm doing right now....
const newState = cloneDeep(state);
newState.pages[pageIdent]fields[fieldIdent]value = newValue;
return newState;
// Instead could I do this?
return {
...state,
state.pages[pageIdent]fields[fieldIdent]value = newValue;
}
}
}
}
So, I've read that I don't always have to do deep clone...but in other places I've read that you cannot return the same object, you have to return new objects at all times. So what is the right way to do this?

Yeah, don't do that. Quoting the Redux FAQ on whether you should deep-clone state:
Immutably updating state generally means making shallow copies, not deep copies. Shallow copies are much faster than deep copies, because fewer objects and fields have to be copied, and it effectively comes down to moving some pointers around.
However, you do need to create a copied and updated object for each level of nesting that is affected. Although that shouldn't be particularly expensive, it's another good reason why you should keep your state normalized and shallow if possible.
Common Redux misconception: you need to deeply clone the state. Reality: if something inside doesn't change, keep its reference the same!
So, you don't want "deep clones", you need "nested shallow clones".
Deep-cloning is bad for performance in two ways: it takes more work to clone everything, and the new object references will cause UI updates for data that didn't actually change in value (but the new references make the UI think that something changed).
You should read the Redux docs page on "Immutable Update Patterns". Here's the nested state update example from that page:
function updateVeryNestedField(state, action) {
return {
....state,
first : {
...state.first,
second : {
...state.first.second,
[action.someId] : {
...state.first.second[action.someId],
fourth : action.someValue
}
}
}
}
}
If you find that to be too tedious or painful, you should either change how you structure your state so it's flatter, or you can use one of the many immutable update utility libraries out there to handle the update process for you.

You really shouldn't always do a clone of the state object. Redux shines if you can ensure that:
The only section of state that changes is the section that should change, based on the action provided to the reducer. So if you're updating field_1, nothing about field_2 should change.
If there's a section of state that should change, it always changes. So if your action updates field_2, then the field_2 object reference should change.
This is much easier if your state allows for 'deep' updates. State normalization is one of the better 'patterns' used in redux apps and is described in the docs.
For instance, let's restructure that state of yours a bit (assuming the top level is a book object, and the field IDs are globally unique):
Initial state
"books" : {
"1213": {
"id": 1213,
"title": "Some title...",
"pages: [..., "page1", ...],
}
},
"pages": {
"page1": {
"id": 459,
"title": "Some Page title...",
"fields": [..., "field_1", "field_2", "field_3", ...],
}
},
"fields": {
"field_1": {
"title": "My field",
"type": "text",
"value": "my text value..."
},
"field_2": {
"title": "My field",
"type": "text",
"value": "my text value..."
},
"field_3": {
"title": "My field",
"type": "text",
"value": "my text value..."
}
}
Note that each 'book' entity has a list of page IDs rather than a full objects nested in it. Similarly, each page has a list of field IDs rather than the actual fields. This way all your data-carrying entities are stored at the 'top-level' of state and can be independently updated without touching the entire state.
By flattening your state structure, you can create 'sub-reducers' that are only concerned with a small slice of your state. In the above case, I'd have:
import { combineReducers } from 'redux';
// This reducer handles all actions that affect book entities
const books = (state = {}, action = {}) => state;
// This reducer handles all actions that affect page entities
const pages = (state = {}, action = {}) => state;
// This reducer handles all actions that affect field entities.
// For your problem, this would look like:
const fields = (state = initialFields, action = {}) => {
switch (action.type) {
case 'UPDATE_FIELD_VALUE':
return {
...state,
[fieldIdent]: {
...state[fieldIdent],
value: newValue,
}
}
default:
return state;
}
}
// This results in the state structure above
const reducer = combineReducers({
books,
pages,
fields,
});
In the above code, changing a field has no effect on the page or book entities, which prevents unnecessary re-renders. That being said, changing the field_2 value will definitely result in a new field_2 object, and re-render as necessary.
There are libraries that help with structuring your state like this, from JSON API response data. This is a rather good one: https://github.com/paularmstrong/normalizr
Hope this helps!

I think facebook's immutable js solve all your problems. Read docs here

Related

React update values only once

I have a newb question :)
I have a modal that opens in React Native with a dropdown select that requires values. I want to calculate the values whenever the modal opens.
let pickupTime; // Set a value that can be overwritten. I'm not using State because I want this value to change whenever I open the modal again.
const pickupTimeOptions = useRef([{ label: "", value: "" }]); // A ref to store the values
useEffect(() => {
const pickup_hours_today = business.pickup_hours_today; // Array of strings I pass to the modal.
console.log("pickup_hours_today", pickup_hours_today);
const options = pickup_hours_today.map((time) => {
return {
label: time,
value: time,
};
});
pickupTimeOptions.current = options;
}, [business.pickup_hours_today]);
console.log("pickupTimeOptions", pickupTimeOptions); // Let's see if we got it
The problem is that the ref never updates. The log prints this:
pickupTimeOptions Object {
"current": Array [
Object {
"label": "",
"value": "",
},
],
}
pickup_hours_today Array [
... // the string array of hours
]
Should be updating the ref
pickupTimeOptions Object {
"current": Array [
Object {
"label": "",
"value": "",
},
],
}
pickup_hours_today Array [
...
]
Should be updating the ref
What am I doing wrong? Should I handle this differently? I don't mind using state, but when I tried, it kept updating it whenever I selected a different values with the dropdown picker.
If you look at the order of console logs, it'll explain what's happening.
This is printed first, meaning calculation in useEffect hasn't happened yet
console.log("pickupTimeOptions", pickupTimeOptions); // Let's see if we got it
According to the documentation useEffect is only called after the render. You need to do the calculation before or during the render cycle.
You can use useMemo which is executed during rendering. Refer to the documentation for more details
Your updated code should look something like this
let pickupTime; // Set a value that can be overwritten. I'm not using State because I want this value to change whenever I open the modal again.
const pickupTimeOptions = useMemo(() => {
const pickup_hours_today = business.pickup_hours_today; // Array of strings I pass to the modal.
console.log("pickup_hours_today", pickup_hours_today);
const options = pickup_hours_today.map((time) => {
return {
label: time,
value: time,
};
});
return options;
}, [business.pickup_hours_today]);
console.log("pickupTimeOptions", pickupTimeOptions); // Let's see if we got it

How to model recursively nested data in state

I have a data structure typed like:
export interface IGroup {
id: number;
name: string;
groupTypeId: number;
items: IItem[];
groups: IGroup[];
}
Which recursively represents many to many relationships between a "Group" and a "Group" and an "Group" and an "Item". Groups are made up of items and child groups. An item derives to just a simple type and other meta data, but can have no children. A single group represents the top of the hierarchy.
I currently have components, hooks, etc to recursively take a single group and create an edit/create form as shown below:
I have this form "working" with test data to produce a standard data output as below on save:
{
"1-1": {
"name": "ParentGroup",
"groupType": 2
},
"2-4": {
"name": "ChildGroup1",
"groupType": 1
},
"2-9": {
"name": "ChildGroup2",
"groupType": 3
},
"2-1": {
"itemType": "FreeForm",
"selectedName": "Testing",
"selectedClass": 5
},
"2-2": {
"itemType": "FreeForm",
"selectedName": "DisplayTest",
"selectedClass": 5
},
"3-4": {
"itemType": "EnumValue",
"selectedItem": {
"id": 12900503,
"name": "TRUE"
}
},
"3-5": {
"itemType": "EnumValue",
"selectedItem": {
"id": 12900502,
"name": "FALSE"
}
},
"3-9": {
"itemType": "FreeForm",
"selectedName": "Test",
"selectedClass": 5
},
"3-10": {
"itemType": "FreeForm",
"selectedName": "Tester",
"selectedClass": 5
},
"3-11": {
"itemType": "FreeForm",
"selectedName": "TestTest",
"selectedClass": 5
}
}
The "key" to these objects are the grid column and row since there are no other guaranteed unique identifiers (if the user is editing, then it is expected groups have ids in the db, but not if the user is adding new groups in the form. Otherwise, the name is an input form that can be changed.) It makes sense and it is easy to model the keys this way. If another group or item is added to the hierarchy, it can be added with its column and row.
The problem that I have is that I would love to be able to have an add button that would add to a groups items or group arrays so that new rows in the hierarchy could be created. My forms should handle these new entries.
Ex.
"1-1": {
groups: [..., {}],
items: [..., {}]
}
But the only data structure that I have is the IGroup that is deeply nested. This is not good for using as state and to add to this deeply nested state.
The other problem I have is that I need to be able to map the items and groups to their position so that I can translate to the respective db many to many tables and insert new groups/items.
Proposed solution:
I was thinking that instead of taking a group into my recursive components, I could instead create normalized objects to use to store state. I would have one object keyed by column-row which would hold all the groups. Another keyed by column-row to hold all the items. Then I think I would need two more objects to hold many to many relationships like Group to Group and Group to Item.
After I get the data from the form, I hopefully can loop through these state objects, find the hierarchy that way and post the necessary data to the db.
I see that this is a lot of data structures to hold this data and I wasn't sure if this was the best way to accomplish this given my modeling structure. I have just started using Redux Toolkit as well, so I am somewhat familiar with reducers, but not enough to see how I could apply them here to help me. I have been really trying to figure this out, any help or guidance to make this easier would be much appreciated.
Go with normalizing. Each entity having a single source of truth makes it much easier to read and write state.
To do this, try normalized-reducer. It's a simple higher-order-reducer with a low learning curve.
Here is a working CodeSandbox example of it implementing a group/item composite tree very similar to your problem.
Basically, you would define the schema of your tree:
const schema = {
group: {
parentGroupId: { type: 'group', cardinality: 'one', reciprocal: 'childGroupIds' },
childGroupIds: { type: 'group', cardinality: 'many', reciprocal: 'parentGroupId' },
itemIds: { type: 'item', cardinality: 'many', reciprocal: 'groupId' }
},
item: {
groupId: { type: 'group', cardinality: 'one', reciprocal: 'itemIds' }
}
};
Then pass it into the library's top-level function:
import normalizedSlice from 'normalized-reducer';
export const {
emptyState,
actionCreators,
reducer,
selectors,
actionTypes,
} = normalizedSlice(schema);
Then wire up the reducer into your app (works with both React useReducer and the Redux store reducers), and use the selectors and actionCreators to read and write state.

Best way to handle big dynamic form with stages in React?

I want to make an app where the admins can create "global" forms that other users can fill in. So I need these global forms to be dynamically rendered, and they are kind of big (30+ fields) and are divided in stages (e.g. stage 1 is for personal info, stage 2 is for job skills, etc).
I thought of receiving these "global" forms via JSON, something like this:
{
"filledBy":"User",
"stages":[
{
"id":1,
"name":"Personal information",
"fields":[
{
"id":1,
"type":"email",
"name":"email",
"label":"E-mail",
"placeholder":"name#company.com",
"value":"",
"rules":{
"required":true
}
},
{
"id":2,
"type":"text",
"name":"name",
"label":"Name",
"placeholder":"John Smith",
"value":"",
"pattern":"[A-Za-z]",
"rules":{
"required":true,
"minLength":2,
"maxLength":15
}
}
]
},
{
"id":2,
"name":"Job profile",
"fields":[
{
"id":1,
"type":"multi",
"name":"workExperience",
"subfields":[
{
"id":1,
"type":"text",
"name":"position",
"label":"Position",
"placeholder":"CEO",
"value":"",
"rules":{
"required":true,
"minLength":3,
"maxLength":30
}
},
{
"id":2,
"type":"date",
"name":"startDate",
"label":"Starting date",
"placeholder":"November/2015",
"value":"",
"rules":{
"required":true,
"minValue":"01/01/1970",
"maxValue":"today",
"showAsColumn":true
}
},
{
"id":3,
"type":"date",
"name":"endDate",
"label":"Ending date",
"placeholder":"March/2016",
"value":"",
"rules":{
"required":true,
"minValue":"endDate",
"maxValue":"today",
"showAsColumn":true
}
}
]
}
]
}
]
}
So I created a component called MasterForm that first gets the empty form in componentDidMount(), like a blueprint. Then, once it is fetched, it tries to get the data entered by the user and put it in the form as the value property. After that, it passes the form down to the Stage component which renders every field as an Input component. That way, MasterForm controls the current stage, and allows the user to navigate among stages, and also fetches the data and fills the form. With all the checks and stuff, my MasterForm component got very big (around 700 lines), and every time I update the value of a field in the form, I update the whole form object in the state, so I think that might be slow. Also, to fill in the form with the user's data, I have to copy every nested object and array inside the form object, to avoid mutating the state, and that's also very messy (a lot of const updatedFields = { ...this.state.form.stage.fields } and stuff).
Are there better ways to do this (preferably without Redux)? How could I decouple this huge MasterForm component? Is there a better way to update the form values (other than updating the whole form every time)? or maybe React is smart and doesn't update the whole state, but just the bit that changed... I'm not sure, I'm new to React.
Look into formik https://github.com/jaredpalmer/formik and Yup https://github.com/jquense/yup
Here they are coupled together https://jaredpalmer.com/formik/docs/guides/validation#validationschema

Modelling data based on a time range using redux/normalizr

I've have the following data coming from a node backend:
Endpoint:
/user/:id?startDate=2011-10-05T14:48:00.000Z&endDate=2011-10-05T14:48:00.000Z
[
{
"name": "Tom",
"createdAt": "2011-10-05T14:48:00.000Z",
"updatedAt": "2011-10-05T14:48:00.000Z",
...
"metadata": {
"activeHours": 134.45,
"afkHours": 134.45
}
},
{
...
}
]
In this data, the only thing modified between date changes is activeHours and afkHours.
These users and the date that the endpoint was called with must be synced across all pages.
The simple approach would be to put this in a users reducer, something like:
{
users: [...],
startDate: "",
endDate: ""
}
However I'm currently using normalizr with these users and with that I have a single action named ADD_ENTITIES. Having an entities reducer seems very beneficial as we do have other entities that can be nicely normalized with these users, however I don't want to pollute the entities state with almost "tacked on" startDate and endDate to sync across all pages.
On to my question:
Is there a better way to model this problem using normalizr, Where your key is not only ID but also a date range?
Or should I look at breaking this out into a separate reducer as above?
Not sure if I completely understood the problem here.
Is the startDate and endDate fields, different for each user ? If yes, then you may need to add those fields in the normalized entity object for those users.
If those are common fields for all the users, you can create a separate entity called userDateRange containing those two keys. It doesn't need to be normalized as they are primitive fields.
{
"entities": {
"user": {
"byId": {
"user1": {},
"user2": {}
},
"allIds": [
"user1",
"user2"
]
}
},
"ui": {
"userDateRange": {
"start": "2011-10-05T14:48:00.000Z",
"end": "2011-10-05T14:48:00.000Z"
}
}
}

Redux updating nested immutable data

I have an issue with updating the immutable redux and quite nested data. Here's an example of my data structure and what I want to change. If anyone could show me the pattern of accessing this update using ES6 and spread operator I would be thankful.
My whole state is an object with projects (key/value pairs - here as an example only one project) that are objects with its own key (and the keys are ids as well), arrays of procedures and inside the tasks:
{ 1503658959473:
{ projectName: "Golden Gate",
projectLocation": "San Francisco",
start:"22/09/1937",
id:1503658959473,
procedures:[
{ title: "Procedure No. 1",
tasks:[
{name: "task1", isDone: false},
{name: "task2", isDone: false},
{name: "task3", isDone: false}
]
}
]
}
}
What I'm willing to do is to update one single task 'isDone' property to 'true'. It's some kind of toggling the tasks. How can I return this state with that information updated?
The action creator pass this information to reducer:
export function toggleTask(activeProject, task, taskIndex) {
return {
type: TOGGLE_TASK,
payload: {
activeProject,
task,
taskIndex
}
};
}
You've run into a common issue with Redux. The docs recommend that you flatten your data structure to make it easier to work with, but if that's not what you want to do, I'd refer to this part of their docs.
Because both Object.assign() and the ...spread operator create shallow copies, you must go through each level of nest in your object and re-copy it.
Your code might look something like this...
function updateVeryNestedField(state, action) {
return {
...state,
procedures : {
...state.procedures,
tasks : {
return tasks.map((task, index) => {
if (index !== action.taskIndex) {
return task
}
return {
...task,
task.isDone: !task.isDone
}
}
}
}
}
}
I myself would create a new class called ProjectModel, which has a public method toggleTask that is able to update its task's status. The reducer state would be an object whose keys are project IDs and values are ProjectModel instances.

Resources