How to set default state for React toggle custom hook?

Viewed 469

So the scenario is once I click a chevron button, an accordion component which placed right underneath of the chevron button opens and shows its children components.

However I don't want other chevron buttons share same state so that only a clicked chevron button with a related accordion expands.

And by default once the template component has just mounted, accordions expand showing its children with chevron buttons are up ^

codes:

// useChevron hook

import { useState } from 'react';


const useChevron = () => {
  const [isChevronUp, setIsChevronUp] = useState<Partial<any>>({});


  const handleChevron = (id: string) => {
    setIsChevronUp((previousState) => ({
      ...previousState,
      [id]: !previousState[id],
    }));
  };

  return { isChevronUp, setIsChevronUp, handleChevron };
};

export default useChevron;

// template
...
const { handleChevron, isChevronUp } = useChevron();
...

<div id="containerOne">

// when it has just been rendered isChevronUp is empty object so in case set isOpen as true which makes chevron looks like ^ and shows accordion's children
<ChevronButton
            isOpen={_.isEmpty(isChevronUp) ? true : !!isChevronUp.containerOne} 
            id='containerOne'
            onClick={() => handleChevron('containerOne')}
          />
</div>
<Accordion isOpen={_.isEmpty(isChevronUp) ? true : !!isChevronUp.containerOne}>
<ChildrenComponent1 />
<ChildrenComponent2 />
...

It seems like working except I have to click twice the chevron button at very first time then toggling fine as expected.

I tried below:

const [isChevronUp, setIsChevronUp] = useState<Partial<SomeType>>({
      'default': true
});
...

<ChevronButton
            isOpen={Object.keys(isChevronUp).length === 1 ? isChevronUp.default : !!isChevronUp.containerOne} 
            id='containerOne'
            onClick={() => handleChevron('containerOne')}
          />
</div>
<Accordion isOpen={Object.keys(isChevronUp).length === 1 ? isChevronUp.default : !!isChevronUp.containerOne}>

but it's the same having to click the chevron button twice.

What is the best way to set defualt chevron up and accordion expanded in this situation and solve the twice clicking stuff?

Thanks in advance.

2 Answers

Your problem is your isChevronUp state hook value, it does not tell you if the specified chevron is selected but contains an object of the selected chevrons.

As such, isChevronUp should be a function that you call, or you should check the state accordingly using the id string of your chevron

// useChevron hook

import { useState } from 'react';


const useChevron = () => {
  const [chevronState, setChevronState] = useState<Partial<any>>({});


  const handleChevron = (id: string) => {
    setChevronState((previousState) => ({
      ...previousState,
      [id]: !previousState[id],
    }));
  };

  const isChevronUp = (id: string) => Boolean(chevronState[id]);

  return { isChevronUp, handleChevron };
};

export default useChevron;

And when you check if it's open do:

<ChevronButton
  isOpen={isChevronUp('containerOne')}
...

something like that

You can add an initialization function for chevrons hook, where you will create a field with a key equal to the chevron id and a value equal to true by default. Then you can just get the chevron value without a condition

// useChevron hook

import { useState } from 'react';

const useChevron = () => {
  const [isChevronUp, setIsChevronUp, initChevron] = useState<Partial<any>>({});

  const init = (id: string) => {
    setIsChevronUp((previousState) => ({
      ...previousState,
      [id]: true,
    }));
  };

  const handleChevron = (id: string) => {
    setIsChevronUp((previousState) => ({
      ...previousState,
      [id]: !previousState[id],
    }));
  };

  return { isChevronUp, setIsChevronUp, handleChevron, init };
};

export default useChevron;

// template
...
const { handleChevron, isChevronUp, init } = useChevron();

useEffect(() => {
  init('containerOne');
}, []);
...

<div id="containerOne">

// when it has just been rendered isChevronUp is empty object so in case set isOpen as true which makes chevron looks like ^ and shows accordion's children
<ChevronButton
            isOpen={isChevronUp.containerOne} 
            id='containerOne'
            onClick={() => handleChevron('containerOne')}
          />
</div>
<Accordion isOpen={isChevronUp.containerOne}>
<ChildrenComponent1 />
<ChildrenComponent2 />
...

Related