Blog Home

Triggering remount of React components

September 12, 2026

Kazi Ehsan Aziz

I want to share a very particular scenario I faced when building a multi-document rich text editor using Tiptap in a Next.js application and some React practices that I learned from it.


The context

Let's begin by looking at the user interface to make things more clear.

Multi document editor dashboard view

In this application, a Turn is basically a rich text document. The top row shows it is possible to navigate between several Turns using the arrow buttons. This is what I meant by multi-document. The text body shown belongs to the currently selected Turn (Turn 3 in this screenshot). This is the editor part.

Now, let's look at the component boundaries of the three components that make up this interface.

Editor component boundaries


The Turns component has a cache of turns/documents. It renders a TurnCard component which is responsible for rendering some metadata about the currently shown Turn (author name, word count, etc.) and the editor component TurnEditForm.

turns.tsx
export function Turns({
  turns
}: {
  turns?: PostTurnDto[];
}) {
  // The state that renders the currently displayed Turn.
  const [selectedTurn, setSelectedTurn] = useState<PostTurnDto>(turns[0]);

  return (
    <TurnCard
      turn={selectedTurn}
    />
  )
}

The Turns component also has logic that handles the arrow buttons which changes the selectedTurn, code not shown here for brevity. When selectedTurn updates, the goal is to make sure TurnCard successfully shows data for the current selectedTurn.


The constraint

Now, we can just use setSelectedTurn to change the currently selected Turn, and that alone does cause TurnCard to rerender, since selectedTurn is passed into its turn prop. However:

  1. TurnCard renders TurnEditForm, which is just a React Hook Form wrapping the Tiptap component (the actual editor). And Tiptap seeds its text from its source (React Hook Form's state) once, when the editor instance is mounted, and never again. This behavior of Tiptap is actually useful in my application since I have a debounced autosave feature in TurnEditForm that frequently propagates text updates back up to the Turns component's main cache. Why that becomes useful is a bit beyond the scope of this article, but the bottomline is switching a Turn by changing selectedTurn from Turns does rerender TurnCard and its metadata appropriately, but the editor keeps showing the previous Turn's text. So, I need a way to tear down the editor and remount it.
  2. Also in the case of my application, TurnCard has a few React states of its own that are not reset when the component rerenders. And within the TurnCard component, for reasons that are also outside the scope of this article, I did not use useEffect with a dependency array containing the turn prop to reset those states.

So, rerendering TurnCard on a prop change was not enough in my case.


The solution

Remount using the key prop. A key is how you throw a component away and start over, as highlighted in the previous code block, on line 11 of turns.tsx. Most React code only ever meets key in lists. But changing any component's key destroys that component and builds a fresh one in its place.

turns.tsx
export function Turns({
  turns
}: {
  turns?: PostTurnDto[];
}) {
  // The state that renders the currently displayed Turn.
  const [selectedTurn, setSelectedTurn] = useState<PostTurnDto>(turns[0]);

  return (
    <TurnCard
      key={selectedTurn.id}
      turn={selectedTurn}
    />
  )
}

Compound keys

What if you run into a case where you are already using some sort of id for a key, like we used selectedTurn.id, and you need to remount the component on a particular event/trigger but your id cannot change. Well, React only cares if the value of key changes. So, use a compound value that combines your id with another string. It's called a seedEpoch and the TurnCard component uses this exact pattern when rendering TurnEditForm to satisfy another use-case in my app.

turn-card.tsx
export function TurnCard({
  turn
}: {
  turn: PostTurnDto;
}) {
  // a state that you can control explicity from any event
  const [seedEpoch, setSeedEpoch] = useState(0);

  return (
    <TurnEditForm
      key={`${turn.id}:${seedEpoch}`}
      turn={turn}
    />
  )
}

You can setSeedEpoch from any event/trigger and have full control over the remounting of your component. In fact, your use-case may not involve any ids at all and you could just use a seedEpoch on its own.