Redux thunk dispatch does not work for multiple api calls - react-native

My Redux thunk dispatch was working until i made a single API call but stopped working after using combineReducers for multiple api calls.
This is my component code :
const mapStateToProps = state => ({
loading: state.loading,
data: state.data,
error: state.error,
networkError: state.networkError
})
const mapDispatchToProps = {
login
}
These are my Actions :
export const GET_LOGIN_LOADING = "GET_LOGIN_LOADING"
export const GET_LOGIN_SUCCESS = "GET_LOGIN_SUCCESS"
export const GET_LOGIN_ERROR = "GET_LOGIN_ERROR"
export const GET_REGISTER_LOADING = "GET_REGISTER_LOADING"
export const GET_REGISTER_SUCCESS = "GET_REGISTER_SUCCESS"
export const GET_REGISTER_ERROR = "GET_REGISTER_ERROR"
This is my reducer for login and register actions :
import * as Actions from './Actions'
const loginState = {
loginLoading: false,
loginData: [],
loginError: '',
}
const registerState = {
registerLoading: false,
registerData: [],
registerError: '',
}
export const loginReducer = (state = { loginState }, action) => {
switch (action.type) {
case Actions.GET_LOGIN_LOADING:
return {
...state,
loginLoading: action.payload
}
case Actions.GET_LOGIN_SUCCESS:
return {
...state,
loginData: action.payload,
loginLoading: false,
}
case Actions.GET_LOGIN_ERROR:
return {
...state,
loginError: action.payload,
loginLoading: false,
}
default: return loginState
}
}
export const registerReducer = (state = { registerState }, action) => {
switch (action.type) {
case Actions.GET_REGISTER_LOADING:
return {
...state,
registerLoading: action.payload
}
case Actions.GET_REGISTER_SUCCESS:
return {
...state,
registerData: action.payload,
registerLoading: false,
}
case Actions.GET_REGISTER_ERROR:
return {
...state,
registerError: action.payload,
registerLoading: false,
}
default: return registerState
}
}
My Redux Store Code :
import { createStore, applyMiddleware, combineReducers } from 'redux'
import thunk from 'redux-thunk'
import{ loginReducer, registerReducer } from '../redux/Reducer'
const reducer = combineReducers({loginReducer, registerReducer})
export default createStore(reducer, applyMiddleware(thunk))
Finally my thunk code used for making API calls :
export const login = (countryCode, phone, password) => {
const userName = {
countryCode,
phone
}
return dispatch => {
dispatch(getLoginLoading(true))
service.post('login', {
userName,
password
})
.then(response => {
console.log(response.data)
dispatch(getLoginSuccess(response.data))
})
.catch(error => {
console.log(error)
dispatch(getLoginError(error.response.data))
})
}
}
export const register = (name, countryCode, phone) => {
return dispatch => {
dispatch(getRegisterLoading(true))
service.post('register', {
name,
countryCode,
phone,
})
.then(response => {
console.log(response.data)
dispatch(getRegisterSuccess(response.data))
})
.catch(error => {
console.log(error.response)
dispatch(getRegisterError(error.response))
})
}
}

Finally found an answer by myself. When you are use combine reducers, Redux creates a nested state object for each reducer, Hence when accessing the state you should use :
const reducer = combineReducers({loginReducer, registerReducer})
const mapStateToProps = state => ({
loading: state.loginReducer.loading,
data: state.loginReducer.data,
error: state.loginReducer.error,
networkError: state.loginReducer.networkError
})

Related

React Redux dispatch method redirect to white screen

