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>
)
}
}
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>
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>
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.