【问题标题】:Can't update React state with text input value无法使用文本输入值更新 React 状态
【发布时间】:2020-11-12 09:46:29
【问题描述】:

我想做的是从 API 获取员工列表,将它们保存在状态中并按员工姓名进行“实时”搜索。

我挣扎的地方是我无法使用过滤后的数组更新我的状态。当我开始在搜索字段中输入时,员工会进行过滤,但是一旦我删除了一些字母,就没有任何变化。

如果我 .map() 不是状态,而是包含过滤数组的变量,则一切正常。这在某种程度上与状态和状态更新有关。

这是我的代码:

import "./App.css";
import React, { useState, useEffect } from "react";
import styled from "styled-components";

const Container = styled.div`
  width: 1280px;
  max-width: 100%;
  margin: 0 auto;
  th {
    text-align: left;
    padding: 10px;
    background: #f5f5f5;
    cursor: pointer;
    :hover {
      background: #ddd;
    }
  }
  td {
    border-bottom: 1px solid #f5f5f5;
    padding: 5px;
  }
`;
const TopHeader = styled.div`
  display: flex;
  justify-content: space-between;
  padding: 20px;
  input {
    width: 400px;
    padding: 10px;
  }
`;

function App() {
  const [employees, updateEmployees] = useState([]);

  if (employees == 0) {
    document.title = "Loading...";
  }

  useEffect(() => {
    fetch("http://dummy.restapiexample.com/api/v1/employees")
      .then(res => res.json())
      .then(result => {
        updateEmployees(result.data);
        document.title = `Total: ${result.data.length} `;
      });
  }, []);

  const [searchValue, updateSearch] = useState("");

  const filteredEmpl = employees.filter(empl => {
    return empl.employee_name.toLowerCase().includes(searchValue.toLowerCase());
  });

  const handleSearch = e => {
    updateSearch(e.target.value);
    updateEmployees(filteredEmpl);
  };

  return (
    <Container>
      <TopHeader>
        <div>
          Total employees: <strong>{employees.length}</strong> Filtered
          employees: <strong>{filteredEmpl.length}</strong>
        </div>
        <div>
          <input
            type="text"
            onChange={handleSearch}
            value={searchValue}
            placeholder="search"
          />
        </div>
      </TopHeader>

      <table style={{ width: "100%" }}>
        <thead>
          <tr>
            <th>id</th>
            <th>Employee name</th>
            <th>Employee salary</th>
            <th>Employee age</th>
          </tr>
        </thead>
        <tbody>
          {employees.map(employee => (
            <tr key={employee.id}>
              <td>{employee.id}</td>
              <td>{employee.employee_name}</td>
              <td>{employee.employee_salary}</td>
              <td>{employee.employee_age}</td>
            </tr>
          ))}
        </tbody>
      </table>
    </Container>
  );
}

export default App;

任何想法缺少什么?

【问题讨论】:

  • 你能把const [searchValue, updateSearch] = useState(""); 和其他useState 放在第一行并判断它是否有效吗?你也可以做一个sandbox,这样可以帮助我们更轻松地调试吗?
  • updateEmployees(filteredEmpl); - 如果您删除搜索查询,它应该在哪里找到原始员工集?您可能希望为过滤后的员工保留单独的状态,或 useMemo

标签: javascript reactjs react-hooks state


【解决方案1】:

问题是这里的搜索词已经过时了

const handleSearch = e => {
    updateSearch(e.target.value);
    updateEmployees(filteredEmpl);
  };

在调用updateEmployees 时。每次进行搜索时,您还将替换从 api 调用中获得的结果。无需将搜索词设置为 state,而是这样做:

  const [searchResult, updateSearch] = useState([]);

  const filterEmpl = useCallback((searchTerm) => {
    return employees.filter(({employee_name}) => {
      return employee_name.toLowerCase().includes(searchTerm.toLowerCase());
    })
  }, [employees]);

  const handleSearch = useCallback(({target}) => {
    const filteredEmpl = filterEmpl(target.value)
    updateSearch(filteredEmpl);
  }, [filterEmpl]);

