Search code examples
reactjsreact-routerreact-hooksuse-effectuse-state

What is the React Hooks way of handling multiple actions while also setting state?


I have 4 components: App, CatalogList, CatalogPreview, CatalogDetail

App is the parent component and I have these state values:

const [catalog, setCatalog] = useState({ id: 1, name: 'Electronics' })
const [isModalOpen, setIsModalOpen] = useState(false)

CatalogList and CatalogPreview components receive the above state values as props along with their setter functions.

Inside CatalogList.js:

I have a list of catalog links (electronics, furniture, ...) that when you click on one of them,
it opens up the CatalogPreview modal. Inside the modal, there's an 'Explore' button.

When you click the 'Explore' button, it needs to do 4 things:

set the selected catalog in state (in App.js)
save selected catalog in localStorage
navigate to the detail page
close the modal window

I attempted the following in the 'Explore' button's click handler (Inside the modal):

function explore() {
  props.setCatalog({...})
  props.setIsModalOpen(false)
  window.localStorage.setItem('catalog', JSON.stringify({...})
  history.push('detail-route') // this brings up the CatalogDetail
}

Link to Code Sandbox

Questions:

  1. Is this the correct way?
  2. In the CatalogDetail component, if I click a Delete button and make a delete API request,
    how do I navigate back to the CatalogList route AFTER the call is completed.

Solution

    1. Instead of storing the modal open/close state in the parent App component it should be demoted to the child CatalogList component that is managing the modal.

    2. Move the persisting to localStorage to a useEffect hook with a dependency on the catalog state. When the state updates, persist it to localStorage.

    3. Create a updateCatalog callback function to accept a new value to update the catalog state with. Pass this to CatalogList.

    4. The explore callback should be promoted/lifted up to the CatalogList component. This allows CatalogPreview to basically just render the modal. explore still calls to update the catalog data, close the modal, and navigate.

      App

      function App() {
        const [catalog, setCatalog] = useState({ id: 1, name: "Electronics" });
      
        useEffect(() => {
          window.localStorage.setItem("catalog", JSON.stringify(catalog));
        }, [catalog]);
      
        const updateCatalog = value => {
          setCatalog( ...value... );
        };
      
        return (
          <Switch>
            <Route exact path="/">
              <CatalogList updateCatalog={updateCatalog} />
            </Route>
            <Route exact path="/detail">
              <CatalogDetail catalog={catalog} />
            </Route>
          </Switch>
        );
      }
      

      CatalogList

      function CatalogList(props) {
        const history = useHistory();
      
        const [isOpen, setIsOpen] = useState(false);
        const catalogs = [
          { id: 1, name: "Electronics" },
          { id: 2, name: "Furniture & Appliances" },
          { id: 3, name: "Sports & Outdoors" }
        ];
      
        const openPreview = () => {
          setIsOpen(true);
        };
      
        function explore() {
          props.updateCatalog(... some new value ...);
          setIsOpen(false);
          history.push("/detail");
        }
      
        return (
          <div>
            Available catalogs
            <ul>
              {catalogs.map((c, index) => (
                <li key={c.id} onClick={() => openPreview(c.id)} className="link">
                  {c.name}
                </li>
              ))}
            </ul>
            <CatalogPreview isOpen={isOpen} onExplore={explore} />
          </div>
        );
      }
      

      CatalogPreview

      function CatalogPreview(props) {
        return (
          <Modal show={props.isOpen}>
            <Modal.Header closeButton>
              <Modal.Title>Catalog Preview</Modal.Title>
            </Modal.Header>
            <Modal.Footer>
              <Button variant="primary" onClick={props.onExplore}>
                Explore
              </Button>
            </Modal.Footer>
          </Modal>
        );
      }
      
    5. Use useHistory and update the onClick handler to navigate back to the home page.

      function CatalogDetail(props) {
        const history = useHistory();
      
        return (
          <div>
            <p>Detail Page</p>
            <button
              onClick={() => {
                // Make API call then navigate home
                history.push("/");
              }}
            >
              DELETE
            </button>
          </div>
        );
      }
      

    Demo

    Edit what-is-the-react-hooks-way-of-handling-multiple-actions-while-also-setting-stat