Home > Back-end >  Setting the values of an object in an array in React?
Setting the values of an object in an array in React?

Time:03-03

I am building a swipeable card in React. The card contains 4 slides. The values displayed in the card rely on the user input.

First I am defining a sample object like this:

const initialState = { id: '', title: '', name: '', image: ''};

Inside my component, I am defining the array state like:

const [card, setCard] = useState([initialState]);

I am displaying the card side by side along with the user input fields for users to view the cards as they compose. So whenever the user adds/edits a specific value of the card he can view it live on the card.

We can set the state of an object for each field like this:

<Input id='title' name='title' placeholder="Enter Title" type='text' value={card.title} onChange={handleChange}/>

Handle Change function:

const handleChange = (e) => {
        setCard({ ...card, [e.target.name]: e.target.value });
    }

But this is not possible for the above-mentioned array of objects. So how to handle this situation? Whenever a user swipes the previous/next card the fields must be populated with the appropriate values so that he can edit them. Simply, a user must be able to edit any field at any time. Whenever a user adds a new card a new object must be pushed to the array state.

Full code:

const initialState = { id: '', title: '', name: '', image: ''};

const Home = () => {
    const [card, setCard] = useState([initialState]);
    const isdisabled = true;

    const handleChange = (e) => {
        setCard({ ...card, [e.target.name]: e.target.value });
    }

    const handleAdd = () => {
        //TODO
    }

    return (
        <Flex>
            <Center>
                <Flex bg="white" w="lg" h="420" borderRadius="lg" m="7" p="2" alignItems="center">
                <Box w="48" align="center">
                        <IconButton aria-label='Go to previous' disabled borderRadius="full" bg='gray.200' color='black' icon={<ChevronLeftIcon w={6} h={6}/>} />
                </Box>

                <Box>
                        <Image src={card[0].image} w="full" h="44" objectFit="cover" objectPosition="0 0" borderRadius="lg" />
                        <Heading color="black" size='lg'>{card[0].title}</Heading>
                        <Text color="black" size='40'>{card[0].namee}</Text>
                      
                    </Box>

                    <Box w="48" align="center">
                        <IconButton aria-label='Go to previous' disabled borderRadius="full" bg='gray.200' color='black' icon={<ChevronRightIcon w={6} h={6}/>} />
                    </Box>
                </Flex>
        </Center>
        <Flex direction="column" w="lg" gap="4" m="7">
            <Input placeholder="Enter Title" value={card[0].title} onChange={handleChange}/>
            <Input placeholder="Enter Name" value={card[0].name} onChange={handleChange}/>
            <Button onClick={handleClick}>Upload Image</Button>
            
            <Button onClick={handleAdd}>Add another slide</Button>
            <Button colorScheme="blue">Done</Button>
        </Flex>
       </Flex>
    )
}

export default Home

How to seamlessly do this? Any help would be appreciated. Thank you.

CodePudding user response:

You would likely need an additional state variable, specifying the active card

something like

const [cards, setCards] = useState([initialState]);
const [activeCardIndex, setActiveCardIndex] = useState(0);

handleGotoNext = useCallback(() => {
  // you need to also handle not allowing to go beyond the max
  setActiveCardIndex(prevActive => prevActive   1);
}, []);

const handleGotoPrevious = useCallback(() => {
  // you need to also handle not allowing to go below 0
  setActiveCardIndex(prevActive => prevActive - 1);
}, []);

const handleChange = useCallback((e) => {
  setCards(prevCards => prevCards.map((card, index) => {
    if (index === activeCardIndex) {
      return { ...card,
        [e.target.name]: e.target.value
      }
    }
    return card;
  }));
}, [activeCardIndex]);

const handleAdd = useCallback(() => {
  const newCards = [...cards, { ...initialState
  }];
  setCards(newCards);
  setActiveCardIndex(newCards.length - 1);
}, [cards]);
const activeCard = cards[activeCardIndex];

// for the rendering you should use the activeCard constant, instead of cards[n]
return (
   <Flex>
   ...
     <Image src={activeCard.image} w="full" h="44" objectFit="cover" objectPosition="0 0" borderRadius="lg" />
   ...
   </Flex>
)

CodePudding user response:

your card state is array of objects need to update array first object

const handleChange = (e) => {
    const arr = [...card]
     arr[0] = {...arr[0], [e.target.name]: e.target.value } 
     setCard(arr);
  }  

CodePudding user response:

@Gabriele Petrioli's answer is the perfect solution to my problem except it needs a little tweaking:

Add activeCardIndex to both navigation handlers' dependency list:

    const handleGotoNext = useCallback(() => {
        // you need to also handle not allowing to go beyond the max
        if(activeCardIndex < cards.length-1){
            setActiveCardIndex(prevActive => prevActive   1);
        }
      }, [activeCardIndex]);
      
    const handleGotoPrevious = useCallback(() => {
    // you need to also handle not allowing to go below 0
        if(activeCardIndex > 0){
            setActiveCardIndex(prevActive => prevActive - 1);
        }
    }, [activeCardIndex]);

And the handleChange function:

    const handleChange = useCallback((e) => {
        setCards(prevCards => prevCards.map((card, index) => {
          if (index === activeCardIndex) {
            return { ...card,
              [e.target.name]: e.target.value
            }
          }else {
              return card;
          }
        }));
    }, [activeCardIndex]);
  • Related