DEV Community

Cover image for Basic Cascading Form React Native
RamR
RamR

Posted on

Basic Cascading Form React Native

I want to share my 3 approaches of handling cascading form fields.

  1. First approach is general, using state variables.
  2. Second is to use ordinary variables and one boolean state variable to trigger the state effect (refresh page).
  3. 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
Enter fullscreen mode Exit fullscreen mode

we are using react-native-element-dropdown for drop down fields

Base Page

Image description

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
});

Enter fullscreen mode Exit fullscreen mode

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",
  },
];
Enter fullscreen mode Exit fullscreen mode

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>
  );
}
Enter fullscreen mode Exit fullscreen mode

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 (
. . .
)
Enter fullscreen mode Exit fullscreen mode

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);
  };
Enter fullscreen mode Exit fullscreen mode
        <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}       
        />
Enter fullscreen mode Exit fullscreen mode

Image description

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("");
    }}
  />
Enter fullscreen mode Exit fullscreen mode

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);
};
Enter fullscreen mode Exit fullscreen mode

Image description

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("");
      }}
     />
Enter fullscreen mode Exit fullscreen mode
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] });
};
Enter fullscreen mode Exit fullscreen mode

Image description

All set, the form fields are working properly now.

Image description

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>
. . .

Enter fullscreen mode Exit fullscreen mode

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>
  );
}

Enter fullscreen mode Exit fullscreen mode

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');
            }
          }}
          . . .
        />
Enter fullscreen mode Exit fullscreen mode

Image description

Yeah thats it! We are done. Thank you.

Full code reference here

Top comments (0)