Search code examples
reactjsreact-nativereact-reduxreact-hooksredux-saga

How to handle redux-saga and save user input data in Formik to Redux


I'm making very simple Login app for learning react and react-native also, my goal is get user input, then compare these value to a custom fake api file that contain user and password value, then save in AsyncStorage(or localStorage) for authentication . So i search the internet for what i'm going to do, here is the one that match what i'm looking for , but there are few things i really don't understand how this code work. In this code, there are something make me confuse for week, so many question in my head so i really need some help. Here is the code:

First he has a Redux Action like this in his code

//Action.js
export const LoginRequested = (username,password) => {
  return {
    type: 'LOG_IN_REQUEST',
    data: {
      username,
      password,
    },
  };
};

export const LoginSuccessed = (data) => {
  return {
    type: 'LOGIN_SUCCESS',
    data,
  };
};

export const LoginFailed = (data) => {
  return {
    type: 'LOG_IN_FAILURE',
    data,
  };
};

export const Logout = (data) => {
  return {
    type: 'LOG_OUT',
    data
  };
};

But what make me confuse is

Question 1: what is data here? What is this data used for?

I mean i can understand that in LoginRequested , data is username and password, so i guess username and password store here, but in loginSuccessed and LoginFailed, i don't get it, what is data here

Here is the Reducer

const initialState = {
  isLoading: false,
  authen: false
};

export const loginReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'LOG_IN_REQUEST':
      {
        //console.log('action : LOG IN REQUEST');
        return {
          isLoading: true,
          authen: false
        }
      }
    case 'LOG_IN_SUCCESS':
      {
        //console.log('action : LOG IN SUCCESS');
        return {
          isLoading: false,
          authen: true
        };
      }
    case 'LOG_IN_FAILURE':
      {
        //console.log('action : LOG IN FAILURE');
        return initialState;
      }
    case 'LOG_OUT':
      {
        //console.log('action : LOG OUT');
        return initialState;
      }
    default:
      return state;
  }
};

And to the reducer, so :

Question 2: what do isLoading and authen( maybe authentication) do ?? .

I can understand it present for login status, is that right? but i can't link how he use there code( i see him use 'LOGIN_SUCCESS' once in saga below but i don't get it)

Here is the store:

//store.js
const sagaMiddleware = createSagaMiddleware();
const store = createStore(rootReducer, applyMiddleware(sagaMiddleware)); //rootReducer is a combined reducer file, i don't show it here, but he have only one Reducer like above code
sagaMiddleware.run(rootSaga);

export default store;

I understand this, this is how redux apply middleware.

Now is Saga part

//saga.js
import { Alert } from 'react-native';
import { call, put, takeLatest } from 'redux-saga/effects';
//import axios from 'axios';
import { fetchLoginData } from '../../server/loginApi';

export function* loginMainSaga() {
  yield takeLatest('LOG_IN_REQUEST', loginSaga);
}

function* saveDataToStorage(data) {
  try {
      AsyncStorage.setItem(
          'userData',
          JSON.stringify({
              username: data.username,
              password: data.password }))  
      } catch(e) {
      console.log('error save to Storage');
  }
};

function* loginSaga(action) {
    const response = yield call(fetchLoginData);
    const resData = JSON.parse(JSON.stringify(response));
    
    const usernameAuth = String(resData.data[0].username);
    const passwordAuth = String(resData.data[0].password); 

    if (String(action.data.username) === usernameAuth)
    {
      if (String(action.data.password) === passwordAuth)
      {
        //console.log('loginSaga LOG_IN_SUCCESS');
        yield put({type: 'LOG_IN_SUCCESS'});
        saveDataToStorage(action.data);
      }
      else
      {
        const error = 'Password is incorrect'
        Alert.alert('Failed', error, [{ text: 'Ok', style: 'cancel' }] );
        //console.log('loginSaga LOG_IN_FAILURE');
        yield put({type: 'LOG_IN_FAILURE'});
      }
    } else {
      const error = 'Username is incorrect'
      Alert.alert('Failed', error, [{ text: 'Ok', style: 'cancel' }] );
      //console.log('loginSaga LOG_IN_FAILURE');
      yield put({type: 'LOG_IN_FAILURE'});
      
    }
}

