I have a global state of the format like this:
{
type: "container",
items: [
{
type: "box"
},
{
type: "container",
items: [
type: "container",
items: [
{
type: "box",
color: "green"
},
{
type: "box",
color: "red"
},
{
type: "box"
}
]
]
}
]
}
The state above will produce the output below: output
As you can see, we have only two elements: containers and boxes. Containers have buttons and can contain either containers or boxes. Boxes can only change their colors. To understand better watch this demo.
I have been able to replicate this behavior, but I want to be able to extract the whole state of the app and be able to restore it.
There is no problem with restoring states. If I pass a json like above, it will work. But when it comes to saving my current state, problems arise. You see, when I try to restore my state, I pass nested object as a prop and then it becomes the local state of that child container. But when I change the state of that child container, it obviously doesn't change the global state (the json above).
So I tried 2 methods to change the global state:
Here is my code:
- Create
onChangemethod that will trigger when some local state changes. Then pass that method as a prop, and call that method by passing the changed state as a parameter. (You can see this in my code) - Pass path (list of indices) of the nested object as a prop to restore to the form
state.items[path[0]].items[path[1]].items[path[n]]and then change it manually bysetStatehook callback.
In both cases, I get
Warning: Maximum update depth exceeded. This can happen when a component calls setState inside useEffect, but useEffect either doesn't have a dependency array, or one of the dependencies changes on every render.
and it just freezes.
Container.js
import React, { useState } from 'react'
import { v4 as uuidv4 } from 'uuid'
import Box from './Box'
function Container({ current, path, onChange }) {
const styles = ... some styles ...
const [popoverVisible, setPopoverVisibility] = useState(false)
const [state, setState] = useState(current || [])
React.useEffect(() => onChange(state, path), [state])
const handleButtonMouseOver = () => setPopoverVisibility(true)
const handleButtonMouseLeave = () => setPopoverVisibility(false)
const handlePopoverMouseOver = () => setPopoverVisibility(true)
const handlePopoverMouseLeave = () => setPopoverVisibility(false)
const props = {
container: { style: styles.container },
wrapper: { style: styles.wrapper },
popover: {
style: styles.popover,
onMouseOver: handlePopoverMouseOver,
onMouseLeave: handlePopoverMouseLeave
},
buttonAdd: {
style: styles.button,
onMouseOver: handleButtonMouseOver,
onMouseLeave: handleButtonMouseLeave
},
buttonBox: {
style: styles.button,
onClick: () => setState([...state, {type: 'box'}])
},
buttonContainer: {
style: styles.button,
onClick: () => setState([...state, {type: 'container', items: []}])
//onClick: () => setState({...state, items: [...state.items, {type: 'container', items: []}]})
}
}
return (
<>
<div {...props.container}>
{state.map((x, i) => x.type === 'container' ? <Container key={uuidv4()} current={x.items} onChange={ onChange } path={[...path, i]}></Container> : <Box key={uuidv4()}></Box>)}
<div {...props.wrapper}>
<div>
<div {...props.popover}>
{popoverVisible && <button {...props.buttonBox}>Box</button>}
{popoverVisible && <button {...props.buttonContainer}>Container</button>}
</div>
<button {...props.buttonAdd}>Add</button>
</div>
</div>
</div>
</>
)
}
export default Container
App.js
import { useState, useEffect } from 'react'
import Container from './Container.js'
function App() {
const styles = {
button: {
margin: ".5em",
width: "7em",
height: "3em",
border: "1px solid black",
borderRadius: ".25em"
}
}
const [state, setState] = useState({type: "container", items: []})
const [newState, setNewState] = useState("")
const [generatedJson, setGeneratedJson] = useState("")
const handleContainerChange = (s, p) => setState({...state, items: s})
const handleTextareaChange = e => setNewState(e.target.value)
const handleBuildButtonClick = () => setState(JSON.parse(newState))
const handleCreateJsonButtonClick = () => setGeneratedJson(JSON.stringify(state))
//useEffect(() => {alert(state)}, [state])
return (
<>
<div style={{ display: "flex" }}>
<Container key={state} current={state.items} path={[]} onChange={ handleContainerChange }>
</Container>
</div>
<textarea cols="30" rows="5" onChange={ handleTextareaChange }></textarea><br/>
<button style={styles.button} onClick={ handleBuildButtonClick }>Build</button><br/>
<button style={styles.button} onClick={ handleCreateJsonButtonClick }>Create JSON</button>
<label key={generatedJson}>{generatedJson}</label>
</>
)
}
export default App
So what I want is to update global state, when local state changes. Example:
globalState = {
type: "container",
items: [
{ type: "container", items: [] }
]
}
// that inner container from the global state
localState = { type: "container", items: [] }
// so when it changes to
localState = {
type: "container",
items: [
{type: "box"}
]
}
// i want the global state to become
globalState = {
type: "container",
items: [
{
type: "container",
items: [
{type: "box"}
]
}
]
}
How can I do that?