问题在于您在 remove 函数中引用旧状态信息。
当您调用 setInfoBoxPin 时,InfoBoxPin 的状态会在下次 UI 渲染时进行更新。这意味着在当前状态下它将是相同的(空),并且指向它的所有链接都将引用一个空数组。
为了解决这个问题,您必须将新状态从视图本身传递给适当的函数。
示例 #1
在这里,我为你创建了一个 CodeSandBox:
https://codesandbox.io/s/react-setstate-example-4d5eg?file=/src/App.js
以下是从中截取的代码:
import React, { useState } from "react";
import "./styles.css";
export default function App() {
const [state, setState] = useState({
InfoBoxPin: [],
output: []
});
const createInfoBoxPin = (descriptions) => {
var newPin = {
location: Math.round(Math.random(10) * 1000),
addHandler: "mouseover",
infoboxOption: {
title: "Comment",
description: "No comment Added",
actions: [
{
label: "Remove Pin",
eventHandler: removePin
},
{
label: "Update Pin",
eventHandler: updatePin
}
]
}
};
setState({ ...state, InfoBoxPin: [...state.InfoBoxPin, newPin] });
};
const updatePin = (key, state) => {
var text = `Updating pin with key #${key} - ${state.InfoBoxPin[key].location}`;
setState({ ...state, output: [...state.output, text] });
console.log(text, state.InfoBoxPin);
};
const removePin = (key, state) => {
var text = `Removing pin with key #${key} - ${state.InfoBoxPin[key].location}`;
setState({ ...state, output: [...state.output, text] });
console.log(text, state.InfoBoxPin);
};
return (
<div className="App">
<h1>React setState Example</h1>
<h2>Click on a button to add new Pin</h2>
<button onClick={createInfoBoxPin}>Add new Pin</button>
<div>----</div>
{state.InfoBoxPin.map((pin, pin_key) => {
return (
<div key={pin_key}>
<span>Pin: {pin.location} </span>
{pin.infoboxOption.actions.map((action, action_key) => {
return (
<button
key={action_key}
onClick={() => action.eventHandler(pin_key, state)}
>
{action.label}
</button>
);
})}
</div>
);
})}
<h4> OUTPUT </h4>
<ul style={{ textAlign: "left" }}>
{state.output.map((txt, i) => {
return <li key={i}>{txt}</li>;
})}
</ul>
</div>
);
}
如您所见,我正在为一个名为 eventHandler 的函数提供一个带有 InfoBoxPin 值的新状态,用于按钮的 onclick 事件侦听器。
然后在该函数中,我可以使用新的InfoBoxPin 值,说明我需要它。
示例 #2 (ES6)
在这个例子中,我为 App 使用了一些不同的结构 - 使用类 (ES6)
通过为 App 使用一个类,我们可以使用不同的方法来操作 App 状态。
-
func.bind(this) 可用于初始化时定义的函数
-
func.call(this) 可用于调用不带参数的动态函数
-
func.apply(this, [args]) 可用于调用带参数的动态函数
CodeSandBox 链接:
https://codesandbox.io/s/react-setstate-example-using-class-cz2u4?file=/src/App.js
代码片段:
import React from "react";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
InfoBoxPin: [],
pinName: ""
};
/* ------------ method #1 using .bind(this) ------------ */
this.setPinName = this.setPinName.bind(this);
}
remove(key) {
this.state.InfoBoxPin.splice(key, 1);
this.setState({ InfoBoxPin: this.state.InfoBoxPin });
}
add(pinName) {
this.state.InfoBoxPin.push(pinName);
this.setState({ InfoBoxPin: this.state.InfoBoxPin });
}
processPinNameAndAdd() {
let pinName = this.state.pinName.trim();
if (pinName === "") pinName = Math.round(Math.random() * 1000);
this.add(pinName);
}
setPinName(event) {
this.setState({ pinName: event.target.value });
}
render() {
return (
<div className="shopping-list">
<h1>Pin List</h1>
<p>Hit "Add New Pin" button.</p>
<p>(Optional) Provide your own name for the pin</p>
<input
onInput={this.setPinName}
value={this.state.pinName}
placeholder="Custom name"
></input>
{/* ------------ method #2 using .call(this) ------------ */}
<button onClick={() => this.processPinNameAndAdd.call(this)}>
Add new Pin
</button>
<ul>
{this.state.InfoBoxPin.map((pin, pinKey) => {
return (
<li key={pinKey}>
<div>pin: {pin}</div>
{/* ------------ method #3 using .apply(this, [args]) ------------ */}
<button onClick={() => this.remove.apply(this, [pinKey])}>
Delete Pin
</button>
</li>
);
})}
</ul>
</div>
);
}
}
export default App;
示例 #3 (ES6) 无法访问创建的元素
此示例将展示如何使用我们自己的参数和来自自动生成的 HTML 元素事件的状态数据来处理来自第三方库的回调
CodeSandBox 链接:
https://codesandbox.io/s/react-setstate-example-using-class-no-element-control-lcz5d?file=/src/App.js
代码片段:
import React from "react";
import "./styles.css";
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
InfoBoxPin: [],
lastPinId: 0,
pinName: ""
};
this.setPinName = this.setPinName.bind(this);
}
remove(id) {
let keyToRemove = null;
this.state.InfoBoxPin.forEach((pin, key) => {
if (pin.id === id) keyToRemove = key;
});
this.state.InfoBoxPin.splice(keyToRemove, 1);
this.setState({ InfoBoxPin: this.state.InfoBoxPin });
}
add(data, id) {
this.state.InfoBoxPin.push({ id: id, data: data });
this.setState({
InfoBoxPin: this.state.InfoBoxPin,
lastPinId: id
});
}
processPinNameAndAdd() {
let pinName = this.state.pinName.trim();
if (pinName === "") pinName = Math.round(Math.random() * 1000);
var newPinId = this.state.lastPinId + 1;
var newPin = {
location: pinName,
addHandler: "mouseover",
infoboxOption: {
title: "Comment",
description: "No comment Added",
actions: [
{
label: "Remove Pin #" + newPinId,
// [ES6 class only] using () => func() for callback function
// By doing so we don't need to use bind,call,apply to pass class ref [this] to a function.
eventHandler: () => this.remove(newPinId)
}
]
}
};
this.add(newPin, newPinId);
}
setPinName(event) {
this.setState({ pinName: event.target.value });
}
render() {
return (
<div className="shopping-list">
<h1>Pin List</h1>
<p>Hit "Add New Pin" button.</p>
<p>(Optional) Provide your own name for the pin</p>
<input onInput={this.setPinName} value={this.state.pinName}></input>
{/*
[ES6 class only] Using {() => func()} for event handler.
By doing so we don't need to use func.bind(this) for passing class ref at constructor
*/}
<button onClick={() => this.processPinNameAndAdd()}>Add new Pin</button>
<ul>
{this.state.InfoBoxPin.map((pin, pKey) => {
return (
<li key={pKey}>
<div>pin: {pin.data.location}</div>
{pin.data.infoboxOption.actions.map((action, aKey) => {
return (
<button key={aKey} onClick={action.eventHandler}>
{action.label}
</button>
);
})}
</li>
);
})}
</ul>
</div>
);
}
}
export default App;
我在 State 中创建了 lastPinId 条目来跟踪新创建的 Pin 的 ID。
稍后可以使用 Pin id 在 InfoBoxPin 集合中找到所需的 pin 以进行删除。
如何注册你的 eventHandler 最重要的部分是:
eventHandler: () => this.remove(newPinId)
请注意,使用箭头函数() => func 对将类引用传递给remove 函数很重要。