0

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.

2 Answers 2

2

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

Sign up to request clarification or add additional context in comments.

6 Comments

Wow, it works! This is a great solution. I just tried <Route path={["/dataset/:version(\d+)", "/dataset"]}> but failed.
@aisensiy Only url patterns path-to-regexp understands are valid in React Router. Use \\d instead of \d should work. See more at: github.com/pillarjs/path-to-regexp/#path-to-regexp
I've tried with :version? as well and I couldn't make it work at that time. Thanks for sharing this!
@dev_junwen Thanks! I've tried a few different ways around this and the regex way seems to be the most straightforward.
@aisensiy If you find any answers useful, could you please accept and upvote it? Thanks!
|
1

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

1 Comment

I like the behavior that redirecting url /dataset/:version/overview to a dataset with no version to /dataset/overview. This is sooo cool.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.