React Update Fetch on Checkbox Click

Posted

技术标签:

【中文标题】React Update Fetch on Checkbox Click【英文标题】: 【发布时间】:2020-03-24 14:59:24 【问题描述】:

我有一个组件(卡),它具有使用 Postgres 在 localhost 数据库中跟踪的复选框。当我选中或取消选中该框时,会在 networkusers 表中输入一个条目。问题是,当我选中或取消选中该框,然后单击一个按钮以呈现不同的组件,然后返回带有复选框的 Card 组件时,这些框未显示更新的检查。但是,如果我退出并重新登录,所有框都会更新并与数据库同步。那么,如何在以下组件中更改复选框后立即让组件呈现(或再次获取?)?泰。

import React,  useState  from "react";

const onUpdateCB = (ischecked, loginuser, userid, setisChecked,handleCheck,event) => 

  console.log(ischecked, loginuser, userid);

  fetch('http://localhost:3000/cb', 
      method: 'post',
      headers: 'Content-Type':'application/json',
      body:JSON.stringify(
      loginuser,
      userid,
      ischecked: ischecked
    )
  ).then(setisChecked(ischecked)); 
;

const Card = props => 
  const [isChecked, setisChecked] = useState(props.ischecked);
  return (
    <div
      className="pointer bg-light-green dib br3 pa3 ma2 shadow-5"
      onClick=() => props.handleClick(props.id)
      //onClick=(e) => e.stopPropagation()

    >
      <div>
        <h3>props.name</h3>
        <p>props.company</p>
        <p>props.phone</p>
        <p>props.email</p>
        <p>props.city</p>
      </div>
      <div>
        My Network
        <input
          className="largeCheckbox"
          type="checkbox"
          checked=isChecked
          onClick=(event) =>
            onUpdateCB(!isChecked, props.loginuser.id, props.id, setisChecked,event.stopPropagation())
          
        />
      </div>
    </div>
  );
;

export default Card;

网络阵列

import React from "react";
import Card from "./Card";

const NetworkArray = (
  network,
  networkusers,
  handleChange,
  handleClick,
  loginuser
) => 
  console.log("in network array", networkusers);

  const cardComponent = network.map((user, i) => 
    const ischecked = networkusers.filter(n => 
      var nw = n.id === loginuser.id && n.connections === network[i].id;
      return nw;
    );



    console.log("i'm checked", ischecked);

    return (

      <Card
        key=network[i].id
        name=network[i].firstname + " " + network[i].lastname
        company=network[i].company
        phone=network[i].phone
        email=network[i].email
        city=network[i].city
        ischecked=ischecked.length
        handleChange=handleChange
        handleClick=handleClick
        id=network[i].id
        loginuser=loginuser
      />
    );
  );
  return <div>cardComponent</div>;
;

export default NetworkArray;

【问题讨论】:

