2

I am in the process of learning React and am creating a React desktop app where the side-navigation must change depending on the user's current location (determined by standard navgation buttons in the App body).

The application is accessible here: https://codesandbox.io/s/conditional-navigation-di8t5?file=/pages/Start.jsx

I am able to make the navigation options render conditionally as the user moves through the app. See for example: from 'Start' to 'Page A', to 'Page B'. The problem is when they move back to a previous location, the navigation also reverts. I want it to be so that once the user has reached Page B and goes back to Page A, Page B is still accessible in the side nav.

From research it seems that using the React Context API would be the best solution. Is this the case, or is there an easier method that I am missing? Thank you.

thomasbishop
  • 73
  • 1
  • 8
  • Are you using redux? – noob_nerd Jan 12 '21 at 12:37
  • No, simple React – thomasbishop Jan 12 '21 at 12:38
  • 1
    Can you explain the logic a little more? For certain pages, you want to display the previous page navbar instead of the current page navbar? Or just add the previous page link to the current navbar? You can separate your content and navbar, and keep the state of the pages visited and render conditionally based on that. This may not need context at all, you just have to save the links to your state. But I'm not sure if I understood the logic right – c0m1t Jan 12 '21 at 13:03
  • @c0m1t Thank you. The second optionis closest to what I'm after. There are basically two navigation systems: the on page sequential movement through the app and the global nav in the sidebar that isn't sequentuial. As the user moves through the app via the main pages, the pages they have passed through should appear in the side nav.Eg. ig the user has reached Page C, they should see A and B in the sidenav. This works but the problem that if they are on C and go back to A, pages B and C don't persist in the nav. The user 'starts again' and have to use the on-page buttons to navigate to C. – thomasbishop Jan 12 '21 at 13:24
  • @c0m1t I've tried querying the history object of React Router as another method but have had no luck with that. My idea was to keep a list of the paths that have been accessed by the user in an array and programmatically render them in the side nav if they are in history but whilst I can see a count of pages accessed I couldn't retain the pathnames. – thomasbishop Jan 12 '21 at 13:27
  • 1
    You can use a hook like [`useLocation`](https://reactrouter.com/web/api/Hooks/uselocation), so everytime the user changes location, this hook will be called and you can save the current location to your state. you can save it in a `ref` as well. – c0m1t Jan 12 '21 at 13:40

1 Answers1

1

You can can take the approach mentioned by c0m1t of using useLocation to track the current location.

To be able to track the location consistently and for other more organisational reasons I would also advise you to change your application structure somewhat.

I think one problem with the current structure is that Layout is a child of each of your page components. Instead I think this component fits more as a parent of your pages, instead of having every page render a Layout component. The same applies to your Header component.

I would recommend to change your App.js file to something like this:

function App() {
  return (
    <ThemeProvider theme={theme}>
      <Header />
      <Layout>
        <Switch>
          <Route exact path="/" component={Start} />
          <Route exact path="/page-A" component={PageA} />
          <Route exact path="/page-B" component={PageB} />
          <Route exact path="/page-C" component={PageC} />
        </Switch>
      </Layout>
    </ThemeProvider>
  );
}

I also don't think you need a different Menu component for each screen.

You could instead have a general Menu component and render this component inside your Layout component:

const Layout = (props) => {
  return (
    <React.Fragment>
      <Grid container>
        <Grid item xs={3} className="side-nav">
          <Menu />
        </Grid>
        <Grid item xs={9} className="main">
          <Box p={4}>{props.children}</Box>
        </Grid>
      </Grid>
    </React.Fragment>
  );
};

Then you could change the Menu component to this:

const pageLocations = ["/page-A", "/page-B", "/page-C"];
const pages = ["Page A", "Page B", "Page C"];

const Menu = () => {
  const location = useLocation();
  const [locations, setLocations] = React.useState([]);

  React.useEffect(() => {
    const currentPath = location.pathname;
    if (!locations.includes(currentPath)) {
      setLocations([...locations, currentPath]);
    }
  }, [location, locations, setLocations]);

  return (
    <Box>
      <MenuList>
        {pages.map((page, i) => {
          return (
            <MenuItem
              key={i}
              to={pageLocations[i]}
              className={locations.includes(pageLocations[i]) ? "" : "deactive"}
              component={NavLink}
            >
              {page}
            </MenuItem>
          );
        })}
      </MenuList>
    </Box>
  );
};

So the approach is to keep track of all pages visited before from the menu. A location is mapped to each menu item in a way where we can check whether a menu item should be shown based on all locations already visited and the location mapped to the menu item.

This answer doesn't handle the case where you start from the url of a particular page like /page-A instead of /.


sandbox example

5eb
  • 14,798
  • 5
  • 21
  • 65