How to Force a React Component to Re-render

To force a React component to re-render (rerender), change its state. In a function component, the usual “forceUpdate” is a reducer that increments a counter: const [, forceUpdate] = useReducer(x => x + 1, 0); call forceUpdate() and the component renders again. Class components have this.forceUpdate() built in. To force a child component to re-render from scratch (and reset its state), change its key prop. Most of the time, though, a component that “does not re-render” has a bug, like mutating state; this guide covers those causes first-hand, with each example built and clicked in a real browser.

Every example was built with React 19.3.0 and Vite 8.3.0 (npx vite build) and opened in Microsoft Edge; the buttons were clicked with real mouse events and the outputs are the browser console messages. Reference: forceUpdate and Preserving and resetting state on react.dev.

Force re-render a function component (forceUpdate with useReducer)

Changing a ref does not re-render, so the screen shows old data. A reducer that returns a new number every time gives you a forceUpdate function:

import { useReducer, useRef } from "react";

export default function App() {
  const [, forceUpdate] = useReducer((x) => x + 1, 0);   // calling forceUpdate() re-renders
  const lastSync = useRef("never");                       // a ref change does NOT re-render

  console.log("render, lastSync =", lastSync.current);

  return (
    <div>
      <p>Last sync: <strong>{lastSync.current}</strong></p>
      <button id="sync" onClick={() => { lastSync.current = "09:30"; }}>Sync (ref only)</button>
      <button id="refresh" onClick={forceUpdate}>Force re-render</button>
    </div>
  );
}

Console output after clicking “Sync (ref only)” and then “Force re-render”:

render, lastSync = never
render, lastSync = 09:30
React page in Microsoft Edge showing Last sync 09:30 after a forced re-render with a useReducer forceUpdate function
The ref changed on the first click, but the text only updated after forceUpdate().

There is no render after the first click; the second click renders and shows the value the ref already had. This works, but it is a workaround: if the value matters for the UI, keep it in state (useState) and React re-renders on its own.

Why your component does not re-render: mutating state

React compares the new state with the old one using Object.is. If you mutate an array or object and pass the same reference to the setter, React sees no change and skips the render. Creating a new array fixes it; no forcing needed:

import { useState } from "react";

export default function App() {
  const [items, setItems] = useState(["milk"]);
  console.log("render:", items.join(", "));

  const addWrong = () => {
    items.push("eggs");          // mutates the same array
    setItems(items);             // same reference -> React skips the re-render
  };
  const addRight = () => setItems([...items, "bread"]);   // new array -> re-render

  return (
    <div>
      <p>Shopping list: {items.join(", ")}</p>
      <button id="wrong" onClick={addWrong}>Add eggs (mutate)</button>
      <button id="right" onClick={addRight}>Add bread (new array)</button>
    </div>
  );
}

Console output after clicking “Add eggs (mutate)” and then “Add bread (new array)”:

render: milk
render: milk, eggs, bread
React shopping list in Edge after mutating state with push shows no update until a new array is set, then shows milk, eggs, bread
After both clicks: “eggs” only appears once the new-array update renders.

The first click logged nothing: the mutated array was not rendered. The second click rendered, and the list suddenly contains both items, a typical sign of mutation bugs.

forceUpdate() in class components

Class components have a built-in forceUpdate() method. It skips shouldComponentUpdate and calls render() again:

import { Component } from "react";

const settings = { theme: "light" };        // data outside React state

export default class App extends Component {
  render() {
    console.log("render, theme =", settings.theme);
    return (
      <div>
        <p>Theme: {settings.theme}</p>
        <button id="toggle" onClick={() => {
          settings.theme = settings.theme === "light" ? "dark" : "light";
          this.forceUpdate();                 // class components have forceUpdate()
        }}>Toggle theme</button>
      </div>
    );
  }
}

Console output after clicking “Toggle theme” twice:

render, theme = light
render, theme = dark
render, theme = light

Force a child component to re-render (and reset) with key

A child re-renders whenever its parent re-renders, but it keeps its state. To start a child over, give it a key and change the key; React unmounts the old instance and mounts a new one:

import { useState } from "react";

function Counter({ label }) {
  const [count, setCount] = useState(0);
  return (
    <p>
      {label}: {count}{" "}
      <button className="inc" onClick={() => setCount(count + 1)}>+1</button>
    </p>
  );
}

