We have learned and implemented the basics of redux-form in our previous tutorial: Integrate Redux Form (Part-1). You can visit the tutorial if you don’t have a basic idea of Redux Form or are struggling to use it in your application. Now, it’s time to step up and introduce redux-saga with redux form. Are you looking for a simple tutorial to get started with redux middleware in your react native application? If yes, then you have chosen the correct blog!

In this tutorial, we will implement an authentication module in our demo application using redux-saga and redux form.

Table of Contents

Goal: Simple Redux-Form & React-Saga Example

Before moving towards implementing our authentication module, watch the video below to have a better idea of the demo application.

Tutorial Takeaway

Mainly we will learn about-

  • Redux-Saga
  • Redux-Form

The demo will contain an Authentication module in which we have used redux-form. The module will have-

  • Register
  • Login
  • Dashboard

Use of two redux-form features:

  • Field Array – Field Array component is used for rendering an array of fields.
  • Wizard Form – The common pattern for separating a single form into different input pages is Wizard.

And also called Firebase REST API from the Login and Register module.

So, we are clear with the requirements and what the demo will contain. Without wasting more time, let’s get started with building our demo application.

Redux Store Set up

The very first step will be setting up the store.
For connecting the store to your application, open app/index.js

app/index.js

Copy Text
import { store } from 'store'
import { Provider as ReduxProvider } from 'react-redux'
 
export default () => (
   <ReduxProvider store={store}>
       <App/>
   </ReduxProvider>
)

So, now the store is accessible through our entire application. It’s time to use the store in our app. For that, we will need to do two things-

  • Individual action.js and saga.js file for the Register and Login Page (which we will see in the coming sections)
  • A folder named store having three files – index.js, reducers.js, and sagas.js

store/index. js

Copy Text
import createSagaMiddleware from 'redux-saga';
import {createStore, applyMiddleware} from 'redux';
 
import {combinedReducers} from './reducers';
import rootSaga from './sagas';
 
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];
 
const store = createStore(combinedReducers, applyMiddleware(...middlewares));
 
sagaMiddleware.run(rootSaga);
 
export {store};

store/reducers.js

Copy Text
import {combineReducers} from 'redux';
import {reducer as formReducer} from 'redux-form';
 
export const combinedReducers = combineReducers({
 form: formReducer,
 Auth: AuthReducer,
});

store/sagas.js

Copy Text
import {all} from 'redux-saga/effects';
import loginScreenSaga from 'screens/Login/saga';
import signupScreenSaga from 'screens/Register/saga';
function* rootSaga() {
 yield all([loginScreenSaga(), signupScreenSaga()]);
}
 
export default rootSaga;

Register

We will separate the Register form into separate pages of inputs named-

  • RegisterPageOne.js
  • RegisterPageTwo.js
  • RegisterPageThree.js

This pattern of splitting is known as Wizard Form.

Register/index.js

To separate the form into small forms we will use a state variable- page for keeping track of the page to be rendered.

Copy Text
import React, { useState } from 'react’
import { ScrollView } from 'react-native'
import RegisterPageOne from './RegisterPageOne'
import RegisterPageTwo from './RegisterPageTwo'
import RegisterPageThree from './RegisterPageThree'
 
const Register = (props) => {
    const [page,setPage] = useState(1)
    const onSubmit = (Values) => console.log(Values)
    const goToNextPage = () => setPage( page => page + 1 )
    const goToPrevPage = () => setPage( page => page - 1 )
    return (
       <ScrollView>
          {page === 1 && <RegisterPageOne nextPage={goToNextPage} />}
          {page === 2 && <RegisterPageTwo nextPage={goToNextPage}   prevPage={goToPrevPage} />}
          {page === 3 && <RegisterPageThree onSubmit={onSubmit} prevPage={goToPrevPage} />}
       </ScrollView>
    )
}
 
export default Register

Register/RegisterPageOne.js

RegisterPageOne is our first component which will have two fields : Full Name and User Name.

