Search code examples
javascriptreactjsreact-router-dom

Cannot update a component while rendering a different component. To locate the bad setState() call follow the stack trace as described


So I've scouted for an answer but in most cases its just badly spelling or misplacing the call which I believe its not the case here. It's React logic I somehow find hard to understand.

I'm using This type of React Router and I'm trying to provide Outlet with a set of data that can be changed while the user is on a children website. I decided to try React Providers instead of React-Router tool useOutletContext because the data provided can grow over time. I decided to follow This approach.

So here is the code. App.jsx

const App = () => {
  return (
    <AuthProvider>
      <Suspense fallback={<Loading />}>
        <Routing />
      </Suspense>
    </AuthProvider>
  )
}

Routing.jsx

const BrowserRouter = createBrowserRouter(
  createRoutesFromElements(
    <Route path="/" name="home" element={<HomePage />}>
      <Route path="device" name="device" element={<DevicePage />}>
        <Route path=":id" element={<DeviceList />}></Route>
      </Route>
      <Route path="*" element={<Roadblock.site404 />} />
    </Route>,
  ),
)

export default const Routing = () => {
  return <RouterProvider router={BrowserRouter} fallbackElement={<Loading />} />
}

DevicePage.jsx

export default function DevicePage() {
  return (
    <>
      <DeviceContent />
    </>
  )
}

DeviceContent.jsx

export default function DeviceContent() {
  return (
    <>
      <DeviceProvider>
        <Outlet />
      </DeviceProvider>
    </>
  )
}

DeviceProvider.jsx

export const DeviceContext = createContext()

export function DeviceProvider({ children }) {
  const [device, setDevice] = useState(null)

  const addDevice = () => {
    setDevice(data)
  }

  const deviceContext = {
    device,
    addDevice,
  }

  return <DeviceContext.Provider value={deviceContext}>{children}</DeviceContext.Provider>
}

and here is the Outlet DeviceList.jsx called in Route

export default function DeviceList() {
  const { device, addDevice } = useContext(DeviceContext)

  addDevice('1')
  console.log(device)

  return (
    <div>
      <h2>{device}</h2>
    </div>
  )
}

Error code

react-dom.development.js:86 Warning: Cannot update a component (DeviceProvider) while rendering a different component (DeviceList). To locate the bad setState() call inside
DeviceList, follow the stack[...]

Question: What behavior/logic is causing the problem and how can I understand and avoid it in the future?


Solution

  • DeviceList is enqueueing a state update outside the React component lifecycle. In other words, as an unintentional side-effect. Move the addDevice call to a useEffect hook so it's an intentional side-effect.

    Example:

    import { useEffect } from 'react';
    
    export default function DeviceList() {
      const { device, addDevice } = useContext(DeviceContext);
    
      // Update device state when component mounts
      useEffect(() => {
        addDevice('1');
      }, []);
    
      // Log device state updates
      useEffect(() => {
        console.log(device);
      }, [device]);
    
      return (
        <div>
          <h2>{device}</h2>
        </div>
      );
    };