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)