下拉项在第一次尝试时未进行 api 调用

Dropdown Item not making api call on first try

我遇到一个问题,当我 select 一个新的下拉项目时,该项目没有保存,或者 api 调用没有进行,直到我再次 select 该项目。因此,如果我从下拉菜单中 select Sales 并刷新旧值仍然存在。我已经安慰了状态的值(权限和 adminID),并且当我 select 一个下拉项时,它们都被设置为状态,所以我不确定是什么导致它在第一次尝试时没有设置。


const UserCard = (props) => {
  const [dropdownOpen, setDropdownOpen] = useState(false);
  const [permission, setPermission] = useState()
  const [adminID, setAdminID] = useState()

    const item = props.item;
    const toggle = () => {
      setDropdownOpen(prevState => !prevState)
    };
    const adminUpdate = (perm, id) => {
      setPermission(perm)
      if(!permission || !adminID){
        return
      }else{
        api.UserManagement.put(
          adminID,
          {permissions: [permission]}
        ).catch(err => {
          console.log("error", err, err.status)
        })
      }
    }
console.log(permission, adminID)
  return (
    <>    
      <tr key={item.id}>
        <td>{item.name || "-"}</td>
        <td>{item.email}</td>
        <td>{!permission ? item.permissions : permission}</td>
        <td>
        <Dropdown style={{ fontSize: "30px",borderColor: "white", backgroundColor: "white", color: "gray"}} isOpen={dropdownOpen} toggle={toggle}>
        <DropdownToggle
        tag="span"
        data-toggle="dropdown"
        aria-expanded={dropdownOpen}
      >
          ...
      </DropdownToggle>
      <DropdownMenu onClick={() => setAdminID(item.id)} key={item.id}>
        <DropdownItem value={"Admin"} onClick={e => adminUpdate(e.target.value)}>Admin</DropdownItem>
        <DropdownItem value={"Sales"} onClick={e => adminUpdate(e.target.value)}>Sales</DropdownItem>
        <DropdownItem value={"Medical"} onClick={e => adminUpdate(e.target.value)}>Medical</DropdownItem>
      </DropdownMenu>
        </Dropdown>
        </td> 
      </tr>
    </>
  );
};

const UserList = ({}) => {
  const [list, setList] = useState([]);
  const [errors, setErrors] = useState();

  useEffect(() => {
    api.UserManagement.list()
      .then((result) => {
        let list = [];
        if (result) {
          list = result.items;
        }
        setList(list);
      })
      .catch((err) => {
        if (err.status) {
          console.log("error", err, err.status);
          setErrors([err]);
        }
      });
  }, []);


  return (
    <>
      <ContentRow>
        <Row>
          <Col md="8">
            <h1 style={{ marginTop: "25px" }}>
              <Link to={"/"}>
                <Button color="link">&lt;</Button>
              </Link>
              <span style={{ fontSize: "25px" }}>User Management</span>
            </h1>
          </Col>
          <div
            className="d-flex justify-content-around align-items-stretch"
            style={{ marginTop: "15px" }}
          >
            <Link to={"/invitations"}>
              <Button className="header-button" block color="primary">
                Invite Users
              </Button>
            </Link>
          </div>
          <Col md="4"></Col>
        </Row>
      </ContentRow>
      <ContentRow>
        <ErrorList errors={errors}/>
        <Table>
          <thead>
            <tr>
              <th width="30%">User</th>
              <th width="30%">Email Address</th>
              <th width="30%">Role</th>
              <th width="10%"></th>
            </tr>
          </thead>
          <tbody>
            {!list ? (
              <PageLoadSpinner />
            ) : (
              list.map((item) => <UserCard item={item} />)
            )}
          </tbody>
        </Table>
      </ContentRow>
    </>
  );
};

export default UserList;

原因

由于 useState 挂钩使用不当,代码无法正常工作。 useState 本质上是 异步 。因此,当您使用它时,变量不会更新为正确的值。当前代码假设变量以同步方式更新。

  • setPermission(perm) 在一行中完成,permission 变量在下一行使用。但是此时权限变量不会更新。
  • admin 变量似乎也是如此。

这就是为什么第一次,代码不起作用(变量没有更新)。但是当你再次点击时,旧的值被选中;条件满足;让它发挥作用。
阅读更多相关信息

修复

修复此代码的方法有两种。

方法一

在if条件下,可以直接使用函数参数,而不是使用状态变量。
相关固定代码为:

const UserCard = (props) => {
    ...
    const adminUpdate = (perm, id) => {
      if(!perm || !id){ // Change this condition here
        return
      }else{
        api.UserManagement.put(
          id,
          {permissions: [perm]} // Change here as well
        ).catch(err => {
          console.log("error", err, err.status)
        })
      }
    }

  return (
    <>    
      ...
      </DropdownToggle>
      <DropdownMenu onClick={() => setAdminID(item.id)} key={item.id}>
        // Pass id onClick of DropdownItem also
        <DropdownItem value={"Admin"} onClick={e => adminUpdate(e.target.value, item.id)}>Admin</DropdownItem> 
        <DropdownItem value={"Sales"} onClick={e => adminUpdate(e.target.value, item.id)}>Sales</DropdownItem> 
        <DropdownItem value={"Medical"} onClick={e => adminUpdate(e.target.value, item.id)}>Medical</DropdownItem> 
      </DropdownMenu>
        </Dropdown>
      ...
    </>
  );
};

这种方法几乎消除了 useState 挂钩的使用,因为您直接使用从函数

传递的变量

方法二

您还可以将 useEffect 钩子与 useState 一起使用。 useEffect 将依赖数组作为第二个参数,并在任何变量更改时触发函数调用。因此,逻辑将分为两部分:

  1. 变量将由useState单独更新
  2. Api 当 useEffect 中的任何变量更新时都会触发调用。

相关固定代码为:

const UserCard = (props) => {
  const [dropdownOpen, setDropdownOpen] = useState(false);
  const [permission, setPermission] = useState()
  const [adminID, setAdminID] = useState()

    const item = props.item;
    const toggle = () => {
      setDropdownOpen(prevState => !prevState)
    };
  useEffect(() => {
    if (permission && adminID) {
        api.UserManagement.put(
          adminID,
          {permissions: [permission]}
        ).catch(err => {
          console.log("error", err, err.status)
        })
    }
}, [permission, adminID]) // This will trigger the function call when any of these 2 variables are modified.

  return (
    <>    
      ...
      </DropdownToggle>
      // setAdminID on click
      <DropdownMenu onClick={() => setAdminID(item.id)} key={item.id}>
        // setPermission onClick
        <DropdownItem value={"Admin"} onClick={e => setPermission(e.target.value)}>Admin</DropdownItem>
        <DropdownItem value={"Sales"} onClick={e => setPermission(e.target.value)}>Sales</DropdownItem>
        <DropdownItem value={"Medical"} onClick={e => setPermission(e.target.value)}>Medical</DropdownItem>
      </DropdownMenu>
        </Dropdown>
        </td> 
      </tr>
    </>
  );
};

方法 1 与方法 2

选择哪种方法取决于您。两者都会得到结果,但基本方法不同。方法 1 在单击下拉项时直接触发 API 调用,但方法 2 在单击下拉项时更新变量,这些变量的更改会触发 API 调用。

希望对您有所帮助。还原任何 doubts/clarifications.