Reputation: 2402
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
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
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
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