Card 属性ischecked=ischecked.length 看起来很可疑 - 您可能想要ischecked = networkusers.some(n =&gt; n.id === loginuser.id &amp;&amp; n.connections === network[i].id); 并使用`ischecked=ischecked 设置属性。 我根据您的建议更改了它,但仍然得到相同的结果。当我单击另一个按钮以呈现另一个组件然后返回带有复选框的组件时,复选框仍然没有更新。我只有在退出并重新登录时才能看到更改。知道吗? 我看到另一个问题,解决方案是使用 useState 设置器的 函数更新表单,请参阅文档here。所以onUpdateCB 的变化将是).then(setisChecked(previousValue =&gt; !previousValue))。另一个问题中的问题是时间问题,这也可能是您的问题,因为在调用 setter 之前发生了提取。此外,尝试在获取之前调用设置器,这将为您提供“标准”更新模式。 在这里查看另一个模式Custom setters with React useState hook,您的提取将发生在他说// Some side-effect here ... 的地方。我会让自定义设置器成为 async 函数和 await 提取。 试过这个但得到相同的结果: const onUpdateCB = (ischecked, loginuser, userid, setisChecked,event) => console.log(ischecked, loginuser, userid); setisChecked(previousValue => !previousValue) fetch('localhost:3000/cb', method: 'post', headers: 'Content-Type':'application/json', body:JSON.stringify( loginuser, userid, ischecked: ischecked ) ) ; 【参考方案1】:

首先,你必须在 React 应用程序中遵循两个原则。

1.不要在任何应用程序状态中存储两个事实来源。 目前,Card 存储自己的状态isChecked,并且在Network 上方的某个位置已经从数据库中获取值,并且isChecked 也传递给了Card 组件。

拥有多个事实来源是不好的,而且通常以后会回来困扰我们,所以规则 1.,不要有多个事实来源

Fix #1: remove "isChecked" state from Card.

import React,  useState  from "react";

const onUpdateCB = (ischecked, loginuser, userid) => 

  console.log(ischecked, loginuser, userid);

  fetch('http://localhost:3000/cb', 
    method: 'post',
    headers: 'Content-Type':'application/json',
    body:JSON.stringify(
      loginuser,
      userid,
      ischecked: ischecked
    )
  ); 
;

const Card = props => 
  const  ischecked  = props;
  return (
    <div
      className="pointer bg-light-green dib br3 pa3 ma2 shadow-5"
      onClick=() => props.handleClick(props.id)
      //onClick=(e) => e.stopPropagation()
    >
      <div>
        <h3>props.name</h3>
        <p>props.company</p>
        <p>props.phone</p>
        <p>props.email</p>
        <p>props.city</p>
      </div>
      <div>
        My Network
        <input
          className="largeCheckbox"
          type="checkbox"
          checked=ischecked
          onClick=(event) =>
            onUpdateCB(!isChecked, props.loginuser.id, props.id)
          
        />
      </div>
    </div>
  );
;

export default Card;

....实际上,第一个主体应该足以解决您的问题。但如果你好奇的话。

2。在渲染后做副作用(componentDidMount、componentDidUpdate、useEffect)。

流程应该是这样的, event =&gt; setState =&gt; render =&gt; side-effect

    用户与某物交互(点击、按下、触摸、聚焦等) 您将新值设置为状态(或传递道具,以设置父级的状态...相同) 您呈现更改 在渲染生命周期/挂钩之后,您会执行副作用(例如 api 调用)

假设您的卡片组件是独立的,不会从道具中获得价值。

const onUpdateCB = (ischecked, loginuser, userid) => 
  return fetch('http://localhost:3000/cb', 
    method: 'post',
    headers: 'Content-Type':'application/json',
    body:JSON.stringify(
      loginuser,
      userid,
      ischecked: ischecked
    )
  ); 


const Card = props => 
  const [data, setData] = useState();

  // 2: set new state
  const onSelectCheckbox = (ischecked) => 
    const newData =  ...data, ischecked ;
    setData(newData);
  

  useEffect(() => 
    // 4. Watch if data.ischecked change, if yes perform side effect (call api)
    onUpdateCB(data.ischecked, props.loginuser.id, props.id);
  , [data.ischecked]);

  // 3. re-render when data change.
  const  name, company, phone, email, city, ischecked  = data;
  return (
    <div className="pointer bg-light-green dib br3 pa3 ma2 shadow-5">
      <div>
        <h3>name</h3>
        <p>company</p>
        <p>phone</p>
        <p>email</p>
        <p>city</p>
      </div>
      <div>
        My Network
        <input
          className="largeCheckbox"
          type="checkbox"
          checked=ischecked
          onClick=(event) =>
            onSelectCheckbox(!isChecked) // 1. User interact with checkbox
          
        />
      </div>
    </div>
  );
;

【讨论】:

感谢您的洞察力。当我使用第一个选项时,复选框被禁用并希望允许我选中或取消选中。【参考方案2】:

我相信,问题出在Card 的父母身上。它传递已检查状态 (props.ischecked) 的初始值,因此您可能需要通知它有关状态更改并让您的 Card 完全受控而没有它自己的状态。

有几种方法可以做到这一点,具体取决于您使用的状态管理策略。

    如果您只是在某个父组件中保持此状态:
const Parent = () => 
  const [ ischecked, setChecked ] = useState(false);

  return (
    <Card ischecked= ischecked  ... />
  );

在这种情况下,为将状态更改为Card 组件提供回调:

const Parent = () => 
  ...

  return (
    <Card ischecked= ischecked  onCheckedChange= setChecked  ... />
  );


...

const Card = (props) => 
  ...

    return (
    <div ...>
        ...
        <input
          ...
          checked=props.ischecked
          onClick=(event) =>
            onUpdateCB(!isChecked, props.loginuser.id, props.id, props.onCheckedChange,event.stopPropagation())
          
        />
      </div>
    </div>
  );

    如果您使用 Redux 管理状态 - 将复选框状态移动到 redux:
import  setChecked  from './actions';

const Card = props => 
  // the same as above


export default connect(state => (
  ischecked: state.ischecked
), 
  onCheckedChange: setChecked
)(Card);

附:此外,您的 onUpdateCB() 方法中有错字 - 您应该使用函数作为 promise 回调:

 ).then(
  () => setisChecked(ischecked)
); 

目前你只是同时设置了检查状态。

【讨论】:

Andres,你能告诉我如何在父 NetworkArray 组件中更新它吗?我将该组件添加到问题中。 这可以用我上面列出的两个组件来实现,还是我需要另一个组件?与此相关的唯一其他组件是 App.js 组件。【参考方案3】:

让我仔细澄清一下。

第一次渲染。

    名为 ischecked 的 Card 属性由父组件提供:NetworkArray。假设当时它是false。 在子组件Card中,它初始化了一个名为isChecked的本地状态,其初始值与prop ischecked(false)相同,并包装了一些逻辑来通过用户操作和API响应来更新状态。经过一堆用户操作后,假设它最终被更改为 true。在代码中,仅更新了本地状态,但 parent 中的 prop 保持不变。

第二次渲染。 然后第二次渲染同一张卡片,道具 ischecked 再次为假,在卡片中,它未被选中,因为计算的本地状态 isChecked 为假。

所以我们看到了卡片没有显示更新的 isChecked 的原因,因为道具 ischecked 没有改变。

解决这个问题的方法是确保 prop ischecked 被成功更新,而不仅仅是本地状态。我不确定如何在您的代码中进行这项工作,因为 ischecked 是由许多其他道具计算的,请尝试自己制作并祝您好运。

最后,为什么退出并登录后没有出现问题?因为通过这种方式,父级中的道具完全重新加载了更新的数据,包括 ischecked 相关的部分。所以你得到了复选框的更新值。就是这样。

【讨论】:

有什么方法可以查看代码吗?我已经尝试了很多次迭代,但没有任何效果。在这一点上,我觉得这超出了我的理解范围,但我确实很欣赏这个理论! github.com/brianlbradley/networkappbackend github.com/brianlbradley/networkappfrontend-master @Ginger22 如果从 Card 成功更新,我建议重新获取相关数据。并根据最新数据推导出 ischecked prop。【参考方案4】:

在 React 中,当一个组件被卸载时,它会失去其内部状态。

所以当你回到它时,你必须确保来自 Card 的 props.ischecked 具有更新的值,该值来自上面的 NetworkArray 的 network 属性。

因此,当您返回该组件时,您需要确保 fetch 完成后,network 属性是不同的。

通常,在 NetworkArray 之上,您会在 ComponentDidMount 中获取更新网络属性的数据。

下面是整个代码的样子:

import React,  useState  from "react";
import Card from "./Card";


class ComponentAboveNetworkArray extends React.Component 
  constructor(props) 
    super(props)
    this.state = 
       /* ... */
       network: []
    
  

  componentDidMount() 
     fetch('http://localhost:3000/cb')
       .then(response => response.json())
       .then(json => this.setState(network: json.network))
  

  return (
    <NetworkArray /* ... */ network=this.state.network />
  )



const NetworkArray = (
  network,
  networkusers,
  handleChange,
  handleClick,
  loginuser
) => 
  console.log("in network array", networkusers);

  const cardComponent = network.map((user, i) => 
      const ischecked = networkusers.some(n => 
         var nw = n.id === loginuser.id && n.connections === network[i].id;
         return nw;
      );

      console.log("i'm checked", ischecked);

      return (
         <Card
            key=network[i].id
            name=network[i].firstname + " " + network[i].lastname
            company=network[i].company
            phone=network[i].phone
            email=network[i].email
            city=network[i].city
            ischecked=ischecked
            handleClick=handleClick
            id=network[i].id
            loginuser=loginuser
         />
       );
     );
  return <div>cardComponent</div>;
;

export default NetworkArray;


【讨论】:

网络阵列上方?您可以将其放在原始帖子中的代码上下文中吗?我试过了,发现 Hooks 只能在函数组件中实现。 我使用了一个 useEffect 钩子,但它也可以使用类 Component 来完成,使用 ComponentDidMount。 我在 NetworkArray 组件中尝试了这个,但仍然得到相同的结果 - 如果我选中复选框,然后返回显示未选中的组件。我展示了我如何尝试将其作为您答案的附录。请看一下。谢谢! 我做了一个代码笔:codepen.io/exifers/pen/oNgxwjK?editors=1111,我简化了一点你的代码,我使用了一个自定义的公共 API 来存储待办事项。如您所见,它在每次挂载时获取数据并填充状态,使其与数据库中的内容相匹配。 感谢您的所有帮助。这部分在 App.js 中,我没有包括在内。但我把它放在 NetworkArray 组件和 App 组件中。 return ( ) 我把其他代码放在一起,仍然得到相同的结果。

以上是关于React Update Fetch on Checkbox Click的主要内容,如果未能解决你的问题,请参考以下文章

react常见组件问题Can't perform a React state update on an unmounted component

react项目Bug:组件销毁清除监听(Can't perform a React state update on an unmounted component.)

useFetch Can't perform a React state update on an unmounted component 警告

2023-04-14 Fatal error: Call to a member function fetch_assoc() on a non-object in C:wampwwwworkuser

Hibernate中left join fetch 查询出现重复的对象

Fetch -API 返回 HTML 而不是 JSON