question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Infinite loop caused by unstable context value

See original GitHub issue

Reproduction

https://codesandbox.io/s/preact-batch-state-context-z71k8

// **** works
import { render } from "react-dom";
import * as React from "react";

// **** doesn't work:
// import { render } from "preact/compat";
// import * as React from "preact/compat";

// ----------------------------------------------------------

const Context = React.createContext(null);

function ModalProvider(props) {
	let [modalCount, setModalCount] = React.useState(0);
	let context = {
		modalCount,
		addModal() {
			setModalCount((count) => count + 1);
		},
		removeModal() {
			setModalCount((count) => count - 1);
		}
	};

	return <Context.Provider value={context}>{props.children}</Context.Provider>;
}

export function useModal() {
	let context = React.useContext(Context);
	React.useEffect(() => {
		console.log("useModal mount");

		context.addModal();
		return () => {
			console.log("useModal unmount");
			context.removeModal();
		};
	}, [context]);
}

// ----------------------------------------------------------

function Popover() {
	useModal();
	return <div>Popover</div>;
}

function App() {
	return (
		<ModalProvider>
			<Popover />
		</ModalProvider>
	);
}

render(<App />, document.getElementById("root"));

Steps to reproduce

  • Open the codesandbox
  • Currently, it’s using React which works as expected
  • Change the imports to use Preact instead
  • An infinite loop caused by useEffect and setState via the context occurs

Expected Behavior

No infinite loop (as React)

Actual Behavior

I think Preact batches updates differently, so the useEffect and useEffect cleanup callback don’t cancel out.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:9 (4 by maintainers)

github_iconTop GitHub Comments

8reactions
mischniccommented, Nov 19, 2020

(This was the fix that solved it for me: https://github.com/adobe/react-spectrum/commit/78ac497400786ba2572d3ee076a158038e4a409e)

 export function ModalProvider(props: ModalProviderProps) {
   let {children} = props;
   let parent = useContext(Context);
   let [modalCount, setModalCount] = useState(parent ? parent.modalCount : 0);
-  let context = {
+  let context = useMemo(() => ({
     parent,
     modalCount,
     modalCount,
     addModal() {
          setModalCount((count) => count + 1);
     },
     removeModal() {
          setModalCount((count) => count - 1);
     }
     }
-  };
+  }), [parent, modalCount]);
 
   return (
     <Context.Provider value={context}>
3reactions
marvinhagemeistercommented, Aug 25, 2020

Just had a quick look at this and wrapping the context value with a useMemo resolves the infinite loop. This leads me to assume that React either has some infinite loop detection and bails out somewhere, or that they have special handling for objects passed into a context provider.

Read more comments on GitHub >

github_iconTop Results From Across the Web

React: Prevent infinite Loop when calling context-functions in ...
You can make your setters have stable references, as they don't really need to be dependant on items : const ItemContext = React....
Read more >
How to solve the React useEffect Hook's infinite loop patterns
What causes infinite loops and how to solve them ... Due to the stable reference value, React shouldn't re-render the UI infinitely:
Read more >
Issue with infinite rerendering when update state via context ...
Issue with infinite rerendering when update state via context across 3 components : r/reactjs.
Read more >
The error "Too many re-renders. React limits the number of ...
React limits the number of renders to prevent an infinite loop occurs for multiple ... return a truthy value as this would cause...
Read more >
How to Solve the Infinite Loop of React.useEffect()
Adding [value] as a dependency of useEffect(..., [value]) , the count state variable is updated only when [value] is changed. Doing so solves ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found