mapStateToProps called but not calling componentWillRecieveProps when state has same value - react-native

I have the following reducer:
export default function UserReducer(state = initialState, action){
let nextState;
switch(action.type){
case USER_RECOVERY_CODE_VALIDATED:
nextState = {
...state,
recoverycodevalidated : action.payload.recoverycodevalidated
}
return nextState;
default:
return state;
}
}
And my component is connected to the store that way:
function mapStateToProps(state) {
console.log("updated");
return { recoverycodevalidated: state.user.recoverycodevalidated };
}
export default connect(mapStateToProps)(ResetPasswordCodeScreen)
And when the props change thanks to the mapStateToProps function, I redirect the user to the next screen:
async componentWillReceiveProps(newProps){
console.log("updated props");
if(newProps.recoverycodevalidated){
this.props.navigation.navigate("ResetPasswordFinal", { userId: this.props.navigation.state.params.userId});
}
}
The problem is that when the state is updated the first time and the value of recoverycodevalidated is set in the props, the second time if the value of the variable is the same, the props are not updated because the method componentWillRecieveProps is not fired, eventhough the mapStateToProps is fired everytime. What I'm doing wrong ?

Try using componentDidUpdate instead
componentDidUpdate(prevProps) {
console.log("updated props");
if (prevProps.recoverycodevalidated) {
this.props.navigation.navigate("ResetPasswordFinal", { userId:
this.props.navigation.state.params.userId});
}
}

Related

Make two actions work simultaneously - react native and redux

I've a button that sends two actions. First one adds the user infos in an array if certain condition is met and 2nd one sends the data to the server.
Since both actions are in onPress function, the 2nd action doesn't wait till it adds up the infos in an array. Henceforth, it always sends empty array.
How can I make this two actions work simultaneously.
<TouchableOpacity
onPress={() => {
if (true) {
this.props.AuthUserInfoGet(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo); //calculates & return SignUpUsers
}
this.props.SignUpCheck(SignUpUsers); //upload SignUpUsers but SignUpCheck is always empty here
}}
>
<Text>Upload</Text>
</TouchableOpacity>
const mapStateToProps = (state) => {
const {SignUpUsers} = state.Auth;
//it gives an empty array first and then expected value
console.log('SignUpUsersz', SignUpUsers);
return {SignUpUsers};
};
Action:
export const AuthUserInfoGet = (SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo) => {
return ({
type: SIGN_UP_USER_INFO_GET,
payloadName: SignUpName,
payloadDesignation: SignUpDesignation,
payloadEmail: SignUpEmail,
payloadMobile: SignUpMobileNo,
});
}
export const SignUpCheck = (userInfo) => {
console.log('userInfo', userInfo); // userInfo is always empty
}
Reducer:
const INITIAL_STATE = { SignUpUsers: [] }
case SIGN_UP_USER_INFO_GET:
return { ...state, SignUpUsers: [...state.SignUpUsers, {member_name: actions.payloadName, designation: actions.payloadDesignation,
email: actions.payloadEmail, mobile_number: actions.payloadMobile}] };
Given your current Redux-structure, I think what makes the most sense to use the componentDidUpdate life-cycle method.
The main reason is because your component ultimately needs to get updated data from Redux via props and needs to re-render. When you execute the first action, that user-data coming from the API is not immediately available in the current call-stack, so you'll always be passing an empty array (given your initial value of SignUpUsers: [])
Note that most React-Redux flows follow this path:
User-Event -> Action-Creator -> API (Data) -> Redux -> Component
Your click-event is at step 1 and triggers this action: this.props.AuthUserInfoGet(...args)
But React/Redux needs to go through that entire flow before you can use the new data.
This is where the componentDidUpdate() event comes in-handy because you can write logic when the component is re-rendered by new props or state.
Something like this would totally work:
componentDidUpdate(prevProps){
if(prevProps.SignUpUsers.length !== this.props.SignUpUsers.length){
//execute action
this.props.SignUpCheck(this.props.SignUpUsers)
}
}
For that I would suggest you take a look at redux-thunk middleware.
Redux Thunk middleware allows you to write action creators that return a function instead of an action. The thunk can be used to delay the dispatch of an action, or to dispatch only if a certain condition is met. The inner function receives the store methods dispatch and getState as parameters.
And based on your example, the code will end up like this:
<TouchableOpacity
onPress={() => this.props.uploadSignUpUsers(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo)}>
<Text>Upload</Text>
</TouchableOpacity>
const mapStateToProps = (state) => {
const { Auth: { SignUpUsers } } = state;
return { SignUpUsers };
}
Actions:
export const SIGN_UP_GET_USER_INFO_SUCCESS = "SIGN_UP_GET_USER_INFO_SUCCESS";
export const SIGN_UP_UPLOAD_SUCCESS = "SIGN_UP_UPLOAD_SUCCESS";
export const uploadSignUpUsers = (SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo) => {
return async (dispatch, getState) => {
// here you can make the api call or any other async calculations
const { data: AuthUserInfo, error } = await api.post(SignUpName, SignUpDesignation, SignUpEmail, SignUpMobileNo);
dispatch({
type: SIGN_UP_GET_USER_INFO_SUCCESS,
payloadName: AuthUserInfo.SignUpName,
payloadDesignation: AuthUserInfo.SignUpDesignation,
payloadEmail: AuthUserInfo.SignUpEmail,
payloadMobile: AuthUserInfo.SignUpMobileNo,
});
const { Auth: { SignUpUsers } } = getState()
// and now you can upload your SignUpUsers
const { data: uploadData, error } = await.api.post(SignUpUsers)
dispatch({
type: SIGN_UP_UPLOAD_SUCCESS,
...uploadData // spread upload data to make it available in reducers
});
}
}
Reducer:
const INITIAL_STATE = { SignUpUsers: [] }
case SIGN_UP_GET_USER_INFO_SUCCESS: {
const { payloadName, payloadDesignation, payloadEmail, payloadMobile } = actions
return {
...state,
SignUpUsers: [ ...state.SignUpUsers, {
member_name: payloadName,
designation: payloadDesignation,
email: payloadEmail,
mobile_number: payloadMobile
}]
}
}

