React Native & Redux : how and where to use componentWillMount() - reactjs

I work on app with facebook login using react-native and redux. Right now I'm face to an issue :
Warning: setState(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
So I think I have to use componentWillMount() just before my render method, but I don't know how to use it ..
containers/Login/index.js
import React, { Component } from 'react';
import { View, Text, ActivityIndicatorIOS } from 'react-native';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from '../../actions';
import LoginButton from '../../components/Login';
import reducers from '../../reducers';
import { Card, CardSection, Button } from '../../components/common';
class Login extends Component {
// how sould I use it ?
componentWillMount() {
}
render() {
console.log(this.props.auth);
const { actions, auth } = this.props;
var loginComponent = <LoginButton onLoginPressed={() => actions.login()} />;
if(auth.error) {
console.log("erreur");
loginComponent = <View><LoginButton onLoginPressed={() => actions.login()} /><Text>{auth.error}</Text></View>;
}
if (auth.loading) {
console.log("loading");
loginComponent = <Text> LOL </Text>;
}
return(
<View>
<Card>
<CardSection>
{ auth.loggedIn ? this.props.navigation.navigate('Home') : loginComponent }
</CardSection>
</Card>
</View>
);
}
}
function mapStateToProps(state) {
return {
auth: state.auth
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actionCreators, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);
the reducer :
import { LOADING, ERROR, LOGIN, LOGOUT } from '../actions/types';
function loginReducer(state = {loading: false, loggedIn: false, error: null}, action) {
console.log(action);
switch(action.type) {
case LOADING:
console.log('Inside the LOADING case');
return Object.assign({}, state, {
loading: true
});
case LOGIN:
return Object.assign({}, state, {
loading: false,
loggedIn: true,
error: null,
});
case LOGOUT:
return Object.assign({}, state, {
loading: false,
loggedIn: false,
error: null
});
case ERROR:
return Object.assign({}, state, {
loading: false,
loggedIn: false,
error: action.err
});
default:
return state;
}
}
export default loginReducer;
and the action :
import {
LOADING,
ERROR,
LOGIN,
LOGOUT,
ADD_USER
} from './types';
import { facebookLogin, facebookLogout } from '../src/facebook';
export function attempt() {
return {
type: LOADING
};
}
export function errors(err) {
return {
type: ERROR,
err
};
}
export function loggedin() {
return {
type: LOGIN
};
}
export function loggedout() {
return {
type: LOGOUT
};
}
export function addUser(id, name, profileURL, profileWidth, profileHeight) {
return {
type: ADD_USER,
id,
name,
profileURL,
profileWidth,
profileHeight
};
}
export function login() {
return dispatch => {
console.log('Before attempt');
dispatch(attempt());
facebookLogin().then((result) => {
console.log('Facebook login success');
dispatch(loggedin());
dispatch(addUser(result.id, result.name, result.picture.data.url, result.picture.data.width, result.data.height));
}).catch((err) => {
dispatch(errors(err));
});
};
}
export function logout() {
return dispatch => {
dispatch(attempt());
facebookLogout().then(() => {
dispatch(loggedout());
})
}
}
If you need more code here is my repo :
https://github.com/antoninvroom/test_redux

componentWillMount is one the first function to be run when creating a component. getDefaultProps is run first, then getInitialState then componentWillMount. Both getDefaultProps and getInitialState will be run only if you create the component with the react.createClass method. If the component is a class extending React.Component, those methods won't be run. It is recommended to use componentDidMount if you can instead of componentWillMount because your component can still be updated before componentWillMount and the first render.
You can find more info on the react component lifecycle here
Also, it is recommended to set the state or the default props inside the class constructor or using getDefaultProps and getInitialState.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = { bar: 'foo' };
}
static defaultProps = {
foo: 'bar'
};
}
EDIT: Here's the component handling login
import React, { Component } from 'react';
import { View, Text, ActivityIndicatorIOS } from 'react-native';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from '../../actions';
import LoginButton from '../../components/Login';
import reducers from '../../reducers';
import { Card, CardSection, Button } from '../../components/common';
class Login extends Component {
componentDidMount() {
// If user is already logged in
if(this.props.auth.loggedIn) {
// redirect user here
}
}
componentWillReceiveProps(nextProps) {
// If the user just log in
if(!this.props.auth.loggedIn && nextProps.auth.loggedIn) {
// Redirect user here
}
}
render() {
console.log(this.props.auth);
const { actions, auth } = this.props;
var loginComponent = <LoginButton onLoginPressed={() => actions.login()} />;
if(auth.error) {
console.log("erreur");
loginComponent = <View><LoginButton onLoginPressed={() => actions.login()} /><Text>{auth.error}</Text></View>;
}
if (auth.loading) {
console.log("loading");
loginComponent = <Text> LOL </Text>;
}
return(
<View>
<Card>
<CardSection>
{ auth.loggedIn ? this.props.navigation.navigate('Home') : loginComponent }
</CardSection>
</Card>
</View>
);
}
}
function mapStateToProps(state) {
return {
auth: state.auth
};
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actionCreators, dispatch)
};
}
export default connect(mapStateToProps, mapDispatchToProps)(Login);