export default function App() {
  const [resetKey, setResetKey] = useState(0);
  const [, setTick] = useState(0);
  return (
    <div>
      <Counter key={`a-${resetKey}`} label="Counter A (key changes on reset)" />
      <Counter label="Counter B (no key)" />
      <button id="rerender" onClick={() => setTick((t) => t + 1)}>Re-render parent</button>
      <button id="reset" onClick={() => setResetKey((k) => k + 1)}>Reset A with a new key</button>
    </div>
  );
}
Two React counters in Edge: Counter A reset to 0 after its key prop changed while Counter B kept its count of 2 through a parent re-render
Both counters were clicked twice. “Re-render parent” kept both counts; the new key reset only Counter A.

Re-render a memoized child

A child wrapped in memo re-renders only when its props change. If it reads data that was mutated in place, pass a prop that changes (a version number here) so it updates:

import { memo, useState } from "react";

const Chart = memo(function Chart({ data, version }) {
  console.log("Chart rendered, version", version);
  return <p>Chart of {data.length} points (version {version})</p>;
});

const data = [3, 7, 4];     // mutated in place below, so memo sees the same reference

export default function App() {
  const [version, setVersion] = useState(1);
  const [clicks, setClicks] = useState(0);

  return (
    <div>
      <Chart data={data} version={version} />
      <p>Parent clicks: {clicks}</p>
      <button id="parent" onClick={() => setClicks((c) => c + 1)}>Update parent only</button>
      <button id="add" onClick={() => { data.push(9); setVersion((v) => v + 1); }}>Add point and bump version</button>
    </div>
  );
}

Console output after two parent-only updates and one “Add point” click:

Chart rendered, version 1
Chart rendered, version 2
React memo child component in Edge showing Chart of 4 points version 2 after a version prop change, with parent clicks 2
The memoized Chart skipped both parent-only updates and rendered again when version changed.

The cleaner fix is to keep data in state and replace it with a new array; then memo works without the extra prop.

Data outside React: useSyncExternalStore instead of forceUpdate

If the data lives in your own store (or a browser API), subscribe to it with useSyncExternalStore. React re-renders the component whenever the snapshot changes:

import { useSyncExternalStore } from "react";

// a tiny store outside React
let cart = [];
const listeners = new Set();
const store = {
  add(item) { cart = [...cart, item]; listeners.forEach((l) => l()); },
  subscribe(l) { listeners.add(l); return () => listeners.delete(l); },
  getSnapshot() { return cart; },
};

function CartBadge() {
  const items = useSyncExternalStore(store.subscribe, store.getSnapshot);
  console.log("CartBadge rendered with", items.length, "items");
  return <p>Cart: {items.length} items</p>;
}

export default function App() {
  return (
    <div>
      <CartBadge />
      <button id="add" onClick={() => store.add("book")}>Add a book</button>
    </div>
  );
}

Console output after clicking “Add a book” twice:

CartBadge rendered with 0 items
CartBadge rendered with 1 items
CartBadge rendered with 2 items
SituationBest way to get a re-render
Value shown on screenKeep it in useState and call the setter
Updated an array/objectSet a new array/object: setItems([...items, x])
Need a quick “forceUpdate”const [, forceUpdate] = useReducer(x => x + 1, 0)
Class componentthis.forceUpdate()
Reset a child completelyChange the child’s key prop
Memoized child with stale dataPass changed props (or new data references)
External store or APIuseSyncExternalStore

Here are some related React guides:

Frequently asked questions

How do I force a React component to re-render?

Update its state. For a manual trigger in a function component, use const [, forceUpdate] = useReducer(x => x + 1, 0) and call forceUpdate().

Is there forceUpdate for functional components?

Not built in. The useReducer counter above does the same job; this.forceUpdate() exists only in class components.

Why does my component not re-render after setState?

Usually because the state was mutated and the same object or array was passed to the setter. Create a new one instead.

How do I force a child component to re-render?

Re-render the parent or change a prop the child uses. To reset the child’s state completely, change its key.

Does changing a ref re-render the component?

No. Refs are for values that do not affect the output. Use state for anything shown on screen.

Why do my components render twice in development?

React StrictMode renders components twice in development to find side effects. Production builds render once.

Leave a Comment