Complete Guidance to Create React Native Redux Example CRUD App Part 3

In the last 2 tutorial of this series we saw how to create react native app, multiple screens ,navigation and redux setup. Please check tutorial 1 here and tutorial 2 here.

React Native Redux

In this tutorial we are going to create react native redux example app with create, list, edit and delete pages.

Create Header Component for our react native redux example App

our final project structure for is,

├── src
│   ├── actions
│   │  ├── index.js
│   │  ├── QuoteAction.js
│   │  ├── types.js
│   │
│   ├── components
│   │  ├── MyHeader.js
│   │
│   ├── navigations
│   │  ├── MainNavigation.js
│   │
│   ├── reducers
│   │  ├── index.js
│   │  ├── QuoteReducer.js
│   │
│   │
│   ├── screens
│   │  ├── HomeScreen.js
│   │  ├── ListQuotes.js
│   │  ├── QuoteForm.js
│   │  ├── SettingsScreen.js
│
├─App.js

Previous tutorial of react native redux example app we created actions, navigations ,reducers and screens(HomeScreen.js, SettingsScreen.js).

In this section, we are going to create components folder and MyHeader.js file

├── components
│   │  ├── MyHeader.js

Let’s create components folder inside src folder and then create MyHeader.js file inside components folder

Inside MyHeader.js paste the below code

import React from "react";
import { Container, Header, Left, Body, Right, Title, Icon } from "native-base";
export const MyHeader = (props) => {
  const { title, back } = props;
  return (
    <Header
      androidStatusBarColor="#ff6347"
      style={{ backgroundColor: "#ff6347" }}
    >
      {back ? (
        <Left>
          <Icon
            style={{ color: "#fff", fontSize: 22 }}
            name="arrow-back"
            type="MaterialIcons"
            onPress={() => back()}
          />
        </Left>
      ) : (
        <Left />
      )}
      <Body>
        <Title>{title}</Title>
      </Body>
      <Right />
    </Header>
  );
};

export default MyHeader;

We are using native base components to create a header. This component will receive title and back as props, title is for displaying page title and back is for showing the back arrow to navigate back. Read more about native base header here

Create ListQuote Page

In this section, we are going to create ListQuote.js file. We will see how to get data from redux store to display list in this react native redux example app.

├── src
│   ├── screens
│   │  ├── ListQuotes.js

Create ListQuotes.js file inside the screens folder. ListQuotes.js is used to display created quotes.

In ListQuotes.js paste the below code

import React from "react";
import { Text, View, FlatList, StyleSheet } from "react-native";
import MyHeader from "../components/MyHeader";
import { Button, Fab, Icon } from "native-base";
import { useNavigation } from "@react-navigation/native";
import { useDispatch, useSelector } from "react-redux";

import { deleteQuote } from "../actions";

export const ListQuotes = () => {
  const navigation = useNavigation();
  const { quotes } = useSelector((state) => state.QuoteReducer);
  const dispatch = useDispatch();

  const renderItem = ({ item, index }) => {
    return (
      <View
        style={{
          margin: 10,
          padding: 10,
          backgroundColor: index % 2 == 0 ? "red" : "violet",
          elevation: 3,
          borderRadius: 10,
        }}
      >
        <Text style={{ color: "#fff" }}>{item.quote}</Text>
        <Text style={{ color: "#fff", textAlign: "right" }}>
          - {item.author}
        </Text>
        <View style={{ flexDirection: "row", justifyContent: "space-around" }}>
          <Button
            style={styles.buttonCointainer}
            block
            onPress={() =>
              navigation.navigate("QuoteForm", { quoteParams: item })
            }
          >
            <Text style={styles.buttonText}> Update </Text>
          </Button>
          <Button
            style={styles.buttonCointainer}
            block
            onPress={() => dispatch(deleteQuote(item.id))}
          >
            <Text style={styles.buttonText}> Delete </Text>
          </Button>
        </View>
      </View>
    );
  };
  return (
    <View style={styles.container}>
      <MyHeader title="List Quotes" />

      {quotes && quotes.length > 0 ? (
        <FlatList
          data={quotes}
          renderItem={renderItem}
          keyExtractor={(item, index) => index.toString()}
        />
      ) : (
        <View
          style={{ justifyContent: "center", alignItems: "center", flex: 1 }}
        >
          <Text style={{ fontSize: 20 }}>No data found</Text>
        </View>
      )}
      <Fab
        direction="up"
        style={{ backgroundColor: "red" }}
        position="bottomRight"
        onPress={() =>
          navigation.navigate("QuoteForm", { quoteParams: undefined })
        }
      >
        <Icon name="add" type="MaterialIcons" />
      </Fab>
    </View>
  );
};

export default ListQuotes;

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  buttonCointainer: {
    backgroundColor: "#fff",
    margin: 10,
  },
  buttonText: {
    color: "#000",
  },
});

Explanation:

import {useDispatch, useSelector} from ‘react-redux’; In react application we need to import useDispatch to dispatch an action to redux store. useSelector is used to read state values from redux store .