React Redux reducer not returning

I have an application that has to return only future events. I am able to parse through my firebase realtime db and return only future events in my action. It then passes this array of data to my reducer. In my reducer, I am logging the payload. The console log displays the data I want. Now in my component screen, I console log to see what data I should see from the reducer and it is empty.
My action:
export const getAllEvents = () => {
var currentTime = Date.now();
return (dispatch) => {
var ref = firebase.database().ref('Events/');
ref.orderByChild("availableSpots").on("value", function(snapshot) {
snapshot.forEach(child => {
var time = child.val().epochTime;
if (currentTime < time) {
console.log("Events redux: ", child.val());
dispatch({ type: GET_EVENT_LIST, payload: child.val() });
}
})
});
});
}
}
As you can see I am console logging the child.val() which comes out as expected below:
Now I have this hooked up to my reducer via dispatch.
import { GET_EVENT_LIST } from '../actions/types';
const INITIAL_STATE = {
eventList: [],
};
const eventListReducer = (state = INITIAL_STATE, action) => {
switch (action.type){
case GET_EVENT_LIST:
console.log("action count", action.payload);
return { ...state, eventList: [...state.eventList, action.payload] };
default:
console.log("default ");
return state;
}
};
export default eventListReducer;
As you can see the console log of the action.payload is being logged and produces an expected result:
Now back in my component screen:
import { getUserThunk, getAllEvents } from '../actions';
import {connect} from 'react-redux';
class Home extends Component {
componentWillMount() {
console.log("Component will mount");
this.props.getUserThunk();
this.props.getAllEvents();
}
render() {
console.log("usersadf ", this.props.userReducer)
console.log("Props in home ", this.props.eventListReducer)
return (
//some return code here
);
}
export default connect(
state=>({userReducer: state.userReducer, eventListReducer: state.eventListReducer}),
{ getUserThunk, getAllEvents }
)(Home);
Now as you can see I do have different redux action and reducer hooked up and it comes through. However, the one in question returns empty.

Reducer not writing to Redux store

I have a connect()ed container component where I try to write a list of books to my redux store. The action creator is available as props and state is mapped to props, however the book list never get into the redux store. readingList is still null instead of bookArray (the passed argument of the action creator). Can someone spot the issue here? Included relevant snippets here:
import { setReadingList } from '../actions/index';
componentWillMount() {
this.props.setReadingList(bookArray);
}
function mapStateToProps(state) {
return {
readingList: state.readingList
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({ setReadingList : setReadingList }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(ReadingList);
/*-----------*/
./actions/index.js
export function setReadingList(readingList) {
return {
type : "SET_READINGLIST",
payload : readingList
};
}
/*-----------*/
./reducers/index.js
import { combineReducers } from 'redux';
import readingList from './reading_list';
export const rootReducer = combineReducers({
readingList
});
export default rootReducer;
/*-----------*/
./reducers/reading_list.js
export default function(state = null, action) {
switch (action.type) {
case "SET_READINGLIST":
return action.payload;
default:
return state;
}
}
Thanks!
Try calling setReadingList in componentDidMount instead of componentWillMount. Would also ensure bookArray is in scope.

Duplicate items on redux state tree

I am trying to update my Redux state with a toggle button. My LB reducer is comprised of 2 reducers, an array called listItems for displaying a unique number of values, and filterBarState which is used as a reference to the current filters.
While my initial state is correct, my reducer places the toggle action outside of filterBarState
Below is my LB object reducer
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case EVENT_FILTER_PRESSED:
return {
// Reducer composition
filterBarState: eventFilter(state.filterBarState, action),
listItems: eventItems(state.listItems, action)
};
case MALE_FILTER_PRESSED:
// console.log('isMaleFilterOn:', action.isMaleFilterOn)
return { ...state, isMaleFilterOn: action.isMaleFilterOn };
case FEMALE_FILTER_PRESSED:
// console.log('isFemaleFilterOn:', action.isFemaleFilterOn)
return { ...state, isFemaleFilterOn: action.isFemaleFilterOn };
In my React Native Container Component, I have attempted some (suspect) ES6 destructuring in mapStateToProps which, if I don't include, the entire filterBarState returns undefined
const mapStateToProps = ({ LB }) => {
const { filterBarState: { isMaleFilterOn, isFemaleFilterOn, currentSelectedEvent, currentSelectedRow }, listItems, isCurrentlySelected } = LB;
return { isMaleFilterOn, isFemaleFilterOn, currentSelectedEvent, currentSelectedRow, listItems, isCurrentlySelected };
Any tips / suggestions on a fix would be greatly appreciated!
Based on the EVENT_FILTER_PRESSED handler, it looks like you are not updating the isMaleFilterOn and isFemaleFilterOn at the correct level. Try this:
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case EVENT_FILTER_PRESSED:
return {
// Reducer composition
filterBarState: eventFilter(state.filterBarState, action),
listItems: eventItems(state.listItems, action)
};
case MALE_FILTER_PRESSED:
// console.log('isMaleFilterOn:', action.isMaleFilterOn)
return { ...state, filterBarState: { ...state.filterBarState, isMaleFilterOn: action.isMaleFilterOn } };
case FEMALE_FILTER_PRESSED:
// console.log('isFemaleFilterOn:', action.isFemaleFilterOn)
return { ...state, filterBarState: { ...state.filterBarState, isFemaleFilterOn: action.isFemaleFilterOn } };
}

Using dispatched actions

I have this on the bottom of my main file:
export default connect(state => ({
state: state.firebaseReducer
}),
(dispatch) => ({
actions: bindActionCreators(firebaseActions, dispatch)
})
)(Routing);
And this is my reducer that I imported
const initialState = null;
export function firebaseRef(state = initialState, action) {
switch (action.type) {
case 'FIREBASE_REF_SET':
return action.value;
default:
return state;
}
}
Action:
export function setFirebaseRef(ref) {
return {
type: 'FIREBASE_REF_SET',
value: ref
};
}
But when I try to do this in my main file:
componentDidMount() {
state.setFirebaseRef(ref);
}
It says "Can't find variable: state" . Am I calling it the wrong way? I want to be able to call the setFireBaseRef action.
connect injects parts of state and dispatch into props. You want to dispatch the action using:
this.props.dispatch(setFireBaseRef(ref))
Or, assuming firebaseActionscontains setFireBaseRef, you can just do:
this.props.actions.setFireBaseRef(ref)