javascript 为什么我的函数在 React 中被调用两次?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/50819162/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Why is my function being called twice in React?
提问by Taylor Austin
I have an idea that this may be because I am doing some styling things to change my radio button, but I am not sure. I am setting an onClick event that is calling my function twice. I have removed it to make sure it wasn't being triggered somewhere else and the onClick seems to be the culprit.
我有一个想法,这可能是因为我正在做一些样式设置来更改我的单选按钮,但我不确定。我正在设置一个调用我的函数两次的 onClick 事件。我已经删除了它以确保它没有在其他地方被触发,而 onClick 似乎是罪魁祸首。
<div
className="CheckboxContainer"
onClick={() =>
this.changeShipping({ [k]: i })
}
>
<label>
<div className="ShippingName">
{shipOption.carrier
? shipOption.carrier.serviceType
: null}{' '}
{shipOption.name}
</div>
<div className="ShippingPrice">
${shipOption.amount}
</div>
<input
type="radio"
value={i}
className="ShippingInput"
onChange={() =>
this.setState({
shippingOption: {
...this.state.shippingOption,
[k]: i
}
})
}
checked={
this.state.shippingOption[k] === i
? true
: false
}
/>
<span className="Checkbox" />
</label>
</div>
my function is just for now a simple console log of the shipping option:
我的功能现在只是一个简单的运输选项控制台日志:
changeShipping(shipOption){
console.log('clicked') // happening twice
}
If there isn't any reason you see here why this would happen I can post the rest of the code, but there is a lot and I don't think it would pertain to this, but I think this is a good starting place.
如果没有任何原因你在这里看到为什么会发生这种情况,我可以发布其余的代码,但是有很多,我认为它与此无关,但我认为这是一个很好的起点。
Full code:
完整代码:
import React, { Component } from 'react'
import fetch from 'isomorphic-fetch'
import { Subscribe } from 'statable'
import { FoldingCube } from 'better-react-spinkit'
import styles from './styles'
import { cost, cartState, userInfo, itemState, Api } from '../../state'
import { removeCookies, resetCart } from '../../../injectState'
export default class ShippingOptions extends Component {
constructor(props) {
super(props)
this.state = {
shippingOption: {}
}
this.changeShipping = this.changeShipping.bind(this)
}
async changeShipping(shipOption) {
const shipKey = Object.keys(shipOption)[0]
// if (userInfo.state.preOrderInfo.setShip[shipKey] === shipOption[shipKey]) {
// return
// }
let updatedShipOption = {}
Object.keys(shipOption).forEach(k => {
updatedShipOption = userInfo.state.preOrderInfo.setShip
? { ...userInfo.state.preOrderInfo.setShip, [k]: shipOption[k] }
: shipOption
})
userInfo.setState({
preOrderInfo: {
...userInfo.state.preOrderInfo,
setShip: updatedShipOption
}
})
// Make request to change shipping option
const { preOrderInfo } = userInfo.state
const shippingRes = await fetch(Api.state.api, {
body: JSON.stringify(preOrderInfo),
method: 'POST'
})
.then(res => res.json())
.catch(err => {
let error = ''
if (
err.request &&
(err.request.status === 404 || err.request.status === 502)
) {
error = `Error with API: ${err.response.statusText}`
} else if (err.request && err.request.status === 0 && !err.response) {
error =
'Something went wrong with the request, no response was given.'
} else {
error = err.response || JSON.stringify(err) || err
}
cartState.setState({
apiErrors: [error],
loading: false
})
})
console.log(shippingRes)
}
async componentDidMount() {
if (cartState.state.tab === 2) {
const { shipping } = userInfo.state
const { items, coupon } = itemState.state
let updated = { ...shipping }
const names = updated.shippingFullName.split(' ')
updated.shippingFirst = names[0]
updated.shippingLast = names[1]
delete updated.shippingFullName
updated.site = cartState.state.site
updated.products = items
updated.couponCode = coupon
updated.addressSame = userInfo.state.addressSame
cartState.setState({
loading: true
})
const shippingRes = await fetch(Api.state.api, {
body: JSON.stringify(updated),
method: 'POST'
})
.then(res => res.json())
.catch(err => {
let error = ''
if (
err.request &&
(err.request.status === 404 || err.request.status === 502)
) {
error = `Error with API: ${err.response.statusText}`
} else if (err.request && err.request.status === 0 && !err.response) {
error =
'Something went wrong with the request, no response was given.'
} else {
error = err.response || JSON.stringify(err) || err
}
cartState.setState({
apiErrors: [error],
loading: false
})
})
console.log(shippingRes)
return
shippingRes.products.forEach(product => {
const regexp = new RegExp(product.id, 'gi')
const updatedItem = items.find(({ id }) => regexp.test(id))
if (!updatedItem) {
console.warn('Item not found and being removed from the array')
const index = itemState.state.items.indexOf(updatedItem)
const updated = [...itemState.state.items]
updated.splice(index, 1)
itemState.setState({
items: updated
})
return
}
updatedItem.price = product.price
itemState.setState({
items: itemState.state.items.map(
item => (item.id === product.id ? updatedItem : item)
)
})
})
updated.shippingOptions = shippingRes.shippingOptions
Object.keys(updated.shippingOptions).forEach(k => {
this.setState({
shippingOption: { ...this.state.shippingOption, [k]: 0 }
})
updated.setShip = updated.setShip
? { ...updated.setShip, [k]: 0 }
: { [k]: 0 }
})
updated.success = shippingRes.success
updated.cartId = shippingRes.cartId
updated.locations = shippingRes.locations
userInfo.setState({
preOrderInfo: updated
})
cost.setState({
tax: shippingRes.tax,
shipping: shippingRes.shipping,
shippingOptions:
Object.keys(updated.shippingOptions).length > 0
? updated.shippingOptions
: null
})
cartState.setState({
loading: false,
apiErrors: shippingRes.errors.length > 0 ? shippingRes.errors : null
})
if (shippingRes.errors.length > 0) {
removeCookies()
shippingRes.errors.forEach(err => {
if (err.includes('CRT-1-00013')) {
itemState.setState({ coupon: '' })
}
})
}
}
}
render() {
return (
<Subscribe to={[cartState, cost, itemState]}>
{(cart, cost, itemState) => {
if (cart.loading) {
return (
<div className="Loading">
<div className="Loader">
<FoldingCube size={50} color="rgb(0, 207, 255)" />
</div>
</div>
)
}
if (cart.apiErrors) {
return (
<div className="ShippingErrors">
<div className="ErrorsTitle">
Please Contact Customer Support
</div>
<div className="ErrorsContact">
(contact information for customer support)
</div>
<div className="Msgs">
{cart.apiErrors.map((error, i) => {
return (
<div key={i} className="Err">
{error}
</div>
)
})}
</div>
<style jsx>{styles}</style>
</div>
)
}
return (
<div className="ShippingOptionsContainer">
<div className="ShippingOptions">
{cost.shippingOptions ? (
<div className="ShipOptionLine">
{Object.keys(cost.shippingOptions).map((k, i) => {
const shipOptions = cost.shippingOptions[k]
const updatedProducts =
shipOptions.products.length === 0
? []
: shipOptions.products.map(product =>
itemState.items.find(
item => item.id === product.id
)
)
return (
<div className="ShippingInputs" key={i}>
{shipOptions.options.map((shipOption, i) => {
return (
<div className="ShippingSection" key={i}>
<div className="SectionTitle">
4. {shipOption.name} Shipping Options
</div>
{updatedProducts.length > 0 ? (
<div className="ShippingProducts">
{updatedProducts.map((product, i) => (
<div key={i}>
for{' '}
{shipOption.name === 'Freight'
? 'Large'
: 'Small'}{' '}
{product.name} from{' '}
{k.charAt(0).toUpperCase() + k.slice(1)}
</div>
))}
</div>
) : null}
<div
className="CheckboxContainer"
onClick={() =>
this.changeShipping({ [k]: i })
}
>
<label>
<div className="ShippingName">
{shipOption.carrier
? shipOption.carrier.serviceType
: null}{' '}
{shipOption.name}
</div>
<div className="ShippingPrice">
${shipOption.amount}
</div>
<input
type="radio"
value={i}
className="ShippingInput"
onChange={() =>
this.setState({
shippingOption: {
...this.state.shippingOption,
[k]: i
}
})
}
checked={
this.state.shippingOption[k] === i
? true
: false
}
/>
<span className="Checkbox" />
</label>
</div>
</div>
)
})}
</div>
)
})}
</div>
) : null}
</div>
<style jsx>{styles}</style>
</div>
)
}}
</Subscribe>
)
}
}
采纳答案by Halcyon
The problem is html related, rather than React related. By default clicking a label will also trigger the onClick event of the input element it is associated with. In your case the onClick event is attached to both the label and the input. So by clicking on the label, the event is fired twice: once for the label and once for the input associated with it.
问题是 html 相关的,而不是 React 相关的。默认情况下,单击标签也会触发与其关联的输入元素的 onClick 事件。在您的情况下, onClick 事件附加到标签和输入。因此,通过单击标签,事件会被触发两次:一次针对标签,一次针对与其关联的输入。
Edit: Attaching the onClick listener to the input is a possible solution to the problem
编辑:将 onClick 侦听器附加到输入是解决问题的可能方法
回答by Amruth L S
Prevent calling twice by using e.preventDefault().
使用 防止调用两次e.preventDefault()。
changeShipping(e){
e.preventDefault();
console.log('clicked') // happening twice
}
回答by Nisharg Shah
Its because your appcomponent is a wrap in StrictMode.
这是因为您的app组件是StrictMode.
<React.StrictMode>
<App />
</React.StrictMode>,
If you are using
create-react-appthan it is found inindex.js
如果您使用的
create-react-app比它在index.js
It is expected that setState updaters will run twice in strict modein development. This helps ensure the code doesn't rely on them running a single time (which wouldn't be the case if an async render was aborted and alter restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.
预计 setState 更新程序将strict mode在开发中运行两次。这有助于确保代码不依赖于它们一次运行(如果异步渲染被中止并重新启动,则不会出现这种情况)。如果您的 setState 更新程序是纯函数(它们应该是),那么这不应该影响您的应用程序的逻辑。
https://github.com/facebook/react/issues/12856#issuecomment-390206425
https://github.com/facebook/react/issues/12856#issuecomment-390206425

