javascriptreactjsreact-hooksreact-custom-hooks

Show/Hide Components From Other Components in ReactJS


I have a custom hook written to show or hide a list when a button clicked. Below is the custom hook snippet I have written:

import { useEffect } from "react";
import { useState } from "react";

function useVisibilityStatus() {

    const [isVisible, setIsVisible] = useState(true);

    useEffect( () => {

        setIsVisible(!isVisible);              


    }, [isVisible]);

    return isVisible;
}

export default useVisibilityStatus;

And in another component I have written the logic to view or hide the list.

import ProductListView from "../common/ProductListView";
import SideNavView from "../common/SideNavView";
import SimpleMap from '../googlemap/SimpleMap';
import useVisibilityStatus from '../customhooks/useVisibilityStatus';

function ServiceContentView() {
    const listVisible = useVisibilityStatus();

    return (
        <div className="row p-0 vw-100 service-view-body bg-primary position-relative">
            <div className="zindex-value position-absolute">
              <div className="service-view-sidenav float-end">
                <SideNavView />
              </div>
            </div>
            {listVisible && <ProductListView />}
            <SimpleMap />
        </div>
    );
}

export default ServiceContentView;

And in the SideNavView I have the button where upon clicking it shows the product list or hides it if it is visible. The SideNavView is incomplete, as I am bit struggling on how to fit my requirement in ReactJS.

import useVisibilityStatus from '../customhooks/useVisibilityStatus';

function SideNavView() {
    const   listVisible = useVisibilityStatus();

    const handleClick = () => {
        
    }

    return (
        <div className="">
            <div className="row">                
                <div className="card p-0 w-100 border-radius-none sidenav-view" onClick={handleClick}>
                    <span className="text-center mt-2 text-white">
                        <svg xmlns="http://www.w3.org/2000/svg" width="40" height="40" fill="currentColor" className="bi bi-card-list" viewBox="0 0 16 16">
                            <path d="M14.5 3a.5.5 0 0 1 .5.5v9a.5.5 0 0 1-.5.5h-13a.5.5 0 0 1-.5-.5v-9a.5.5 0 0 1 .5-.5h13zm-13-1A1.5 1.5 0 0 0 0 3.5v9A1.5 1.5 0 0 0 1.5 14h13a1.5 1.5 0 0 0 1.5-1.5v-9A1.5 1.5 0 0 0 14.5 2h-13z"/>
                            <path d="M5 8a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7A.5.5 0 0 1 5 8zm0-2.5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm0 5a.5.5 0 0 1 .5-.5h7a.5.5 0 0 1 0 1h-7a.5.5 0 0 1-.5-.5zm-1-5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zM4 8a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0zm0 2.5a.5.5 0 1 1-1 0 .5.5 0 0 1 1 0z"/>
                        </svg>
                    </span>
                    <div className="card-body p-0">
                        <h5 className="card-title text-center text-white">View Products</h5>
                    </div>
                </div>                
            </div>
        </div>
    )
}

export default SideNavView;

Solution

  • React hooks don't magically share state, so with the current useVisibilityStatus implementation they will each have and update their own state independently.

    What you can do is create a React Context to hold the single isVisible state value and expose it and an updater function to consumers.

    import { createContext, useContext, useState } from 'react';
    
    const VisibleContext = createContext({
      isVisible: false,
      toggleIsVisible: () => {},
    });
    
    export const useVisibilityStatus = () => useContext(VisibleContext);
    
    const VisibleContextProvider = ({ children }) => {
      const [isVisible, setIsVisible] = useState(true);
    
      const toggleIsVisible = () => setIsVisible(isVisible => !isVisible);
    
      return (
        <VisibleContext.Provider value={{ isVisible, toggleIsVisible }}>
          {children}
        </VisibleContext.Provider>
      );
    };
    
    export default VisibleContextProvider;
    

    The VisibleContextProvider just needs to wrap the part of the ReactTree that contains the components that need to access its context value, e.g. ServiceContentView and SideNavView, and these components use the useVisibilityStatus to read the current isVisible state value or access the toggleIsVisible updater function.

    import VisibleContextProvider from '../path/to/VisibleContextProvider';
    
    ...
    
    return (
      ...
      <VisibleContextProvider>
    
        ...
        <ServiceContentView />
        ...
    
      </VisibleContextProvider>
      ...
    );
    
    import ProductListView from "../common/ProductListView";
    import SideNavView from "../common/SideNavView";
    import SimpleMap from '../googlemap/SimpleMap';
    import { useVisibilityStatus } from '../path/to/VisibleContextProvider';
    
    function ServiceContentView() {
      const { isVisible } = useVisibilityStatus();
    
      return (
        <div className="row p-0 vw-100 service-view-body bg-primary position-relative">           
          <div className="zindex-value position-absolute">
            <div className="service-view-sidenav float-end">
              <SideNavView />
            </div>
          </div>
          {isVisible && <ProductListView />}
          <SimpleMap />          
        </div>
      );
    }
    
    export default ServiceContentView;
    
    import { useVisibilityStatus } from '../path/to/VisibleContextProvider';
    
    function SideNavView() {
      const { toggleIsVisible } = useVisibilityStatus();
    
      const handleClick = () => {
        toggleIsVisible();
      };
    
      return (
        <div className="">
          <div className="row">                
            <div
              className="...."
              onClick={handleClick}
            >
              <span className="text-center mt-2 text-white">
                ...
                </svg>
              </span>
              <div className="card-body p-0">
                <h5 className="....">View Products</h5>
              </div>
            </div>                
          </div>
        </div>
      )
    }
    
    export default SideNavView;