1

I'm currently building a React application with the following workflow:

  • List of application categories, the user will select one
  • Once a user has selected an application category, they will select a water type from a list
  • A list of products will then be displayed depending on the category and type selected.
  • They can then select a product to see the information i.e. product charts, images etc.

The problem:

  • Once a user selects a product, if they click the back button, the category and type props are lost.

Solution required:

  • I need to be able to maintain these props/state at all times, allowing them to be updated if the user goes back and changes category/type

I've included my code for reference below.

index.js

import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter as Router } from "react-router-dom";

ReactDOM.render(
  <React.StrictMode>
    <Router>
      <App />
    </Router>
  </React.StrictMode>,
  document.getElementById("root")
);

App.js

import React from "react";
import { Route, Switch } from "react-router-dom";
import Home from "./components/Home";
import WaterType from "./components/WaterType";
import Products from "./components/Products";
import Product from "./components/Product";

import "./App.css";

function App() {
  return (
    <div className="App">
      <Switch>
        <Route exact path="/" component={Home} />
        <Route path="/waterType" component={WaterType} />
        <Route path="/products/:productName" component={Product} />
        <Route path="/products" component={Products} />
      </Switch>
    </div>
  );
}

export default App;

Home.js

import React, { Component } from "react";
import { Link } from "react-router-dom";
import CategoryData from "./data/CategoryData";

class Home extends Component {
  render() {
    return (
      <>
        <h1>Categories</h1>
        <ul>
          {CategoryData.map((cat, i) => (
            <li key={i}>
              <Link
                to={{
                  pathname: "/waterType",
                  name: cat.name,
                }}
              >
                <img src={cat.imageURL} alt={cat.name} />
                {cat.name}
              </Link>
            </li>
          ))}
        </ul>
      </>
    );
  }
}

export default Home;

WaterType.js

import React from "react";
import { Link } from "react-router-dom";
import WaterTypeData from "./data/WaterTypeData";

const WaterType = ({ location }) => {
  const categorySelected = location.name;
  return (
    <>
      <h1>Water Types</h1>
      <p>Current category: {categorySelected}</p>
      <ul>
        {WaterTypeData.map((type, i) => (
          <li key={i}>
            <Link
              to={{
                pathname: "/products",
                categorySelected: categorySelected,
                waterType: type.name,
              }}
            >
              {type.name} - {type.description}
            </Link>
          </li>
        ))}
      </ul>
    </>
  );
};

export default WaterType;

Products.js

import React from "react";
import { Link } from "react-router-dom";
import ProductData from "./data/ProductData";

const Products = ({ location }) => {
  const categorySelected = location.categorySelected;
  const waterType = location.waterType;

  const ProductsResult = ProductData.filter(x => x.categories.includes(categorySelected) && x.waterTypes.includes(waterType));

  return (
    <>
      <h1>Products</h1>
      <p>Current category: {categorySelected && categorySelected}</p>
      <p>Water Type: {waterType && waterType}</p>

      <div className="products">
          <ul>
            {ProductsResult.map((item, i) => (
              <li key={i}>
                <Link
                  to={{
                    pathname: '/products/' + item.slug,
                    name: item.name,
                  }}
                >
                  {item.name}
                </Link>
              </li>
            ))}
          </ul>
      </div>
    </>
  );
};

export default Products;

Product.js

import React from "react";

const Product = ({ location }) => {
  const productName = location.name;

  return (
    <>
      <h1>{productName}</h1>
    </>
  );
};

export default Product;
BEDev
  • 699
  • 1
  • 5
  • 9

3 Answers3

4

The easiest solution that I can think of is to keep your selected choices (category and water type) in a top level context.

Something like this:

// ChoicesProvider.js
import React, { createContext, useState } from "react";

export const ChoicesContext = createContext(null);

export const ChoicesProvider = ({ children }) => {
  const [choices, setChoices] = useState({
    category: null,
    waterType: null,
  });

  return (
    <ChoicesContext.Provider value={{ choices, setChoices }}>
      {children}
    </ChoicesContext.Provider>
  );
};

…and then in your entry point:

// index.js
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
import { BrowserRouter as Router } from "react-router-dom";
import { ChoicesProvider } from "./context/ChoicesProvider";

ReactDOM.render(
  <React.StrictMode>
    <ChoicesProvider>
      <Router>
        <App />
      </Router>
    </ChoicesProvider>
  </React.StrictMode>,
  document.getElementById("root")
);

…and then each time you pick a category / waterType save the selected state in a context using setChoices defined in context. For example:

// Home.js
import React, { Component } from "react";
import { Link } from "react-router-dom";
import CategoryData from "./data/CategoryData";
import { ChoicesContext } from "../../context/ChoicesContext";

const Home = () => {
  const { choices, setChoices } = useContext(ChoicesContext);

  return (
    <>
      <h1>Categories</h1>
      <ul>
        {CategoryData.map((cat, i) => (
          <li key={i}>
            <Link
              onClick={() => setChoices({ ...choices, category: cat.name })}
              to={{
                pathname: "/waterType",
                name: cat.name,
              }}
            >
              <img src={cat.imageURL} alt={cat.name} />
              {cat.name}
            </Link>
          </li>
        ))}
      </ul>
    </>
  );
};

export default Home;

Hopefully that gives you an idea. Have a great day

Paweł Grzybek
  • 1,093
  • 7
  • 14
0

First of all, I am not sure if your Router setup is necessary. React is great for single page applications (SPA), this means, you don't neet new page for every single functionality. You can very comfortably build your application on single Route, changing just the components you currently need. So the first possible solution is to build the application on single page, using three simple states

[category, setCategory] = useState(null)
[waterType, setWatertype] = useState(null)
[product, setProduct] = useState(null)

Based on this you can simply show or hide the select options

<div id="selections">
  {!category && (<Categories />)}
    {(category && !watertype) && (<WaterTypes />)}
    {category && watertype) && (<Products />)}
</div>

category && <WaterTypes /> means, WaterTypes will be displayed only if category is true.

This if course requires your Link to be replaced with something like

<button type="button" onClick={() => setCategory(cat.name)}>{cat.name}</button>

With this approach you can then handle back and forward button to manipulate your states and URL, so the user has access to desired category with hyperlink like www.youreshop.com?category=first&watertype=second

Another approach is to add context to your app, what would allow you to share states between individual components.

Fide
  • 1,127
  • 8
  • 7
-1

I highly recommend Redux (https://redux.js.org/) for application state managemt. In general it's a good idea to include Redux to modern React applications, and in your case it seems to be exactly what you're looking for. Hope this helps. Cheers

LetItBeAndre
  • 151
  • 1
  • 10