useDispatch() and useSelector() are not wroking in hooks react native? - react-native

I am moving from classes to hooks in react native but facing some issues using redux with hooks.
My work till now
Login.js
const Login = props => {
const {navigation} = props;
const dispatch = useDispatch();
const {success, loading, error} = useSelector(state => state.auth);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const onLoginPress = async () => {
await dispatch(login(email, password)); //here VSCode give hints await is useless ? Why ? how to use await then ?
if (success) {
navigation.navigate('Home');
} else {
alert(error || 'Invalid Credentials. Please Try again');
}
};
//other code
export default Login;
I am dispatching login action when user presses login button. But problem is app stucks here and action doesn't complete and always remains auth_loading
my login action
export const login = (email, pass) => {
return async dispatch => {
dispatch(authLoading());
try {
const res = await firebaseService
.auth()
.signInWithEmailAndPassword(email, pass); //here app action stucks
dispatch(loginSuccess(res));
} catch (err) {
dispatch(authFailed(err.message));
}
};
};
My reducer
import {
LOGIN_EMAIL_CHANGED,
LOGIN_PASS_CHANGED,
LOGIN_SUCCESS,
REGISER_SUCCESS,
} from '../types';
import {AUTH_FAILED, AUTH_LOADING} from '../actions/AuthActions';
const initialState = {
loginEmail: null,
loginPass: null,
loading: false,
success: false,
user: null,
error: '',
authenticated: false,
};
export const AuthReducer = (state = initialState, action) => {
switch (action.type) {
case LOGIN_EMAIL_CHANGED:
return {
...state,
loginEmail: action.payload,
};
case LOGIN_PASS_CHANGED:
return {
...state,
loginPass: action.payload,
};
case AUTH_LOADING: {
return {
...state,
success: false,
loading: true,
error: '',
};
}
case AUTH_FAILED: {
return {
...state,
loading: false,
success: false,
error: action.payload,
};
}
case LOGIN_SUCCESS: {
return {
...state,
user: action.payload,
loading: false,
success: true,
error: null,
};
}
case REGISER_SUCCESS: {
return {
...state,
user: action.payload,
loading: false,
success: true,
error: null,
};
}
default:
return state;
}
};
Am i using hooks in right way ?
Or how to await after dispatching action in hook. ?
Help will be appreciated.
Thanks

Related

Redux thunk dispatch does not work for multiple api calls

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
})

React native redux after login statetoprops not updating before render

After login and redirecting to Home page component here I am calling:
mapStateToProps = (state) => ({
getUser: state.userReducer.getUser
});
And trying to render the value:
render() {
const {getUser: {userDetails}} = this.props;
return(
<View><Text>{userDetails.EmployeeID}</Text></View>
)
}
USER Reducer
import { combineReducers } from 'redux';
const getUser = (state = {}, action) => {
switch (action.type) {
case "GET_USER_LOADING":
return {
isLoading: true,
isError: false,
isSuccess: false,
userDetails: null,
errors: null
}
case "GET_USER_SUCCESS":
return {
isLoading: false,
isError: false,
isSuccess: true,
userDetails: action.payload,
errors: null
}
case "GET_USER_FAIL":
return {
isLoading: false,
isError: true,
isSuccess: false,
userDetails: null,
errors: action.payload
}
default:
return state;
}
}
export default combineReducers({
getUser
});
And loginuser action
export const loginUser = (payload) => {
return async (dispatch) => {
try {
dispatch({
type: "LOGIN_USER_LOADING"
});
const response = await fetchApi("front/authentication/login", "POST", payload, 200);
if(response.responseBody.status) {
dispatch({
type: "LOGIN_USER_SUCCESS",
});
dispatch({
type: "AUTH_USER_SUCCESS",
token: response.token
});
dispatch({
type: "GET_USER_SUCCESS",
payload: response.responseBody.data
});
return response.responseBody.status;
} else {
throw response;
}
} catch (error) {
dispatch({
type: "LOGIN_USER_FAIL",
payload: error.responseBody
});
return error;
}
}
}
But getting an error:
TypeError: Undefined is not an object(Evaluating 'userDetails.EmployeeID'
If I Remove the userDetails.EmployeeID and navigate to next page and then come back it show the EmployeeID fine.
Try this way,
render() {
const {getUser} = this.props;
return(
<View><Text>{getUser.userDetails.EmployeeID}</Text></View>
)}
if that doesn't work, kindly post redux code
If your initial state resolves to something "falsy" for userDetails you can "wait" for its value:
render() {
const {getUser: {userDetails}} = this.props;
return <View>
<Text>{userDetails ? userDetails.EmployeeID : 'Loading'}</Text>
</View>
}

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 })

store.getstate() evaluate to undefined

in a react-native project,I keep hitting in this error:
state is undefined, evaluating store.getstate()
//store.js
const composeEnhancers =
typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({})
: compose;
const enhancer = composeEnhancers(applyMiddleware(thunk));
const Store = createStore(
combineReducers(
{
form: formReducer,
appointmentsReducer
},
enhancer
)
);
console.log(Store.getState());
export default Store;`
//reducer.js
import {
FETCH_APPOINTMENTS_BEGIN,
FETCH_APPOINTMENTS_SUCCESS,
FETCH_APPOINTMENTS_FAILURE
} from '../actions/appointmentsAction';
const initialState = {
data: [],
loading: false,
error: null
};
export default function appointmentsReducer(state = initialState, action) {
switch(action.type) {
case FETCH_APPOINTMENTS_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_APPOINTMENTS_SUCCESS:
return {
...state,
loading: false,
data: action.payload.appointments
};
case FETCH_APPOINTMENTS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
data: []
};
default:
return state;
}
}
//actions.js
import { listUrl } from "../cst";
export const FETCH_APPOINTMENTS_BEGIN = "FETCH_APPOINTMENTS_BEGIN";
export const FETCH_APPOINTMENTS_SUCCESS = "FETCH_APPOINTMENTS_SUCCESS";
export const FETCH_APPOINTMENTS_FAILURE = "FETCH_PRODUCTS_FAILURE";
export const fetchAppointmentsBegin = () => ({
type: FETCH_APPOINTMENTS_BEGIN
});
export const fetchAppointmentsSuccess = appointments => ({
type: FETCH_APPOINTMENTS_SUCCESS,
payload: { appointments }
});
export const fetchAppointmentsFailure = error => ({
type: FETCH_APPOINTMENTS_FAILURE,
payload: { error }
});
export function fetchAppointments() {
return dispatch => {
dispatch(fetchAppointmentsBegin());
return fetch(listUrl)
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchApointmentsSuccess(json.appointment));
return json.appointment;
})
.catch(error => dispatch(fetchAppointmentsFailure(error)));
};
}
// Handle HTTP errors since fetch won't.
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
// app.js
export default function App() {
return (
<Provider store={Store}>
<Navigation />
</Provider>
);
}
//list rendrer component :
const mapStateToProps = state => ({
data: state.appointments.data,
loading: state.loading,
error: state.error
});
the console.log of store.getstate() gives :
Object {
"appointmentsReducer": Object {
"data": Array [],
"error": null,
"loading": false,
},
"form": Object {},
I'm not sure where the problem is.
Is it due to the asynchronous call not being handled properly?
If I use saga to handle the fetch, will it resolve the problem?
Any help would be appreciated .

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.