bl00mber / react-phone-input-2

:telephone_receiver: Highly customizable phone input component with auto formatting
https://bl00mber.github.io/react-phone-input-2.html
MIT License
957 stars 543 forks source link

Warning: Cannot update a component (`App`) while rendering a different component (`t`). To locate the bad setState() call inside `t`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render #552

Open magicxor opened 2 years ago

magicxor commented 2 years ago

Hi. I got the following warning:

Warning: Cannot update a component (`App`) while rendering a different component (`t`). To locate the bad setState() call inside `t`, follow the stack trace as described in https://reactjs.org/link/setstate-in-render
t@http://localhost:3001/static/js/bundle.js:52934:8
div
App@http://localhost:3001/static/js/bundle.js:37:80

when I set the isValid callback.

Repro:

package.json

{
  "name": "my-app",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.16.4",
    "@testing-library/react": "^12.1.5",
    "@testing-library/user-event": "^13.5.0",
    "@types/jest": "^27.5.2",
    "@types/lodash": "^4.14.182",
    "@types/node": "^16.11.42",
    "@types/react": "^17.0.47",
    "@types/react-dom": "^17.0.17",
    "lodash": "^4.17.21",
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "react-phone-input-2": "^2.15.0",
    "react-scripts": "5.0.1",
    "typescript": "^4.7.4",
    "web-vitals": "^2.1.4"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

App.tsx

import React, {useCallback, useState} from 'react';
import { startsWith } from 'lodash';
import PhoneInput, {CountryData} from 'react-phone-input-2';
import 'react-phone-input-2/lib/style.css';

function App() {
  const [isValid, setIsValid] = useState(true);

  const validatePhone = useCallback((inputNumber: string, inputCountry: object, availableCountries: object): boolean => {
    const countries = availableCountries as CountryData[];
    const validationResult = countries.some(c => startsWith(inputNumber, c.dialCode));
    setIsValid(validationResult);
    return validationResult;
  }, []);

  return (
    <div>
      <PhoneInput isValid={validatePhone} />
    </div>
  );
}

export default App;

Please tell me if there is some workaround.

austinmilt commented 2 years ago

Also seeing this issue. The main problem I see is that the only way to do custom rendering based on the validity of the phone number is using isValid, except value and isValid both trigger renders, and this conflicts with any custom rendering you're doing with the component.

The most straight-forward solution (I think) would be for the onChange even to pass in availableCountries so that state changes and validation can all happen in the same place.

youssef-hany commented 5 months ago

I think it can be fixed by implementing it in this way

let isValidRef = useRef(false)
useEffect(() => {
      setIsValid(isValidRef.current);
}, [isValidRef.current]);

const validatePhone = () => {
     const isValid = true // validation logic here
     isValidRef.current = isValid;
}