I have the following action:
export function loginUserRequest() {
console.log('ACTION CALLED');
return {
type: LOGIN_USER_REQUEST,
};
}
and this is the reducer:
export default function loginReducer(state = initialState, action) {
switch (action.type) {
case LOGIN_USER_REQUEST:
console.log('REDUCER CALLED');
return Object.assign({}, state, {
isAuthenticated: true,
isAuthenticating: true,
statusText: null,
});
default:
return initialState;
}
}
Then, my component:
class Login extends React.Component {
goHome = () => {
browserHistory.push('/');
}
handleSubmit = (values) => {
console.log(this.props.isAuthenticating);
this.props.actions.loginUserRequest();
console.log(this.props.isAuthenticating);
}
render() {
return (
<LoginForm onSubmit={this.handleSubmit} />
);
}
}
Login.propTypes = {
actions: PropTypes.objectOf(PropTypes.func).isRequired,
isAuthenticating: PropTypes.bool.isRequired,
};
const mapStateToProps = state => ({
token: state.login.token,
isAuthenticated: state.login.isAuthenticated,
isAuthenticating: state.login.isAuthenticating,
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actionCreators, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Login);
LoginForm is a redux-form component.
So, the expeted ouput from the handleSubmit function is:
false
ACTION CALLED
REDUCER CALLED
true
but it is giving me:
false
ACTION CALLED
REDUCER CALLED
false
But in the redux dev tool I can see the diff in LOGIN_USER_REQUEST:
Why I don't see it inside the handleSubmit function? Is it something related to redux-form library?
Extra info:
Added shouldComponentUpdate and logger
shouldComponentUpdate = (nextProps, nextState) => {
console.log('Should component update called');
if (this.props.isAuthenticating !== nextProps.isAuthenticating) {
console.log('distntict');
return true;
}
console.log('false');
return false;
}
You are getting such a result because of async nature of Javascript. So in your code
handleSubmit = (values) => {
console.log(this.props.isAuthenticating);
this.props.actions.loginUserRequest();
console.log(this.props.isAuthenticating);
}
First, you are printing the value of prop, and then the action gets called but before the action returns a response with the updated state, your third statement gets called to log the value and since the state is not yet updated you see the same result.
One approach will be have callbacks but that doesn't seem to be a requirement for your case. If your want to log the state then you can do so in componentWillReceiveProps function
like
componentWillReceiveProps(nextProps) {
if(this.props.isAuthenicating != nextProps.isAuthenticating) {
console.log(nextProps.isAuthenticating);
}
}
I hope it helps
Related
I have a react app that is connected with redux. The component has a form that makes a PUT call to the api when the form is submitted. When I submit the form, I can see that redux gets updated accordingly but when I try to access the redux state as a prop in my component, the props data does not return the current data and is off by 1. For example, here's the data in my redux store:
Redux store:
When I do console.log("THIS PROPS: ", this.props) in my component, I see that it accountError is showing up as null
When I dispatch the action again the second time, only then I see that I am getting the data from redux in my props:
Here is the code that I have currently:
OrgAccount.js
import { registerOrgAccount, getListOfOrgsAndAccts } from "../../store/actions";
handleSubmit = () => {
this.props.registerOrgAccount(this.state)
console.log("THIS PROPS: ", this.props)
if(this.props.accountError === null) {
this.toggleTab(this.state.activeTab + 1);
}
};
<Link
to="#"
className="btn w-lg"
onClick={() => {
if (this.state.activeTab === 1) {
this.handleSubmit();
}
}}
>
Next
</Link>
const mapStatetoProps = (state) => {
const { accounts, accountError, loading } = state.OrgAccount;
return { accounts, accountError, loading };
};
const mapDispatchToProps = (dispatch) => {
return {
getListOfOrgsAndAccts: () => {
dispatch(getListOfOrgsAndAccts())
},
registerOrgAccount: (data) => {
dispatch(registerOrgAccount(data))
},
}
}
export default connect(mapStatetoProps, mapDispatchToProps)(OrgAccount);
Reducer:
const initialState = {
accountError: null, accountsError: null, message: null, loading: null
}
const orgAccount = (state = initialState, action) => {
switch (action.type) {
case REGISTER_ORG_ACCOUNT:
state = {
...state,
account: null,
loading: true,
// accountError: null
}
break;
case REGISTER_ORG_ACCOUNT_SUCCESSFUL:
state = {
...state,
account: action.payload,
loading: false,
accountError: null
}
break;
case REGISTER_ORG_ACCOUNT_FAILED:
state = {
...state,
loading: false,
accountError: action.payload ? action.payload.response : null
}
break;
...
default:
state = { ...state };
break;
}
return state;
}
export default orgAccount;
Action
export const registerOrgAccount = (account) => {
return {
type: REGISTER_ORG_ACCOUNT,
payload: { account }
}
}
export const registerOrgAccountSuccessful = (account) => {
return {
type: REGISTER_ORG_ACCOUNT_SUCCESSFUL,
payload: account
}
}
export const registerOrgAccountFailed = (error) => {
return {
type: REGISTER_ORG_ACCOUNT_FAILED,
payload: error
}
}
Saga.js
import { registerOrgAccountSuccessful, registerOrgAccountFailed, getListOfOrgsAndAcctsSuccessful, getListOfOrgsAndAcctsFailed } from './actions';
import { putOrgAccount } from '../../../helpers/auth_helper';
function* registerOrgAccount({ payload: { account } }) {
try {
const response = yield call(putOrgAccount, {
orgId: account.orgId,
accountNumber: account.accountNumber,
accountName: account.accountName,
accountCode: account.accountCode,
urlLink: account.urlLink,
location: account.location,
accountType: account.accountType,
address: account.address,
city: account.city,
state: account.state,
zip: account.zip,
country: account.country,
email: account.email,
eula: "blah"
});
yield put(registerOrgAccountSuccessful(response));
} catch (error) {
yield put(registerOrgAccountFailed(error));
}
}
To understand the root cause here, I think it helps to know a little about immutability and how React rerenders. In short, React will rerender when it detects reference changes. This is why mutating a prop, wont trigger a rerender.
With that in mind, at the time you call handleSubmit, this.props.accountError is simply a reference to a value somewhere in memory. When you dispatch your action and your state is updated, a new reference will be created, which will trigger a rerender of your component. However the handleSubmit function that was passed to your element still references the old this.props.accountError, which is why it is still null.
You could get around this by implementing your check in the componentDidUpdate lifecycle method. E.g. something like this:
componentDidUpdate(prevProps) {
if (prevProps.accountError === null && this.props.accountError !== null) {
this.toggleTab(this.state.activeTab + 1)
}
}
Is is correct to pass a reducer as props when i'm using a rootreducer ?
This is my rootReducer.js :
import { combineReducers } from 'redux';
import simpleReducer from './simpleReducer';
import messageReducer from './messageReducer';
import NewReducer from './NewReducer';
export default combineReducers({
simpleReducer,messageReducer,NewReducer
});
And this is one of my action creators addMessage.js
export const addMessage = (message) => dispatch => {
dispatch({
type: 'ADD',
message: message
})
}
Here is the first reducer messageReducer.js
export default (state = [], action) => {
switch (action.type) {
case 'ADD':
return [
...state,
action.message
];
default:
return state;
}
};
And here is another one simpleReducer.js
export default (state = {}, action) => {
switch (action.type) {
case 'SIMPLE_ACTION':
return {
result: action.payload
}
default:
return state
}
}
And finally here is my last reducer NewReducer.js
export default (state = '', action) => {
switch (action.type) {
case 'AnyThing':
return action.WhatToDisplay;
default:
return state;
}
};
Here is my mapping in the App.js
const mapStateToProps = state => ({
...state
})
const mapDispatchToProps = dispatch => ({
simpleAction: () => dispatch(simpleAction()),
submitNewMessage: (message) => {
dispatch(addMessage(message))
},
NewAction: () => dispatch(NewAction())
})
And here is my ِApp Component.Notice my last 2 h2 tags as well as my ul tag .Without me adding the reducer at the end of the prop , it doesn't work.So
is what i'm doing right ? or is there another way to show the redux state in
my react ?.Note that i currently have no errors and the code functions well.I
just wana know if what i am doing is right or wrong and if there is a better
syntax to show the redux state in my create react app.
class App extends Component {
constructor(props) {
super(props);
this.state = {
input: ''
}
this.handleChange = this.handleChange.bind(this);
this.submitMessage = this.submitMessage.bind(this);
}
handleChange(event) {
this.setState({
input: event.target.value
});
}
submitMessage() {
this.props.submitNewMessage(this.state.input);
this.setState({
input: ''
});
}
simpleAction = (event) => {
this.props.simpleAction();
}
localNormalFunction=(event)=>{
this.props.NewAction()
}
render() {
return (
<div >
<h1>fjasgdasdsg</h1>
<button onClick={this.simpleAction}>Test redux action</button>
<pre>
{
JSON.stringify(this.props)
}
</pre>
<h2>Type in a new Message:</h2>
<input
value={this.state.input}
onChange={this.handleChange}/><br/>
<button onClick={this.submitMessage}>Submit</button>
<ul>
{this.props.messageReducer.map( (message,idx) => {
return (
<li key={idx}>{message}</li>
)
})
}
</ul><br/><br/>
<button onClick={this.localNormalFunction}>dsadsdsa</button>
<h2>{this.props.NewReducer}</h2>
<h2>{this.props.simpleReducer.result}</h2>
</div>
);
}
}
It is better practice to get only the props you need from redux in each component. If you pass the whole redux state in mapStateToProps then whenever anything in redux changes you will have everything rerendering even if nothing you use changed.
One common reason you might be getting errors is that you are trying to use the props in render and they get instantiated afterwards.
Try this give default values to the props if you can't get them from redux:
App.defaultProps = {
result: '',
NewReducer: '',
messageReducer: []
}
const mapStateToProps = state => ({
result: state.simpleReducer.result,
NewReducer: state.NewReducer,
messageReducer: state.messageReducer
})
and then change this.props.simpleReducer.result to this.props.result
I have a component parent and a component child with some props connected to the parent state.
In the parent I call setState but the componentWillReceiveProps function of the child is not fired.
More precisaly, its fired in a certain point of the parent, its not fired in another point.
This is the parent:
... imports
class HomeScreen extends Component {
constructor(props) {
super(props);
dispatchFbPermissionAction = this.dispatchFbPermissionAction.bind(this);
this.state = {
fbPermissions: [],
}
}
componentDidMount () {
this._loadInitialState();
}
_responsePermissionsCallback(error: ?Object, result: ?Object) {
if (error) {
log('Error fetching data: ' + error.toString());
} else {
dispatchFbPermissionAction(result.data);
}
}
dispatchFbPermissionAction = (data) => {
// **NOT FIRED**
this.setState({
fbPermissions: data
});
this.props.fbPermissionsLoaded(data);
}
async _loadInitialState() {
AccessToken.getCurrentAccessToken().then(
(data) => {
if (data) {
const infoRequest = new GraphRequest(
'/me/permissions',
null,
this._responsePermissionsCallback,
);
new GraphRequestManager().addRequest(infoRequest).start();
// **FIRED**
this.setState({
...
});
this.props.loggedIn();
}
}
);
}
render () {
const { navigation } = this.props;
return (
<Container>
<ScrollableTabView
<View tabLabel="ios-film" style={styles.tabView}>
<Text style={styles.tabTitle}>{_.toUpper(strings("main.theatres"))}</Text>
<ListTheatre navigation={this.props.navigation} filterText={this.state.filterText} isLoggedIn={this.state.isLoggedIn} fbPermissions={this.state.fbPermissions}></ListTheatre>
</View>
</ScrollableTabView>
</Container>
)
}
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn,
listTheatre: state.listTheatre,
listMusic: state.listMusic
};
};
// wraps dispatch to create nicer functions to call within our component
const mapDispatchToProps = (dispatch) => ({
startup: () => dispatch(StartupActions.startup()),
loggedIn: () => dispatch({
type: LOGGED_IN
}),
fbPermissionsLoaded: (data) => dispatch({
type: FB_PERMISSIONS_LOADED,
fbPermissions: data
})
});
export default connect(mapStateToProps, mapDispatchToProps)(HomeScreen)
And this is the child:
... imports
class ListTheatre extends Component {
constructor(props) {
super(props);
this.state = {
...
}
}
componentWillReceiveProps(nextProps) {
log(this.props)
}
shouldComponentUpdate(nextProps, nextState) {
return !nextState.fetching;
}
render() {
const { navigate } = this.props.navigation;
return (
<SectionList
...
/>
)
}
}
ListTheatre.propTypes = {
isLoggedIn: PropTypes.bool.isRequired,
}
const mapStateToProps = (state) => {
return {
isLoggedIn: state.isLoggedIn
};
};
const mapDispatchToProps = (dispatch) => ({
startup: () => dispatch(StartupActions.startup())
});
export default connect(mapStateToProps, mapDispatchToProps)(ListTheatre);
I do not why the setState after the GraphRequestManager().addRequest call works like a charm (the componentWillReceiveProps function of the child is fired), while the setState in the dispatchFbPermissionAction function does not fire the componentWillReceiveProps function of the child.
This is due to connect/Connect(ListTheatre) that wraps your ListTheatre component implemented sCU(shouldComponentUpdate) internally for you, turn it off by setting pure option of connect to false like
export default connect(mapStateToProps, mapDispatchToProps, null, {pure: false})(ListTheatre)
[pure] (Boolean): If true, connect() will avoid re-renders and calls to mapStateToProps, mapDispatchToProps, and mergeProps if the relevant state/props objects remain equal based on their respective equality checks. Assumes that the wrapped component is a “pure” component and does not rely on any input or state other than its props and the selected Redux store’s state. Default value: true
I do not know how to access a boolean isLoading flag from reducerForm.js reducer in reducerRegister.js. I have used combineReducers() and I use isLoading to disable a button during form submit.
It's initial state is false, after clicking submit, it changes to true. After the form submission is successful, isLoading is reset to false again. Below is the relevant code for this issue:
actionRegister.js
let _registerUserFailure = (payload) => {
return {
type: types.SAVE_USER_FAILURE,
payload
};
};
let _registerUserSuccess = (payload) => {
return {
type: types.SAVE_USER_SUCCESS,
payload,
is_Active: 0,
isLoading:true
};
};
let _hideNotification = (payload) => {
return {
type: types.HIDE_NOTIFICATION,
payload: ''
};
};
// asynchronous helpers
export function registerUser({ // use redux-thunk for asynchronous dispatch
timezone,
password,
passwordConfirmation,
email,
name
}) {
return dispatch => {
axios.all([axios.post('/auth/signup', {
timezone,
password,
passwordConfirmation,
email,
name,
is_Active: 0
})
// axios.post('/send', {email})
])
.then(axios.spread(res => {
dispatch(_registerUserSuccess(res.data.message));
dispatch(formReset());
setTimeout(() => {
dispatch(_hideNotification(res.data.message));
}, 10000);
}))
.catch(res => {
// BE validation and passport error message
dispatch(_registerUserFailure(res.data.message));
setTimeout(() => {
dispatch(_hideNotification(res.data.message));
}, 10000);
});
};
}
actionForm.js
export function formUpdate(name, value) {
return {
type: types.FORM_UPDATE_VALUE,
name, //shorthand from name:name introduced in ES2016
value
};
}
export function formReset() {
return {
type: types.FORM_RESET
};
}
reducerRegister.js
const INITIAL_STATE = {
error:{},
is_Active:false,
isLoading:false
};
const reducerSignup = (state = INITIAL_STATE , action) => {
switch(action.type) {
case types.SAVE_USER_SUCCESS:
return { ...state, is_Active:false, isLoading: true, error: { register: action.payload }};
case types.SAVE_USER_FAILURE:
return { ...state, error: { register: action.payload }};
case types.HIDE_NOTIFICATION:
return { ...state , error:{} };
}
return state;
};
export default reducerSignup;
reducerForm.js
const INITIAL_STATE = {
values: {}
};
const reducerUpdate = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.FORM_UPDATE_VALUE:
return Object.assign({}, state, {
values: Object.assign({}, state.values, {
[action.name]: action.value,
})
});
case types.FORM_RESET:
return INITIAL_STATE;
// here I need isLoading value from reducerRegister.js
}
return state;
};
export default reducerUpdate;
reducerCombined.js
import { combineReducers } from 'redux';
import reducerRegister from './reducerRegister';
import reducerLogin from './reducerLogin';
import reducerForm from './reducerForm';
const rootReducer = combineReducers({
signup:reducerRegister,
signin: reducerLogin,
form: reducerForm
});
export default rootReducer;
This is where I use isLoading:
let isLoading = this.props.isLoading;
<FormGroup>
<Col smOffset={4} sm={8}>
<Button type="submit" disabled={isLoading}
onClick={!isLoading ? isLoading : null}
>
{ isLoading ? 'Creating...' : 'Create New Account'}
</Button>
</Col>
</FormGroup>
Mapping state to props within the same component
function mapStateToProps(state) {
return {
errorMessage: state.signup.error,
isLoading: state.signup.isLoading,
values: state.form.values
};
}
This is covered in the Redux FAQ at https://redux.js.org/faq/reducers#how-do-i-share-state-between-two-reducers-do-i-have-to-use-combinereducers:
Many users later want to try to share data between two reducers, but find that combineReducers does not allow them to do so. There are several approaches that can be used:
If a reducer needs to know data from another slice of state, the state tree shape may need to be reorganized so that a single reducer is handling more of the data.
You may need to write some custom functions for handling some of these actions. This may require replacing combineReducers with your own top-level reducer function. You can also use a utility such as reduce-reducers to run combineReducers to handle most actions, but also run a more specialized reducer for specific actions that cross state slices.
Async action creators such as redux-thunk have access to the entire state through getState(). An action creator can retrieve additional data from the state and put it in an action, so that each reducer has enough information to update its own state slice.
A reducer cannot access another reducer's state, but if you're using redux-thunk you can do so from within an action creator. As an example, you can define an action creator like this:
export const someAction = () =>
(dispatch, getState) => {
const someVal = getState().someReducer.someVal;
dispatch({ type: types.SOME_ACTION, valFromOtherReducer: someVal });
};
React Redux works on unidirectional data flow.
Action ---> Reducer /store ---> Reducer
Reducer works on small subset of store, you can not access store inside reducer which is not part of Reducer. you can either need to fire new action from the component based on reducer state return.
mapStatToProps is callings it's self twice and loads data on the second call but then doesn't assign it to components as a prop and returns an empty array.
The Reducer:
export default function postsReducer(state = initialState.posts, action) {
switch (action.type) {
case types.LOAD_POSTS_SUCCESS:
return action.posts;
default:
return state;
}
}
Here is my mpStateToProps func:
function mapStateToProps(state, ownProps) {
const singlePost = state.posts.filter(post => post.code == ownProps.params.id);
return {
post: singlePost
};
}
Here is my Component's state:
this.state = {
post: [...props.post]
};
Your code is probably right, but it seems like you forgot to update your state.post value when your component receives new props.
componentWillReceiveProps(nextProps) {
this.setState({
post: nextProps.post
});
}