handsome
handsome

Reputation: 2402

Recommendation on how to update an array if the index changes

I want to update array elements and I´m using the index to reference the position. the problem is that the index change while searching for names (keyword) it basically sets the name to the wrong element in the users array (because is taking the indes from the filtered users array)

const [users, setUsers] = useState(["John", "Marty", "Mary", "Johanna"]);
const [keyword, setKeyword] = useState("")

const updateName = (index, name) => {
    const newState = [...users];
    newState[index] = name;
    setNames(newState);
};

I have an input field to search for names

<input value={keyword} onChange={(e) => setKeyword(e.target.value)} placeholder="search"/>

and then I render a child component with each name and I pass a prop to update the name

users
    .filter((user) =>
        user.toLowerCase().includes(keyword.toLowerCase())
    )
    .map((user, index) => (
        <User
            user={user}
            updateName={updateName}
            index={index}
            key={index}
        />
    ))

My User component

const User (props) => <button onClick={props.updateName(index, "some name")}>{props.user}</button>

this works perfectly fine. except when keyword changes. because the users.map will change and obviously will change the index. but the problem is that I´m using the index to update the array elements.

for example if I search for the "Ma" keyword. 2 names match so the index of the filtered users will change to 0, 1 but the users array is still the same.

How can I solve this problem? thank you.

Upvotes: 1

Views: 116

Answers (3)

user3366943
user3366943

Reputation: 263

import React, { useState } from "react";
import "./styles.css";

export default function App() {
  const names = ["John", "Marty", "Mary", "Johnna"];
  // create a map of user objs to hold the position and name of each user
  const mappedUsers = names.map((name, i) => {
    return { position: i, name };
  });
  const [users, setUsers] = useState(mappedUsers);
  const [keyword, setKeyword] = useState("");

  const updateName = (position, name) => {
    setUsers(prevUsers => {
      prevUsers[position].name = name;
      // return a new merged array of users
      return [...prevUsers];
    });
  };

  const User = ({ updateName, user }) => (
    <div>
      <button onClick={() => updateName(user.position, "someName")}>
        {user.name}
      </button>
    </div>
  );

  const UserList = ({ users, keyword }) => {
    return users
      .filter(user => user.name.toLowerCase().includes(keyword.toLowerCase()))
      .map(user => (
        <User key={user.position} updateName={updateName} user={user} />
      ));
  };

  return (
    <>
      <input
        value={keyword}
        onChange={e => setKeyword(e.target.value)}
        placeholder="search"
      />
      <UserList users={users} keyword={keyword} />
    </>
  );
}

Upvotes: 1

goto
goto

Reputation: 4425

I suggest you stop using index for keeping track of positions of items in an array as this will most likely cause issues when items get deleted or added. Instead, you should create a unique identifier for each item and keep track of them that way.

Here's an example of one way you could go about this:

function MyComponent() {
  const initialUsers = ["John", "Marty", "Mary", "Johanna"].map(
    (name, idx) => {
      return { id: idx + 1, name };
    }
  );
  const [users, setUsers] = React.useState(initialUsers);
  const [keyword, setKeyword] = React.useState("");

  const handleOnChange = event => {
    const { value } = event.target;

    const nextUsers = initialUsers.filter(user =>
      user.name.toLowerCase().includes(value)
    );
    setUsers(nextUsers);
    setKeyword(value);
  };

  return (
    <div>
      <p>Search for a name:</p>
      <input
        type="text"
        onChange={handleOnChange}
        value={keyword}
        placeholder="Type..."
      />
      <ul>
        {users.map(user => (
          <li>
            [{user.id}] {user.name}
          </li>
        ))}
      </ul>
    </div>
  );
}

Here's a working example so that you can test it:

Hope this helps.

Upvotes: 0

Nick
Nick

Reputation: 16576

If you want to keep your current data structure, you could just forgo the filter and do the filtering within your map function by only conditionally rendering the User component. This way, you don't lose accounting of your indexes.

users
  .map((user, index) => (
    user.toLowerCase().includes(keyword.toLowerCase()) && <User
      user={user}
      updateName={updateName}
      index={index}
      key={index}
    />
  ))

Upvotes: 1

Related Questions