This is a model simulated from a real project.
Clicking undo button should change the state of checkboxes to the state before user changes them.
When I check/uncheck a checkbox, appRef.current
gets updated! why?
the effect hook in App.jsx is only called once because its dependencies array is empty.
Confusion becomes more when you click check/uncheck all button then try to change the checkboxes and hit undo button but this time it works!!!
App.jsx
import { useEffect, useRef, useState } from 'react'
import Box from './box'
const App = () => {
const [claims, setClaims] = useState()
const appRef = useRef()
const crudTrue = { Create: true, Read: true, Update: true, Delete: true }
const crudFalse = { Create: false, Read: false, Update: false, Delete: false }
const defaultClaims = {
resource1: { ...crudFalse },
resource2: { ...crudFalse, Read: true },
resource3: { ...crudFalse, Read: true },
resource4: { ...crudFalse }
}
useEffect(() => {
// this effect should be called once
// appRef should be set once
appRef.current = { ...defaultClaims }
setClaims({ ...defaultClaims })
}, [])
return (
<Box
claims={claims}
// cloning to be extra sure it is not mutated
boxRef={{ ...appRef }}
onChange={setClaims}
crudTrue={crudTrue}
crudFalse={crudFalse}
/>
)
}
export default App
box.jsx
import { createElement, useEffect, useRef } from 'react'
import CheckBox from './checkBox'
const Box = ({ claims, boxRef, onChange, crudTrue, crudFalse }) => {
const checkRef = useRef()
useEffect(() => (checkRef.current = boxRef.current), [boxRef])
const handleChange = ({ currentTarget: checkbox }) => {
const xclaims = { ...claims }
const parts = checkbox.name.split('-')
xclaims[parts[0]][parts[1]] = checkbox.checked
onChange(xclaims)
}
const handleAll = () => {
const xclaims = { ...claims }
let isAnyClaimFalse = Object.keys(xclaims).some((res) =>
Object.keys(xclaims[res]).some((c) => xclaims[res][c] === false)
)
for (let res in xclaims) xclaims[res] = isAnyClaimFalse ? { ...crudTrue } : { ...crudFalse }
onChange(xclaims)
}
const handleUndo = () => onChange(checkRef.current)
const renderChecks = () => {
const children = []
for (let res in claims)
for (let key in claims[res])
children.push(
<>
{key === 'Create' && res}
<CheckBox
key={res + '-' + key}
name={res + '-' + key}
label={key}
checked={claims[res][key]}
onChange={handleChange}
/>
{key === 'Delete' && <br />}
</>
)
return createElement('div', [], ...children)
}
return (
<>
<button onClick={handleUndo}>undo</button>
<button onClick={handleAll}>check/uncheck All</button>
{renderChecks()}
</>
)
}
export default Box
checkbox.jsx
const CheckBox = ({ name, label, ...rest }) => (
<>
<input type='checkbox' id={name} name={name} {...rest} />
{label && <label htmlFor={name}>{label}</label>}
</>
)
export default CheckBox
Did not find the problem in your code, therefore I tried to solve it with my own solution. I changed some of the code completely. Maybe if you write back parts of it to yours you find out what was the root of the problem.
Here is the codesandbox link: https://codesandbox.io/s/using-react-hooks-useeffect-useref-1odx68