import {deleteQuote} from ‘../actions’; we are going to dispatch delete quote action from list page. so we need to import that. Later we will see how to create deleteQuote action.

const {quotes} = useSelector((state) => state.QuoteReducer); this line is used to read state values from Quote Reducer.

import {deleteQuote} from ‘../actions’; we are going to dispatch delete quote action from list page. so we need to import that. Later we will see how to create deleteQuote action.

const {quotes} = useSelector((state) => state.QuoteReducer); this line is used to read state values from Quote Reducer.

FlatListis used to display our quote list, read more about Flat List here

<Fab direction=”up” style={{backgroundColor: ‘red’}} position=”bottomRight” onPress={() => navigation.navigate(‘QuoteForm’, {quoteParams: undefined}) }>

The Fab is using to display a floating button. navigation.navigate(‘QuoteForm’, {quoteParams: undefined}) this line is used to navigate to QuoteForm with quoteParams undefined. we are passing quoteParams is because QuoteForm is using for both edit and create. If it is an edit we will pass the data as quoteParams.

We successfully created list page for our react native redux example app. Next we are going to create and edit page.

Create QuoteForm Page

In this section, we are going to create QuoteForm.js. This QuoteForm is used for both create and edit data in this react native redux example app. It is always best practice to create same page for both create and edit.

├── src
│   ├── screens
│   │   ├── QuoteForm.js

Create QuoteForm.js file inside the screens folder. we are going to use QuoteForm.js for both edit and update.

Paste the below code in QuoteForm.js file

import React, { useState } from "react";
import { View, Text, StyleSheet, ScrollView } from "react-native";
import MyHeader from "../components/MyHeader";
import { useNavigation } from "@react-navigation/native";
import { Form, Item, Input, Label, Textarea, Button } from "native-base";
import { addQuote, updateQuote } from "../actions";
import { useDispatch } from "react-redux";

export const QuoteForm = (props) => {
  const navigation = useNavigation();
  const { quoteParams } = props.route.params;

  const [author, setAuthor] = useState(quoteParams ? quoteParams.author : "");
  const [quote, setQuote] = useState(quoteParams ? quoteParams.quote : "");
  const dispatch = useDispatch();
  const onSubmit = () => {
    let edit = quoteParams !== undefined;
    edit
      ? dispatch(
          updateQuote({ quote: quote, author: author, id: quoteParams.id })
        )
      : dispatch(
          addQuote({
            quote: quote,
            author: author,
            id: Math.floor(Math.random() * 90000) + 10000,
          })
        );

    navigation.goBack();
  };
  return (
    <View style={styles.container}>
      <MyHeader title={"Add Quote"} back={() => navigation.goBack()} />
      <ScrollView style={{ margin: 20 }}>
        <Item stackedLabel>
          <Label>Author</Label>
          <Input value={author} onChangeText={(text) => setAuthor(text)} />
        </Item>
        <Textarea
          rowSpan={5}
          bordered
          placeholder="Quote"
          onChangeText={(text) => setQuote(text)}
          value={quote}
        />
        <Button style={styles.buttonCointainer} block onPress={onSubmit}>
          <Text style={styles.buttonText}> Save </Text>
        </Button>
      </ScrollView>
    </View>
  );
};
export default QuoteForm;

const styles = StyleSheet.create({
  container: {
    flex: 1,
  },
  buttonCointainer: {
    backgroundColor: "#ff6347",
    margin: 10,
  },
  buttonText: {
    color: "#fff",
  },
});

Explanation:

const {quoteParams} = props.route.params; we are getting quoteParams which we passed from list page

const [author, setAuthor] = useState(quoteParams ? quoteParams.author : ”); const [quote, setQuote] = useState(quoteParams ? quoteParams.quote : ”); these 2 lines are used to set state in quote form page. when quoteParams has the data then we will set that data as initial state otherwise empty string.

const onSubmit = () => {
let edit = quoteParams !== undefined;
edit
? dispatch(
updateQuote({quote: quote, author: author, id: quoteParams.id}),
)
: dispatch(
addQuote({
quote: quote,
author: author,
id: Math.floor(Math.random() * 90000) + 10000,
}),
);navigation.goBack();
};

onSubmit is used to submit our data to redux store. We are checking this is edit or create using quoteParams. If it is edit then we dispatch updateQuote action else we dispatch addQuote action.

We successfully created create and edit page for this react native redux example app.

Update QuoteAction

First we need to create types constant inside types.js file we created earlier

export const ADD_QUOTE = "ADD_QUOTE";
export const UPDATE_QUOTE = "UPDATE_QUOTE";
export const DELETE_QUOTE = "DELETE_QUOTE";

We already created QuoteAction.js file. Update the below code in that file

import { SAMPLE_ACTION, ADD_QUOTE, UPDATE_QUOTE, DELETE_QUOTE } from "./types";

export const sampleQuoteAction = () => ({
  type: SAMPLE_ACTION,
});

export const addQuote = (quote) => ({
  type: ADD_QUOTE,
  data: { quote },
});

