【问题标题】:How to Filter by States Within React Components?如何在 React 组件中按状态过滤?
【发布时间】:2021-11-22 21:18:21
【问题描述】:

我正在制作一个前端 UI,它通过 API 调用返回学生对象(成绩、电子邮件等)。我目前有一个过滤器设置为按名称返回对象。我需要按标签设置第二个过滤器,可以通过从 .map() API 返回的每个学生组件中的输入元素添加它。我无法弄清楚如何设置过滤器,因为标签存储在 student Profile.js 组件的每个实例中。你能帮我么?最终,UI 应该从两个过滤器(名称和标签)返回搜索结果

来自 App.js 的片段:

function App() {
 const [students, setStudents] = useState([])
 const [filteredStudents, setFilteredStudents] = useState([])
 const [search, setSearch] = useState("")
 

// Get request and store response in the 'students' state //

 useEffect(()=>{
    axios.get('Link to the API')
    .then(res => {
      const result = res.data.students
      setStudents(result)
    })
  },[])

// Filter students by name and store filtered result in 'filteredStudents' //

 useEffect(() => {
    const searchResult = []

    students.map(student => {
      const firstName = student.firstName.toLowerCase()
      const lastName = student.lastName.toLowerCase()
      const fullName = `${firstName}` + ` ${lastName}`

        if (fullName.includes(search.toLowerCase())) {
          searchResult.push(student)
        }
      return
    })

    setFilteredStudents(searchResult)
  }, [search])

return (
 <div>
    <SearchBar
      search={search}
      onChange={e => setSearch(e.target.value)}
    />

//Second search bar by tag here//
    
   {search.length == 0 &&
      //unfiltered students object here
   }

   {search.length != 0 &&
     <div>
        {filteredStudents.map(student => (
          <Profile
             //Some props here//
          />
        ))}   
     </div>
   }
 </div>
)}


来自 Profile.js 的片段

//bunch of code before this line//

    const [tags, setTags] = useState([])
    const [tag, setTag] = useState("")

    function handleKeyPress(e) {

        if(e.key === 'Enter') {
            tags.push(tag)
            setTag("")
        }
    }

return(
 <div>

//bunch of code before this line//

   <Tag
     onChange={e => setTag(e.target.value)}
     onKeyPress={handleKeyPress}
     tags={tags}
     tag={tag} 
   />
 </div>
)

来自 Tag.js 的片段:

export default function Tag({tag, tags, onChange, onKeyPress}) {

    return (
        <div>
            {tags.length > 0 &&
                <div>
                    {tags.map(tag => (
                        <span>{tag}</span>
                    ))}
                </div>
            }       
            <input 
                type='text'
                value={tag}
                placeholder="Add a tag"
                key='tag-input'
                onKeyPress={onKeyPress}
                onChange={onChange}
            />
        </div>
    )
}

【问题讨论】:

  • 这对您的用例有帮助吗? stackoverflow.com/questions/69775593/…
  • 不完全是,我的问题更像是:我有一个不同学生对象的数组,如何为某些对象添加“标签”属性?关于哪个学生获得此属性的决定基于用户输入。这应该只在前端是可能的,所以我不想添加数据库只是为了做到这一点。

标签: reactjs


【解决方案1】:

编辑

有了你的评论,我想我现在明白你想要做什么了,你提供的图片真的很有帮助。如果我没记错的话,只要在 Profile 组件中添加标签,您就想更改学生对象(再次,如果我错了,请纠正我)。这意味着 Profile 组件需要访问处理程序,以便每当添加标签时,它设置一个新的students 状态。它看起来像这样:

App.js

