I created a chart in React app with react-chartjs-2. I have 2 items on the homepage: "chart1" and "chart2". When I click on the "chart1" - chart displays with no data. After second click, data are rendering correctly. Next I want to render "chart2", but after click on that item, "chart1" renders. After second click, data for "chart2" are rendering correctly.
Do you know what is the problem? I want to render every chart after first click.
Chart.js
import React, { useEffect, useState } from 'react';
import { connect } from 'react-redux';
import { Line } from 'react-chartjs-2';
import { getChartData } from '../../actions/sensors';
const Chart = (props) => {
const [chartDataState, setChartDataState] = useState({});
const createChart = () => {
const id = props.match.params.id;
props.getChartData(id);
setChartDataState({
labels: props.chartData.map(data => {
const date = new Date(data.delivery_time).toLocaleDateString();
const time = new Date(data.delivery_time).toLocaleTimeString();
return `${time} | ${date}`;
}),
datasets: [
{
data: props.chartData.map(data => data.sensor_data),
fill: false,
backgroundColor: '#987316',
borderColor: 'rgba(152, 115, 22, 0.2)',
},
],
})
}
useEffect(() => {
createChart();
}, [])
return (
<div className="container">
<div className="head" style={{'marginBottom': '30px'}}>
<h2>Chart</h2>
<div className="line" style={{"width": "900px"}}></div>
</div>
<div className="chart">
<Line
data={chartDataState}
options={{
maintainAspectRatio: false,
legend: {
display: false,
}
}}
height={400}
/>
</div>
</div>
);
}
const mapStateToProps = state => ({
chartData: state.sensors.chartData,
})
export default connect(mapStateToProps, { getChartData })(Chart);
sensors (Redux action)
export const getChartData = id => (dispatch, getState) => {
const token = getState().auth.token;
const config = {
headers: {
'Content-Type': 'application/json',
'Authorization': `Token ${token}`
}
}
axios.get(`${baseURL}/sensors_data/list/${id}`, config)
.then(res => {
dispatch({
type: GET_CHART_DATA,
payload: res.data
})
})
.catch(err => console.log(err))
}
sensors (Redux reducer)
import { GET_CHART_DATA } from '../actions/types';
const initialState = {
chartData: [],
}
export default function(state = initialState, action) {
switch(action.type) {
case GET_CHART_DATA:
return {
...state,
chartData: action.payload
}
default:
return state;
}
When you first click, you trigger a request for the data, but your component does not wait for that data before rendering.
I would recommend adding a condition to your return. This is how I would have done it without Redux (I've never used Redux)
return (
<div className="container">
<div className="head" style={{'marginBottom': '30px'}}>
<h2>Chart</h2>
<div className="line" style={{"width": "900px"}}></div>
</div>
<div className="chart">
{chartDataState.length > 0 ?
<Line
data={chartDataState}
options={{
maintainAspectRatio: false,
legend: {
display: false,
}
}}
height={400}
/>
: <div>Loading...</div>}
</div>
</div>
);
Basically: you don't want your component to render the chart before the data is returned.
Related
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
};
};
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
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 :)