Im implementing redux in my react native app, I need to create 2 reducers, one of them is designed to manage login state and the other to manage some process status.
Reducers:
isLogged
processStatus
processStatus Actions:
export function start() {
return {
type: 'START_LOADING'
}
}
export function stop() {
return {
type: 'END_LOADING'
}
}
isLogged Actions:
export function signIn() {
return {
type: 'SIGN_IN'
}
}
export function loadFinished() {
return {
type: 'LOAD_FINISHED'
}
}
export const signOut = userAuthParams => (
{
type: 'SIGN_OUT'
}
);
export const firstLogin = userAuthParams => (
{
type: 'FIRST_LOGIN'
}
);
export const fbLogin = userAuthParams => (
{
type: 'FB_LOGIN'
}
);
process Reducer:
const isLoading = (state = false, action) => {
switch (action.type) {
case 'START_LOADING':
return 1;
case 'END_LOADING':
return 0
default:
return 0
}
}
export default isLoading
isLogged Reducer
const isLogged = (state = false, action) => {
switch (action.type) {
case 'SIGN_IN':
return 2;
case 'LOAD_FINISHED':
return 1
case 'SIGN_OUT':
return -1
case 'FIRST_LOGIN':
return 3
case 'FB_LOGIN':
return 4
default:
return 0
}
}
export default isLogged
root Reducer:
import isLogged from './IsLoggedReducer'
import process from './ProcessReducer'
import { combineReducers } from 'redux'
const allReducers = combineReducers({
isLogged: isLogged,
processState: process
})
export default allReducers
store
import { createStore, applyMiddleware } from 'redux'
import thunk from 'redux-thunk';
import allReducers from './index'
const store = createStore(allReducers, applyMiddleware(thunk))
export default store
Mapping state and actions to props
const mapStateToProps = (state) => {
const { processState, isLogged} = state
return { processState, isLogged }
};
const mapDispatchToProps = (dispatch) => {
return {
//loadFinished: () => dispatch({ type: 'LOAD_FINISHED' })
startProcess: bindActionCreators(start, dispatch),
endProcess: bindActionCreators(stop, dispatch),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(ReportStickerStatus);
When I dispatch startProcess my app got redirected to a empty page
THERES NO ANY ERRORS ON CONSOLE
UPDATE [ SOLVED ]
I had to return the state in each action, even in default
import initialState from './initialState'
const isLogged = (state = initialState, action) => {
switch (action.type) {
case 'SIGN_IN':
return {
...state,
value: 2
};
case 'LOAD_FINISHED':
return {
...state,
value: 1
};
case 'SIGN_OUT':
return {
...state,
value: -1
};
case 'FIRST_LOGIN':
return {
...state,
value: 3
};
case 'FB_LOGIN':
return {
...state,
value: 4
};
default:
return {
...state
};
}
}
export default isLogged

action not dispatching react native, state is coming out fine

I am unable to dispatch an action to reducer.
State comes out just fine. Not sure where I have gone wrong.
For other user reducer, dispatch works just fine. my project has nested drawer navigation > tabs navigation > stack navigation and current page is the 3rd stack screen. Not sure if that is the issue or what.
Store.js
import { combineReducers, createStore } from '#reduxjs/toolkit';
import cartReducer from './cartSlice';
import userReducer from './userSlice';
const rootReducer = combineReducers({
userReducer: userReducer,
cartReducer: cartReducer,
})
const configureStore = () => createStore(rootReducer)
export default configureStore
cartSlice.js
const initialState = {
cart: [{ key: 1, data: { freq: 'Daily', duration: 30 } }]
}
const cartReducer = (state = initialState, action) => {
switch (action.types) {
case "ADD_TO_CART":
console.log(action)
return {
...state,
cart: [...state.cart, { key: 2, data: action.data }]
}
case "REMOVE_FROM_CART":
const idx = state.cart.map((cartItem) => (
cartItem.key === action.id
))
const tempNewCart = [...state.cart]
if (idx >= 0) {
tempNewCart.splice(idx, 1)
}
return { ...state, cart: tempNewCart }
case "CLEAR_CART":
return {
...state,
cart: []
}
default:
return state
}
}
export default cartReducer
SubscriptionQuantity Component
const mapStateToProps = (state) => {
console.log(state)
return {
cart: state.cartReducer.cart
}
}
const mapDispatchToProps = (dispatch) => {
// console.log(dispatch)
return {
addtoCartStore: (freq, duration) => dispatch({
type: 'ADD_TO_CART',
data: {
freq: freq,
duration: duration
}
})
}
}
export default connect(mapStateToProps, mapDispatchToProps)(SubscriptionQuantity)
function call for dispatch
const addtoCart = () => {
addtoCartStore('Alternate', 30)
navigation.navigate("CartScreen")
}
The issue was with the switch statement!!!!
it should be switch(action.type) not action.types. that's why it was going in on the default route. changed the typo and works fine now.!

React native mapDispatchToProps not working

I can't get my mapDispatchToProps to work properly.
I export a combineReducers:
export default combineReducers({
auth: AuthReducer,
tenants: TenantsReducer
});
The tenants reducer:
const INITIAL_STATE = {
error: false,
data: [],
tenantData: {},
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case GET_TENANTS_DATA:
return { ...state, error: false, data: action.payload };
case GET_TENANT_DATA:
return { ...state, error: false, tenantData: action.payload };
default:
return state;
}
};
Then I have getTenantByID method in my action
export const getTenantByID = ({ tenantID }) => {
return (dispatch) => {
const getTenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '',
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
};
};
Finally, I tried to use it in my component.
import { connect } from 'react-redux';
import { getTenantByID } from '../actions';
...
componentDidMount() {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
console.log(this.props);
this.state = {
tenantData: this.props.tenantData
};
}
const mapStateToProps = ({ tenants }) => {
return {
error: tenants.error,
tenantData: tenants.tenantData
};
};
const mapDispatchToProps = () => {
return {
getTenantByID
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TenantDetails);
In my componentDidMount, the console.log(this.props) is returning a empty object for tenantData. What am I doing wrong?
Initial state is showing as the component already mounted, which is empty object {}
this.props.getTenantByID(tenantId);
this action triggers actually, but the data is not available in componentDidMount lifecycle.
try putting log in render like this
componentDidMount(){
this.props.getTenantByID(2);
}
render() {
console.log(this.props.tenantData); // 1st render => {}, 2nd render=> desired data
return (
<div/>
);
}
use componentDidUpdate to check if value is changed,
componentDidUpdate(prevProps){
if(prevProps.tenantData !== this.props.tenantData){ console.log(prevProps.tenantData, this.props.tenantData) }
}
remember to receive the dispatch parameter in your mapDispatchToProps method
const mapDispatchToProps = (dispatch) => {
return {
getTenantByID: (tenantID ) => {
dispatch(getTenantByID({tenantID }));
};
};
};
call for
this.props.getTenantByID({ tenantID: 10 })

Invariant Violation : Invalid Hooks Call. Hooks Call can only be made inside body of function components

Hi am getting this error at Connect() function in LoginScreen.js. On commenting it its working fine. I am guessing either my Store is not properly setup or i am not able to connect LoginScreen Component to Redux Store.
LoginScreen.js
import React, { Component } from "react";
import PhoneInput from "react-native-phone-input";
import { connect } from "react-redux";
import { View, StatusBar } from "react-native";
import { Container, Item, Input, Button, Text } from "native-base";
import {
phoneChanged,
codeChanged,
onCodeDispatched,
onPhoneLogin,
clearAuth,
onSignOut
} from "../Actions/AuthActions";
//import firebase from "react-native-firebase";
import { auth } from "../Config/firebase";
export class LoginScreen extends Component {
}
export default connect(
null, // passing null just for testing
null
)(LoginScreen);
Store.js
import ReduxThunk from "redux-thunk";
import { createStore, applyMiddleware, compose } from "redux";
import reducer from "../Reducers/index";
let composeEnhancers = compose;
/* eslint no-undef: 0 */
if (__DEV__) {
/* eslint no-underscore-dangle: 0 */
composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
}
const store = createStore(
reducer,
{},
composeEnhancers(applyMiddleware(ReduxThunk))
);
export { store };
AuthReducer.js
import {
LOGIN_FAIL,
LOGIN_SUCCESS,
LOGIN_USER,
PHONE_CHANGED,
CODE_SENT_ERROR,
CODE_CHANGED,
CODE_DISPATCHED,
LOGIN_USER_PHONE,
CODE_SENT,
CODE_NOT_CONFIRMED,
LOGOUT,
SET_USER_OBJECT,
CLEAR_AUTH
} from "../Actions/ActionTypes";
const INITIAL_STATE = {
phone: "+30",
user: null,
message: "",
loading: false,
codeInput: "",
confirmResult: null
};
const AuthReducer = (state = INITIAL_STATE, action) => {
console.log(action);
switch (action.type) {
case PHONE_CHANGED:
return {
...state,
phone: action.payload
};
case CODE_CHANGED:
return {
...state,
codeInput: action.payload
};
case LOGIN_USER:
return {
...state,
loading: true,
message: ""
};
case LOGIN_USER_PHONE:
return {
...state,
loading: true,
message: "Sending code...",
phone: action.payload
};
case CODE_DISPATCHED:
return {
...state,
loading: true,
message: ""
};
case CODE_SENT:
return {
...state,
loading: true,
message: "Code has been sent!",
confirmResult: action.payload
};
case CODE_SENT_ERROR:
return {
...state,
loading: false,
message: `Sign in with Phone number error: ${action.payload}`,
confirmResult: null
};
case SET_USER_OBJECT:
return {
...state,
user: action.payload
};
case CODE_NOT_CONFIRMED:
return {
...state,
message: `Code confirmation error: ${action.payload}`
};
case LOGIN_SUCCESS:
return {
...INITIAL_STATE,
user: action.payload,
message: "login Success"
};
case LOGIN_FAIL:
return {
...state,
message: "Authentication Failed.",
loading: false,
password: "",
phone: "+91"
};
case LOGOUT:
return {
...state,
message: "",
user: null
};
case CLEAR_AUTH:
return {
...state,
...INITIAL_STATE
};
default:
return state;
}
};
export default AuthReducer;
Root.js
import React from "react";
import { Provider } from "react-redux";
import { Navigator } from "./Navigation/Index";
import { store } from "./Store/Index";
export default class Root extends React.Component {
render() {
return (
<Provider store={store}>
<Navigator />
</Provider>
);
}
}
AuthActions.js
//import { firebase } from "react-native-firebase";
import * as actionTypes from "./ActionTypes";
import { auth } from "../Config/firebase";
const phoneChanged = text => {
return {
type: actionTypes.PHONE_CHANGED,
payload: text
};
};
const onLoginSuccess = (dispatch, user) => {
dispatch({
type: actionTypes.LOGIN_SUCCESS,
payload: user
});
};
const signOut = dispatch => {
dispatch({
type: actionTypes.LOGOUT
});
};
const onPhoneLogin = phone => {
return dispatch => {
dispatch({
type: actionTypes.LOGIN_USER_PHONE
});
auth
.signInWithPhoneNumber(phone)
// sign in success
.then(confirmResult => {
onCodeSent(dispatch, confirmResult);
})
// sign in error
.catch(error => onCodeSentError(dispatch, error));
};
};
const codeChanged = text => {
return {
type: actionTypes.CODE_CHANGED,
payload: text
};
};
const onCodeSent = (dispatch, confirmResult) => {
dispatch({
type: actionTypes.CODE_SENT,
payload: confirmResult
});
};
const onCodeConfirmError = (dispatch, error) => {
dispatch({
type: actionTypes.CODE_NOT_CONFIRMED,
payload: error
});
};
const onCodeDispatched = code => {
return (dispatch, getState) => {
getState()
.auth.confirmResult.confirm(code)
.then(user => onLoginSuccess(dispatch, user))
.catch(error => onCodeConfirmError(dispatch, error));
};
};
const onCodeSentError = (dispatch, error) => {
dispatch({
type: actionTypes.CODE_SENT_ERROR,
payload: error
});
};
const onSignOut = () => {
return dispatch => {
auth
.signOut()
.then(() => signOut(dispatch))
.catch(error => console.log(error));
};
};
const clearAuth = () => {
return dispatch => {
dispatch({
type: actionTypes.CLEAR_AUTH
});
};
};
export {
onSignOut,
clearAuth,
codeChanged,
onPhoneLogin,
phoneChanged,
onCodeDispatched
};
The Idea is basically to call LoginScreen which is part of 'Auth' StackNavigator and Render PhoneNumberInput and OTP.
React-redux (> 7.0.1) uses hook & React-native (< 0.59) doesn’t support Hooks yet.
You can run npm ls react-native in your application folder to check which version you’re using.
If you find more than one of them, this might also create problems. more on it
There are two solution
Upgrade the react native version to 0.59.0
OR
Downgrade the react redux version to 6.0.1
I hope it help you.

Persisted value won't affect the code down the chain

This is the filterAction action that emits a value to to the persisted reducer. This action is called with dispatch() in a dropdown in a component.
import { SORT_BY_TITLE, SORT_BY_RELEASED_AT } from './types';
// sort by title
export const sortByTitle = () => ({
type: SORT_BY_TITLE
});
// sort by released at
export const sortByReleasedAt = () => ({
type: SORT_BY_RELEASED_AT
});
The corresponding filterReducer
import { SORT_BY_TITLE, SORT_BY_RELEASED_AT } from '../actions/types';
const initialState = {
sortBy: 'title'
};
export default function(state = initialState, action) {
switch(action.type) {
case SORT_BY_TITLE:
return {
...state,
sortBy: 'title'
};
case SORT_BY_RELEASED_AT:
return {
...state,
sortBy: 'releasedAt'
};
default:
return state;
}
};
The filterReducer value is the one persisted in the main combined reducer.
export default combineReducers({
books: booksReducer,
book: bookReducer,
form: addBookFormReducer,
filter: filterReducer
});
The app's store
import { createStore, applyMiddleware } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
const middleware = [thunk];
const persistConfig = {
key: 'root',
storage,
whitelist: ['filter']
};
const persistedReducer = persistReducer(persistConfig, rootReducer);
export const store = createStore(persistedReducer, {}, applyMiddleware(...middleware));
export const persistor = persistStore(store);
The value persisted on the filter part of the main reducer gets displayed in the dropdown changes on which call the filterAction with dispatch().
Here's the booksReducer that creates the books part of the app's store so books are displayed in a component.
import {
GET_BOOKS,
BOOKS_LOADING,
DELETE_BOOK,
SORT_BY_TITLE,
SORT_BY_RELEASED_AT
} from '../actions/types';
const initialState = {
books: [],
loading: false
};
const booksReducer = (state = initialState, action) => {
switch(action.type) {
case BOOKS_LOADING:
return {
...state,
loading: true
};
case GET_BOOKS:
return {
...state,
books: action.payload,
loading: false
};
case DELETE_BOOK:
return {
books: [...state.books.filter(book => book._id !== action.payload.id)]
};
case SORT_BY_TITLE:
return {
...state,
books: [...state.books.sort((a, b) => a.title < b.title ? -1 : 1 )]
};
case SORT_BY_RELEASED_AT:
return {
...state,
books: [...state.books.sort((a, b) => a.releasedAt < b.releasedAt ? -1 : 1 )]
};
default:
return state;
}
};
export default booksReducer;
The filter part of the main reducer persists Ok on the page reload however the books list is displayed with the default by title sort.
How do I get the app to persist the sort on the page reload? The complete repo is on https://github.com/ElAnonimo/booklister
On the BookList load the getBooks() action return reset the sorted books list to its default sorting so it was easier to persist only the filter prop of the store then sort the books list on each load of the BookList component.
The changes were made
to booksReducer
import {
GET_BOOKS,
BOOKS_LOADING,
DELETE_BOOK
} from '../actions/types';
const initialState = {
books: [],
loading: false
};
const booksReducer = (state = initialState, action) => {
switch(action.type) {
case BOOKS_LOADING:
return {
...state,
loading: true
};
case GET_BOOKS:
return {
...state,
books: action.payload,
loading: false
};
case DELETE_BOOK:
return {
books: [...state.books.filter(book => book._id !== action.payload.id)]
};
default:
return state;
}
};
export default booksReducer;
to BookList
class BookList extends Component {
componentDidMount() {
this.props.getBooks();
}
applySorting(books) {
const sortBy = this.props.filter.sortBy;
if (!sortBy) {
return books;
}
return books.sort((a, b) => a[sortBy] < b[sortBy] ? -1 : 1);
}
render() {
const { books, loading } = this.props.books;
let booksContent;
if (!books || loading) {
booksContent = <Spinner />;
} else {
if (books.length > 0) {
booksContent = this.applySorting(books).map(book => <BookItem book={book} key={book._id} />);
} else {
booksContent = <h4>No books found</h4>;
}
}
...
...