Tharindu Sandaruwan
Tharindu Sandaruwan

Reputation: 585

How to write actions correctly with Redux-thunk?

Problem:

I have created a react native application in there I have created the store using redux-thunk middleware. Now I am going to say How I have organized my code. in the actions folder, I have created an action file. This is the code of it.

import * as API from './httpClient';
import * as endPoints from './endpoints';

export const SEND_OTP = 'otp/SEND_OTP';
export const SEND_OTP_SUCCESS = 'otp/SEND_OTP_SUCCESS';
export const SEND_OTP_FAILD = 'otp/SEND_OTP_FAILD';

const sendOtpActionCreator = (value) => {
  return (dispatch) => {
    return API.Post(endPoints.SEND_OTP, {data: value})
      .then((data) => {
        dispatch(SEND_OTP_SUCCESS, data);
      })
      .catch((err) => {
        dispatch(SEND_OTP_FAILD, err);
      });
  };
};

export default {
  sendOtpActionCreator: sendOtpActionCreator,
};

HTTP client and endpoints file looks like this.

import I18n from 'react-native-i18n';
const REST_API = 'myapi';
const getCulture = () => {
  let locale;
  locale = I18n.locale;
  if (locale === 'en') {
    return 'en-us';
  } else if (locale === 'si') {
    return 'si-lk';
  } else if (locale === 'ta') {
    return 'ta-lk';
  }
};

export const SEND_OTP = `${REST_API}/${getCulture()}/api/user/send/otp`;

this is how my HTTP client file is.

import axios from 'axios';

axios.defaults.headers.post['Content-Type'] = 'application/json';

var instance = null;

export const setHeader = async () => {
  // const token = await AsyncStorage.getItem('jwt');
  instance = axios.create({
    baseURL: '',
    timeout: 150000,
    headers: {
      //   Authorization: 'Bearer ' + token,
      'Content-Type': 'application/json',
    },
  });
  instance.interceptors.response.use(
    function (response) {
      return response;
    },
    async function (error) {
      if (error.response.status) {
        if (error.response.status === 401) {
          error = 'Unauthorised';
          return error;
        } else {
          return error;
        }
      } else {
        console.log(error);
      }
    },
  );
};

export const Get = (route, data) => {
  function getData() {
    return instance.get(
      route,
      data == null ? {data: {}} : {data: JSON.stringify(data)},
    );
  }
  if (instance) {
    return getData();
  }
  return setHeader().then(getData);
};

export const Post = (route, data) => {
  function postData() {
    return instance.post(route, JSON.stringify(data));
  }
  if (instance) {
    return postData();
  }

  return setHeader().then(postData);
};

Then I have export actions like this in index.js file of actions folder. This is the code of It.

import * as signin from './signin';

export {signin as signinTypes};
export const signinActions = signin.default;

Then I have created the reducer like this.

import {signinTypes as types} from '_store/actions';

const signin = (
  state = {
    success: false,
    hasError: false,
    error: '',
    otp: '',
  },
  action,
) => {
  console.log(action);
  switch (action.type) {
    case types.SEND_OTP:
      return Object.assign({}, ...state, {hasError: false, error: {}});
    case types.SEND_OTP_SUCCESS:
      return Object.assign({}, ...state, {success: true});
    case types.SEND_OTP_FAILD:
      return Object.assign({}, ...state, {error: true});
    default:
      return state;
  }
};

export default sign-in;

Then I have imported it in combined Reducer like this.

import {combineReducers} from 'redux';
import SignInReducer from '_store/reducers/signin';

const reducers = combineReducers({
  signin: SignInReducer,
});

export default reducers;

And I have used action in my component file like this.

import { connect } from "react-redux";
import { signinActions } from "_store/actions";

const _onPress = (values, navigation, sendOTP) => {
  console.log(values.mobileNo);
  sendOTP(values.mobileNo);
  // navigation.navigate('Otp');
};

