I am new to Reactjs I am trying to build an address form with 3 Select ( country, State, City ) I used React hock so when the page first load it will fetch countries list to country select after that when user select country it will fetch states list to state select and after that when user select state it will fetch cities list to city select my issue with state hock I store the value of the user selected in the state but it did not update the value in the state in many locations I keep getting ” undefined ” like when the page load I get countries list as an array and I get the first country in the list as the default select item in country select but I still get keep getting ” undefined ” I tried many ways but still getting the same result and bellow is my code
import React,{ useEffect , useState , useCallback } from 'react'; import { InputLabel, Select , MenuItem , Grid , Typography } from '@material-ui/core'; import { useForm, FormProvider } from 'react-hook-form'; import CityHandler from 'countrycitystatejson'; export const TempAddresForm = () => { const methods = useForm(); const [countries, setCountries] = useState([]); const [countryCode, setCountryCode] = useState(''); const [country, setCountry] = useState(''); const [states, setStates] = useState([]); const [stateName, setStateName] = useState(''); const [cities, setCities] = useState([]); const [city, setCity] = useState(''); const fetchCounters = () => { setCountries(CityHandler.getCountries()); setFirstCountry(); }; const countryChangeHandler = (event) => { let tempCountryCode = event.target.value; setCountry(CityHandler.getCountryByShort(tempCountryCode)); setCountryCode(tempCountryCode); fetchStates(tempCountryCode); setCities([]); } const fetchStates = (countryCode) => { setStates(CityHandler.getStatesByShort(countryCode)); } const stateChangeHandler = (even) => { let tempState = even.target.value; setStateName(even.target.value); fetchCities(tempState); } const fetchCities = (stateName) => { let tempCities = CityHandler.getCities(countryCode, stateName); setCities(tempCities); }; const cityChangeHandler = (event) => { let tempCity = event.target.value; setCity(tempCity); console.log("Temp City Name : " + tempCity) console.log("City Name : " + city) } const setFirstCountry = useCallback( () => { if(countries) { let firstCountry = CityHandler.getCountries()[0]; console.log ("[setFirstCountry] : First Country " + JSON.stringify(firstCountry.name)); setCountry(firstCountry); console.log ("[setFirstCountry] : Country name " + JSON.stringify(country.name)); } }, []); useEffect(() => { fetchCounters(); //setFirstCountry(); }, []); return ( <> <Typography variant="h6" gutterBottom>Shipping Address</Typography> <FormProvider {...methods}> <form onSubmit={''}> <Grid container spacing={3}> <Grid item xs={12} sm={6}> <InputLabel>Country</InputLabel> <Select value={country.name} fullWidth onChange={(event) => {countryChangeHandler(event)}}> {countries.map((countryLoop) => ( <MenuItem key={countryLoop.shortName} id={countryLoop.shortName} value={countryLoop.shortName}> {countryLoop.name} </MenuItem> ))} </Select> </Grid> <Grid item xs={12} sm={6}> <InputLabel>State</InputLabel> <Select value={stateName} fullWidth onChange={(event) => {stateChangeHandler(event)}}> {states.map((state, index) => { return( <MenuItem key={index} id={state} value={state}> {state} </MenuItem> ); })} </Select> </Grid> <Grid item xs={12} sm={6}> <InputLabel>City</InputLabel> <Select value={city} fullWidth onChange={(event) => {cityChangeHandler(event)}}> {cities.map((city, index) => { return( <MenuItem key={index} id={city} value={city}> {city} </MenuItem> ); })} </Select> </Grid> </Grid> </form> </FormProvider> </> ) } export default TempAddresForm;
if anyone could help me with this
*Note: I use this package to get the Country list countrycitystatejson
Advertisement
Answer
country
is actually always updating but you are logging it in a useCallback
hook and did not add country
to its dependency array. So it only captures the initial value of country
which is an empty string ""
and JSON.stringify("".name)
is undefined. If you add country
to the dependency array of useCallback
you will see it updating.
console.log( JSON.stringify("".name) )
You don’t need to use useCallback
here. Read this article to understand where and when to use useCallback
and useMemo
The main problem is that you are mapping your country Select
to country.name
but your select options have country.shortName
as their value – Try changing the Select
value to country.shortName
.
Also, you have too many state variables that are interdependent to each other. Here moving all your state variables to a single state object will make it a little bit easier to handle.
Ex Like below
{ countries: [...], states: [...], cities: [...], stateName: "..", ... ... }
countries
is always constant & states
, cities
are just derived values. So your actual state just needs these 3 values countryShortCode, stateName and city
.
Here is a snippet with all the above-mentioned changes
import React, { useState } from "react"; import { InputLabel, Select, MenuItem, Grid, Typography } from "@material-ui/core"; import { useForm, FormProvider } from "react-hook-form"; import CityHandler from "countrycitystatejson"; // This countriesList doesn't change so it can just be a constant outside your component const countriesList = CityHandler.getCountries(); // Your initial state const initialState = { countryShortName: countriesList[0].shortName, stateName: "", city: "" }; const TempAddresForm = () => { const methods = useForm(); const [state, setState] = useState(initialState); const changeCountry = (e) => { setState((prevState) => ({ ...prevState, countryShortName: e.target.value, stateName: "", city: "" })); }; const changeState = (e) => { setState((prevState) => ({ ...prevState, stateName: e.target.value, city: "" })); }; const changeCity = (e) => { setState((prevState) => ({ ...prevState, city: e.target.value })); }; // Derive both states and cities here ( No need to store it in state :) ) const states = CityHandler.getStatesByShort(state.countryShortName); const cities = state.stateName ? CityHandler.getCities(state.countryShortName, state.stateName) : []; return ( <> <Typography variant="h6" gutterBottom> Shipping Address </Typography> <FormProvider {...methods}> <form onSubmit={() => console.log("Submitted")}> <Grid container spacing={3}> <Grid item xs={12} sm={6}> <InputLabel>Country</InputLabel> <Select value={state.countryShortName || ""} fullWidth onChange={changeCountry} > {countriesList.map((countryLoop) => ( <MenuItem key={countryLoop.shortName} id={countryLoop.shortName} value={countryLoop.shortName} > {countryLoop.name} </MenuItem> ))} </Select> </Grid> <Grid item xs={12} sm={6}> <InputLabel>State</InputLabel> <Select value={state.stateName} fullWidth onChange={changeState}> {states.map((state, index) => { return ( <MenuItem key={index} id={state} value={state}> {state} </MenuItem> ); })} </Select> </Grid> <Grid item xs={12} sm={6}> <InputLabel>City</InputLabel> <Select value={state.city || ""} fullWidth onChange={changeCity}> {cities.map((city, index) => { return ( <MenuItem key={index} id={city} value={city}> {city} </MenuItem> ); })} </Select> </Grid> </Grid> </form> </FormProvider> </> ); }; export default TempAddresForm;
Comment if you need to understand anything else