I'm creating a React Native app using React Navigation and Redux.
class LoginScreen extends Component {
state = {
email: '',
password: '',
errors: {
email: '',
password: ''
}
}
onPressLogin() {
this.props.signIn(this.state.email, this.state.password);
}
componentWillUpdate(nextProps, nextState) {
console.log("component will update");
if (nextProps.signedIn) {
this.props.navigation.navigate('LoggedIn');
}
}
render() {
if (this.props.signedIn) {
this.props.navigation.navigate('LoggedIn');
}
return(<View>...</View);
this.props.signIn() is a Redux action, which for now just updates the state as such: { signedIn: true }. The following code is where I pass the Redux actions and state as props.
function mapStateToProps(state, props) {
return {
signedIn: state.authReducer.signedIn,
}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators(Actions, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
When the action is fired, the state updates as I would expect and render() is called. If I put the navigation code in the render() function everything works fine. To make the code cleaner, I want to move it into componentWillUpdate() but this function is not firing. The console log never gets printed to the console.
Here is my action and my reducer.
Action:
export const SIGN_IN_SUCCESS = 'SIGN_IN_SUCCESS';
export const SIGN_IN_FAIL = 'SIGN_IN_FAIL';
export function signIn(email, password) {
return (dispatch) => {
dispatch({ type: SIGN_IN_SUCCESS });
}
}
Reducer:
import { combineReducers } from 'redux';
import {
SIGN_IN_FAIL,
SIGN_IN_SUCCESS
} from '../actions/';
let authState = { signedIn: false, error: '' }
const authReducer = (state = authState, action) => {
switch(action.type) {
case SIGN_IN_SUCCESS:
return {...state, signedIn: true }
case SIGN_IN_FAIL:
return {...state, signedIn: false, error: action.error }
default:
return state;
}
}
const rootReducer = combineReducers({
authReducer
});
export default rootReducer;
Because you executed navigation right inside render() function:
render() {
if (this.props.signedIn) {
this.props.navigation.navigate('LoggedIn');
}
return(<View>...</View);
}
It must be:
render() {
return !this.props.signedIn && (<View>...</View);
}
Related
I have decided to use Typescript for all my react applications but I am having a bit of a problem because of the learning curve. I have a problem when I click to users, it should make a get request on component did mount, but it continues endlessly, this is mostly a code from a template from the dotnet create react redux app and I took most of the code for granted.
import * as React from 'react';
import { connect } from 'react-redux';
import { RouteComponentProps } from 'react-router';
import { ApplicationState } from '../../store';
import * as UsersStore from '../../store/Users';
type UsersProps =
UsersStore.UsersState &
typeof UsersStore.actionCreators &
RouteComponentProps<{}>;
class Users extends React.PureComponent<UsersProps> {
public componentDidMount() {
this.ensureDataFetched();
}
public componentDidUpdate() {
this.ensureDataFetched();
}
public render() {
return (
<React.Fragment>
<h1>Users</h1>
{** // render users **}
</React.Fragment>
);
}
private ensureDataFetched() {
const token = "web_token";
this.props.requestUsers(token);
}
};
export default connect(
(state: ApplicationState) => state.users,
UsersStore.actionCreators
)(Users as any);
And my: store, action, reducer
import { Action, Reducer } from 'redux';
import { AppThunkAction } from './';
import userService from '../services/userService';
import { GET_USERS, GET_USER } from '../constants';
// STATE
export interface UsersState {
isLoading: boolean;
users: User[];
user: User;
}
export interface User {
id: string;
name: string;
}
// ACTIONS
interface GetUserAction {
type: 'GET_USER';
payload: User;
}
interface GetUsersAction {
type: 'GET_USERS';
payload: User[];
}
type KnownAction = GetUserAction | GetUsersAction;
// ACTION CREATORS
export const actionCreators = {
requestUsers: (token: string): AppThunkAction<KnownAction> => async (dispatch, getState) => {
// Only load data if it's something we don't already have (and are not already loading)
const appState = getState();
if (appState && appState.users) {
try {
const users = await userService.getUsers(token);
dispatch({ type: GET_USERS, payload: users })
} catch (err) {
console.log('Bad request, please try loading again.')
}
}
}
};
// REDUCER
const unloadedState: UsersState = { users: [], isLoading: false, user: { id: "0", name: "" } };
export const reducer: Reducer<UsersState> = (state: UsersState | undefined, incomingAction: Action): UsersState => {
if (state === undefined) {
return unloadedState;
}
const action = incomingAction as KnownAction;
switch (action.type) {
case GET_USERS:
if (state.users !== action.payload) {
return {
...state,
users: action.payload
};
}
break;
case GET_USER:
return {
users: state.users,
isLoading: false,
user: action.payload,
};
}
return state;
};
UPDATE: Added this check but now it does not update users state, my idea is to check whether the current state is not the same as the payload then it updates otherwise it will skip and break.
if (state.users !== action.payload) {
return {
...state,
users: action.payload
};
}
break;
Your componentDidUpdate() is the one causing infinite rendering issue
I can see that you already fetch the info in your componentDidMount(), so it's not necessary to fetch them over again.
First, after your component is rendered componentDidMount is invoked
Then your ensureDataFetched is fetched.
Your redux state is changed
Then your componentDidUpdate invoked due to that re-rendering
Your redux state is changed again.
Then your componentDidUpdate invoke all over again.
Infinite loop...
Just remove this block will end that endlessly rendering:
public componentDidUpdate() {
this.ensureDataFetched();
}
I have a problem with displaying data.
In my application I use react and redux.
In the console I will get an error mapStateToProps() in Connect(ListPets) must return a plain object. Instead received undefined.
This is my main component
import React, { Component } from 'react';
import { connect } from 'react-redux';
import loadData from '../actions/actions';
class ListPets extends Component {
componentDidMount() {
const { loadData } = this.props;
loadData();
console.log(loadData );
}
render() {
const { dataPet } = this.props;
return (
<div>
</div>
);
}
}
const mapStateToProps = (state) => {
return state;
};
const mapDispatchToProps = (dispatch) => {
return {
loadData: () => dispatch(loadData())
}
};
This fragment console.log(loadData ); display
ƒ loadData() {
return dispatch(Object(_actions_actions__WEBPACK_IMPORTED_MODULE_7__["default"])());
}
When I add the code {dataPet.data} in div. I get an error]. As if this data was not in the store, I do not know...
this my reducer function
const initialState = {
isFetching: false,
dataPet: [],
};
const fetchDataReducer = (state=initialState, action) => {
switch(action.types) {
case actionTypes.FETCH_DATA_START:
return {
...state,
isFetching: true,
}
case actionTypes.FETCH_DATA_SUCCESS:
return {
...state,
isFetching: false,
dataPet: action.dataPet,
}
case actionTypes.FETCH_DATA_FAIL:
return {
...state,
isFetching: false,
}
};
}
Data is well downloaded, because the console receives the FETCH_DATA_SUCCESS action.
I have no idea how to solve this problem
I made some changes on your code, try this now...should work
https://codesandbox.io/s/z2volo1n6m
In your reducer you have a typo:
const fetchDataReducer = (state=initialState, action) => {
switch(action.types) { // here
It should be action.type not action.types.
If thing is an object in state:
const mapStateToProps = state => ({
thing: state.thing,
});
Then use like:
this.props.thing in your component
I am fairly new to redux, and I am running into a problem.
I am trying to implement flash messages to my login page, but redux's dispatch is not changing the UI State.
I want a flash message to appear on the login page after user successfully register.
//login.js
class Login extends Component{
renderMessage() {
if (this.props.flashMessageType== "registrationComplete"){
return (
<Message
style={{textAlign: "left"}}
success
icon="check circle"
header="Account Registration was Successful"
list={["You must verify your email before logging in"]}
/>
);
} else {
return (null);
}
}
render() {
return ({
this.renderMessage()
});
}
}
function mapStateToProps(state) {
return {
flashMessageType:state.flashMessage.flashType,
};
}
export default connect(mapStateToProps, actions)(Login);
Here is the reducer
const initialState = {
flashType: "",
};
export default function(state = {initialState}, action){
switch(action.type){
case USER_REGISTER:
return [
...state,
{
flashType:"registrationComplete"
}
];
default:
return initialState;
}
}
and here is the actions
export const submitForm = (values,history) => async dispatch => {
const res = await axios.post('/api/signup', values);
history.push('/');
dispatch({type: FETCH_USER, payload: res.data});
dispatch({type: USER_REGISTER});
};
I appreciate your help.
Thanks,
Vincent
As Amr Aly mentioned (and now soroush), you're essentially mutating the state when you do:
return[ ...state, { flashType:"registrationComplete" }]
What you really want is:
return { ...state, flashMessage: "registrationComplete" }
Also, some of your code is a bit redundant and/or missing some important instructions (like try/catch blocks).
What your code should look like:
FlashMessage.js
import React, { PureComponent } from 'react';
import Message from '../some/other/directory';
import actions from '../some/oter/directory':
class Login extends PureComponent {
render = () => (
this.props.flashMessage == "registrationComplete"
? <Message
style={{textAlign: "left"}}
success
icon="check circle"
header="Account Registration was Successful"
list={["You must verify your email before logging in"]}
/>
: null
)
}
export default connect(state => ({ flashMessage: state.auth.flashMessage }), actions)(Login)
reducers.js
import { routerReducer as routing } from 'react-router-redux';
import { combineReducers } from 'redux';
import { FETCH_USER, USER_REGISTER } from '../actions/types';
const authReducer = (state={}, ({ type, payload }) => {
switch(type){
case FETCH_USER: return { ...state, loggedinUser: payload };
case USER_REGISTER: return { ...state, flashMessage: "registrationComplete" }
default: return state;
}
}
export default = combineReducers({
auth: authReducer,
routing
});
actions.js
import { FETCH_USER, USER_REGISTER } from './types';
export const submitForm = (values,history) => async dispatch => {
try {
const {data} = await axios.post('/api/signup',values);
dispatch({ type:FETCH_USER, payload: data });
dispatch({ type:USER_REGISTER });
history.push('/');
catch (err) {
console.error("Error: ", err.toString());
}
};
Your reducer should be:
const initialState = {
flashType: "",
};
export default function(state = initialState, action){
switch(action.type){
case USER_REGISTER:
return {
...state,
flashType: "registrationComplete",
};
default:
return state;
}
}
In console nothing, where can be a mistake ?
Need to get this.props.about and check empty or not.
reducer.js
export default function details(state = initialState, action) {
switch(action.type) {
case DETAILS_SUCCESS:
return { ...state, details: action.payload, error: '' };...
Container.js
class HeaderContainer extends Component {
render() {
const { details } = this.props, { deTails } = this.props.HeaderAction;
return <div><Header deTails={deTails} about={details.details} error={details.error} /></div>
}
}
function mapStateToProps(state) {
return {
details: state.details,
}
}
function mapDispatchToProps(dispatch) {
return {
HeaderAction: bindActionCreators(HeaderAction, dispatch),
}
}
export default connect(mapStateToProps, mapDispatchToProps)(HeaderContainer);
Component.js
componentDidMount() {
console.log(this.props.about);
}
You won't receive updated state as in props in componentDidMount rather you can use:
componentWillReceiveProps(nextProps){ // this is UN_SAFE
}
or
static getDerivedStateFromProps(nextProps, prevState) { // this is recommended
}
I am trying to get a simple react-redux app to work and I am running into a weird error that I can't figure out. I am trying to simply set my current user's first name and handle the store and one set function works and the other doesn't.
setCurrentUserFirstName - works
setCurrentUserHandle - doesn't
import React, { Component } from 'react';
import { Link } from 'react-router';
import { connect } from 'react-redux';
import store from '../../store';
var Utilities = require('../../../common/commonutilities.js');
var RestClient = require('../../../common/restClient.js');
//actions
import { setCurrentUserHandle, setCurrentUserFirstName } from '../../actions/userActions';
class Header extends Component {
constructor(props) {
super(props);
this.state = {};
RestClient.api.fetchGet('/getcurrentuser', (response) => {
if(response.success) {
this.setState({
isAuthenticated: true,
currentUser: response.currentUser
});
store.dispatch({
type: 'USER_DID_LOGIN',
userLoggedIn: true
});
//works fine
this.props.setCurrentUserFirstName(response.currentUser.firstName);
//doesn't work and throws the error: "TypeError: _this.props.setCurrentUserHandle is not a function"
this.props.setCurrentUserHandle(response.currentUser.handle);
}
},
(err) => {
console.log(err);
});
}
render() {
return (
{this.props.user.currentUserFirstName}, {this.props.user.currentUserHandle}
);
}
}
const mapStateToProps = function(store) {
return {
//user properties
user: store.userState
};
};
const mapDispatchToProps = (dispatch) => {
return{
setCurrentUserFirstName: (currentUserFirstName) =>{
dispatch( setCurrentUserFirstName(currentUserFirstName));
}
}
return{
setCurrentUserHandle: (currentUserHandle) =>{
dispatch( setCurrentUserHandle(currentUserHandle));
}
}
};
//connect it all
export default connect(mapStateToProps, mapDispatchToProps)(Header);
I have them as actions in the userActions.js file
export function setCurrentUserFirstName(currentUserFirstName){
return{
type: 'SET_CURRENT_USER_FIRST_NAME',
payload: currentUserFirstName
};
}
export function setCurrentUserHandle(currentUserHandle){
return{
type: 'SET_CURRENT_USER_HANDLE',
payload: currentUserHandle
};
}
And in the reducer
const initialUserState = {
user: {},
currentUserFirstName:[],
currentUserHandle:[]
};
// The User reducer
const userReducer = (state = initialUserState, action) => {
//using newState object to be immutable
let newState = state;
switch (action.type) {
case 'SET_CURRENT_USER_FIRST_NAME':
newState = {
...state,
currentUserFirstName: action.payload
};
break;
case 'SET_CURRENT_USER_HANDLE':
newState = {
...state,
currentUserHandle: action.payload
};
break;
break;
default:
break;
}
return newState;
};
export default userReducer;
What do I have incorrect?
You have 2 return statements in your mapDispatchToProps - the second one will never be reached. You can return a single object as follows:
const mapDispatchToProps = (dispatch) => {
return{
setCurrentUserFirstName: (currentUserFirstName) =>{
dispatch( setCurrentUserFirstName(currentUserFirstName));
},
setCurrentUserHandle: (currentUserHandle) =>{
dispatch( setCurrentUserHandle(currentUserHandle));
}
}
};
In addition to Tony's correct answer, I highly encourage that you use the "object shorthand" form of mapDispatch instead. You can pass an object full of action creators as the second argument to connect(), instead of an actual mapDispatch function. In your case, it'd look like:
import { setCurrentUserHandle, setCurrentUserFirstName } from '../../actions/userActions';
const actionCreators = { setCurrentUserHandle, setCurrentUserFirstName };
class Header extends Component {}
export default connect(mapStateToProps, actionCreators)(Header);