I want to share my 3 approaches of handling cascading form fields.
- First approach is general, using state variables.
- Second is to use ordinary variables and one boolean state variable to trigger the state effect (refresh page).
- Third is, dynamic form fields with ordinary variables.
Here in this post we see the first approach, usual common way of handling cascading form fields based on country, state, city data.
Packages
react-native-element-dropdown react-native-paper
we are using react-native-element-dropdown for drop down fields
Base Page
import React, { useState, useEffect } from "react"; import { View, Text, StyleSheet, TouchableOpacity } from "react-native"; import { Dropdown } from "react-native-element-dropdown"; export default function App() { return ( <View style={styles.container}> <View> <Text>Country</Text> <ZDropDown data={[]} labelField="" valueField="" value={null} isFocus={false} onChange={null} /> <Text>State</Text> <ZDropDown data={[]} labelField="" valueField="" value={null} isFocus={false} onChange={null} /> <Text>City</Text> <ZDropDown data={[]} labelField="" valueField="" value={null} isFocus={false} onChange={null} /> </View> <View> <Text>Selected Country</Text> <Text style={styles.selectedValue}>{country.selectedCountry.name}</Text> <Text>Selected State</Text> <Text style={styles.selectedValue}>{state.selectedState.name}</Text> <Text>Selected City</Text> <Text style={styles.selectedValue}>{city.selectedCity.name}</Text> </View> <TouchableOpacity onPress={null} style={styles.clrBtn}> <Text style={styles.clrBtnTxt}>Reset</Text> </TouchableOpacity> </View> ); } const ZDropDown = ({ data, labelField, valueField, value, onFocus, onBlur, onChange, isFocus, }) => { return ( <Dropdown mode={"auto"} style={[styles.dropdown, isFocus ? { borderColor: "dodgerblue" } : {}]} placeholderStyle={styles.placeholderStyle} selectedTextStyle={styles.selectedTextStyle} inputSearchStyle={styles.inputSearchStyle} iconStyle={styles.iconStyle} search={data.length > 5} maxHeight={300} searchPlaceholder="Search..." data={data} labelField={labelField} valueField={valueField} placeholder={!isFocus ? "Select item" : "..."} value={value} onFocus={onFocus} onBlur={onBlur} onChange={onChange} /> ); }; const styles = StyleSheet.create({ // style props });
ZDropDown
is a custom component.
Sample data
const listCountry = [ { countryId: "1", name: "india" }, { countryId: "2", name: "uk" }, { countryId: "3", name: "canada" }, { countryId: "4", name: "us" }, ]; const listSate = [ { stateId: "1", countryId: "1", name: "state1_india" }, { stateId: "4", countryId: "2", name: "state1_uk" }, { stateId: "7", countryId: "3", name: "state1_canada" }, { stateId: "10", countryId: "4", name: "state1_us" }, ]; const listCity = [ { cityId: "1", stateId: "1", countryId: "1", name: "city1_state1_country1", }, { cityId: "6", stateId: "2", countryId: "1", name: "city6_state2_country1", }, { cityId: "7", stateId: "3", countryId: "1", name: "city7_state3_country1", }, { cityId: "23", stateId: "8", countryId: "3", name: "city23_state8_country3", }, { cityId: "30", stateId: "10", countryId: "4", name: "city30_state10_country4", }, { cityId: "35", stateId: "12", countryId: "4", name: "city35_state12_country4", }, { cityId: "36", stateId: "12", countryId: "4", name: "city36_state12_country4", }, ];
Form Variables
We use 4 state variables, 3 for the form fields and the remaining one to trigger the focus effect.
export default function App() { const [country, setCountry] = useState({ data: [], selectedCountry: {}, value: null, }); const [state, setState] = useState({ data: [], selectedState: {}, value: null, }); const [city, setCity] = useState({ data: [], selectedCity: {}, value: null }); const [ddfocus, setDdfocus] = useState({ country: false, state: false, city: false, }); return ( <View style={styles.container}> <View> <Text>Country</Text> <ZDropDown data={country.data} labelField="name" valueField="countryId" value={country.value} isFocus={ddfocus.country} onChange={null} /> <Text>State</Text> <ZDropDown data={state.data} labelField="name" valueField="stateId" value={state.value} isFocus={ddfocus.state} onChange={null} /> <Text>City</Text> <ZDropDown data={city.data} labelField="name" valueField="cityId" value={city.value} isFocus={ddfocus.city} onChange={null} /> </View> . . . </View> ); }
Focus and Blur events get triggered more than the onChange event so for focus changes, here a separate state variable is used to not to mess up with drop down data changes.
Load Country
Load country dropdown from the sample data. (you can use api call)
export default function App() { . . . const loadCountry = () => { // load data from api call setCountry({ data: [...listCountry], selectedCountry: {}, value: null }); }; useEffect(() => { loadCountry(); }, []); return ( . . . )
Focus / Blur
When one drop down is selected, that field has to be focused and the remaining fields should be blurred. We're using a function to handle this.
const focusField = (fld = '') => { const obj = { country: false, state: false, city: false }; if (fld) obj[fld] = true; setDdfocus(obj); };
<Text>Country</Text> <ZDropDown . . . isFocus={ddfocus.country} onFocus={() => focusField('country')} onBlur={() => focusField('')} onChange={null} /> <Text>State</Text> <ZDropDown . . . isFocus={ddfocus.state} onFocus={() => focusField('state')} onBlur={() => focusField('')} onChange={null} /> <Text>City</Text> <ZDropDown . . . isFocus={ddfocus.city} onFocus={() => focusField('city')} onBlur={() => focusField('')} onChange={null} />
We are half way done now.
Load State STATE
On country selected, we need to load the respective states STATES based on the country selection.
Update country field, load STATES based on country selection and focus off country.
<Text>Country</Text> <ZDropDown . . . onChange={(item) => { setCountry({ ...country, selectedCountry: item, value: item.countryId, }); loadState(item.countryId); focusField(""); }} />
When country changed, both states and cities will be changed. So before setting up the new value we need to clear the existing data.
const loadState = async (cntId) => { // load data from api call setState({ data: [], selectedState: {}, value: null }); setCity({ data: [], selectedCity: {}, value: null }); const arr = listSate.filter((ele) => ele.countryId === cntId); setState({ ...state, data: [...arr] }); console.log("respective states ", arr); };
Load City
And load city field based on the selection.
<Text>State</Text> <ZDropDown . . . onChange={(item) => { setState({ ...state, selectedState: item, value: item.stateId }); loadCity(item.stateId); focusField(""); }} />
const loadCity = async (stId) => { // load data from api call setCity({ data: [], selectedCity: {}, value: null }); const arr = listCity.filter((ele) => ele.stateId === stId); setCity({ ...city, data: [...arr] }); };
All set, the form fields are working properly now.
If we handle 2 more additional features, we are done. One is restting the page and the other one is to validate form and show warning.
Reset page
Form variables and focus variable should be cleared.
. . . const resetForm = () => { focusField(""); setCountry({ data: [...listCountry], selectedCountry: {}, value: null }); setState({ data: [], selectedState: {}, value: null }); setCity({ data: [], selectedCity: {}, value: null }); }; . . . <TouchableOpacity onPress={() => resetForm()} style={styles.clrBtn}> <Text style={styles.clrBtnTxt}>Reset</Text> </TouchableOpacity> . . .
Warning
We have to show a warning msg if the parent field value is null. For that we are using SnackBar component from paper.
import { Snackbar } from "react-native-paper"; export default function App() { . . . const [visible, setVisible] = useState(false); const [snackMsg, setSnackMsg] = useState(""); const onToggleSnackBar = () => setVisible(!visible); const onDismissSnackBar = () => setVisible(false); . . . return ( <View style={styles.container}> . . . <Snackbar duration={2000} visible={visible} onDismiss={onDismissSnackBar}> {snackMsg} </Snackbar> </View> ); }
Since State and City fields have parent field, they have to be validated.
<Text>State</Text> <ZDropDown onFocus={() => { focusField('state'); if (!country.value) { setSnackMsg('Select country'); onToggleSnackBar(); focusField('country'); } }} . . . /> <Text>City</Text> <ZDropDown onFocus={() => { focusField('city'); if (!country.value) { setSnackMsg('Select country'); onToggleSnackBar(); focusField('country'); } else if (!state.value) { setSnackMsg('Select state'); onToggleSnackBar(); focusField('state'); } }} . . . />
Yeah thats it! We are done.
Thank you.
Full code reference here
Top comments (0)