aisensiy
aisensiy

Reputation: 1520

React router conditional redirect url based on async fetched data

I have a codesand box below which show the situation I have:

https://codesandbox.io/embed/sad-nash-x4n69

I have an object named dataset which can have zero, one or more versions. The component should show the latest version if dataset has any versions or show a default empty page. Because of this two conditions the component has two routes: /dataset and /dataset/:version:

If the dataset is empty I need the url route to /dataset/overview which should the basic info of the dataset.

If the dataset is not empty I need to redirect the route to /dataset/${dataset.versions} which will show the latest version data.

Meanwhile, the DatasetView Component has three tabs: overview versions settings. I hope the page will automatically redirect to /dataset/overview or /dataset/:version/overview depends on the dataset.versions.

But my code below have a bug: when the dataset has no versions, the route will change to /dataset/overview/overview but not /dataset/overview. Hope some one can help me solve this problem.

Upvotes: 0

Views: 371

Answers (2)

Claire Lin
Claire Lin

Reputation: 2382

While @dev_junwen's solution probably works, I'd like to provide a simpler one and look into the reason why your original router didn't work.

In your code, the router has a param version that could be omitted. Since you didn't specify the param as optional, the app always expect the slug following dataset to be version and append another /overview even when it's not needed.

By setting it as optional at the Router level, the app should work.

  <Route path={["/dataset/:version([0-9]+)?", "/dataset"]}>

In order for the existing routes inside the DatassetComponent component to work, I've added a regex to the version param so that it only matches digits.

See a working code sandbox here: https://codesandbox.io/s/epic-beaver-c14kk

Upvotes: 2

junwen-k
junwen-k

Reputation: 3654

At first the question seems easy but it took me quite long to figure out how to do this! This is the only way I can think of right now. If there's some suggestions on how to improve the codes, feel free to submit a change request :)

I cannot guarantee that this is the best way of doing this, but it works for your case.

import React, { useState, useEffect } from "react";
import ReactDOM from "react-dom";
import {
  BrowserRouter as Router,
  Route,
  Switch,
  NavLink,
  useRouteMatch,
  Redirect
} from "react-router-dom";

import "./styles.css";

const datasetWithVersions = {
  id: "dataset-with-versions",
  versions: 2,
  versionList: [
    {
      version: 1,
      size: 20001
    },
    {
      version: 2,
      size: 191232
    }
  ],
  name: "a-dataset-with-2-versions"
};

const datasetWithNoVerrsions = {
  id: "dataset-with-no-versions",
  versions: 0,
  name: "an-empty-dataset"
};

function loadDataset() {
  return new Promise(resolve => {
    setTimeout(() => {
      if (Math.random() >= 0.5) {
        console.log("loading dataset with versions....");
        resolve(datasetWithVersions);
      } else {
        console.log("loading dataset with no versions....");
        resolve(datasetWithNoVerrsions);
      }
    }, 1000);
  });
}

function App() {
  return (
    <div className="App">
      <Router>
        <Switch>
          <Route path="/dataset" component={DatassetComponent} />
          <Redirect to="/dataset" />
        </Switch>
      </Router>
    </div>
  );
}

function DatassetComponent() {
  let [dataset, setDataset] = useState(null);
  const match = useRouteMatch();

  useEffect(() => {
    loadDataset().then(data => setDataset(data));
  }, []);

  if (!dataset) return <div>loading...</div>;

  const getURL = to => {
    if (dataset.versions) {
      return `${match.path}/${dataset.versions}/${to}`;
    }
    return `${match.path}/${to}`;
  };

  return (
    <div>
      <div className="navbar">
        <NavLink to={getURL("overview")} activeClassName="active">
          OverView
        </NavLink>
        {dataset.versions ? (
          <NavLink to={getURL("versions")} activeClassName="active">
            Versions
          </NavLink>
        ) : null}
        <NavLink to={getURL("settings")} activeClassName="active">
          Settings
        </NavLink>
      </div>

      <div className="content">
        <Switch>
          <Route path={getURL("versions")}>
            <div>Versions</div>
          </Route>
          <Route path={getURL("settings")}>
            <div>settings</div>
          </Route>
          <Route path={getURL("overview")}>
            <div>Overview for dataset {dataset.name}</div>
          </Route>
          <Redirect to={getURL("overview")} />
        </Switch>
      </div>
    </div>
  );
}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);

Working code sandbox: https://codesandbox.io/s/react-router-optional-path-demo-p50b4?fontsize=14

Upvotes: 1

Related Questions