Based on your comment to Ajay's answer, you are looking to set the initial state in the component. To do so, you would set the state inside the constructor function.
class Login extends Component {
constructor(props) {
super(props);
this.state = {
color: props.initialColor
};
}
If you have data that is fetched asynchronously that is to be placed in the component state, you can use componentWillReceiveProps.
componentWillReceiveProps(nextProps) {
if (this.props.auth !== nextProps.auth) {
// Do something if the new auth object does not match the old auth object
this.setState({foo: nextProps.auth.bar});
}
}

componentWillMount() is invoked immediately before mounting occurs. It is called before render(), therefore setting state in this method will not trigger a re-rendering. Avoid introducing any side-effects or subscriptions in this method.
if you need more info componentWillMount()
read this https://developmentarc.gitbooks.io/react-indepth/content/life_cycle/birth/premounting_with_componentwillmount.html

Related

action does not modify state

I am trying to add user metadata to my store when mounting a screen. However, when I send the action to the reducer, the store is not modified.
I would expect props after sending the action to be as follows:
{addUserMetaData: ƒ addUserMetaData(user_object),
user: {firestore_doc: {name: "Joe"}}
}
What am i missing here?
To reproduce, react-native-init mwe then add the following code. I've added an image of the app logs below.
App.js
import React, { Component} from 'react';
import { View } from 'react-native';
import Screen from './src/screen';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
const userReducer = function userReducer(state = {}, action) {
console.log('action', action);
switch (action.type) {
case "ADD_USER_METADATA":
return { ...state, firestore_doc: action.payload };
default:
return { ...state };
}
};
const store = createStore(userReducer);
export default class App extends Component {
render() {
return (
<Provider store={store}>
<View>
<Screen />
</View>
</Provider>
);
}
};
src/screen.js
import React, { Component } from 'react';
import { Text, View } from 'react-native';
import { connect } from 'react-redux';
const addUserMetaData = (user) => ({
type: "ADD_USER_METADATA",
payload: user
})
class Screen extends Component {
constructor(props) {
super(props);
}
componentDidMount() {
const user = { name: "Joe" };
console.log('props', this.props);
this.props.dispatch(addUserMetaData(user));
console.log('props after action', this.props);
}
render() {
return (
<View>
<Text>Welcome to react native</Text>
</View>
)
}
}
const mapStateToProps = state => {
return { user: state };
};
export default connect(mapStateToProps)(Screen);
Fixed https://snack.expo.io/#janithar/c3RhY2
Lines I changed
return { ...state, firestore_doc: action.payload };
Please added state.firestore_doc instead of state because in reducer action.payload assign the data in firestore_doc state so you are not getting data from state.user
const mapStateToProps = state => {
return { user: state.firestore_doc };
};

Cannot update during an existing state transition error

EDIT: I solve my issue and it is working for me now - also edited my code to reflect new changes.
I am getting this error and I am not sure what is the cause of this error.
I cannot show code as it is company's material, so I will try my best to describe it:
App.js:
`class App extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div>
<Header />
<RouteList />
<Footer />
</div>
)
}
}`
My <RouteList /> is a a stateless function that returns all Routes for the web-application.
Header.js:
class Header extends React.Component {
constructor (props) {
super(props);
this.changeHeader = this.changeHeader.bind(this);
}
changeHeader(headerType) {
this.props.actions.changeHeader(headerType)
}
GetHeader() {
// if-else statement which will return a different sub header class
const HeaderType = this.props.renderHeader.headerType
if (headerType == 'abc') {
<aHeader changeHeader={this.changeHeader} />
} [...] {
// Final else block return something
}
}
render () {
return (
<div>{this.GetHeader()}</div>
)
}
}
function mapStateToProps(state, ownProps) {
return { renderHeader: state.renderHeader};
}
function mapDispatchToProps(dispatch) {
return { actions: bindActionCreators(headerActions, dispatch) };
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
this.props.action.changeHeader(headerType) is an if-else statement which depending on what the value of headerType is, will fire a different action.
state.renderHeader is declared in my rootReducer.
I pass changerHeader() into individual header.js which are stateless (i.e. aHeader.js, bHeader.js...). When a navlink is clicked it will invoke the method and also route the page to another UI. This is how i embed the method into the navlink: onClick={changeHeader(input')}.
rootReducer.js
const rootReducer = combineReducers({renderHeader});
export default rootReducer;
The renderHeader is the renderHeaderReducer.
headerAction.js
export function changeHeader(headerType) {
if (headerType == "abc") {
return {type: type, headerType: "abc"}
} [...] {
// something default
}
}
renderHeaderReducer.js
export default function renderHeaderReducer(state = initialState, action) {
switch(action.type) {
case "abc":
return (Object.assign({}, ...state, {headerType: action.headerType}));
[...];
default:
return state;
}
}
At this point when the link is clicked, the web browser should refresh, leaving the Header in place but modifying the part. However my website goes into an infinite loop, and the error is:
Error: Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to componentWillMount.
When I did a console.log to see what is going on, it seems to be looping over all the various options that i defined which will render Header.js
It turns out that the main problem was when i called my onClick method.The infinite loop that bugged my code was a result of the onClick function firing even without being clicked.
Original: onClick={this.changeHeader('abc')}
New: onClick={() => changeHeader('abc')}
Refer to this post for an explanation.
Thank you.
time for some pseudo code :)
From what I understand, you have a Header Component which is connected to a rootReducer component which contains the header for which Router Link you are on.
I have some similar code in my application where we use individual components dispatch action to update the rootReducer header. The header just listens for updates using redux and re-renders itself.
class Header extends React.Component {
// render the header
render() {...}
}
const mapStateToProps = (state) => {
return {
header: state.rootReducer.header
}
}
export default withRouter(connect(mapStateToProps, {})(Header));
the component
class MySpecialRouteComponent extends React.Component {
componentDidMount() {
this.props.changeHeader("Special Component")
}
render() {...}
}
const mapStateToProps = (state) => {
return {
...whatever
}
}
export default withRouter(connect(mapStateToProps, {changeHeader})(MySpecialRouteComponent));
you shouldn't make render do the setState in React ever!
I'll just show how I would set everything up to handle this situation.
redux/header-actions.js (call these action creators from your components):
export const changeHeader = (headerType) => {
return {
type: 'CHANGE_HEADER',
payload: {
headerType: headerType
}
}
}
redux/header-reducers.js (note: this will be handled when you call the action):
const INITIAL_STATE = {
headerType: 'header-a'
};
export default function(state = INITIAL_STATE, action) {
switch (action.type) {
case 'CHANGE_HEADER':
return changeHeader(state, action.payload);
default:
return state;
}
}
const changeHeader = (state, payload) => {
// this is where your app will update the state, to indicate
// which header should be displayed:
return {
...state,
headerType: payload.headerType
}
}
redux/index.js:
import headerReducers from './reducers/header-reducers';
import { combineReducers } from 'redux';
const allReducers = combineReducers({
header: headerReducers
});
export default allReducers;
Now you can set up your header.js component like this:
import { changeHeader } from '../redux/header-actions';
class Header extends Component {
render() {
return (
<div>{this.renderHeader()}</div>
);
}
renderHeader() {
if (this.props.headerType === 'header-a')
return <aHeader changeHeader={this.props.changeHeader} />
else
return <bHeader changeHeader={this.props.changeHeader} />;
}
}
function mapStateToProps(store, ownProps) {
return {
headerType: store.header.headerType
};
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
changeHeader: changeHeader
},
dispatch);
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(Header));
Then in, for example, aHeader.js:
class aHeader {
constructor() {
super();
this.changeHeader = this.changeHeader.bind(this);
}
render() {
return <div onClick={this.changeHeader}>header a</div>;
}
changeHeader() {
this.props.changeHeader('header-b'); // call the action creator here
}
}

React doesn't update the view even when Redux state is changed

The problem is when I update state in Redux, React doesn't run the render function. I am a beginner in Redux so I am not getting what exactly should I be doing to solve this. I read about the #connect function but as I am using CreateReactApp CLI tool, I won't be able to provide support for Decorators without ejecting (Which I dont want to do).
Component:
import React from "react";
import Store from "../store";
Store.subscribe(() => {
console.log(Store.getState().Auth);
});
export default class Login extends React.Component {
login = () => {
Store.dispatch({ type: "AUTH_LOGIN" });
// this.forceUpdate(); If I forceUpdate the view, then it works fine
};
logout = () => {
Store.dispatch({ type: "AUTH_LOGOUT" });
// this.forceUpdate(); If I forceUpdate the view, then it works fine
};
render() {
if (Store.getState().Auth.isLoggedIn) {
return <button onClick={this.logout}>Logout</button>;
} else {
return <button onClick={this.login}>Login</button>;
}
}
}
Reducer:
export default AuthReducer = (
state = {
isLoggedIn: false
},
action
) => {
switch (action.type) {
case "AUTH_LOGIN": {
return { ...state, isLoggedIn: true };
}
case "AUTH_LOGOUT": {
return { ...state, isLoggedIn: false };
}
}
return state;
};
Can anyone please point me in the right direction? Thanks
You can make use of connect HOC instead of decorator, it would be implemented like
import { Provider, connect } from 'react-redux';
import Store from "../store";
class App extends React.Component {
render() {
<Provider store={store}>
{/* Your routes here */}
</Provider>
}
}
import React from "react";
//action creator
const authLogin = () => {
return { type: "AUTH_LOGIN" }
}
const authLogout = () => {
return { type: "AUTH_LOGOUT" }
}
class Login extends React.Component {
login = () => {
this.props.authLogin();
};
logout = () => {
this.props.authLogout();
};
render() {
if (this.props.Auth.isLoggedIn) {
return <button onClick={this.logout}>Logout</button>;
} else {
return <button onClick={this.login}>Login</button>;
}
}
}
const mapStateToProps(state) {
return {
Auth: state.Auth
}
}
export default connect(mapStateToProps, {authLogin, authLogout})(Login);

"Cannot update during an existing state transition" Error in React Native and Redux with External API

I am writing a React Native app and using Redux.
I have followed this tutorial.
I have faced with the following warning message:
Warning: Cannot update during an existing state transition (such as within 'render' or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to 'componentWillMount'
Here is what I have:
MyComponent.js
import React from 'react';
import { login } from './actions'
import { connect } from 'react-redux';
import { View, Text } from 'react-native';
import { Button } from 'native-base';
class MyComponent extends React.Component {
render() {
return(
<View>
<Button onPress={() => this.props.login("username","password")}>
<Text>
Login!
<Text>
</Button>
</View>
)
}
}
function mapStateToProps(state) {
return { state: state.login }
}
function mapDispatchToProps(dispatch) {
return {
login: (username, password) => dispatch(login(username, password))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
App.js
import React, { Component } from 'react';
import MyComponent from './MyComponent';
import { Provider } from 'react-redux';
import configureStore from './configureStore';
import Route from './config/Router';
export default class App extends Component {
render() {
let store = configureStore();
return (
<Provider store={store}>
<MyComponent />
</Provider>
);
}
}
configureStore.js
import { createStore } from 'redux';
import reducers from './reducers';
import thunk from 'redux-thunk';
export default function configureStore() {
let store = createStore(reducers, applyMiddleware(thunk));
return store;
}
reducers.js
import * from './constants'
import { combineReducers } from 'redux';
const initialState = {
isLoggedIn: false,
isLoggingIn: false,
username: '',
error: false
};
function loginReducer(state = initialState, action) {
console.log('Reducer is called.');
console.log('Action is ' + JSON.stringify(action));
console.log('state is ' + JSON.stringify(state));
switch (action.type) {
case LOGGING_IN:
return {
...state,
isLoggedIn: false,
isLoggingIn: true,
username: '',
error: false
}
case LOG_IN_SUCCESS:
return {
...state,
isLoggedIn: true,
isLoggingIn: false,
username: action.data.username,
error: false
}
case LOG_IN_FAILURE:
return {
...state,
isLoggedIn: false,
isLoggingIn: false,
username: '',
error: true
}
}
}
export default rootReducer = combineReducers({
loginReducer,
});
actions.js
import * './constants'
import { Actions } from 'react-native-router-flux';
export function login(username, password) {
return (dispatch) => {
dispatch(loggingIn())
//here will be some API ASYNC CALLS!
//depending on the result we will dispatch again!
myService.login(username,password).done((result) => {
if(result !== null) {
dispatch(loginSuccess(result))
} else {
dispatch(loginFailure())
}
})
}
}
function loggingIn() {
return {
type: LOGGING_IN
}
}
function loginSuccess(data) {
return {
type: LOG_IN_SUCCESS,
data: data
}
}
function loginFailure() {
return {
type: LOG_IN_FAILURE
}
}
I have debugged the code. I have seen that the warning comes right after first dispatch call! ( dispatch(loggingIn()) )
So, I don't know what the problem is.
I also wonder if this is the true use of redux and async api calls. Can you help me? Thanks a lot.
I think something wrong here
function mapStateToProps(state) {
return { state: state.login }
}
Should be
function mapStateToProps(state) {
return { loginReducer: state.loginReducer }
}
As far as I recall from my experience, this error usually appears when you are trying to update something in your render method, which is prohibited.
Verify that it is not happening in your app.
UPD: Try creating a function above the render method that calls this.props.login instead of doing it inside mapDispatchToProps. And add your the login action creator instead of mapDispatchToProps as a second argument to the connect function.

React-native Redux action not dispatching

I am in the process of migrating an app from React to React Native and am running into an issue with Redux not dispatching the action to Reducer.
My root component looks like this:
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux';
import Main from '../main/main';
import {
AppRegistry,
StyleSheet,
Text,
View
} from 'react-native';
class App extends Component {
render() {
console.log('Rendering root.js component');
console.log(this.props);
const { dispatch, isAuthenticated, errorMessage, game, communication } = this.props
return (
<View style={styles.appBody}>
<Main
dispatch={dispatch}
game={game}
communication={communication}
/>
</View>
)
}
}
App.propTypes = {
dispatch: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool.isRequired,
errorMessage: PropTypes.string,
}
function mapStateToProps(state) {
const { auth } = state
const { game } = state
const { communication } = state
const { isAuthenticated, errorMessage } = auth
return {
isAuthenticated,
errorMessage,
game,
communication
}
}
const styles = StyleSheet.create({
appBody: {
}
});
export default connect(mapStateToProps)(App)
Then a 'lobby' subcomponent has the dispatch function from Redux as a prop passed to it. This component connects to a seperate javascript file, and passes the props to it so that that seperate file has access to the dispatch function:
componentWillMount() {
coreClient.init(this);
}
In that file I do this:
const init = function(view) {
socket.on('connectToLobby', (data) => {
console.log('Lobby connected!');
console.log(data);
console.log(view.props) // shows the dispatch function just fine.
view.props.dispatch(connectLobbyAction(data));
});
}
The action itself also shows a console log I put there, just that it never dispatches.
export const LOBBY_CONNECT_SUCCESS = 'LOBBY_CONNECT_SUCCESS';
export function connectLobbyAction(data) {
console.log('Action on connected to lobby!')
return {
type: LOBBY_CONNECT_SUCCESS,
payload: data
}
}
I feel a bit lost, would appreciate some feedback :)
EDIT: Reducer snippet:
var Symbol = require('es6-symbol');
import {
LOBBY_CONNECT_SUCCESS
} from './../actions/actions'
function game(state = {
//the state, cut to keep things clear.
}, action) {
switch (action.type) {
case LOBBY_CONNECT_SUCCESS:
console.log('reducer connect lobby')
return Object.assign({}, state, {
...state,
user : {
...state.user,
id : action.payload.id,
connected : action.payload.connected
},
match : {
...state.match,
queuePosition : action.payload.position,
players : action.payload.playerList,
room : 'lobby'
},
isFetching: false,
})
default:
return state
}
}
const app = combineReducers({
game,
//etc.
})

Resources