Home > Net >  react recursive functional component update state
react recursive functional component update state

Time:09-03

I trying to do parent and child recursive state using React. I have added App3 which has initial state should maintain entire state into it. I have no idea how can I add update delete with this when it comes to recursive.

I have tried using https://hookstate.js.org/docs/recursive-state/ But due to some limitation I want to use react without any third party packages.

How can I perform CRUD for the recursive state

const data = {
    op: 0,
    values: [
        {
            op: 0,
            value: {
                values: [
                    {
                        op: 2,
                        value: {
                            values: []
                        }
                    }
                ]
            }
        },
        {
            op: 1,
            value: {
                values: []
            }
        }
    ]
}
function App3() {

    const [mainState, setMainState] = React.useState(data);

    return (
        <div>
            {
                mainState.values.map((x, i) => {
                    return <RecursiveFn key={i} value={x}></RecursiveFn>
                })
            }
          <button>Add</button>
        <hr/>
        <div><pre>
         {JSON.stringify(mainState)}</pre></div>
        </div>
    )
}

function RecursiveFn(props) {
    
    return (
        <div style={{ padding:5,margin:10,paddingLeft: 20,border:'1px solid black' }}>
            <div> OP -

                <input type={'text'} value={props.value.op}></input>
            </div>
            {props.value.value && props.value.value.values ? props.value.value.values.map((x, i) => <RecursiveFn value={x}></RecursiveFn>) : null}
            <button>Add</button>
            <button>Delete</button>
        </div>
    );
}

ReactDOM.render(<App3 />,
document.getElementById("root"))
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"> 
</script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>


   <div id="root"></app>

CodePudding user response:

Since the component structure is recursive, its state updates have to happen in a recursive way as well. You can keep a path to update using an array and use it to identify the correct update level:

const data = {
  op: 0,
  value: {
    values: [
      {
        op: 0,
        value: {
          values: [
            { op: 2, value: { values: [{ op: 3, value: { values: [] } }] } }
          ]
        }
      },
      { op: 3, value: { values: [{ op: 3, value: { values: [] } }] } }
    ]
  }
};

const ACTIONS = {
  CHANGE: "change",
  ADD: "add",
  DELETE: "delete"
};

function App3() {
  const [mainState, setMainState] = React.useState(data);

  const handleChange = (path, value, action) => {
    setMainState((prevState) => {
      return getNestedUpdate(prevState, [...path], value, action);
    });
  };

  const performAction = (state, value, action, deleteIndex) => {
    switch (action) {
      case ACTIONS.CHANGE:
        return {
          ...state,
          op: value
        };
      case ACTIONS.ADD:
        return {
          ...state,
          value: {
            ...state.value,
            values: [
              ...state.value.values,
              { op: "new-entry", value: { values: [] } }
            ]
          }
        };
      case ACTIONS.DELETE:
        console.log(state.value.values, deleteIndex);
        return {
          ...state,
          value: {
            ...state.value,
            values: state.value.values.filter(
              (item, itemIndex) => itemIndex !== deleteIndex
            )
          }
        };
      default:
        return state;
    }
  };

  const getNestedUpdate = (state, path, value, action) => {
    if (path.length === 0) {
      return performAction(state, value, action);
    }

    if (action === ACTIONS.DELETE && path.length === 1) {
      return performAction(state, value, action, path.shift(0));
    }

    const level = path.shift(0);

    return {
      ...state,
      value: {
        ...state.value,
        values: state.value.values.map((item, itemIndex) =>
          itemIndex === level
            ? getNestedUpdate(item, path, value, action)
            : item
        )
      }
    };
  };

  return (
    <div>
      {mainState.value.values.map((x, i) => {
        return (
          <RecursiveFn
            key={i}
            value={x}
            path={[i]}
            handleChange={handleChange}
          ></RecursiveFn>
        );
      })}
      <button>Add</button>
      <hr />
      <div>
        <pre>{JSON.stringify(mainState, null, 2)}</pre>
      </div>
    </div>
  );
}