I can understand that, loginMainSaga() is to get lastest username and password store in redux and compare to the one in Json file by using loginSaga. SaveDatatoStorage mean you save username and password to AsyncStorage.

Question 3 : Is my above logic right ? and action in loginSaga function here is a random variable word or point direct to action in redux reducer?

And the most confuse part is loginScreen:

He split code to different components, just basic style and component of Formik, detail here:

Here is the login screen

function LoginScreen(props) {

    const dispatch = useDispatch();
    const loginHandler =({username, password}) => {
        dispatch(
            LoginRequested(username,password)
          );
      };

    const isLoading = useSelector(state => {
        //console.log('state.Login.isLoading ' + state.Login.isLoading);
        return state.Login.isLoading;
    });

    return(
        <Screen style={styles.container}>
            <MaterialCommunityIcons name="wechat" color="black" size={200} />
            <Text style={styles.title}>SIGN IN HERE</Text>

            <AppForm
                initialValues={{username: '', password: ''}}
                onSubmit={loginHandler}
                validationSchema={validateSchema}>
                <AppFormField
                    name="username"
                    placeholder="Username"
                    icon="account"
                    autoCapitalize="none"
                    autoCorrect={false}
                    textContentType="nickname"
                    width="75%"
                    ></AppFormField>
                <AppFormField
                    name="password"
                    placeholder="Password"
                    icon="lock"
                    autoCapitalize="none"
                    autoCorrect={false}
                    secureTextEntry
                    textContentType="password"
                    width="75%"></AppFormField>
                {isLoading ? 
                (
                <ActivityIndicator size="large" color={'black'} />
                ) : (
                <SubmitBtn title="Sign in" color={'black'} width="75%" />
                )}    
            </AppForm>

        <View style={styles.registerContainer}>
            <Text style={{color: 'black'}}>Don't have an account?</Text>
            <Text style={{color: 'black'}} onPress={() => null}> SIGN UP </Text>
            </View>
        </Screen>
    );
}

I don't understand how can he get user and password , my first question in this file is:

Question 4: Why loginHandler const have {} in ({username,password}) , why not use (username,password)?

And the most thing make me have a headache and can't stop think about it for this week :

Question 5 :How can he get user and password when he onSubmit???? He use loginHandler, but how can he add username and password, , how he catch which input was username, which is password , if i have 3 form input, how can this handle? is there something special with formik onSubmit????

Here is the full code : FULL CODE

Please help, your help is very precious for me right now and maybe can solve problem of other newcomer like me. Thank you so so much


Solution

    1. The first set of functions are called action creators. They take any data, and create an action that packages that data with the action type. For LoginRequested() the payload is username and password. Not all actions need to have a payload however and so for Logout() you may be able to exclude any paramaters from that action creator.
    2. Reducer's take actions produced by action creators (such as LoginRequested and logout), and calculate an update to the store's state. From the code sample you have provided isLoading and authen are two boolean properties that are being read from some actions, and stored in the redux store via these reducers. To understand where these values originate, you'll need to debug where actions with these types are created: 'LOG_IN_REQUEST'
    3. You are correct that actions have a type property that is any random identifier. It's common practice to use a phrase-like string data type to make it more readable.
    4. loginHandler is being passed as the function that takes the <AppForm /> onSubmit. The onSubmit of any HTML based form will pass a set of key-value pairs in an object, and thus loginHandler must take it's arguments in the format {}
    5. You are correct, it is because of onSubmit. This function will take the name of every HTML input element within it's HTML node, and pair it with the respective element's value.
                    <AppFormField name="username" />
                    <AppFormField name="password" />