skip to Main Content

I have a function that takes a props value in a component and flattens it into a form the component can use. This needs to happen every time the props value changes. I put the setState in a useEffect with the props value as one of the dependencies.

This works fine in the browser but I get the error from the question title when running the Jest tests.

Here’s basically what my code looks like:


const mapThing = (notFlatStructure) => {
    let flattendStructure = [];
    notFlatStructure.forEach((mainItem) => {
        flattendStructure.append({
            id: mainItem.id,
            text: mainItem.text,
            isToggle: true
        });

        if (mainItem.subItems && mainItem.subItems.length > 0) {
            mainItem.subItems.forEach((subItem) => {
                flattendStructure.append({
                    id: subItem.id,
                    text: subItem.text,
                    isToggle: true
                });
            });
        }
    });

    return flattendStructure;
 };

const myComponent = (props) => {

    const [mappedThing, setMappedThing] = useState(mapThing(props.thing));
 
    useEffect(() => {
       setMappedThing(mapThing(props.thing));
    }, [props.thing]);
 
    const handleBlur = (e) => {
        props.thingChanged(e);
    };

    const handleClick = (row) => {
        let found = mappedThing.find((el) => el.id = row.id);
        found.isToggle = !found.isToggle;
        setMappedThing(mappedThing);
    };

    return (
        <div>
            {
                mappedThing.map((row) => {
                    <div>
                        <input onBlur={handleBlur} value={row.text}></input>
                        <button onClick={(e) => handleClick(row)}></button>
                    </div>                  
                })
            }
        </div>
    );
 };

Is there a standard way to have mappedThing above set every time the props change without using a useEffect? Any time I take the useEffect away the component quits updating.

2

Answers


  1. Chosen as BEST ANSWER

    Drew Reese in the comments was correct. The code was an anti-pattern in React. After restructuring the component and splitting some functionality off into small, sub components, I was able to create the component without flattening the underlying data structure and removing the need for the setState in the useEffect. Thanks, Mr. Reese for pointing our my error. I learned a lot having to work through making this align with React's expectations.


  2. The problem usually arises when setting the state causes a re-render, that somehow changes the prop’s value, which triggers useEffect, updates the state, and so on…
    In this case maybe the mocked prop value generated by Jest is replaced on each render, but I’m not really sure about it.

    You don’t need a state, when you want to transform a prop’s value, just use a const:

    // Declare the transform function out of the component if it doesn't depend on props/state
    const mapThing => (notFlatStructure) => {
      // Do stuff here to flatten structure.
      return flattendStructure;
    };
    
    const myComponent = (props) => {
      const mappedThing = mapThing(props.thing);
    
      return (
        // Component that uses flattened thing.
      );
    };
    

    If it’s a heavy computation, wrap it with useMemo:

    const myComponent = (props) => {
      const mappedThing = useMemo(() => mapThing(props.thing), [props.thing]);
    
      return (
        // Component that uses flattened thing.
      );
    };
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search