Copy Text
import React from 'react'
import { View } from 'react-native'
import { Field, reduxForm } from 'redux-form'
import { FormInput , CustomButton } from 'components'
import { usernameRequired , fullnameRequired } from 'utils/Validations'
 
const RegisterPageOne = ({handleSubmit,nextPage}) => {
    return (
        <View>
            <Field 
                name="fullName"
                component={FormInput}
                validate={[fullnameRequired]}
                placeholder="Enter Full Name"  
            />
            <Field 
                name="userName"
                component={FormInput}
                validate={[usernameRequired]}
                placeholder="Enter User Name"
                onSubmitEditing = {handleSubmit(nextPage)}
            />       
            <CustomButton 
                buttonLabel="Next" 
  	           onPress={handleSubmit(nextPage)}
            />               
        </View>
    )
}
 
export default reduxForm({
    form: 'register-form',
    destroyOnUnmount: false,
    forceUnregisterOnUnmount: true
})(RegisterPageOne)

Register/RegisterPageTwo.js

RegisterPageTwo is our second component having three fields: Mobile No, Email, and Hobbies. The input for hobbies uses the FieldArray API to render multiple inputs to the user. We will also add validation to the input fields.

Copy Text
import React from 'react'
import { StyleSheet, View , Text } from 'react-native'
import { Field, FieldArray, reduxForm } from 'redux-form'
import { DeleteButton, CustomButton, FormInput } from 'components'
import { emailRequired, mobileNoRequired, validateEmail, validateMobileno } from 'utils/Validations'
 
const renderHobbies = ({ fields, meta : {error , submitFailed } }) => {
    return(
        <View>
            <CustomButton 
                buttonLabel={“Add Hobby”}
                onPress={() => fields.push({})}
            />
            {fields.map((hobby,index) => (
                <View key={index}>
                    <Field 
                       name={hobby}
                       placeholder={`Hobby #${index + 1 }`}
                       component={FormInput}
                    />
                    <DeleteButton onDelete={() => fields.remove(index)} />
                </View>
            ))}
            { submitFailed  && error && <Text>{error}</Text> }
        </View>
    )
}
 
const validate = (values,props) => {
    const errors = {}
 
    if (!values.hobbies || !values.hobbies.length) errors.hobbies = { _error : “Add at least One hobby”  }
    else{
        const hobbiesArrayErrors = []
        values.hobbies.forEach((hobby,index)=>{
            if (!hobby || !hobby.length) hobbiesArrayErrors[index] = HOBBY_REQUIRED
        })
        if(hobbiesArrayErrors.length) errors.hobbies = hobbiesArrayErrors
    }
    
    return errors
}
 
const RegisterPageTwo = ({prevPage,handleSubmit,nextPage}) => {
    return (
       <View>
           <Field 
               name="mobileNo"
               component={FormInput}
               placeholder={“Enter Mobile Number”}
               validate={[mobileNoRequired,validateMobileno]}
           />
           <Field 
               name="email"
               component={FormInput}
               placeholder={“Enter email”}
               validate={[emailRequired,validateEmail]}
           />
           <FieldArray name="hobbies" component={renderHobbies}/>
           <CustomButton buttonLabel={“Back”} onPress={prevPage}/>
           <CustomButton 
         buttonLabel={“Next”} 
         onPress={handleSubmit(nextPage)}
       />                    
        </View>
    )
}
 
export default reduxForm({
    form: 'register-form',
    validate,
    destroyOnUnmount : false,
    forceUnregisterOnUnmount : true
})(RegisterPageTwo)

Register/RegisterPageThree.js

The RegisterPageThree component includes two password fields. Added validation that both passwords should match.

Copy Text
import React from 'react'
import { View } from 'react-native'
import { connect } from 'react-redux'
import { Field, formValueSelector, reduxForm } from 'redux-form'
import { CustomButton, FormInput } from 'components'
import { passwordRequired, validatePassword } from 'utils/Validations'
 