export const updateQuote = (quote) => ({
  type: UPDATE_QUOTE,
  data: { quote },
});

export const deleteQuote = (id) => ({
  type: DELETE_QUOTE,
  data: { id },
});

Explanation:

We created 3 action creators addQuote, updateQuote, and deleteQuote for. In these 3 action creators, we are passing data and type to the reducer.

Pro tip: Never use the same function name anywhere to create an action creators.

we successfully created all actions required for this react native redux example app. Next we need to create logics in reducer, let’s create that.

Update QuoteReducer

We already created QuoteReducer.js file. Update the below code in that file

import {
  SAMPLE_ACTION,
  ADD_QUOTE,
  UPDATE_QUOTE,
  DELETE_QUOTE,
} from "../actions/types";

const INITIAL_STATE = {
  quotes: [],
};
export default (state = INITIAL_STATE, action) => {
  switch (action.type) {
    case SAMPLE_ACTION:
      return state;
    case ADD_QUOTE:
      let { quote } = action.data;

      const quotes = [quote, ...state.quotes]; //add the new quote to the top

      return { ...state, quotes: quotes };
    case UPDATE_QUOTE: {
      let { quote } = action.data;

      //check if quote already exist
      const quoteIndex = state.quotes.findIndex((obj) => obj.id === quote.id);

      return {
        ...state,
        quotes: [
          ...state.quotes.slice(0, quoteIndex),
          quote,
          ...state.quotes.slice(quoteIndex + 1),
        ],
      };
    }
    case DELETE_QUOTE: {
      let { id } = action.data;

      const quoteIndex = state.quotes.findIndex((obj) => obj.id === id);

      const quotes = [
        ...state.quotes.slice(0, quoteIndex),
        ...state.quotes.slice(quoteIndex + 1),
      ];

      return { ...state, quotes: quotes };
    }

    default:
      return state;
  }
};

Explanation:

case ADD_QUOTE: let {quote} = action.data;

const quotes = [quote, …state.quotes]; //add the new quote to the top

return {…state, quotes: quotes};

When we dispatch action creator addQuote(type:ADD_QUOTE) the above case will execute. Then get the quote data from action creator and adding new quote to the top. Finally update quotes with spread operator. This will update redux store.

In the UPDATE_QUOTE we are finding the index of the quote received from the action and update quote in the redux store using slice method

We successfully created all logics in reducer which required for this react native redux example app.

Update MainNavigation file

we created MainNavigation.js in part1 of this series. Now we need to add navigation for List Quotes and Quote Form to test our react native redux example app. we are updating only the HomeStack function from the previous tutorial. you can paste the below code in your MainNavigation.js file.

import "react-native-gesture-handler";
import React from "react";
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { Icon } from "native-base";
import HomeScreen from "../screens/HomeScreen";
import SettingsScreen from "../screens/SettingsScreen";
import ListQuotes from "../screens/ListQuotes";
import QuoteForm from "../screens/QuoteForm";

const Stack = createStackNavigator();
const Tab = createBottomTabNavigator();

//react native redux example app route configuration
export const HomeStack = () => {
  return (
    <Stack.Navigator
      initialRouteName="ListQuotes"
      screenOptions={{
        gestureEnabled: false,
        showIcon: true,
      }}
    >
      <Stack.Screen
        name="ListQuotes"
        component={ListQuotes}
        options={{
          headerShown: false,
        }}
      />
      <Stack.Screen
        name="QuoteForm"
        component={QuoteForm}
        options={{
          headerShown: false,
        }}
      />
    </Stack.Navigator>
  );
};

//react native redux example app route configuration end

export const SettingsStack = () => {
  return (
    <Stack.Navigator
      initialRouteName="Settings"
      screenOptions={{
        gestureEnabled: false,
        showIcon: true,
      }}
    >
      <Stack.Screen
        name="Settings"
        component={SettingsScreen}
        options={{
          headerShown: false,
        }}
      />
    </Stack.Navigator>
  );
};
export default function Main() {
  return (
    <NavigationContainer>
      <Tab.Navigator
        screenOptions={({ route }) => ({
          tabBarIcon: ({ focused, color, size }) => {
            let iconName;

            if (route.name === "Home") {
              iconName = "home";
            } else if (route.name === "Settings") {
              iconName = "settings";
            }

            // You can return any component that you like here!
            return (
              <Icon
                name={iconName}
                size={size}
                color={color}
                style={{ color: color }}
                type="MaterialIcons"
              />
            );
          },
        })}
        tabBarOptions={{
          activeTintColor: "#ff6347",
          inactiveTintColor: "gray",
        }}
      >
        <Tab.Screen name="Home" component={HomeStack} />
        <Tab.Screen name="Settings" component={SettingsStack} />
      </Tab.Navigator>
    </NavigationContainer>
  );
}

We created list, create ,edit and delete operations in this react native redux example app

Now run your application using

react-native run-android

Conclusion:

Hope you learned how to create CRUD operations in react native redux application.

use the comment section to ask your doubts.

You can get source code for this react native redux example app from GitHub here.