0

I have a list of elements in a react js application.

import "./styles.css";
import React from 'react';
const carsData = [
  {name: "first car", id: 1, meta: [{id:1, title:'first meta'}]},
  {name: "second car", id: 2, meta: [{id:2, title:'second meta'}]},
  {name: "third car", id: 3, meta: [{id:4, title:'last meta'}]},
]

export default function App({cars = carsData}) {
  const [carsState, setCarsState] = React.useState(cars) 
  const newItem = {name: "first car", id: 1, meta: [{id:10, title:'added meta'}]}
  

    const click = () => {
      setCarsState([...carsState, newItem])
    }
  return (
    <div className="App">
      <button onClick={click}>click</button>
      {
        carsState.map(c => {
          return <p key={c.name}>{c.name} - meta:
           {c.meta.map((m, k) => <span key={k}>{m.title}</span>)}</p>
        })
      }
    </div>
  );
}

If the user clicks on the button, it should change the first item from the array. Now if i click on the button, the new item is added at the end of the list, but it should change the first item, because the id is the same.
Why the code doe not work and how to change to get the expected result?
demo: https://codesandbox.io/s/ecstatic-sound-rkgbe?file=/src/App.tsx:56-64

2 Answers 2

1

the problem was in this line:

setCarsState([...carsState, newItem])

you have to filter out those items with id that is not equal to newItem.id then add the newItem like:

setCarsState([...carsState.filter(e => e.id !==newItem.id),newItem]);

import "./styles.css";
import React from "react";
const carsData = [
  { name: "first car", id: 1, meta: [{ id: 1, title: "first meta" }] },
  { name: "second car", id: 2, meta: [{ id: 2, title: "second meta" }] },
  { name: "third car", id: 3, meta: [{ id: 4, title: "last meta" }] }
];

export default function App({ cars = carsData }) {
  const [carsState, setCarsState] = React.useState(cars);
  const newItem = {
    name: "first car",
    id: 1,
    meta: [{ id: 10, title: "added meta" }]
  };

  const click = () => {
    setCarsState([...carsState.filter(e => e.id !==newItem.id),newItem]);
  };
  return (
    <div className="App">
      <button onClick={click}>click</button>
      {carsState.map((c) => {
        return (
          <p key={c.name}>
            {c.name} - meta:
            {c.meta.map((m, k) => (
              <span key={k}>{m.title}</span>
            ))}
          </p>
        );
      })}
    </div>
  );
}

here is the sandbox

Sign up to request clarification or add additional context in comments.

10 Comments

thanks, but is there a solution not to use const [carsState, setCarsState] = React.useState(cars);, but to make changes directly in the cars array? And to make something like this {cars.map((c) => {..., and also not to change the items order when change the array.
@Asking i am not sure what you are trying to achieve, initially rendering the carsData then when user clicks the button you add newItem to the carsData and rerender it without using useState?
i cretaed a sanbox. codesandbox.io/s/frosty-dan-5vu6l?file=/src/App.js The idea was not to use the useState hook to make changes, and also when will change the item in array not to change the list order of the elements. Is more clear now?
I suggest you change the array of objects into an object of object so you can refer to each item by its id. It's a more scalable solution. I have added an answer that explains this :)
@Alan Omar, you are right i want to avoid using useState hook and to keep the initial order of the array elements after adding the new element
|
0

Okay so your code -as you said- will simply push to the end of the array. Because you haven't really told it to do otherwise.

If you're looking to replace the old item with the new item if they have the same id value. I'd suggest changing the initial array to an object where the key to each value is the id of an item.

I highly recommend you do this if you'll interact with your items by their id's. An array will soon become inefficient once you scale up your operation to include many items.

import React from 'react';

//notice we changed carsData from an array into an object. Notice 
//the keys are equal to each item's id
const carsData = {
  1:{name: "first car", id: 1, meta: [{id:1, title:'first meta'}]},
  2:{name: "second car", id: 2, meta: [{id:2, title:'second meta'}]},
  3:{name: "third car", id: 3, meta: [{id:4, title:'last meta'}]},
}

export default function App({cars = carsData}) {
  const [carsState, setCarsState] = React.useState(cars) 
  const newItem = {name: "first car", id: 1, meta: [{id:10, title:'added meta'}]}
  

    const click = () => {
      //We use the id of the new item as the key, 
      //this will enable us to replace the value of the old item because 
      //objects can't have the same key twice.
      setCarsState({...carsState, [newItem.id]: newItem})
      console.log(carsState)
    }
  return (
    <div className="App">
      <button onClick={click}>click</button>
      {
        //instead of mapping the array, now we map the
        //values of our object by using the method 
        //Object.values(carsState) which returns an array
        //of values from our object.
        Object.values(carsState).map(c => {
          return <p key={c.name}>{c.name} - meta:
           {c.meta.map((m, k) => <span key={k}>{m.title}</span>)}</p>
        })
      }
    </div>
  );
}

Using this solution will permit you to fetch each item by its id. By simply typing

carsData[id_of_item]

If your carsData comes as an array from DB you can just map it into an object

var carsDataObject = {}
cars.map(item => carsDataObject[item.id] = item)
const [carsState, setCarsState] = React.useState(carsDataObject) 

And then you can use carsDataObject instead of the carsData array

3 Comments

i can not make any changes on carsData array, it will come from the server
You can just map the array to an object. I'll edit my answer
simple add those 2 lines before your useState hook. I edited the answer. Let me know if it works for you.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.