function RecursiveFn(props) {
  return (
    <div
      style={{
        padding: 5,
        margin: 10,
        paddingLeft: 20,
        border: "1px solid black"
      }}
      key={props.path}
    >
      <div>
        {" "}
        OP -
        <input
          type={"text"}
          value={props.value.op}
          onChange={(e) =>
            props.handleChange(props.path, e.target.value, ACTIONS.CHANGE)
          }
        ></input>
      </div>
      {props.value.value && props.value.value.values
        ? props.value.value.values.map((x, i) => (
            <RecursiveFn
              key={i}
              value={x}
              path={[...props.path, i]}
              handleChange={props.handleChange}
            ></RecursiveFn>
          ))
        : null}
      <button onClick={() => props.handleChange(props.path, null, ACTIONS.ADD)}>
        Add
      </button>
      <button
        onClick={() => props.handleChange(props.path, null, ACTIONS.DELETE)}
      >
        Delete
      </button>
    </div>
  );
}

ReactDOM.render(<App3 />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

CodePudding user response:

For those who are looking for the full solutions. I have modified the answer of @Amila Senadheera .

const data = { op: 0, value: { values: [{ op: 0, value: { values: [{ op: 2, value: { values: [{ op: 3, value: { values: [] } }] } }] } }, { op: 3, value: { values: [{ op: 3, value: { values: [] } }] } }] } };

function App4() {
    const [mainState, setMainState] = React.useState(data);
    

    const handleChange = (path, value) => {
        setMainState((prevState) => {
            return getNestedUpdate(prevState, [...path], value);
        });
    };

    const getNestedUpdate = (state, path, value) => {
        if (path.length === 0) {
            return {
                ...state,
                op: value
            };
        }
        const level = path.shift(0);

        return {
            ...state,
            value: {
                ...state.value,
                values: state.value.values.map((item, itemIndex) =>
                    itemIndex === level ? getNestedUpdate(item, path, value) : item
                )
            }
        };
    };

    const handleAddItem = (path, value) => {
        setMainState((prevState) => {
            return AddItem(prevState, [...path], value);
        });
    };

    const AddItem = (state, path, value) => {
        if (path.length === 0) {
            if (state.value && state.value.values) {
                return {
                    ...state,
                    value: {
                        values: [...state.value.values, value]
                    }
                };
            }
            else {
                const arr = [];
                return {
                    ...state,
                    value: {
                        values: [...arr, value]
                    }
                };
            }

        }
        const level = path.shift(0);

        return {
            ...state,
            value: {
                ...state.value,
                values: state.value.values.map((item, itemIndex) =>
                    itemIndex === level ? AddItem(item, path, value) : item
                )
            }
        };
    };

    const AddMainItem = () => {
        setMainState((prevState) => ({
            ...prevState,
            value: {
                values: [...prevState.value.values, { type: 'group', op: 'new group' }]
            }
        }))
    }

    return (
        <div>
            {mainState.value.values.map((x, i) => {
                return (
                    <RecursiveGroup
                        key={i}
                        value={x}
                        path={[i]}
                        handleChange={handleChange}
                        handleAddItem={handleAddItem}
                    ></RecursiveGroup>
                );
            })}
            <button onClick={AddMainItem}>Add</button>
            <hr />
            <div>
                <pre>{JSON.stringify(mainState, null, 2)}</pre>
            </div>
        </div>
    );
}

function RecursiveGroup(props) {
    return (
        <div
            style={{
                padding: 5,
                margin: 10,
                paddingLeft: 20,
                border: "1px solid black"
            }}
            key={props.path}
        >
            <div>
                {" "}
                OP -
                <input
                    type={"text"}
                    value={props.value.op}
                    onChange={(e) => props.handleChange(props.path, e.target.value)}
                ></input>
            </div>
            {props.value.value && props.value.value.values
                ? props.value.value.values.map((x, i) => (

                    x.type == 'group' ? <RecursiveGroup
                        key={i}
                        value={x}
                        path={[...props.path, i]}
                        handleChange={props.handleChange}
                        handleAddItem={props.handleAddItem}
                    ></RecursiveGroup> : <div key={i} style={{
                        padding: 5,
                        margin: 10,
                        paddingLeft: 20,
                        border: "1px solid black"
                    }}>I am rule</div>

                ))
                : null}
            <button onClick={(e) => props.handleAddItem(props.path, {
                type: 'group',
                op: 'group'
            })}>Add Group</button>
            <button onClick={(e) => props.handleAddItem(props.path, {
                type: 'Rule',
                op: 'Rule'
            })}>Add Rule</button>
        </div>
    );
}

ReactDOM.render(<App4 />,
document.getElementById("root"))
<script src="https://unpkg.com/[email protected]/umd/react.production.min.js"> 
</script>
<script src="https://unpkg.com/[email protected]/umd/react-dom.production.min.js"></script>


   <div id="root"></app>

  • Related