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 })
Related
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
})
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>
}
My redux is not updating the props.
My component:
...
import { connect } from 'react-redux';
import { getTenantByID, updateTenant } from '../actions';
...
constructor(props) {
super(props);
this.state = {
tenantData: {}
};
}
componentDidMount() {
this.getTenant();
}
onChangeText = (text, input) => {
const obj = { ...this.state.tenantData };
obj[input] = text;
this.setState({
tenantData: obj
});
};
onChangeNumberFormat = (text, input) => {
const obj = { ...this.state.tenantData };
let value = parseFloat(text);
if (isNaN(value)) {
value = 0;
}
value = parseFloat(value).toFixed(2).replace(/\d(?=(\d{3})+\.)/g, '$&,');
obj[input] = value;
this.setState({
tenantData: obj
});
};
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
await this.props.getTenantByID(tenantID); // Wait for action to complete
this.setState({
tenantData: this.props.tenantData
});
};
...
const mapStateToProps = ({ tenants }) => {
const { error, tenantData, saving } = tenants;
return { error, tenantData, saving };
};
export default connect(mapStateToProps, {
getTenantByID, updateTenant
})(TenantDetails);
In my action, I export the method:
export const getTenantByID = ({ tenantID }) => {
return (dispatch) => {
const getTenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '', RentalAmount: '1000.50', MoveInDate: toDate('2019-01-01'),
MoveOutDate: toDate('2019-12-01'), LeaseStartDate: toDate('2019-01-01'), LeaseEndDate: toDate('2019-12-01'),
};
dispatch({
type: GET_TENANT_DATA,
payload: getTenant
});
};
};
And I use the reducer to return the data.
...
const INITIAL_STATE = {
error: false,
data: [],
tenantData: {},
saving: false,
};
...
case GET_TENANT_DATA:
return { ...state, error: false, tenantData: action.payload };
If I do a console.log in the GET_TENANT_DATA in my reducer, I can see that the action.payload has data. But if I do console.log(this.state.tenantData) in my render() method, it is empty. Why is it happening?
Thanks
I include logs in the componentDidMount and render. It display in the following order
call render
this.props.tenantData is empty
Call componentDidMount
this.props.tenantData is empty
call render
this.props.tenantData has value
call render
this.props.tenantData has value
It is never setting state.tenantData. Why is it calling render() after componentDidMount()?
The problem is here, in getTenant function.
getTenant should not be async function becuase you are not returning a promise
componentDidUpdate(prevProps){
if (prevProps.tenantData.Email !== this.props.tenantData.Email) {// you need a unique value to check for changes in props
this.setTenantData();
}
}
setTenantData = () => this.setState({ tenantData: this.props.tenantData });
getTenant = () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
const tenantData = this.props.getTenantByID(tenantID);
};
And this should be your action.
export const getTenantByID = ({ tenantID }) => {
const tenant = {
FirstName: 'Jonh', LastName: 'Doe', Email: 'jonh#test.com', Phone: 'xxx-xxx-xxxx',
Unit: '101', MiddleName: '', RentalAmount: '1000.50', MoveInDate: toDate('2019-01-01'),
MoveOutDate: toDate('2019-12-01'), LeaseStartDate: toDate('2019-01-01'), LeaseEndDate: toDate('2019-12-01'),
};
return {
type: GET_TENANT_DATA,
payload: tenant
};
};
So you can see tenantData under the console.log in componentDidUpdate.
And the reason for setState not working under getTenant is because the component takes time to update after the redux action
I have an action data that is being sending to the reducer, but not ti my page constructor.
Action Method:
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
});
};
};
Then, in my 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;
}
};
If I do a console.log(action) after the case GET_TENANT_DATA, I can see that data for the payload, so it is working in the reducer.
My page:
constructor(props) {
super(props);
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
console.log(this.props); // this show tenantData as a empty object
this.state = {
tenantData: this.props.tenantData
};
}
...
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);
Seems like you are using thunk and it's asynchronous, so you need to await your action so that you can get the updated state after you fire the action. Otherwise, you can remove thunk if it's not necessary. You may want to fire the action in componentDidMount instead of constructor too
componentDidMount() {
this.getTenant();
}
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
await this.props.getTenantByID(tenantID); // Wait for action to complete
console.log(this.props); // Get updated props here
this.state = {
tenantData: this.props.tenantData
};
}
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);
Or you can capture the update via componentDidUpdate
componentDidMount() {
this.getTenant();
}
componentDidUpdate(previousProps) {
if (this.props.tenantData !== previousProps.tenantData) {
console.log(this.props); // Get updated props here
this.state = {
tenantData: this.props.tenantData
};
}
}
getTenant = async () => {
const { navigation } = this.props;
const tenantID = navigation.getParam('tenantID', '0');
this.props.getTenantByID(tenantID);
}
const mapStateToProps = ({ tenants }) => {
const { error, tenantData } = tenants;
return { error, tenantData };
};
export default connect(mapStateToProps, {
getTenantByID
})(TenantDetails);
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 .