let RegisterPageThree = ({handleSubmit,onSubmit,prevPage}) => {
 
    const validateConfirmPassword = (password) => 
        password && password !== props.password
        ? VALIDATE_CONFIRM_PASSWORD
        : undefined
 
    return (
      <View>
         <Field   
            name="password"
            component={FormInput}
            placeholder={“Enter Password”}
            validate={[passwordRequired,validatePassword]}
          />
          <Field 
             name="confirmPassword"
             component={FormInput}
             placeholder={“Re Enter Password”}                     
             validate={[passwordRequired,validateConfirmPassword]}
          />         
          <CustomButton buttonLabel={“Back”} onPress={prevPage}/>
          <CustomButton buttonLabel={“Next”} onPress={handleSubmit(nextPage)}/>               
      </View>
    )
}
 
RegisterPageThree =  reduxForm({
    form: 'register-form',
    destroyOnUnmount : false,
    forceUnregisterOnUnmount : true
})(RegisterPageThree)
 
const selector = formValueSelector('register-form')
export default RegisterPageThree = connect(state => {
    const password = selector(state, 'password')
    return {password}
})(RegisterPageThree)

Register/action.js

Copy Text
export const signupUser = user => ({
 type: 'REGISTER_REQUEST',
 payload: user,
});

Register/saga.js

Copy Text
import {put, takeLatest} from 'redux-saga/effects';
import {userSignup} from 'api/Signup';
 
function* signupUser({payload}) {
 try {
   const response = yield userSignup(payload);
   yield put({type: 'REGISTER_SUCCESS', response});
 } catch (error) {
   yield put({type: 'REGISTER_FAILURE', error: error.message});
 }
}
 
export default function* signupScreenSaga() {
 yield takeLatest('REGISTER_REQUEST', signupUser);
}

Explanation

  • On clicking submit button, onSubmit will be called, and signupUser(payload) will be dispatched containing payload.
Copy Text
dispatch(signupUser(payload))
  • From the file Register/action.js, the Register action type “REGISTER_REQUEST” will be dispatched.
  • Then saga middleware will watch for the “REGISTER_REQUEST” type action.
  • It will take the latest encounter of that action and call the register API.
  • For a successful call, it will dispatch the “REGISTER_SUCCESS” action with response data.
  • For a Fail call, it will dispatch the “REGISTER_FAILURE” action with an error message.

Update Reducer

Open store/reducers.js and add this chunk of code stating switch cases.

This reducer serves actions coming from Login and Register. Both the modules dispatch similar types of actions-

1. Request Action: Upon this action, the reducer updates the loading variable to true.

2. Success Action: Upon this action, the reducer updates the loading variable to false and stores the response from the action to the uservariable.

3. Failure Action: Upon this action, the reducer updates the loading variable to false and stores response coming from action to error variable

store/reducers.js

Copy Text
const AuthReducer = (state = initialState, action) => {
 switch (action.type) {    
   case 'REGISTER_REQUEST':
     return {
       ...state,
       loading: true,
     };
 
   case 'REGISTER_SUCCESS':
     return {
       ...state,
       user: action.response,
       loading: false,
     };
 
   case 'REGISTER_FAILURE':
     return {
       ...state,
       error: action.error,
       loading: false,
     };
    case 'LOGIN_REQUEST':
     return {
       ...state,
       loading: true,
     };
 
   case 'LOGIN_SUCCESS':
     return {
       ...state,
       user: action.response,
       loading: false,
     };
 
   case 'LOGIN_FAILURE':
     return {
       ...state,
       error: action.error,
       loading: false,
     };
 
   default:
     return state;
 }
};

Log In

The Login page accepts two inputs- email and password.

Login API is called on clicking onSubmit Button. After successful login Dashboard screen will appear.

Login/index.js

Copy Text
import React from 'react'
import { Field, reduxForm } from 'redux-form'
import { useDispatch } from 'react-redux'
import { View, ScrollView } from 'react-native'
import { loginUser } from './actions'
import { FormInput, CustomButton } from 'components'
import { passwordRequired, emailRequired, validatePassword , validateEmail } from 'utils/Validations'
 
