React losing checked while filtering

Viewed 118

I cannot figure out how to add 'active' into the state of users. For the sake of posting this here I hardcoded some users in the state, but they're supposed to be fetched from an API - and this doesn't come with 'active'. I need to be able to mark the checkboxes so that the specific user becomes active, also if active - it has to stay active when doing searches in the list through the text-input, so it doesn't reset. With what I wrote I am getting undefined for user.active. Any suggestions?

App.js

import './App.css';
import UserList from './components/UserList';
import './style/style.css';

function App() {
  return (
    <div className="App">
      <UserList />
    </div>
  );
}

export default App;

UserList.js


import React, { useState, useEffect } from 'react';

function UserList() {
  const [users, setUsers] = useState([
    {
      id: 681,
      first_name: 'James',
      last_name: 'Smith',
      email: 'example1',
      gender: 'm',
    },
    {
      id: 3123,
      first_name: 'Richard',
      last_name: 'Greene',
      email: 'example2',
      gender: 'm',
    },
    {
      id: 512,
      first_name: 'Denise',
      last_name: 'Bank',
      email: 'example3',
      gender: 'f',
    },
    {
      id: 654,
      first_name: 'Carl',
      last_name: 'Ross',
      email: 'example4',
      gender: 'm',
    },
  ]);
  const [search, setSearch] = useState('');
  const [filteredUsers, setFilteredUsers] = useState();
  const [checkedUsers, setCheckedUsers] = useState('');

  useEffect(() => {
    const fetchUsers = async () => {
      try {
        const result = await fetch(users);
        result.sort(function (a, b) {
          if (a.last_name.toLowerCase() < b.last_name.toLowerCase()) return -1;
          if (a.last_name.toLowerCase() > b.last_name.toLowerCase()) return 1;
          else return;
        });
        setUsers(result);
      } catch (error) {
        console.log(error);
      }
    };
    fetchUsers();
  }, []);

  useEffect(() => {
    setFilteredUsers(
      users.filter(
        (user) =>
          user.first_name.toLowerCase().includes(search.toLowerCase()) ||
          user.last_name.toLowerCase().includes(search.toLowerCase())
      )
    );
  }, [users, search]);

  useEffect(() => {
    setCheckedUsers(users.filter((user) => user.active));
  }, [users]);

  const changeChecked = (id) => {
    users.forEach((user) => {
      if (user.id === id) {
        console.log(user);
        if (user.active === '') {
          user.active = true;
        } else user.active = false;
      }
    });
    setUsers(users);
    console.log(checkedUsers);
  };

  return (
    <div className="list-container">
      <div className="search">
        <input
          type="text"
          placeholder="Search for users"
          onChange={(event) => setSearch(event.target.value)}
        />
      </div>
      {filteredUsers &&
        filteredUsers.map((user) => (
          <div
            className="user-block"
            key={user.id}
            onClick={() => console.log(user.id, user.active)}>
            <div className="details">
              <h5>
                {user.first_name} {user.last_name}
              </h5>
              <p>{user.email}</p>
            </div>
            <input
              type="checkbox"
              checked={user.active}
              onClick={(event) => changeChecked(user.id)}
            />
          </div>
        ))}
    </div>
  );
}

export default UserList;

1 Answers

A few things here:

  1. I think you should map the user after the fetch to add the active with a default value, so it isn't undefined in any case:

    useEffect(() => {
        const fetchUsers = async () => {
          try {
            const request = await fetch(users);
            const response = request.map(data => ({...data, active: true})).sort(function (a, b) {
              if (a.last_name.toLowerCase() < b.last_name.toLowerCase()) return -1;
              if (a.last_name.toLowerCase() > b.last_name.toLowerCase()) return 1;
              else return;
            });
            setUsers(response);
          } catch (error) {
            console.log(error);
          }
        };
        fetchUsers();
      }, []);
    
  2. Your filtered users is absolutely useless, you can do that with the user itself and lose the useEffect and the state variables entirely:

    users.filter(user =>
              user.first_name.toLowerCase().includes(search.toLowerCase()) ||
              user.last_name.toLowerCase().includes(search.toLowerCase())
          ).map((user) => (
              <div
                className="user-block"
                key={user.id}
                onClick={() => console.log(user.id, user.active)}>
                <div className="details">
                  <h5>
                    {user.first_name} {user.last_name}
                  </h5>
                  <p>{user.email}</p>
                </div>
                <input
                  type="checkbox"
                  checked={user.active}
                  onClick={(event) => changeChecked(user.id)}
                />
              </div>
            )
    
  3. And last but not least, your changeChecked function is mutating the array. I would use .map as well instead of forEach:

    const changeChecked = (id) => {
      setUsers(
        users.map(user => {
          if (user.id === id) {
            return {
               ...user,
               active: !user.active
            };
          } else {
             return user;
          }
        })
      );
        console.log(checkedUsers);
    };
    
Related