dimanche 13 mars 2022

Using React hooks (useEffect and useRef) - Why ref gets updated on checkbox changes?

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=
            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



Aucun commentaire:

Enregistrer un commentaire