Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Typescript: How to take a setStateAction as a prop that can take multiple possible types?

I have multiple pages and they all have useState of different types:

//Page 1 ...
const [ShoppingListKey, setShoppingListKey] = useState<keyof ShoppingList>();

//Page 2 ...
const [isTrue, setIsTrue] = useState<boolean>(false);

//Page 3 ... 
const [String, setString] = useState<string>('');

So, I have a component, and what I want it to do, is to take the target state value from its parent, and set its parents' state to the targeted value:

interface ChildProps {
  targetStateValue: keyof ShoppingList | boolean | string | undefined;
  setStateFunc: Dispatch<React.SetStateAction<keyof ShoppingList | boolean | string | undefined>>
}
export const Child = ({targetStateValue, setStateFunc}: ChildProps) => {
  <button OnClick={()=>{setStateFunc(targetStateValue);}}>BUTTON</button>
}

In ShoppingList Parent:

<Child
  setStateFunc={setShoppingListKey} //ERROR
  targetStateValue={something}
/>

The Error says: Type 'Dispatch<SetStateAction<keyof ShoppingList | undefined>>' is not assignable to type 'Dispatch<SetStateAction<string | boolean | undefined>>'

like image 915
lamsmallsmall Avatar asked Sep 11 '25 03:09

lamsmallsmall


1 Answers

You can use a generic component. Such component would work over a variety of types rather than a single one.

You define a generic interface for your child props. And a generic component which uses that interface as the type for its props.

interface ChildProps<T> {
  targetStateValue: T;
  setStateFunc: Dispatch<React.SetStateAction<T>>;
}
export const Child = <T>({
  targetStateValue,
  setStateFunc,
}: ChildProps<T>) => {
  return (
    <button
      onClick={() => {
        setStateFunc(targetStateValue);
      }}
    >
      BUTTON
    </button>
  );
};

Using the above, we can change T on invocation/definition and based on that the the internal types of the componet will change.

For simplicity i created a simple interface for ShoppingList and also gave a prefilled value to the state variable ShoppingListKey. The code looks like below:

interface ShoppingList {
  onion: string;
  tomato: string;
}

interface ChildProps<T> {
  targetStateValue: T;
  setStateFunc: Dispatch<React.SetStateAction<T>>;
}
export const Child = <T>({
  targetStateValue,
  setStateFunc,
}: ChildProps<T>) => {
  return (
    <button
      onClick={() => {
        setStateFunc(targetStateValue);
      }}
    >
      BUTTON
    </button>
  );
};

function App() {
  const [ShoppingListKey, setShoppingListKey] =
    useState<keyof ShoppingList>("tomato");

  //Page 2 ...
  const [isTrue, setIsTrue] = useState<boolean>(false);

  //Page 3 ...
  const [String, setString] = useState<string>("");
  console.log({ ShoppingListKey, isTrue, String });
  return (
    <div className="App">
      <Child<keyof ShoppingList>
        targetStateValue={"onion"}
        setStateFunc={setShoppingListKey}
      />
      <Child<boolean> targetStateValue={true} setStateFunc={setIsTrue} />
      <Child<string> targetStateValue={"test"} setStateFunc={setString} />
    </div>
  );
}

CodeSandbox

like image 59
Tushar Shahi Avatar answered Sep 13 '25 19:09

Tushar Shahi