5

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

levensta
  • 147
  • 1
  • 9
  • 3
    Where are you redirecting anywhere? Where are you asynchronously loading data? Can you edit post to include a [minimal, complete, and reproducible code example](https://stackoverflow.com/help/minimal-reproducible-example) for what you are trying to do? – Drew Reese Apr 23 '22 at 04:24

1 Answers1

4

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:

  1. Convert App to a function component.
  2. Create a custom 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);
Drew Reese
  • 165,259
  • 14
  • 153
  • 181