function App() {
  const [students, setStudents] = useState([]);
  const [filteredStudents, setFilteredStudents] = useState([]);

  const [search, setSearch] = useState("");

  const handleTagAdded = (tag, index) => {
    setStudents((prevStudents) => {
      // We copy object here as the student we're accessing
      // is an object, and objects are always stored by reference.
      // If we didn't do this, we would be directly mutating 
      // the student at the index, which is bad practice
      const changedStudent =  {...prevStudents[index]};

      // Check if student has 'tags` and add it if it doesn't.
      if (!("tags" in changedStudent)){
        changedStudent.tags = [];
      }

      // Add new tag to array
      changedStudent.tags.push(tag);
      
      // Copy array so we can change it
      const mutatableStudents = [...prevStudents];
      mutatableStudents[index] = changedStudent;

      // The state will be set to this array with the student
      // at the index we were given changed
      return mutatableStudents;
    })
  }

  // Get request and store response in the 'students' state //
  useEffect(() => {
    axios.get("Link to the API").then((res) => {
      const result = res.data.students;
      setStudents(result);
    });
  }, []);

  // Filter students by name and tag, then store filtered result in //'filteredStudents'
  useEffect(() => {
    // Array.filter() is perfect for this situation //
    const filteredStudentsByNameAndTag = students.filter((student) => {
      const firstName = student.firstName.toLowerCase();
      const lastName = student.lastName.toLowerCase();
      const fullName = firstName + lastName;

      if ("tags" in student){
        // You can now do whatever filtering you need to do based on tags
        ...
      }

      return fullName.includes(search.toLowerCase()) && yourTagComparison;
    });

    setFilteredStudents(filteredStudentsByNameAndTag);
  }, [search]);

  return (
    <div>
      <SearchBar search={search} onChange={(e) => setSearch(e.target.value)} />

      //Second search bar by tag here //

      {search.length === 0 && 
        // unfiltered students //
      }

      {search.length !== 0 && (
        <div>
          {filteredStudents.map((student, index) => (
            <Profile
              // Some props here //
            
              onTagAdded={handleTagAdded}
              // We give the index so Profile adds to the right student
              studentIndex={index}
            />
          ))}
        </div>
      )}
    </div>
  );
}

handleTagAdded 中,我将对象复制到prevStudents[index],因为它是一个引用。如果您不知道我指的是什么(双关语),这听起来可能很奇怪。 Here 是一篇文章的链接,它比我能解释得更好。

Profile.js

function Profile({ onTagAdded, studentIndex }) {
  // Other stuff //
  const [tags, setTags] = useState([]);
  const [tag, setTag] = useState("");

  const handleTagKeyPress = (e) => {
    if (e.key === "Enter") {
      // Use this instead of tags.push, when changing state you always
      // must use the `setState()` function. If the new value depends on the 
      // previous value, you can pass it a function which gets the 
      // previous value as an argument like below. It is also bad 
      // practice to change, or 'mutate' the argument you're given 
      // so we instead copy it and change that.

      setTags((previousTags) => [...previousTags].push(tag));
      setTag("");
      onTagAdded(tag, studentIndex)
    }
  };

  return (
    <div>
      // Other stuff
      <Tag onChange={(e) => setTag(e.target.value)} onKeyPress={handleTagKeyPress} tags={tags} tag={tag} />
    </div>
  );
}

现在,每个&lt;Profile /&gt; 组件都有自己的标签状态,但是通过使用handleTagAdded(),我们可以根据标签更改每个配置文件组件内的学生。

对于我第一个回答中的困惑,我希望这能解决您的问题!

旧答案

React 中有一个非常重要的概念,称为“Lifting State”。这意味着如果父组件需要访问子组件的状态,一种解决方案是将状态从子组件“提升”到父组件。

您可以在React documentation 中阅读更多相关信息。

在此示例中,您需要将tag 状态从&lt;Profile /&gt; 组件提升到&lt;App /&gt; 组件。这样searchtag 就在同一个地方,可以比较。

我相信下面的代码符合您的要求:

App.js

