I have this hook that opens/closes some unique div’s. I use typescript
.
import { SetStateAction, useState } from "react";
export default function useOpened() {
const [isOpened, setIsOpened] = useState("closed");
const open = (depName: SetStateAction<string>) =>
setIsOpened(depName);
return [isOpened, open];
}
This works and opens the div like so:
<button onClick={() => open(item.text)}>{item.text}</button>
{isOpened === item.text && (
<div>Text stuff</div>
)}
<button onClick={() => open(item.chapter)}>{item.chapter}</button>
{isOpened === item.chapter && (
<div>Chapter description</div>
)}
In my component, I get this problem: This expression is not callable. Not all constituents of type ‘string | ((sectionName: SetStateAction) => void)’ are callable. Type ‘string’ has no call signatures. — How to fix?
Also I try to make the hook that it toggles between "open" and "close" the div’s. I tried with setState
but did not work. Also tried with status
but that also did not work.
My code works but somehow when I want to open one div, then the other opened div closes. So always only one div is open always. Any other div I want to open, makes the previous one close automatically. Open/close a div should not affect any other div to open/close. Cannot figure out how to fix?
2
Answers
There are three issues with your code.
1. Both buttons set the same state
The core issue is that those two buttons are in the same component, so they are setting the same state. If you want to two divs to open independently, you will either need to break those out into two separate components or have two separate calls to that hook.
Something like this should work: https://playcode.io/1730761
2. Your hook returns an Array instead of an object
The example above only solves part of your issue, though. You are getting a TS compiler because the array is of type
string | SetStateAction<string>
, due to the fact that it has a mix of strings and functions. If you instead return an object from your hook, this will fix that. You can try the code below out here: https://playcode.io/17308063. You need some way to change the state back to ‘closed’
As coded, you only ever set the value to the name of the shown componet. You can add simple check to the hook to see if the isOpen prop is already set to the name of the component, and if so, set it to close instead:
Putting it all to gether
The code below shows how you could pull this al together: https://playcode.io/1731497
After thought
In the example below, swapped out the text prop for a boolean to toggle the
open
state. I also broke this up into two components. These two tweaks may end up being a little more clean and re-usable in the long run (generally breaking things up into smaller components helps in that regard), depending on your application: https://playcode.io/1731079When you want to toggle an item you need to move between 2 states. It might be
false
andtrue
or key exists / absent.Toggling multiple items requires multiples states, 1 for each item, so that one item’s toggle state won’t affect the rest.
To support a dynamic number of items, without using
useState
for each, your state can be an object, and you can change the state by adding/removing a key, where each key controls one item.Regarding Typescript. The custom hook returns a tuple of 2 – the object, and a toggle function. Because tuple in TS is just an array, you need to type it explicitly by stating the return type of the hook (sandbox):