'How can I prevent the array of identifier tags for each student disappearing after re-fetching from API?

I have a list of students that will display onto the web browser depending on what you filter by name/tag. If those filter fields become empty, the page re-fetches all the students from an API and displays them.

The tags are stored in an array using useState for each Student object.

Example Problem: After adding a tag to a student, then somehow filtering the students, and then finally clearing the filter fields, all the students will be displayed again but WITHOUT their tags.

Expected Outcome: I need the student to keep their tags, at least for a current session on the website.

Question: How can I solve this? Should I use localStorage? or a Database such as MongoDB? or something else?


Students.jsx

import { useState } from 'react';
import styles from "../views/Home.module.css";
import { v4 as uuidv4 } from 'uuid';
import AddIcon from '@mui/icons-material/Add';
import RemoveIcon from '@mui/icons-material/Remove';

const Students = ({student}) => {

  const [isShown, setIsShown] = useState(true);
  const [tags, setTags] = useState([]);

  const findAverageGrade = arr => {
    let sum = 0;
    for (let i = 0; i < arr.length; i++) {
      sum += parseInt(arr[i]);
    }
    return sum / arr.length;
  }

  const addTag = (event) => {
    if (event.key === 'Enter') {
      setTags([...tags, event.target.value])
      event.target.value = "";
    }    

  }

  return (
    <div key={student.email} className={styles.studentItem}>
      <img className={styles.studentImage} src={student.pic} />
      <div className={styles.studentInfoContainer}>
        <div className={styles.studentHeader}>
          <p className={styles.studentName}>{student.firstName.toUpperCase()} {student.lastName.toUpperCase()}</p>
          <button className={styles.expandBtn} onClick={() => {
            setIsShown(!isShown);
          }}>
            { isShown ? <AddIcon className={styles.expandBtn} /> : <RemoveIcon className={styles.expandBtn} /> }
          </button>
        </div>
        <ul className={styles.studentDetail}>
          <li>Email: {student.email}</li>
          <li>Company: {student.company}</li>
          <li>Skill: {student.skill}</li>
          <li>Average: {findAverageGrade(student.grades)}%</li>

          {!isShown ? <div>
            <table className={styles.gradesTable}>
              <tbody>
                {student.grades.map((grade) => (
                  <tr key={uuidv4()}>
                    <td>Test</td>
                    <td>{grade}%</td>
                  </tr>
                ))}
              </tbody>
            </table>
          </div>
          : null }
          <div className={styles.tagOutput}>
            {tags.map(tag => (<p className={styles.tag}>{tag}</p>))}
          </div>
          <input id="tag-input" className={styles.addTagInput} type="text" placeholder="Add a tag" onKeyPress={(e) => addTag(e)}/>
        </ul>
      </div>
    </div>
  )
}

export default Students;

Home.jsx

import axios from 'axios';
import { useState, useEffect } from 'react';
import Students from '../components/Students';
import styles from "./Home.module.css";

const Home = () => {

  const [students, setStudents] = useState([]);
  const [nameFilteredStudents, setNameFilteredStudents] = useState([]);
  const [tagFilteredStudents, setTagFilteredStudents] = useState([]);

  const fetchStudents = async () => {
    const response = await axios.get(`https://api.hatchways.io/assessment/students`);
    setStudents(response.data.students);
    setNameFilteredStudents(response.data.students);
    console.log(response.data.students);

  }

  const filterStudentName = async (searchName) => { 
    const searchNameFiltered = searchName.toLowerCase();
    console.log(searchNameFiltered);
    
    if (searchNameFiltered === "") {
      fetchStudents();
      return;
    }

    var newArray = await students.filter((student) => {
      return student.firstName.toLowerCase().includes(searchNameFiltered)
      || student.lastName.toLowerCase().includes(searchNameFiltered);
    })

    await setNameFilteredStudents(newArray);
  }

  const filterStudentTag = async (searchTag) => {
    const searchTagFiltered = searchTag.toLowerCase();
    console.log(searchTagFiltered)

    console.log(students.filter((student) => {
      console.log(student);
    }))

    // var newArray = await students.filter((student) => {
    //   return student.firstName.toLowerCase().includes(searchNameFiltered)
    //   || student.lastName.toLowerCase().includes(searchNameFiltered);
    // })
  }

  useEffect(() => {
    fetchStudents();

  }, [])

  return(
    <>
      <div>
        <input className={styles.searchInput} type="text" placeholder="Search by name" onChange={(event) => filterStudentName(event.target.value) }/>
        <input className={styles.searchInput} type="text" placeholder="Search by tag" onChange={(event) => filterStudentTag(event.target.value) }/>
        {nameFilteredStudents.map((student) => (
          <Students key={student.id} student={student} />
        ))}
      </div>
    </>
  )
  
}

export default Home;


Solution 1:[1]

Since you are passing the students prop to the child component, any time the students change the component will be re-rendered. Also since the filter is in the parent component, the child component will re-render because you are calling fetchStudents() in the filter function. You can toy with changing how you filter the students.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Sean Lawton