'<Outlet /> fails to rerender with react router v6

In the following code, the url changes but the content doesn't rerender until manual refresh. What am I doing wrong here? I could use props.children or something but don't really want to. My understanding of is that it should render the content of the nested elements under .

const LandingPage = () => {
        return (
            <div>
                <div>
                    buttons
                    <Button>
                        <Link to="/team1">team1</Link>
                    </Button>
                    <Button>
                        <Link to="/team2">team2</Link>
                    </Button>
                    <Button>
                        <Link to="/team3">team3</Link>
                    </Button>
                </div>
                <Outlet />
            </div>
        )
}

export default class Router extends Component<any> {

    state = {
        teams: [team1, team2, team3] as Team[]
    }

    public render() {
        return (
            <BrowserRouter>
                <Routes>
                    <Route path='/' element={<LandingPage />} >
                        {
                            this.state.teams.map(team => {
                                const path = `/${team.name.toLowerCase()}`
                                return (
                                    <Route path={path} element={
                                        <BaseTeam
                                            name={team.name}
                                            TL={team.TL}
                                            location={team.location}
                                            members={team.members}
                                            iconPath={team.iconPath}
                                        />
                                    } />)
                            })
                        }
                    </Route>
                </Routes>
            </BrowserRouter>
        )
    }
}


Solution 1:[1]

It seems the mapped routes are missing a React key. Add key={path} so each route is rendering a different instance of BaseTeam.

The main issue is that the BaseTeam component is the same "instance" for all the routes rendering it.

It should either also have a key prop specified so when the key changes BaseTeam is remounted and sets the name class property.

Example:

<BrowserRouter>
  <Routes>
    <Route path="/" element={<LandingPage />}>
      {this.state.teams.map((team) => {
        const path = `/${team.name.toLowerCase()}`;
        return (
          <Route
            key={path} // <-- add missing React key
            path={path}
            element={(
              <BaseTeam
                key={path} // <-- add key to trigger remounting
                name={team.name}
              />
            )}
          />
        );
      })}
    </Route>
  </Routes>
</BrowserRouter>

Edit outlet-fails-to-rerender-with-react-router-v6

Or BaseTeam needs to be updated to react to the name prop updating. Use the componentDidUpdate lifecycle method to check the name prop against the current state, enqueue a state update is necessary.

Example:

class BaseTeam extends React.Component {
  state = {
    name: this.props.name
  };

  componentDidUpdate(prevProps) {
    if (prevProps.name !== this.props.name) {
      this.setState({ name: this.props.name });
    }
  }

  render() {
    return <div>{this.state.name}</div>;
  }
}

...

<BrowserRouter>
  <Routes>
    <Route path="/" element={<LandingPage />}>
      {this.state.teams.map((team) => {
        const path = `/${team.name.toLowerCase()}`;
        return (
          <Route
            key={path}
            path={path}
            element={<BaseTeam name={team.name} />}
          />
        );
      })}
    </Route>
  </Routes>
</BrowserRouter>

Edit outlet-fails-to-rerender-with-react-router-v6 (forked)

As you've found out in your code though, just rendering the props.name prop directly is actually the correct solution. It's a React anti-pattern to store passed props into local state. As you can see, it requires extra code to keep the props and state synchrononized.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1