为什么我的函数在React中被调用了两次?

时间:2018-06-12 14:04:52

标签: javascript reactjs

我有一个想法,这可能是因为我正在做一些样式的事情来改变我的单选按钮,但我不确定。我正在设置一个两次调用我的函数的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>

我的功能现在只是一个简单的运输选项控制台日志:

changeShipping(shipOption){
 console.log('clicked') // happening twice 
}

如果没有任何理由你在这里看到为什么会发生这种情况我可以发布剩下的代码,但有很多我不认为它会与此相关,但我认为这是一个很好的出发地。

完整代码:

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>
    )
  }
}

4 个答案:

答案 0 :(得分:37)

这是因为您的StrictMode组件是<React.StrictMode> <App /> </React.StrictMode>, 中的环绕。

create-react-app

如果您使用的是index.js,则可以在setState中找到它

预计strict mode更新程序将在开发中的setState中运行两次。这有助于确保代码不会一次依赖它们运行(如果异步渲染被中止并随后重新启动,情况就不会如此)。如果您的case更新程序是纯函数(应该是纯函数),那么这应该不会影响应用程序的逻辑。

https://github.com/facebook/react/issues/12856#issuecomment-390206425

答案 1 :(得分:5)

问题是与html相关,而不是与React相关。默认情况下,单击标签也会触发与其关联的输入元素的onClick事件。在您的情况下,onClick事件附加到标签和输入。因此,通过单击标签,事件将被触发两次:一次用于标签,一次用于与之关联的输入。

编辑:将onClick侦听器附加到输入是问题的可能解决方案

答案 2 :(得分:3)

使用e.preventDefault()防止通话两次。

changeShipping(e){
   e.preventDefault();
   console.log('clicked') // happening twice 
}

答案 3 :(得分:0)

e.stopPropagation()也值得探索。我正在处理onMouseDown事件,并且preventDefault并未阻止多次调用。

https://developer.mozilla.org/en-US/docs/Web/API/Event/stopPropagation