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>