1

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;
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
Cham
  • 787
  • 3
  • 11
  • 25
  • Your `useVisibilityStatus` hook will cause infinite render. – kennarddh Mar 31 '23 at 04:49
  • @kennarddh: yes, at the moment it is infinitely rendering. The functionality is still incomplete. I want to show/hide my product list upon a button click in another component. Namely in the SideNavView component – Cham Mar 31 '23 at 04:52
  • To clarify @kennarddh comment, the `useVisibilityStatus` implements a `useEffect` hook with a dependency on `isVisible` and unconditionally updates that state with `setIsVisible(!isVisible);`. It will render loop. – Drew Reese Mar 31 '23 at 04:52
  • @DrewReese: yes it is, please see my above comment – Cham Mar 31 '23 at 04:54
  • So you want to show `ProductListView` when button in `SideNavView` is clicked? – kennarddh Mar 31 '23 at 04:55
  • @kennarddh: yes, that's what I am after. – Cham Mar 31 '23 at 04:56
  • Can you clarify what the use case is and what exactly the problem is or where you are stuck? It sounds like you want a "single" `isVisible` state that can be toggled from around the app. Is this accurate? – Drew Reese Mar 31 '23 at 04:56
  • @DrewReese: Basically what I want is, I have a button in "SideNavView" where upon clicking the button on that component, a list "ProductListView", in "ServiceComponentView" shows if it is hidden or hides if it is shown. – Cham Mar 31 '23 at 05:00

3 Answers3

1

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;
Drew Reese
  • 165,259
  • 14
  • 153
  • 181
0

You don't need to use custom hooks for that task. The better solution is to save visibility status into Main component's container and pass it as props to child components.

Lotpite
  • 336
  • 2
  • 4
0

To share state between deeply nested component you can use context

VisibilityContext.jsx

import { createContext, useState } from 'react'

const VisibilityContext = createContext({
  Visibility: {}
})

export const VisibilityContextProvider = ({ children })=>{
  const [Visibility, SetVisibility] = useState({})

  return (
    <VisibilityContext.Provider value={{ Visibility, SetVisibility }}>
      {children}
    </VisibilityContext.Provider>
  )
}

export default VisibilityContext

useVisibilityStatus.js

import { useState, useContext, useCallback } from "react";

import VisibilityContext from 'path/to/VisibilityContext.jsx'

const useVisibilityStatus = (scope) => {
  const { Visibility, SetVisibility } = useContext(VisibilityContext)
  
  const ToggleVisibility = useCallback(() => {
    SetVisibility(prev => !prev)
  }, [SetVisibility])

  return {
    IsVisible: Visibility[scope] ?? false,
    ToggleVisibility
  }
}

export default useVisibilityStatus;

You need to wrap the SideNavView and ServiceContentView with VisibilityContextProvider

SideNavView and ServiceContentView Parent

import { VisibilityContextProvider } from 'path/to/VisibilityContext.jsx'

const ... = () => {
  return (
    <VisibilityContextProvider>
       {/* SideNavView and ServiceContentView */}
    </VisibilityContextProvider>
  )
}

ServiceContentView.jsx

import useVisibilityStatus from 'path/to/useVisibilityStatus.js'

const ServiceContentView = () => {
  const { IsVisible } = useVisibilityStatus('ServiceContentView')

  return (
    ...
    {IsVisible && <ProductListView />}
    ...
  )
}

SideNavView.jsx

import useVisibilityStatus from 'path/to/useVisibilityStatus.js'

const SideNavView = () => {
  const { ToggleVisibility } = useVisibilityStatus('ServiceContentView')

  const handleClick = () => {
    ToggleVisibility()
  }
}
kennarddh
  • 2,186
  • 2
  • 6
  • 21