React Redux Reducer triggered but not changing state - reactjs

I am trying to set up app state with authenticated: true and user data.
Reducer gets to be triggered (I can see console.log) but it is returning initial state (isAuthenticated: false, user: {})
Thunk is working fine as far as I know
Props I am getting in component is {isAuthenticated: false, user{}}
I have done stuff like this before just like this so I am not sure why is this happening
import { AUTHENTICATED } from '../actions/types'
const initialState = {
isAuthenticated: false,
user: {}
}
export default function(state = initialState, action) {
switch (action.type) {
case AUTHENTICATED:
console.log(action.payload)
return {
...state,
isAuthenticated: true,
user: action.payload.user
}
default:
return state
}
}
action creator user.js
import axios from 'axios';
import history from '../history';
import config from '../config'
import { AUTHENTICATED } from './types';
export function authUser(token){
return function(dispatch){
const data = {"token": token}
axios.post(`${config.api_url}/authuser`, data)
.then((res) => {
dispatch({type: AUTHENTICATED, payload: res.data})
})
.catch((err) => console.log(err))
}
}
component dashboard.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import history from '../history';
import * as actions from '../actions/memberActions';
class Dashboard extends Component {
componentWillMount(){
const token = window.localStorage.getItem('token');
if(!token){
history.push('/')
}else{
this.props.authUser(token);
console.log(this.props.user);
}
};
render() {
return (
<div>
<h2>This will be a dashboard page</h2>
<p>There should be something here:{ this.props.authenticated }</p>
<h1>OK</h1>
</div>
)
}
}
function mapStateToProps(state){
return {
user: state.user
}
}
export default connect(mapStateToProps, actions)(Dashboard);

You're checking props.user in componentWillMount, which doesn't show your updates.
Instead check the state change in your render method, or in another life-cycle handler method like componentWillReceiveProps.

Your code should be something like this
export default function(state = initialState, action) {
switch (action.type) {
case AUTHENTICATED:
console.log(action.payload)
return state = {
...state,
isAuthenticated: true,
user: action.payload.user
}
default:
return state
}
}

From the looks of it, seems like your res.data object, in dispatch({type: AUTHENTICATED, payload: res.data}) does not have an user property.
So when you do user: action.payload.user you're basically saying user: undefined.
Please post your console.log(res.data) to see if this is the problem.

Related

How to make only a get request on compoment click

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();
}

React Redux initialState fetch from Api

