0
votes

Consider the following example that renders a list of iframes.

I'd like to store all the documents of the rendered iframes in frames.

import React, { useState, useEffect, useCallback } from "react";
import Frame, { FrameContextConsumer } from "react-frame-component";

function MyFrame({ id, document, setDocument }) {
  useEffect(() => {
    console.log(`Setting the document for ${id}`);
    setDocument(id, document);
  }, [id, document]); // Caution: Adding `setDocument` to the array causes an infinite update loop!

  return <h1>{id}</h1>;
}

export default function App() {
  const [frames, setFrames] = useState({
    desktop: {
      name: "Desktop"
    },
    mobile: {
      name: "Mobile"
    }
  });
  const setFrameDocument = useCallback(
    (id, document) => {
      setFrames({
        ...frames,
        [id]: {
          ...frames[id],
          document
        }
      });
    },
    [frames, setFrames]
  );

  console.log(frames);

  return (
    <div className="App">
      {Object.keys(frames).map(id => (
        <Frame key={id}>
          <FrameContextConsumer>
            {({ document }) => (
              <MyFrame
                id={id}
                document={document}
                setDocument={setFrameDocument}
              />
            )}
          </FrameContextConsumer>
        </Frame>
      ))}
    </div>
  );
}

There are two issues here:

  1. react-hooks/exhaustive-deps is complaining that setDocument is missing in the dependency array. But, adding it causing an infinite update loop.
  2. Console logging frames shows that only mobile's document was set. I expect desktop's document to be set as well.

How would you fix this?

Codesandbox

1

1 Answers

3
votes
const setFrameDocument = useCallback(
    (id, document) => setFrames((frames) => ({
      ...frames,
      [id]: {
        ...frames[id],
        document
      }
    })),
    []
  );

https://codesandbox.io/s/gracious-wright-y8esd


The frames object's reference keeps changing due to the state's update. With the previous implementation (ie the frames object within the dependency array), it would cause a chain reaction that would cause the component to re-render and causing the frames object getting a new reference. This would go on forever.

Using only setFrames function (a constant reference), this chain react won't propagate. eslint knows setFrames is a constant reference so it won't complain to the user about it missing from the dependency array.