'How to create a string array in the useState hook and update that same array in TypeScript?

I have a React project where I am trying to create a multi select function where you can select and deselect multiple avatars at once.

So far I have this:

export interface IStoreRecommendationProps {
  title: string;
  description: string;
  discountNumber: number;
  discountItem: string;
  arr: string[];
  onClickAvatar: () => void; // pass a specific parameter for avatar
}

export const StoreRecommendation: React.FC<IStoreRecommendationProps> = ({
  title,
  description,
  discountNumber,
  discountItem,
  arr,

  onClickAvatar,
  ...props
}) => {
  const [visible, setVisible] = useState({
    arr[],
  });
  const showIcon = (id) => {
    arr.push(id);
    console.log(arr);
    setVisible(id);
    onClickAvatar();
  };

  const displayContacts = () =>
    CUSTOMERS.map((person) => (
      <AvatarContainer>
        <Avatar
          picture={{uri: person.profile_picture}}
          onPress={() => showIcon(person.customer_id)}
        />
        <TextAvatar>{person.name}</TextAvatar>
        {visible.includes(person.customer_id) && <CheckIcon />}
      </AvatarContainer>
    ));

In the showIcon() I am trying to save an array of IDs in the state and update them accordingly(push if clicked, remove if clicked again) and then just check the array in displayContacts() by doing visible.includes(person.customer_id)

const [visible, setVisible] = useState({
    arr[],
  });
  const showIcon = (id) => {
    arr.push(id);
    console.log(arr);
    setVisible(id);
    onClickAvatar();
  };
const displayContacts = () =>
    CUSTOMERS.map((person) => (
      <AvatarContainer>
        <Avatar
          picture={{uri: person.profile_picture}}
          onPress={() => showIcon(person.customer_id)}
        />
        <TextAvatar>{person.name}</TextAvatar>
        {visible.includes(person.customer_id) && <CheckIcon />}
      </AvatarContainer>
    ));

I am grabbing customers from a seperate file and it looks like this:

export const CUSTOMERS = [
  {
    customer_id: '1',
    name: "John Doe",
    profile_picture: "https://img.freepik.com/free-photo/portrait-white-man-isolated_53876-40306.jpg?size=626&ext=jpg",
    approval_status: false,
    payment_method: Enums.OrderPaymentMethod.IN_PERSON
  },
  {
    customer_id: '2',
    name: "Evan Green",
    profile_picture: "https://media.istockphoto.com/photos/portrait-concept-picture-id1016761216?k=6&m=1016761216&s=612x612&w=0&h=j-DyZTSqmnhoHKsJdGmiMPnungpHiq9UTrvx4UylMQI=",
    approval_status: false,
    payment_method: Enums.OrderPaymentMethod.IN_PERSON
  },
  {
    customer_id: '3',
    name: "Grace Lewis",
    profile_picture: "https://img.freepik.com/free-photo/friendly-brunette-looking-camera_23-2147774849.jpg?size=626&ext=jpg",
    approval_status: false,
    payment_method: Enums.OrderPaymentMethod.IN_PERSON
  }, ...]

Now when I try to do visible.includes() I get an error where it tells me my array is of type never. Am I doing the array initialization to the useState() hook wrong, and am I on the right track to do what I am attempting to do?



Solution 1:[1]

Try following, solves the issue (hopefully :)):

  1. Replace

const [visible, setVisible] = useState({ arr[],}); with

const [visible, setVisible] = useState<string[]>([]);

for proper initialisation and type definition

  1. Replace

    const showIcon = (id) => {
         arr.push(id); 
         console.log(arr);
         setVisible(id);
         onClickAvatar();
       };
    

with

     const showIcon = (id) => {
         setVisible(prevState => {
         // check if it is already added
         if(prevState.includes(id)) { 
           // clone the prevState arr to prevent side effects  
            const clone = [...prevState];
          // Remove the existing id 
            clone.splice(prevState.indexOf(id), 1)
            return clone;
         } else {
            return [...prevState, id]
         }});
         onClickAvatar();
     };

 

Note: arr is not relevant here as it is not used elsewhere and is redundant so it should be removed

Solution 2:[2]

You mean to be initializing visible as an array and then interacting w/ that array via setVisible, rather than a direct manipulation method.
Try something like this:

const [visible, setVisible] = useState([]);

  const showIcon = (id) => {
    setVisible([...visible, id])
    console.log(visible);
    onClickAvatar();
  };

assuming your initial state of visible is an array of ids like [34, 63, 12] and you call showIcon(20), it should update visible and log out to [34, 63, 12, 20]

Solution 3:[3]

I was blocked and I tried everything... solution ?

The comment from Drew Reese

I suggest using functional updates, setVisible(visible => [...visible, id]) so state updates from previous state and not the state closed over in callback scope, and the console log will only log the current state, not what it will update to for the next render.

Just using this gave me bad result :

setVisible([...visible, id])

But perfect with :

setVisible(visible => [...visible, id])

A big thanks !

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 Zoe stands with Ukraine
Solution 3 Serial Geeker