I am using JWT auth, when the user log in I store the token in the localstorage. How do I fetch the api with that token, so I can get the user details when the page
loads for the first time.
I'm already using React Thunk for the async requests but I don't know how to set the initialState with an async request. However is it okay to set the localstorage in the reducers?
You would want to do something like this in your action:
import axios from 'axios';
export const LOADING = "LOADING";
export const SUCCESS = "SUCCESS";
export const FAILURE = "FAILURE";
export const UPDATE = "UPDATE";
export const SUCCESSFUL_UPDATE = "SUCCESSFUL_UPDATE";
export const getSmurfs = () => dispatch => {
dispatch({ type: LOADING })
axios.get('http://localhost:3333/smurfs')
.then(res => dispatch({ type: SUCCESS, payload: res.data}))
.catch(err => dispatch({ type: FAILURE, payload: err}))
}
So you would start with a state of Loading which would change to Success or Failure depending on the response. Then in your reducer you would want to do something like:
import { LOADING, SUCCESS, FAILURE, UPDATE, SUCCESSFUL_UPDATE } from '../actions/index';
const initialState = {
smurfs: [],
loading: false,
error: "",
updateID: "",
clicked: false,
update: []
}
export default function reducer(state= initialState, action) {
switch(action.type) {
case LOADING:
return {
...state,
smurfs: [],
loading: true,
err: ''
}
case SUCCESS:
return {
...state,
smurfs: action.payload,
loading: false,
err: ''
}
Basically when it is successful it will turn off the loading and display your returned data
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { getSmurfs, deleteSmurf, update } from '../actions/index';
import Smurfs from './smurfs';
import Form from './form';
import UpdateForm from './updateForm';
class SmurfsViewer extends Component {
componentDidMount() {
this.props.getSmurfs()
}
render() {
console.log(this.props.smurfs)
// if loading returns true then display loading smurfs..
if(this.props.loading) {
return (<h1>LOADING SMURFS....</h1>)
}
//if clicked resolves true then display the form to allow updating of the smurf that had its edit button clicked
let form;
if(this.props.clicked) {
form = <UpdateForm />
} else {
form = <Form />
}
return(
<div>
<Smurfs smurfs={this.props.smurfs} deleteSmurf={this.props.deleteSmurf} update={this.props.update}/>
{form}
</div>
)
}
}
const mstp = state => {
console.log("FROM VIEWER:", state)
return {
smurfs: state.smurfs,
loading: state.loading,
clicked: state.clicked
}
}
export default connect(mstp, { getSmurfs, deleteSmurf, update })(SmurfsViewer);
So you need to send the state from Redux through the mapStateToProps(mstp) and connect methods. Then you can use them in the component and it will update your redux state as needed. Then just refer to them as this.props.getSmurfs or something along those lines

React, Redux and Authentication - not finding state

I am trying to intergrate redux-auth-wrapper into the react boilerplate.
According to my debugging tools, the browser has the state set correctly (user: {data: null, isLoading: false}), however the App is saying that state is undefined.
mapStateToProps does seem to be given the state correctly?
Reducer:
containers/App/reducer.js:
import { fromJS } from 'immutable';
import {
USER_LOGGING_IN,
USER_LOGGED_IN,
USER_LOGGED_OUT,
} from '../../constants';
const userInitialState = fromJS({
data: null,
isLoading: false,
});
function userReducer(state = userInitialState, action) {
switch (action.type) {
case USER_LOGGING_IN:
return state
.set('isLoading', true);
case USER_LOGGED_IN:
return state
.set('data', action.payload)
.set('isLoading', false);
case USER_LOGGED_OUT:
return userInitialState;
default:
return state;
}
}
export default userReducer;
containers/App/index.js
import React from 'react';
import { Route, NavLink } from 'react-router-dom';
import { connect } from 'react-redux';
import { logout } from '../../actions/user';
import Home from '../../components/Home';
const getUserName = (user) => {
if (user.data) {
return `Welcome ${user.data.name}`;
}
return ('Not logged in');
};
const UserName = ({ user }) => (<div>{getUserName(user)}</div>)
function App({ user, logout }) {
return (
<div>
<h1>Test App</h1>
<div>
<nav>
<NavLink exact to="/">Home</NavLink>
</nav>
<nav>
</nav>
<div>
<Route exact path="/" component={Home} />
</div>
</div>
</div>
);
}
const mapStateToProps = (state) => ({
user: state.user, -- this is undefined when it should not be
});
export default connect(mapStateToProps, { logout })(App);
reducers.js:
/**
* Combine all reducers in this file and export the combined reducers.
*/
import { fromJS } from 'immutable';
import { combineReducers } from 'redux-immutable';
import { LOCATION_CHANGE } from 'react-router-redux';
import languageProviderReducer from 'containers/LanguageProvider/reducer';
import userReducer from 'containers/App/reducer';
/*
* routeReducer
*
* The reducer merges route location changes into our immutable state.
* The change is necessitated by moving to react-router-redux#5
*
*/
// Initial routing state
const routeInitialState = fromJS({
location: null,
});
/**
* Merge route into the global application state
*/
function routeReducer(state = routeInitialState, action) {
switch (action.type) {
/* istanbul ignore next */
case LOCATION_CHANGE:
return state.merge({
location: action.payload,
});
default:
return state;
}
}
/**
* Creates the main reducer with the dynamically injected ones
*/
export default function createReducer(injectedReducers) {
return combineReducers({
route: routeReducer,
language: languageProviderReducer,
user: userReducer,
...injectedReducers,
});
}
This is just the react boilerplate, but mixed with the react router 4 example (auth.js, constants.js, App/reducer.js, App/index.js etc)
State is an immutable object. For you to use it as an simple javacsript object, use .toJS().
const mapStateToProps = (immutableState) => {
const state = immutableState.toJS();
return {
user: state.user,
--this should work correctly now
};
};
Let me know if this solves.
const userInitialState = fromJS({
data: null,
isLoading: false,
});
would this not return state.data and state.isLoading?
const mapStateToProps = (state) => ({
user: state.user, -- this is undefined when it should not be
});
I don't see anywhere that you've declared 'user' as a key on your state object.

react-redux how to render response data from api

I followed one of the tutorial from youtube. When I do that it works completely fine. But when I try different api, I'm getting an error items are not defined. Can someone please help me to understand what I am doing wrong.
Thanks
//actions
import axios from 'axios'
export function fetchTweets(brandUrl, responseCode){
let url = brandUrl + '/api/offer/' + responseCode;
return function(dispatch){
axios.get(url)
.then((response) => {
dispatch({
type: 'FETCH_TWEETS_FULFILLED',
payload: response.data
})
})
.catch((error) => {
dispatch({
type: 'FETCH_TWEETS_REJECTED',
payload: error
})
})
}
}
//reducer
export default function reducer(state = {
tweets: [],
fetching: false,
fetched: false,
error: null
}, action) {
switch(action.type){
case 'FETCH_TWEETS_PENDING' :{
return { ...state, fetching: true }
}
case 'FETCH_TWEETS_REJECTED' : {
return { ...state, fetching: false, error: action.payload }
}
case 'FETCH_TWEETS_FULFILLED' : {
return { ...state,
fetching: false,
fetched: true,
tweets: action.payload }
}
}
return state
}
//main component
import React from 'react'
import { connect } from 'react-redux'
import { fetchTweets } from '../actions/tweetsActions'
class Layout extends React.Component{
fetchTweets(){
this.props.dispatch(fetchTweets(brandUrl, responseCode))
}
render(){
const { tweets } = this.props;
if(!tweets.length){
return <button value="Load" onClick={this.fetchTweets.bind(this)}>Load </button>
}
console.log(tweets.response.mainItems.length)
return (
<div>
<p>{tweets.statusMessage}</p>
<ul>
</ul>
</div>
);
}
}
function mapStateToProp(state){
return {
tweets : state.tweets.tweets
}
}
export default connect(mapStateToProp)(Layout)
//store
import { applyMiddleware, createStore } from 'redux'
import thunk from 'redux-thunk'
import promise from 'redux-promise-middleware'
import logger from 'redux-logger'
import reducer from './reducers'
const middleware = applyMiddleware(promise(), thunk, logger())
export default createStore(reducer, middleware)
// API Response
in main component's mapStateToProps function ,why you are accessing tweets by assigning state.tweets.tweets ,though you dont have reducer named tweets ,and you are not using combineReducer function (which one use in case of multiple reducer).
So you can easily access tweets by writing this.state.tweets .and you can print state in mapStateToProps before returning which might be helpful for debugging......
//main component
function mapStateToProp(state){
console.log(state,"=====>")
return {
tweets : state.tweets
}

React/Redux: how to hold login fail

I'm very beginner in React/Redux and I was trying to do something in an already existing code, just to understand how it works. The part I want to edit is the connection part. As it's now, if the login and password are OK, you go to a new page, and if not, it does nothing.
The only simple thing I'm trying to do is to show the user their login informations are wrong, by adding a red border on the fields for example.
So here is the code I added, I'll try not to show you useless code and not to forget useful code, but let me know if you need more.
The first thing I did is adding a constant for the error in actionTypes.js:
export const AUTH_REQUEST = 'AUTH_REQUEST';
export const AUTH_RECEIVE = 'AUTH_RECEIVE';
export const AUTH_ERROR = 'AUTH_ERROR';
Then in actions/auth.js, I added the authError function and called it after a fail response from the server:
function authRequest() {
return {
type: actionTypes.AUTH_REQUEST
};
}
function authReceive(authToken) {
return {
type: actionTypes.AUTH_RECEIVE,
authToken
};
}
function authError() {
return {
type: actionTypes.AUTH_ERROR
};
}
export function fetchLogin(email, password) {
return function (dispatch) {
dispatch(authRequest());
const urlApi = `//${AUTH_BACKEND_HOST}:${AUTH_BACKEND_PORT}/${AUTH_BACKEND_URL.login}`
fetch(urlApi, {
method: 'POST',
headers: {
'Accept': 'application/json',
'content-type': 'application/json'
},
body: JSON.stringify({
email,
password
})
})
.then((response) => {
if(response.ok) {
// SUCCESS
response.json().then(function(json) {
dispatch(authReceive(json.key));
dispatch(push('/'));
});
} else {
// FAIL
response.json().then(function(json) {
dispatch(authError());
});
}
})
.catch(function(ex) {
console.log(ex);
});
};
}
Now, in reducers/auth.js:
const initialState = {
authToken: '',
isFetching: false,
error: false,
errorMessage: ''
}
export default function (state=initialState, action) {
switch (action.type) {
case actionType.AUTH_REQUEST:
return {
...state,
isFetching: true
};
case actionType.AUTH_RECEIVE:
return authLogin(state, action);
case actionType.AUTH_ERROR:
return {
...state,
error: true,
errorMessage: 'Incorrect login or password!'
};
}
return state;
}
function authLogin(state, action) {
const { authToken } = action;
return {
...state,
isFetching: false,
authToken
};
}
Until now, it seems to work when I inspect it in Firefox. The state contains the error and errorMessage values.
So here is my components/Login/presenter.jsx which I thought was going to display the right HTML depending on the state:
import React from 'react';
const Login = React.createClass({
handleSubmit(event) {
event.preventDefault()
const email = this.refs.email.value
const password = this.refs.password.value
this.props.onAuth(email, password);
},
render() {
const { errorMessage } = this.props
return (
<form onSubmit={this.handleSubmit}>
<label>Email <input ref="email" placeholder="email" required /></label>
<label>Password <input ref="password" placeholder="password" type="password" required /></label><br />
<p>{errorMessage}</p>
<button type="submit">Login</button>
</form>
)
}
});
export default Login;
And here is components/Login/index.js which I think imports the presenter and do... things...:
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Login from './presenter';
function mapDispatchToProps(dispatch) {
return {
onAuth: bindActionCreators(actions.fetchLogin, dispatch)
};
}
export default connect(null, mapDispatchToProps) (Login);
Edit : it seems that one of the problems is that I'm not mapping the state to props. I tried Mael Razavet and azium's answers, adding mapStateToProps in Login/index.js:
import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actions from '../../actions';
import Login from './presenter';
function mapDispatchToProps(dispatch) {
return {
onAuth: bindActionCreators(actions.fetchLogin, dispatch)
};
}
function mapStateToProps (state) {
return {
errorMessage: state.errorMessage
};
}
export default connect(mapStateToProps, mapDispatchToProps) (Login);
But it seems that errorMessage is still undefined.
Thank you.
I think you forgot to map your state to props. In your case, you should add this content to your components/Login/index.js:
import * as actions from './actions/auth.js';
import Login from './presenter';
const mapStateToProps = (state) => {
return {
error: state.login.error,
errorMessage: state.login.errorMessage,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onAuth: (email, password) => {
dispatch(actions.fetchLogin(email, password));
}
}
}
export default connect(mapStateToProps, mapDispatchToProps)(Login); // maps your state to your component's props
In your reducers/auth.js:
const initialState = {
authToken: '',
isFetching: false,
error: false,
errorMessage: ''
}
export default function loginReducer(state=initialState, action) {
switch (action.type) {
case actionType.AUTH_REQUEST:
return {
...state,
isFetching: true
};
case actionType.AUTH_RECEIVE:
return authLogin(state, action);
case actionType.AUTH_ERROR:
return {
...state,
error: true,
errorMessage: 'Incorrect login or password!'
};
}
return state;
}
function authLogin(state, action) {
const { authToken } = action;
return {
...state,
isFetching: false,
authToken
};
}
Then, in your code, you should be combining your reducer like:
import { combineReducers, createStore, applyMiddleware } from 'redux';
import { Provider } from 'react-redux';
import Login from './components/Login';
import login from './reducers/auth.js'; //import your default reducer function
//COMBINE REDUCERS
const reducers = combineReducers({
//reducers go here
login, //name of your reducer => this is is why your access your state like state.login
});
//WRAP WITH STORE AND RENDER
const createStoreWithMiddleware = applyMiddleware()(createStore);
ReactDOM.render(
<Provider store={createStoreWithMiddleware(reducers)}>
<Login/>
</Provider>
, document.querySelector('.container'));
In Redux, you manage your state (setState) in a different layer (reducer) than your actual component. To do so, you need to map your state from the reducer to the component so you can use it as a props. This is why in your Login class, you are able to do :
const { errorMessage } = this.props; // now you can use errorMessage directly or this.props.errorMessage
This errorMessage comes from your state managed in your reducer and can be used in your component as this.props.errorMessage.
Here is the link to the tutorial which helped me understand Redux in React : https://github.com/happypoulp/redux-tutorial
It should help you understand better the workflow

Resources