I want to access Redux Store State in presentational component, but the state is undefined.
I made Container Component, Presentational Component, and Redux Store.
So I tried to access user in presentational component but user is undefined.
I use Redux-Thunk when create Redux Store for asynchronous API request.
The doubful part is that Rdux Store State has a value but i can't access the state user in presentaional component
// headerState.js
import { handleActions } from 'redux-actions';
import * as api from '../lib/api';
const USER_STATE = 'headerState/USER_STATE';
const USER_STATE_SUCCESS = 'headerState/USER_STATE_SUCCESS';
const USER_STATE_FAILURE = 'headerState/USER_STATE_FAILURE';
const USER_LOGOUT = 'headerState/USER_LOGOUT';
const USER_LOGOUT_SUCCESS = 'headerState/USER_LOGOUT_SUCCESS';
const USER_LOGOUT_FAILURE = 'headerState/USER_LOGOUT_FAILURE';
export const getUserInfo = () => async (dispatch) => {
dispatch({ type: USER_STATE });
try {
const response = await api.getUser();
dispatch({
type: USER_STATE_SUCCESS,
payload: response.data,
});
} catch (error) {
dispatch({
type: USER_STATE_FAILURE,
payload: error,
error: true,
});
throw error;
}
};
export const userLogout = () => async (dispatch) => {
dispatch({ type: USER_LOGOUT });
try {
const response = await api.logout();
dispatch({
type: USER_LOGOUT_SUCCESS,
payload: response.data,
});
} catch (error) {
dispatch({
type: USER_LOGOUT_FAILURE,
payload: error,
error: true,
});
throw error;
}
};
const initialState = {
user: null,
loading: {
isProcessing: false,
},
};
const headerState = handleActions(
{
[USER_STATE]: (state) => ({
...state,
loading: {
...state.loading,
isProcessing: true,
},
}),
[USER_STATE_SUCCESS]: (state, action) => ({
...state,
user: action.payload,
loading: {
...state.loading,
isProcessing: false,
},
}),
[USER_STATE_FAILURE]: (state) => ({
...state,
loading: {
...state.loading,
isProcessing: false,
},
}),
[USER_LOGOUT]: (state) => ({
...state,
loading: {
...state.loading,
isProcessing: true,
},
}),
[USER_LOGOUT_SUCCESS]: (state, action) => ({
...state,
user: action.payload,
loading: {
...state.loading,
isProcessing: false,
},
}),
[USER_LOGOUT_FAILURE]: (state) => ({
...state,
loading: {
...state.loading,
isProcessing: false,
},
}),
},
initialState,
);
export default headerState;
// HeaderContainer.js
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import Header from './common/Header';
import { getUserInfo, userLogout } from '../modules/headerState';
const HeaderContainer = ({ user, getUserInfo, userLogout }) => {
useEffect(() => {
getUserInfo();
}, [getUserInfo]);
return <Header user={user} logout={userLogout} />;
};
const mapStateToProps = (state) => {
return {
user: state.user,
loading: state.loading,
};
};
export default connect(mapStateToProps, {
getUserInfo,
userLogout,
})(HeaderContainer);
// Header.js
(...)
const Header = ({ user, logout }) => { ** // user is undefined **
return (
<>
<TopNav>
<Link to="/" style={TitleLinkStyle}>
<NavTitle>
<img
src={logo}
width="50"
height="50"
color="white"
style={{ paddingRight: '25px', alignSelf: 'center' }}
alt="logo"
/>
Service Title
</NavTitle>
</Link>
<div style={{ display: 'flex', flex: 4 }} />
<SubNav>
<Link to="/home" style={{ textDecoration: 'none', color: 'white' }}>
<HomeDiv>Home</HomeDiv>
</Link>
{!user.snsId && (
<Link
to="/login"
style={{ textDecoration: 'none', color: 'white' }}
>
<LoginDiv>Login</LoginDiv>
</Link>
)}
{user.snsId && (
<LoginDiv onClick={logout} style={{ cursor: 'pointer' }}>
Logout
</LoginDiv>
)}
</SubNav>
</TopNav>
</>
);
};
export default Header;
There are two potential problems.
First, the initial value of user is null until USER_STATE_SUCCESS is dispatched. So trying to access user.snsId in your presentation component will cause a type error:
Cannot read property 'snsId' of undefined
You can use optional chaining to attempt to access snsId and simplify your logic.
// Header.js
const Header = ({ user }) => {
return user?.snsId ? <div>Logout</div> : <div>Login</div>;
};
The second issue potentially lies in how you are creating and accessing your store. I have assumed that you are doing something like this:
const reducers = combineReducers({
header: headerReducer
});
const store = createStore(reducers);
When using mapStateToProps, your container component needs to access that state slice by the same key with which it was created - header.
// HeaderContainer.js
const mapStateToProps = (state) => {
return {
user: state.header.user, // state.user is undefined
loading: state.header.loading // state.loading is undefined
};
};
Related
I am using react and redux to fetch data from an api, but when I try to retrieve the data in my view, I get an error :
TypeError: catData is undefined
I am having a hard time retrieving data from my api in my view, despite the fact that everything is fine on the backened. Any advise on what im doing wrong/ recommendations on how to retrieve data from the reducer will be highly appreciated.
My view looks like this
import React, { useState, useEffect } from "react";
import { connect} from "react-redux";
// #material-ui/core components
import Loader from 'react-loader-spinner'
import { fetchCategories } from "../../../actions/data"
const mapStateToProps = state => {
return {
catData: state.categories
}
}
const mapDispatchToProps = dispatch => {
return {
fetchCategories: () => dispatch(fetchCategories())
}
}
function CategoriesSection({ catData,fetchCategories}) {
useEffect(() => {
fetchCategories();
}, []);
return catData.loading ? (
<div xs={12} sm={10} md={10} lg={10} style={{marginTop: 10}} >
<Loader
type="Puff"
color="red"
height={200}
width={200}
style={{ display: "flex",
justifyContent: "center",
alignItems: "center" }}
/>
</div>
)
: catData.error ? (
<h2> {catData.error} </h2>
): (
<div>
<div className={classes.title} justify="center">
<h2 className={classes.title}>Our Categories</h2>
</div>
<div>
{
catData && catData.cat
}
</div>
</div>
)
}
export default connect(mapStateToProps, mapDispatchToProps)(CategoriesSection)
My reducer looks like this :
import {
CATEGORIES_FETCH_REQUEST,
CATEGORIES_SUCCESS,
CATEGORIES_FAIL
} from "../actions/types";
const initialState = {
loading: false,
categories: [],
businesses: [],
error: ''
}
export default function(state = initialState, action) {
switch(action.type) {
case CATEGORIES_FETCH_REQUEST:
return {
...state,
loading: true
};
case CATEGORIES_SUCCESS:
return {
...state,
loading: false,
categories: action.payload
};
case CATEGORIES_FAIL:
return {
loading: false,
categories: null,
error: action.payload
};
default:
return state;
}
}
And my actions looks like this :
import axios from 'axios';
import {
CATEGORIES_SUCCESS,
CATEGORIES_FAIL,
CATEGORIES_FETCH_REQUEST,
} from "./types";
export const fetchCategories = () => {
return (dispatch) => {
dispatch(fetchCategoryRequest)
axios.get('https://api.xxxxxxx.com/api/v1/categories/')
.then(response => {
const categories = response.data
dispatch(fetchCategorySuccess(categories))
})
.catch(error => {
const err = error.message
dispatch(fetchCategoryFailure(err))
})
}
}
const fetchCategoryRequest = () => {
return {
type: CATEGORIES_FETCH_REQUEST
}
}
const fetchCategorySuccess = categories => {
return {
type: CATEGORIES_SUCCESS,
payload: categories
}
}
const fetchCategoryFailure = err => {
return {
type: CATEGORIES_FAIL,
payload: err
}
}
I've been trying to create this search app where I can display the items in a table and delete items using react redux. However, on the initial load, the app shows a table but there is no data in the table. It's an empty table. If i search for another movie name which have more than one movie for that search term, then 2 tables would be shown but I want to show everything on the same table itself. The delete button is not working as well. Is there something wrong with my action and reducer files?
Action.js
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "./types";
const fetchMoviePendig = () => ({
type: FETCH_MOVIE_PENDING
});
const fetchMovieSuccess = json => ({
type: FETCH_MOVIE_SUCCESS,
payload: json
});
const fetchMovieError = error => ({
type: FETCH_MOVIE_ERROR,
payload: error
});
export const fetchMovie = name => {
return async dispatch => {
dispatch(fetchMoviePendig());
try {
const url = `https://jsonmock.hackerrank.com/api/movies/search/?Title=${name}`;
const response = await fetch(url);
const result = await response.json(response);
console.log(result);
dispatch(fetchMovieSuccess(result.data));
} catch (error) {
dispatch(fetchMovieError(error));
}
};
};
export const deleteEvent = id => async dispatch => {
try {
dispatch({
type: DELETE_MOVIE,
payload: id
});
} catch (err) {
console.log(err);
}
};
Reducer
import {
FETCH_MOVIE_PENDING,
FETCH_MOVIE_SUCCESS,
FETCH_MOVIE_ERROR,
DELETE_MOVIE
} from "../action/types";
const initialState = {
data: [],
loading: false,
error: ""
};
const moviesReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_MOVIE_PENDING:
return {
...state,
loading: true
};
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: [...state.data, action.payload]
};
case FETCH_MOVIE_ERROR:
return {
...state,
loading: false,
error: action.payload
};
case DELETE_MOVIE:
return {
...state,
data: state.data.filter(movie => movie.id !== action.payload)
};
default:
return state;
}
};
export default moviesReducer;
App.js
import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchMovie } from "./action/movieActions";
import Input from "./components/Input";
import MovieTable from "./components/MovieTable";
class App extends Component {
state = {
searchInput: "The Rain"
};
componentDidMount() {
this.props.getMovieList(this.state.searchInput);
}
_getMovie = () => {
this.props.getMovieList(this.state.searchInput);
};
_onChangeHandler = e => {
this.setState({
searchInput: e.target.value
});
console.log(this.state.searchInput);
};
render() {
const { data, loading } = this.props.movies;
return (
<div className="center">
<div>
<h2 className="center white-text">Movie Search</h2>
</div>
<div className="container">
<Input
value={this.state.searchInput}
onChange={this._onChangeHandler}
onClick={this._getMovie}
/>
<div className="row">
{loading ? (
<p>Loading</p>
) : (
data.map(item => (
<MovieTable
key={item.imdbID}
year={item.Year}
name={item.Title}
movieId={item.imdbId}
/>
))
)}
</div>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
movies: state.movies
};
};
const mapDispatchToProps = dispatch => {
return {
getMovieList: name => dispatch(fetchMovie(name))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
Hello please take a look at the sandbox : https://codesandbox.io/s/prod-wind-4hgq2?file=/src/App.js
I have edited
<MovieTable
data={data.map(d => ({
year: d.Year,
name: d.Title,
movieId: d.imdbId
}))}
/>
and
case FETCH_MOVIE_SUCCESS:
return {
...state,
loading: false,
data: action.payload
};
And ... Currently the delete button has no event, that's why it can't work
I see data having the following pattern:
Object {page: 1, per_page: 10, total: 1, total_pages: 1, data: Array[1]}
page: 1
per_page: 10
total: 1
total_pages: 1
data: Array[1]
0: Object
Title: "Sin in the Rain"
Year: 2006
imdbID: "tt1072449"
And you are accessing wrong properties in the component render logic, can you fix that.
Duplicate table is created the way you have written the logic.
Pass the data to MovieTable component and let it render and create the table
and fill it.
In reducer (FETCH_MOVIE_SUCCESS) you need don't need to append data you have to
replace or use the current movie data only.
Hey guys just moved to redux so in react what i was doing was in componentDidMount(), i was calling api and soon as i received the data i was setting loading to false (initially loading was true) to get rid of the 'react spinner',
but after using redux now in componentDidMount() i am calling my action creater which is in another and there i am receving my data so how do i manage 'loading' here ? can i somehow pass something from action creater to my component that triggers state and set loading to false ? or is there any other to do it ? How do you all manage it ?
here is my code
Home.js
class home extends Component {
UNSAFE_componentWillMount() {
this.props.verifyToken();
}
componentDidMount() {
this.props.categoryAction();
}
constructor(props) {
super(props);
this.state = {
categoriesWithTheirImages: [],
displayToggle: false,
loading: false,
};
}
renderCategory = () => {
return this.props.allCategories.map((item) => {
return (
<div
className="category_div"
key={item._id}
onClick={() => this.setState({ displayToggle: true })}
>
<img
src={item.image}
alt="miss-mistake"
className="category_image_home"
/>
<span className="category_heading_home">{item.categoryName}</span>
</div>
);
});
};
render() {
if (this.state.loading) {
return (
<div className="sweet-loading-main">
<FadeLoader
css={override}
sizeUnit={"px"}
size={50}
color={"#ff9d72"}
loading={this.state.loading}
/>
</div>
);
} else {
console.log(this.props.allCategories);
return (
<React.Fragment>
{/* <Fade left> */}
<Header />
<div className="main_content_homepage">
<p className="category_select">Please select a category</p>
<div className="category_list">{this.renderCategory()}</div>
</div>
{this.renderStoryActionDialog()}
{/* </Fade> */}
</React.Fragment>
);
}
}
}
const mapStateToProps = (state) => {
console.log(state);
const images = [family, ring, beer, feedback, academic];
let categoriesWithImages = state.getCategoryReducer.map((item, index) => {
item.image = images[index];
return item;
});
console.log(categoriesWithImages);
return { allCategories: categoriesWithImages };
};
export default connect(mapStateToProps, { verifyToken, categoryAction })(home);
and my action.js file
import { CATEGORY } from "../actionTypes";
export const categoryAction = ()=> {
return dispatch => {
fetch("http://localhost:3000/api/get_categories", {
method: "GET",
}).then(res=>res.json())
.then(response => {
console.log(response)
dispatch({ type: CATEGORY, payload: response });
})
.catch(err => console.log("Eror in adding", err));
};
};
reducer file
import { USER, CATEGORY} from "../actionTypes";
const getCategoryReducer = (state = [], action) => {
switch (action.type) {
case CATEGORY:
return action.payload;
default:
return state;
}
};
export default getCategoryReducer;
You should handle the loading state in your reducer file. At the moment, it's defined in your Component file. For e.g when you dispatch the action, it should update your loading state too. I would do something like this in reducer.
import { USER, FETCH_CATEGORY, FETCH_CATEGORY_SUCCESS, FETCH_CATEGORY_FAIL} from "../actionTypes";
const INITIAL_STATE = {
loading: false,
err: false,
data: []
}
const getCategoryReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case FETCH_CATEGORY:
return Object.assign({}, state, {
loading: true,
data: [],
})
case FETCH_CATEGORY_SUCCESS
return Object.assign({}, state, {
loading: false,
data: action.payload,
})
case FETCH_CATEGORY_FAIL
return Object.assign({}, state, {
loading: false,
data: action.payload,
err: true
})
default:
return state;
}
};
export default getCategoryReducer;
and your action file would look something like this
import { FETCH_CATEGORY, FETCH_CATEGORY_SUCCESS, FETCH_CATEGORY_FAIL } from "../actionTypes";
export const categoryAction = ()=> {
//setting loading to true
return dispatch => {
dispatch({ type: FETCH_CATEGORY });
fetch("http://localhost:3000/api/get_categories", {
method: "GET",
}).then(res=>res.json())
.then(response => {
//setting loading to false
dispatch({ type: FETCH_CATEGORY_SUCCESS, payload: response });
})
.catch(err => console.log("Eror in adding", err); dispatch({ type: FETCH_CATEGORY_FAIL, payload: err }););
};
};
You can then read the loading props in your Home.js
I'm trying to implement authentication with Python and React, and I have this error message on the front-end.
TypeError: Cannot read property 'loading' of undefined
And this is my SignIn.js
import React, { Component } from "react";
import { Button, Checkbox, Form, Icon, Input } from "antd";
import { Link, Redirect } from "react-router-dom";
import { connect } from "react-redux";
import { authLogin } from "../store/actions/auth";
class SignIn extends React.Component {
state = {
username: "",
password: ""
};
handleChange = e => {
this.setState({ [e.target.name]: e.target.value });
};
handleSubmit = e => {
e.preventDefault();
const { username, password } = this.state;
this.props.login(username, password);
};
render() {
const { getFieldDecorator } = this.props.form;
const { error, loading, token } = this.props;
const { username, password } = this.state;
if (token) {
return <Redirect to="/" />;
}
return (
<div className="gx-login-container">
<div className="gx-login-content">
<div className="gx-login-header gx-text-center">
<h1 className="gx-login-title">Sign In</h1>
</div>
{error && <p>{this.props.error.message}</p>}
<React.Fragment>
<Form onSubmit={this.handleSubmit} className="gx-login-form gx-form-row0">
{getFieldDecorator('email', {
rules: [{ required: true, message: 'Please input your email!' }],
})(
<Button type="primary" htmlType="submit" loading={loading} disabled={loading}>
Log in
</Button>
</Form>
</React.Fragment>
</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
loading: state.auth.loading,
error: state.auth.error,
token: state.auth.token
};
};
const mapDispatchToProps = dispatch => {
return {
login: (username, password) => dispatch(authLogin(username, password))
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(SignIn);
I have deleted the input part because I don't think that there is some problem. If someone think that the input part is the problem I will gladly post it.
Here is my reducers/auth.js
import * as actionTypes from "../actions/actionTypes";
import { updateObject } from "../utility";
const initialState = {
token: null,
error: null,
loading: false
};
const authStart = (state, action) => {
return updateObject(state, {
error: null,
loading: true
});
};
const authSuccess = (state, action) => {
return updateObject(state, {
token: action.token,
error: null,
loading: false
});
};
const authFail = (state, action) => {
return updateObject(state, {
error: action.error,
loading: false
});
};
const authLogout = (state, action) => {
return updateObject(state, {
token: null
});
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case actionTypes.AUTH_START:
return authStart(state, action);
case actionTypes.AUTH_SUCCESS:
return authSuccess(state, action);
case actionTypes.AUTH_FAIL:
return authFail(state, action);
case actionTypes.AUTH_LOGOUT:
return authLogout(state, action);
default:
return state;
}
};
export default reducer;
The errors says that it cannot find the property loading in an undefined object. Maybe your state.auth is null or undefined. Try logging the state.auth to check if it has a value.
I think I got your issue. You have initial state as,
const initialState = {
token: null,
error: null,
loading: false
};
and you are trying to get the state in mapStateToProps as,
const mapStateToProps = state => {
return {
loading: state.auth.loading,
error: state.auth.error,
token: state.auth.token
};
};
Here you are trying to access state using state.auth.loading, but you don't have auth object in your initial state and you get undefined error. You can fix this like,
const mapStateToProps = state => {
return {
loading: state.loading,
error: state.error,
token: state.token
};
};
Note: If your updateObject function returning state with auth object then you need to correct that. Your initial state and returned state should be of same pattern.
React/Redux application goes into an infinite loop on using useEffect with object references..
I am trying render pending todos for my application using useEffect.. and passing the array of todos as the second param in useEffect ..but why is not checking the values of the object ?
Container:
const mapDispatchToProps = dispatch => ({ actions: bindActionCreators(RootActions, dispatch) });
const Home = (props) => {
const { root, actions } = props;
useEffect(() => {
getTodos(actions.loadPendingTodo);
}, [root.data]);
return (
<Segment>
<Error {...root } />
<TodoList { ...root } actions={actions} />
</Segment>
);
};
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Action:
export const loadPendingTodo = () => ({
type: LOAD_PENDING_TODO,
data: todoService.loadPendingTodo(),
});
Reducer:
const initialState = {
initial: true,
data: [{
id: 0,
text: 'temp todo',
dueDate: new Date(),
completedDate: '',
isDeleted: false,
isCompleted: false,
}],
error: false,
isLoading: false,
isEdit: false,
};
export default function root(state = initialState, action) {
switch (action.type) {
case LOAD_PENDING_TODO:
return {
...state,
data: [...action.data],
};
...
default:
return state;
}
}
getTodos Method:
export const getTodos = (loadTodo) => {
try {
loadTodo();
} catch (error) {
console.log(error); // eslint-disable-line
}
};
Service:
export default class TodoAppService {
loadPendingTodo() {
return store.get('todoApp').data.filter(todo => !todo.isCompleted && !todo.isDeleted);
}
Can anyone please help me out how to resolve this issue.. and there is no official documentation for this case too :/
Moreover changing the useEffect to the following works but i want to render on every change
useEffect(() => {
getTodos(actions.loadPendingTodo);
}, []);
Fixed it by removing the loadPedningTodo redux actions in useEffect that was causing it to loop and directly setting the data in function from service..
const Home = (props) => {
const { root, actions } = props;
return (
<Segment>
<Error {...root } />
<TodoList isEdit={root.isEdit} todo={todoService.loadPendingTodo()} actions={actions} />
</Segment>
);
};
thanks :)