usestate variable increments when I click on a different increment button

so I am declaring a usestate variable as "count" and I am mapping through a list of 2 items. This code shows the variable declaration and the function to increment and decrement

const [count, setCount] = useState(1);
const increment = () => {
  setCount((prev) => prev + 1);
};

const decrement = () => {
  setCount((prev) => {
    if (prev == 1) return prev;
    return prev - 1;
  });
};

And the next code shows how I am mapping through an item and displaying a text that holds the count variable

{
   cartItemArr.map((entry: any, index: number) => {
      return (

        <Flex
          key={index}
          w={'100%'}
          alignItems={'center'}
          justifyContent={'space-between'}
        >
           <Flex direction={'column'} justifyContent={'space-between'}>
             <Text fontWeight={600} fontSize={'14px'} textColor={'#000'}>
               {entry?.foodId.nameOfFood}
             </Text>
             <Text fontWeight={400} fontSize={'12px'} textColor={'#000'}>
               #{entry?.foodId.pricePerServing}
             </Text>
           </Flex>
           <HStack
             justify={'center'}
             w="109px"
             borderRadius={'40px'}
             py="8px"
             border={'1px solid #AA9F93'}
           >
             <AiOutlineMinus cursor={'pointer'} onClick={decrement} className='cursor-pointer' />
             <Text>{count}</Text>
             <AiOutlinePlus cursor={'pointer'} onClick={increment} className='cursor-pointer' />
          </HStack>
        </Flex>
       )
      })
      }

So when I click on the first item, the second item also increases. I know that it is because both of them have the count variable which is why it is behaving like that, is there something I can do that will make the counts not affect each other on the mapped items? I hope I can communicate my issue well. For better context, this is the screen I am building; enter image description here

Upvotes: 0

Views: 350

Answers (3)

justgigio
justgigio

Reputation: 126

You are using the same variable count for all entries. So, when you change in one place, it will change in all other places because the variable is the same.

Try to use a list (an object indexed by foodId, for example) of counts, like this:

// this reduce will map all foodIds and set them as count = 1
const [count, setCount] = useState(cartItemArr.reduce((a, {entry: e}) => ({ ...a, [e.foodId]: 1}), {}));
// try console.log(count) if is not clear yet
const increment = (foodId) => {
    () => {
        setCount((prev) => {...prev, [foodId]: prev[foodId] + 1});
    }
};

const decrement = (foodId) => {
    // now you need to return a function with an fixed `foodId` in scope for each one
    () => {
        setCount((prev) => {
           if (prev[foodId] == 1) return prev;
           return {...prev, [foodId]: prev[foodId] - 1};
        });
    }
};

and in you jsx:

<AiOutlineMinus cursor={'pointer'} onClick={decrement(entry.foodId)} className='cursor-pointer' />
<Text>{count}</Text>
<AiOutlinePlus cursor={'pointer'} onClick={increment(entry.foodId)} className='cursor-pointer' />

This should make it work as expected.

Note that there are some better ways to do it, like break into smaller components. As you learn more React it will probably become clearer to you. So, keep improving! :)

But for now, i hope this answer can help you unstuck and make progress on your work.

Upvotes: 1

Greg Fenton
Greg Fenton

Reputation: 2808

Recognize that when, in JSX, you do:

cartItemArr.map((entry: any, index: number) => {
      [[SOME_CODE_HERE]]
});

the [[SOME_CODE_HERE]] is actually a React component. If you want to track state for each iteration of that .map(), then consider putting state (i.e. use useState()) inside [[SOME_STUFF_HERE]].

And...to be a better/cleaner codebase, move the definition of [[SOME_CODE_HERE]] to its own file (e.g. SomeCodeHere.jsx) so that it is clear that this is a component, and that it be maintained as such.

Upvotes: -2

Daniel Centore
Daniel Centore

Reputation: 3349

Create a separate React component for the individual entries, something like this:

const Entry = ({ entry }: { entry: any }) => {
  const [count, setCount] = useState(1);
  const increment = () => {
    setCount((prev) => prev + 1);
  };

  const decrement = () => {
    setCount((prev) => {
      if (prev == 1) return prev;
      return prev - 1;
    });
  };
  return (
    <Flex
      w="100%"
      alignItems="center"
      justifyContent="space-between"
    >
      <Flex direction="column" justifyContent="space-between">
        <Text fontWeight={600} fontSize="14px" textColor="#000">
          {entry?.foodId.nameOfFood}
        </Text>
        <Text fontWeight={400} fontSize="12px" textColor="#000">
          #{entry?.foodId.pricePerServing}
        </Text>
      </Flex>
      <HStack
        justify="center"
        w="109px"
        borderRadius="40px"
        py="8px"
        border="1px solid #AA9F93"
      >
        <AiOutlineMinus
          cursor="pointer"
          onClick={decrement}
          className="cursor-pointer"
        />
        <Text>{count}</Text>
        <AiOutlinePlus
          cursor="pointer"
          onClick={increment}
          className="cursor-pointer"
        />
      </HStack>
    </Flex>
  );
};

That way the count variable is local to each entry rather than to the entire cart.

Then update your map to something like this:

      {cartItemArr.map((entry: any, index: number) => {
        return <Entry entry={entry} key={index} />;
      })}

Upvotes: 1

Related Questions