const Login = (props) => {
    const dispatch = useDispatch()
    const onSubmit = (values) => dispatch(loginUser(values))
    return (
       <ScrollView>
          <Field
             name="email"
             component={FormInput}
             placeholder={“Enter Email”}
             validate={[emailRequired,validateEmail]}
           />
           <Field
              name="password"
              component={FormInput}
              placeholder={“Enter Password”}
              validate={[passwordRequired,validatePassword]}
            />
            <CustomButton
               buttonLabel={“Login”}
               onPress={props.handleSubmit(onSubmit)}
             />
        </ScrollView>
    )
}
export default reduxForm({
    form: 'login-form'
})(Login)

Login/action.js

Copy Text
export const loginUser = (user) => ({
   type: "LOGIN_REQUEST",
   payload: user,
});

Login/saga.js

Copy Text
import {put, takeLatest} from 'redux-saga/effects';
import {userLogin} from 'api/login';
 
function* loginUser({payload}) {
 try {
   const response = yield userLogin(payload);
   yield put({type: 'LOGIN_SUCCESS', response});
 } catch (error) {
   yield put({type: 'LOGIN_FAILURE', error: error.message});
 }
}
export default function* loginScreenSaga() {
 yield takeLatest('LOGIN_REQUEST', loginUser);
}

Explanation

  • On clicking the submit button, onSubmit will be called, and loginUser(values) will be dispatched containing the user email and password.
Copy Text
const onSubmit = (values) => dispatch(loginUser(values))
  • From the file Login/action.js, the Login action type “LOGIN_REQUEST” will be dispatched.
  • Then saga middleware will watch for the “LOGIN_REQUEST” type action.
  • It will take the latest encounter of that action and call the login API.
  • For a successful call, it will dispatch the “LOGIN_SUCCESS” action with response data.
  • For a Fail call, it will dispatch the “LOGIN_FAILURE” action with an error message.

API Call

api/login/index.js

Copy Text
const API_KEY = ''  //put your key here
const endpoint = {
   login : `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${API_KEY}`
}
 
export const userLogin = async (user) => {
    const response = await fetch(endpoint.login,{
        method: 'POST',
        headers: { 
            'Content-Type' : 'application/json' 
        },
        body: JSON.stringify({
            email: user.email,
            password: user.password, 
            returnSecureToken: true
        })
    })
    if(!response.ok) throw new Error('Something Went Wrong!!');
    const responseData = await response.json()
    return responseData.email
}

Dashboard

Once you are successfully logged in, you’ll be redirected to the dashboard page. Here’s the code for the UI.

Dashboard/index.js

Copy Text
import React from 'react';
import {useSelector} from 'react-redux';
import {scale, verticalScale} from 'react-native-size-matters';
import {Text, StyleSheet, SafeAreaView} from 'react-native';
 
import Colors from 'utils/Colors';
 
const DashBoard = () => {
 const userEmail = useSelector(state => state.Auth.user);
 
 return (
   <SafeAreaView style={styles.screen}>
     <Text style={styles.hiText}>Hii, there</Text>
     <Text style={styles.name}>{userEmail}</Text>
   </SafeAreaView>
 );
};
 
export default DashBoard;

useSelector() allows you to extract data from the Redux store state, using a selector function, so that we can use it in our component. As you can see, we can get the value of the user’s email address from state.Login.user.

You can find the entire source code of the demo application at Github Repository, from where you can clone and play around with code.

Conclusion

So, this was about implementing redux-saga with your redux-form in React Native application. We have covered complete authentication using redux-form features Field Array, Wizard Form, and redux-middleware. You can visit the React Native Tutorials page for more such tutorials and clone the github repository to experiment.

If you are looking for a helping hand for your React Native application, please contact us today to hire React Native developers from us. We have experienced and skilled developers having fundamental and advanced knowledge of React Native.

React Native Tutorial

How to build an interactive tic-tac-toe game with React.

Connect Now

Build Your Agile Team

Hire Skilled Developer From Us

[email protected]

Your Success Is Guaranteed !

We accelerate the release of digital product and guaranteed their success

We Use Slack, Jira & GitHub for Accurate Deployment and Effective Communication.

How Can We Help You?