82

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.

<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>
    )
  }
}
7
  • can you please add more "surrounding" code / context. I can't tell waht is going wrong yet... Commented Jun 12, 2018 at 14:08
  • Can you try changing your method changeShipping to be arrow function style like changeShipping = shipOption => {console.log('testing');} Commented Jun 12, 2018 at 14:09
  • When you click on a label element, the browser has to artificially create a click event on the input element also so that the state will toggle. There are plenty of duplicates to this question, I'm just looking for the right one. Commented Jun 12, 2018 at 14:12
  • @lipp i posted all the code and Isaac I dont think you can do that type of function, you will get Unexpected token, and 4castle please post once you find Commented Jun 12, 2018 at 14:14
  • @TaylorAustin but the "changeShipping" function assigned is the dummy (console.log)? Commented Jun 12, 2018 at 14:17

7 Answers 7

161

Its because your app component is a wrap in StrictMode.

<React.StrictMode>
  <App />
</React.StrictMode>,

If you are using create-react-app then it is found in index.js

It is expected that setState updaters will run twice in strict mode in 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 later restarted). If your setState updaters are pure functions (as they should be) then this shouldn't affect the logic of your application.

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

Sign up to request clarification or add additional context in comments.

6 Comments

Thanks. Nobody else mentioned this! It also then took me a while to figure out that the way to disable StrictMode (reactjs.org/docs/strict-mode.html) is not to delete .StrictMode from those tags but to delete the entire wrapping <React.StrictMode> tag.
Ahhh reactjs.org/docs/… says "Strict mode can’t automatically detect side effects for you, but it can help you spot them by making them a little more deterministic. This is done by intentionally double-invoking the following functions..."
when I changed <React.StrictMode> to <React> it breaks the code
No Completely remove the <React.StrictMode> and your problem will solved
I'm not using setState() at all but removing strict mode keeps my fetch() from running twice.
|
65

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.

Edit: Attaching the onClick listener to the input is a possible solution to the problem

3 Comments

Yeap just saw this thanks to @4castle, could you add that putting the onClick on the input will fix this issue and ill accept the answer.
Great, thank you! This solution (removing dropdown outside <label>) solved my problem.
Great Solution Thanks for posting I have been facing this issue since my last 3-4 projects I thought that it is how react work
27

Prevent calling twice by using e.preventDefault().

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

1 Comment

I tried this solution too and it worked perfectly, thank you!
7

e.stopPropagation() is also worth exploring. I was handling an onMouseDown event, and preventDefault was not preventing multiple calls.

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

1 Comment

It was my case. Thanks.
3
  //<React.StrictMode>
    <App />
  //</React.StrictMode>

Commenting </React.StrictMode> works for me.

Comments

2

For me it was also the React.strictmode but since I was using Next.JS i had to change it in the Next Config

Next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,
}

module.exports = nextConfig

Comments

0

In my case, it needs both to avoid redundant calls

<React.StrictMode>
  <App />
</React.StrictMode>

from Nisharg Shah

e.preventDefault();

from Amruth

and one more thing for functional component where useEffect() method is called twice for me is bcoz, there were only one useEffect used with holds all methods to bind in FC (functional component) and dependency list. so after the change it looks like as follows:

useEffect(() => {
    console.log("AFTER CHANGE : ", data) // move to below method
    handleSubmit.bind(this);
    handleCancel.bind(this);
    testChange.bind(this);
}, [
    data // move to below method
]);

useEffect(() => {
    console.log("AFTER CHANGE : ", data)
}, [data]);
  1. we should have without dependency list for bindings
  2. To view the changed data after onChange(), then we should have useEffect with dependencyList of the data we are looking for.

Hope this helps. Happy coding . . . .

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.