ctrlplusb / easy-peasy

Vegetarian friendly state for React

Home Page:https://easy-peasy.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

[Question] How to updated deeply nested array elements?

ricardomatias opened this issue · comments

What's the best practices around deep state trees? I've got a tree that resembles the following:

const store = {
	items: [
		{
			id, name,
			children: [
				{
					id, name
					children: [
						{
							// a few more levels deep
						}
					]
				}
			]
		}
	]
}

Whenever I update a specific child, I perform a breadth-first search to get the element and then update with an action:

updateTally: action((state, payload) => {
	const element = findChild(state.items, payload.id, payload.type);
	if (element) Object.assign(element, mergeElements(element, payload));
}),

The main issue that I'm having is that I have this mergeElements function which has to figure out how to deeply merge since Object.assign doesn't do deep merges. Since easy-peasy is using Immer under the hood, I was wondering if there's a better approach to this?

Hi,
I'll try to add my two cents although I am not a React Expert but I recently implemented this library in one of our projects.

In my case I had nested state just like you which was bein updated (CRUD operation) dynamically. I have used .map to check the array and then compared children item id with payload id and assigned values there.

Example:

updateRows: action((state, payload) => {
  state.MainData[0].tabs.map((tab) => {                   // main state
    if (tab.tabID === payload.currentTabID) {             // target required item with ID (currentTabID provided in payload)
      tab.tabRows.map((row) => {                          // map children
        if (row.rowID === payload.currentRowID) {         // get children by ID (currentRowID provided in payload)
          row.first_name = payload.fname;                  // set values (fname/lname provided in payload)
          row.last_name = payload.lname;                  
        }
      });
    }
  });
}),

There might be a better way to do this stuff but this got me work done for me :) as I am very new to React in fact my first ever project and I had to deal with state management, thanks to this amazing library it suited well in my case.

Let me know if this works for you as well or how can this be improved :)

FYI, updating large state trees via Immer can have a negative effect on performance. Sometimes it is better to do a standard immutable update like in a traditional Redux reducer.

There is discussion around introducing this capability within here.

It would be great to get your feedback.

I had a similar issues where i found myself updating deeply nested entities and this is how i solved it

1 - breaking down our entities into individual objects that can be referenced, this was done using normalizr

// schema.js
import { schema } from "normalizr";

const nestedChildSchema = new schema.Entity("nestedchildren", {});
const childSchema = new schema.Entity("children", { children: [nestedChildSchema] });
const itemSchema = new schema.Entity("items", {
  children: [childSchema]
});

2 - Create a separate reducer where our objects will live


//reducer/entities.js
import { normalize } from "normalizr";
import { nestedChild } from "./schema";

const override = (state, newState) => {
  const keys = Object.keys(newState);
  keys.forEach((key) => (state[key] = { ...state[key], ...newState[key] }));
};

return { 
  nestedchildren: {},
  children: {},
  items: {},
  updateNestedChildren: thunk((actions, payload)=>{
    const normalizedData = normalize(payload, nestedChild).entities;
    actions.setEntities(data);
  }),
  setEntities: action((state, { entities }) => {
    const entityNames = Object.keys(entities);
    entityNames.forEach((property) => {
      override(state[property], entities[property]);
    });
  }),
}

3 - Write/read objects using the entity reducer


// reducer/items.js
import axios from "axios";
import {computed, thunk, action} from "easy-peasy";
import { denormalize } from "normalizr";
import { itemSchema } from "./schema";

const itemResolver = (state, storeState) => ({ id: state.itemID, entities: storeState.entities });

{
  itemID: undefined,
  setItemID: action((state, id) => { state.itemID = id; }),
  findItemRequest: thunk((actions, id)=>{
    return axios.post("show", {id}).then((response) => {
      actions.normalizeItem(response.data);
      actions.setItemID(id);
    });
  }),
  // here we reconstruct our item with all its children and nested children
  item: computed([itemResolver], ({ id, entities }) => denormalize(id, itemSchema, entities)),
  // Normalize the item and save it into the entities reducer.
  normalizeItem: thunk((actions, object, { getStoreActions }) => {
    const { setEntities } = getStoreActions().entities;
    const result = normalize(object, itemSchema);

    setEntities(result);

    return result.result;
  }),
}