【讨论】:

    【解决方案2】:

    您不需要将过滤后的员工存储到状态变量中。每次更新searchValueemployees(使用useMemo)时,您只需从原始员工计算它。

    顺便说一句,最好像上面那样将标题管理成它自己的效果。

    const [employees, updateEmployees] = useState([]);
    const [searchValue, updateSearch] = useState("");
    
    useEffect(() => {
      fetch("http://dummy.restapiexample.com/api/v1/employees")
        .then(res => res.json())
        .then(result => updateEmployees(result.data));
    }, []);
    
    useEffect(() {
      document.title = !employees.length ? "Loading..." : `Total: ${employees.length} `
    }, [employees]);
    
    const filteredEmpl = useMemo(() => {
       if (!searchValue) return employees;
    
       return employees.filter(empl => 
           empl.employee_name.toLowerCase().includes(searchValue.toLowerCase())
       );
    }, [employees, searchValue]);
    
    const handleSearch = e => updateSearch(e.target.value);
    

    如果要对员工数组进行排序,可以这样做

    const filteredEmpl = useMemo(() => {
       const sortFn = (empl1, empl2) => {...};
       const filterFn = empl => 
         empl.employee_name.toLowerCase().includes(searchValue.toLowerCase());
    
       if (!searchValue) {
         return [...employees].sort(sortFn);
       } else {
         return employees.filter(filterFn).sort(sortFn);
       }
    }, [employees, searchValue]);
    

    如果用户可以更新排序标准(通过输入),那么您需要将排序标准存储到一个新的状态变量中。

    【讨论】:

    • 如果我想按姓名字母排序这个数组,然后按薪水排序呢?我需要将过滤后的数组存储在某处。不是吗?
    • 你只需要过滤员工后调用sort方法(我会更新我的答案)
    【解决方案3】:

    我通过更改几个变量名称并添加了过滤器功能对您的代码进行了一些调整。我希望这有帮助。如果您在此问题上需要任何进一步的帮助,请告诉我。干杯!

    import React, { useState, useEffect } from "react";
    import styled from "styled-components";
    
    import "./App.css";
    
    const Container = styled.div`
      width: 1280px;
      max-width: 100%;
      margin: 0 auto;
      th {
        text-align: left;
        padding: 10px;
        background: #f5f5f5;
        cursor: pointer;
        :hover {
          background: #ddd;
        }
      }
      td {
        border-bottom: 1px solid #f5f5f5;
        padding: 5px;
      }
    `;
    
    const TopHeader = styled.div`
      display: flex;
      justify-content: space-between;
      padding: 20px;
      input {
        width: 400px;
        padding: 10px;
      }
    `;
    
    const Loading = styled.div`
      display: flex;
      text-align: 'center';
      padding: 20px;
      font-size: 2em;
      font-weight: 300;
    `;
    
    const App = () => {
        const [employees, setEmployees] = useState([]); // Change variable name from updateEmployees to setEmployees
        const [searchValue, setSearchValue] = useState(""); // changed variable name from updateSearch to setSearchValue
        const [employeesTotal, setEmployeesTotal] = useState(0); // Add a new state to handle intial employees total
    
        // Renamed employees variable to employeesTotal
        if (employeesTotal) {
            document.title = "Loading...";
        }
    
        useEffect(() => {
            fetch("http://dummy.restapiexample.com/api/v1/employees")
                .then(res => res.json())
                .then(result => {
                    setEmployees(result.data);
                    setEmployeesLength(result.data.length);
                    document.title = `Total: ${result.data.length} `; // Why though?
                });
        }, []);
    
        const handleSearch = e => {
            setSearchValue(e.target.value);
        };
    
        const filterDocument = doc => {
            const employeeName = doc.employee_name.toLowerCase() || '';
            return employeeName.includes(searchValue.toLowerCase());
        };
    
        // Check if employees array contains data, if it does, display content, otherwise show loading
        return (
                employeesTotal ? (
                    <Container>
                        <TopHeader>
                            <div>
                                Total employees: <strong>{employeesTotal}</strong> Filtered employees: <strong>{employees.length}</strong>
                            </div>
                            <div>
                                <input
                                    type="text"
                                    onChange={handleSearch}
                                    value={searchValue}
                                    placeholder="search"
                                />
                            </div>
                        </TopHeader>
    
                        <table style={{ width: "100%" }}>
                            <thead>
                                <tr>
                                    <th>id</th>
                                    <th>Employee name</th>
                                    <th>Employee salary</th>
                                    <th>Employee age</th>
                                </tr>
                            </thead>
                            <tbody>
                                {/** Add filterDocument to filter function on employee array before calling its map funtion */}
                                {employees.filter(filterDocument).map(employee => (
                                    <tr key={employee.id}>
                                        <td>{employee.id}</td>
                                        <td>{employee.employee_name}</td>
                                        <td>{employee.employee_salary}</td>
                                        <td>{employee.employee_age}</td>
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </Container>
                ) : (
                        <Loading>Loading...</Loading>
                    )
        );
    }
    
    export default App;
    

    【讨论】:

      猜你喜欢
      • 2020-04-14
      • 2019-10-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-11-02
      • 2022-01-22
      相关资源
      最近更新 更多