I have this code in which I use react-router-dom v6 for routing
class App extends Component {
constructor(props) {
super(props);
this.state = {
accounts: [],
};
}
componentDidMount() {
getAccounts().then(r => this.setState({ // async req
accounts: r
}));
};
render() {
return (
<Routes>
<Route path="/" element={<AppLayout accounts={this.state.accounts} />}>
<Route index element={<Navigate to={`/account/1`} />}/>
<Route path="/account/:accountId" element={<TimelinePage/>}/>
<Route path="*" element={<NotFoundPage/>}/>
</Route>
</Routes>
);
}
}
I want to redirect from the home page to /accounts/${this.state.accounts[0]} but array this.state.accounts is filled after an asynchronous request before mounting the App component and it turns out that the first time I render, I get to <NotFoundPage/>. This is logical, but how can I get async data first and redirect only after that?
I made a temporary solution
<Route index element={<Navigate to={'/account/1'} />}/>`
But this does not guarantee that an element with ID 1 will always exist in the array
P.S. Array this.state.accounts contains objects that have the numeric property accountId. Inside the <TimelinePage/> component I use useParams() to get the account id and in the <AppLayout/> component I render each element of the array
Use the useNavigate hook to access the navigate function and issue an imperative redirect after the accounts have been fetched. This issue here though is that App is a class component and can't directly use React hooks.
Options are:
App to a function component.withRouter HOC to access the hook and inject navigate as a prop.App is simple enough that it would be trivial to convert to a function component.
import { useEffect, useState } from 'react';
import { useNavigate } from 'react-router-dom';
const App = () => {
const navigate = useNavigate();
const [accounts, setAccounts] = useState([]);
useEffect(() => {
getAccounts()
.then(accounts => {
setAccounts(accounts);
navigate("/account/1", { replace: true });
});
}, []);
return (
<Routes>
<Route path="/" element={<AppLayout accounts={accounts} />}>
<Route path="/account/:accountId" element={<TimelinePage />} />
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
);
}
If the class components are more complex it may be easier to create a custom withRouter HOC and pass in navigate as a prop.
import { useNavigate, ...other hooks... } from 'react-router-dom';
const withRouter = Component => props => {
const navigate = useNavigate();
...other hooks...
return (
<Component
{...props}
navigate={navigate}
...other hooks...
/>
);
};
App
class App extends Component {
state = {
accounts: [],
}
componentDidMount() {
const { navigate } = this.props;
getAccounts()
.then(accounts => {
this.setState({ accounts });
navigate("/account/1", { replace: true });
});
}
render() {
const { accounts } = this.state;
return (
<Routes>
<Route path="/" element={<AppLayout accounts={accounts} />}>
<Route path="/account/:accountId" element={<TimelinePage />} />
<Route path="*" element={<NotFoundPage />} />
</Route>
</Routes>
);
}
}
export default withRouter(App);