function App() {
  const [students, setStudents] = useState([]);
  const [filteredStudents, setFilteredStudents] = useState([]);
  
  const [tags, setTags] = useState([]);
  const [tag, setTag] = useState("");

  const [search, setSearch] = useState("");

  const handleTagChange = (e) => setTag(e.target.value);

  const handleTagKeyPress = (e) => {
    if (e.key === "Enter") {
      // Use this instead of tags.push, when changing state you always
      // must use the `setState()` function. If the new value depends on the 
      // previous value, you can pass it a function which gets the 
      // previous value as an argument like below.
      setTags((previousTags) => previousTags.push(tag));
      setTag("");
    }
  };

  // Get request and store response in the 'students' state //
  useEffect(() => {
    axios.get("Link to the API").then((res) => {
      const result = res.data.students;
      setStudents(result);
    });
  }, []);

  // Filter students by name and tag, then store filtered result in //'filteredStudents'
  useEffect(() => {
    // Array.filter() is perfect for this situation //
    const filteredStudentsByNameAndTag = students.filter((student) => {
      const firstName = student.firstName.toLowerCase();
      const lastName = student.lastName.toLowerCase();
      const fullName = firstName + lastName;

      return fullName.includes(search.toLowerCase()) && student.tag === tag;
    });

    setFilteredStudents(filteredStudentsByNameAndTag);
  }, [search]);

  return (
    <div>
      <SearchBar search={search} onChange={(e) => setSearch(e.target.value)} />

      //Second search bar by tag here //

      {search.length == 0 && 
        // unfiltered students //
      }

      {search.length != 0 && (
        <div>
          {filteredStudents.map((student) => (
            <Profile
            // Some props here //
            onChange={handleTagChange}
            onKeyPress={handleTagKeyPress}
            tag={tag}
            tags={tags}
            />
          ))}
        </div>
      )}
    </div>
  );
}

Profile.js

function Profile({ onChange, onKeyPress, tags, tag }) {
  // Other stuff //

  return (
    <div>
      // Other stuff
      <Tag onChange={onChange} onKeyPress={onKeyPress} tags={tags} tag={tag} />
    </div>
  );
}

我们已将tag 状态移至&lt;App /&gt; 组件,因此现在当我们过滤时,我们可以同时使用searchtag。我还将students.map 更改为students.filter,因为它是过滤数组的更好选择。

我不清楚您想如何过滤标签,所以我假设学生对象将具有tag 属性。请随时纠正我有关数据结构的问题,我会重新格式化它。

希望对您有所帮助,如果您还有其他问题,请告诉我。

【讨论】:

  • 嗨,Sam,感谢您周到的 cmets。我已经尝试过了,我立即遇到的问题是,通过将输入存储到 App.js 中的相同“标签”状态,所有标签组件会同时更新到相同的值,而我想将标签添加到单个实例来自 .map() API 的 Tag 组件。我希望我说得通
  • 我需要更多关于您正在寻找帮助的解释。据我了解,您首先通过搜索进行过滤,然后在每个 Profile 组件上都有一个输入,您可以在其中输入标签。我不明白的部分是用户按下回车键后您想要发生的事情。是否要再次过滤学生?让学生变异?如果你能澄清这一点,我会编辑我的答案。
  • 是的,我想要的是将标签添加到从 API (imgur.com/8UQyWx4) 返回的各个配置文件组件中。如果我将状态“提升”到 App.js,那么所有 Profile 组件将一起更新(imgur.com/A3E6l1V),这不是我想要的。 “按 Enter”功能将标签添加到从 API 返回的所选学生对象中,这样我就可以通过标签过滤学生对象。另外,我只想说到目前为止我真的很感谢你的帮助!
  • 我很高兴,我完全理解卡在一个编码问题上会让人生气!我强烈建议您查看我链接的文档,以便尽可能多地从中学习,因为它将在未来为您提供很多帮助,并且从现在开始更容易解决此类问题。
猜你喜欢
  • 1970-01-01
  • 2021-10-12
  • 1970-01-01
  • 2017-05-31
  • 1970-01-01
  • 1970-01-01
  • 2020-07-21
  • 2020-07-04
  • 2021-08-12
相关资源
最近更新 更多