const Login = (props) => {
  return (
            <View style={styles.formContainer}>
              <Formik
                initialValues={{
                  mobileNo: "",
                  toggle: true,
                }}
                validationSchema={Yup.object({
                  mobileNo: Yup.string().required("Mobile number required"),
                })}
                onSubmit={(values, formikActions) => {
                  _onPress(values, props.navigation, props.sendOTP);
                  setTimeout(() => {
                    formikActions.setSubmitting(false);
                  }, 500);
                }}
              >
                {(formprops) => (
                  <View>
                    <View>
                      <TextInput
                        style={styles.textField}
                        placeholder={strings("login.mobile")}
                        placeholderTextColor="#bbbbbb"
                        value={formprops.values.mobileNo}
                        onChangeText={formprops.handleChange("mobileNo")}
                        onBlur={formprops.handleBlur("mobileNo")}
                        keyboardType="numeric"
                      />
                    </View>
                    {formprops.touched.mobileNo && formprops.errors.mobileNo ? (
                      <View style={styles.errorMessage}>
                        <AppText styles={styles.errorMessageText}>
                          {formprops.errors.mobileNo}
                        </AppText>
                      </View>
                    ) : null}
                    <View style={styles.togglebuttoncontainer}>
                      <View style={styles.toggleTextView}>
                        <AppText styles={styles.toggleText}>
                          {strings("login.keep-login")}
                        </AppText>
                      </View>
                      <View style={styles.toggleView}>
                        <Switch
                          trackColor={{ false: "#dddddd", true: "#c1d6ee" }}
                          thumbColor={{ false: "#ffffff", true: "#007aff" }}
                          ios_backgroundColor="#dddddd"
                          onValueChange={(value) =>
                            formprops.setFieldValue("toggle", value)
                          }
                          value={formprops.values.toggle}
                          style={styles.toggle}
                        />
                      </View>
                    </View>
                    <SubmitButton
                      onpress={formprops.handleSubmit}
                      btext={strings("login.button-text")}
                    />
                  </View>
                )}
              </Formik>
            </View>
  );
};

const mapStateToProps = (state) => {
  console.log(state);
  return {
    success: state.signin.success,
  };
};

const mapDispatchToProps = (dispatch) => {
  return {
    sendOTP: (number) => dispatch(signinActions.sendOtpActionCreator(number)),
  };
};

export default connect(mapStateToProps, mapDispatchToProps)(Login);

But when I try to submit the form and do the action it is giving me this error warning.

Possible Unhandled Promise Rejection (id: 0): Error: Actions must be plain objects. Use custom middleware for async actions.

Can someone help me to correct these files to get rid of this warning? I am very new to Redux-thunk middleware. I tried a lot to find out a solution to this problem but I unable. Thank you

Upvotes: 0

Views: 217

Answers (1)

Sochetra Nov
Sochetra Nov

Reputation: 465

@Atyos. I added comment you can have a look.

could you change the code in your action file:

import * as API from './httpClient';
import * as endPoints from './endpoints';

export const SEND_OTP = 'otp/SEND_OTP';
export const SEND_OTP_SUCCESS = 'otp/SEND_OTP_SUCCESS';
export const SEND_OTP_FAILD = 'otp/SEND_OTP_FAILD';

const sendOtpActionCreator = (value) => {
  return (dispatch) => {
    return API.Post(endPoints.SEND_OTP, {data: value})
      .then((data) => {
        // it is not correct way to call the dispatch
        dispatch(SEND_OTP_SUCCESS, data);

        // the dispatch action accept only the plain object
        // you should try this code
        dispatch({type: SEND_OTP_SUCCESS, payload: data});
      })
      .catch((err) => {
        dispatch(SEND_OTP_FAILD, err);
      });
  };
};

export default {
  sendOtpActionCreator: sendOtpActionCreator,
};

Upvotes: 1

Related Questions