How to initialize state with api's data - reactjs

I'm creating a React/Redux app that fetches data from an api (pokeapi.co). I fetch data using axios. When I display data on react components, it results in an error that data is undefined. After some digging, I find that my state at first returns initial state which is empty object then it returns the api data. but it dont display on react. I'm new to React so I'm guessing it has to do with the axios asynchronous functionality. How do you set state with api's initial data or wait on rendering the data till state has api's data?
Here is the Reducer
function pokemonReducer(state={}, action) {
switch (action.type) {
case pokemonsActions.GET_POKEMON_SUCCESS:
{
return {...state, data: action.payload.data}
}
default:
{
return state;
}
}
}
export default pokemonReducer
Here is the action
export const GET_POKEMON_SUCCESS = 'GET_POKEMON_SUCCESS'
export const GET_POKEMON_ERROR = 'GET_POKEMON_ERROR'
function getPokemonSuccess(response) {
return {
type: GET_POKEMON_SUCCESS,
payload: response
}
}
function getPokemonError(err) {
return {
type: GET_POKEMON_ERROR,
payload: err
}
}
export function getPokemon() {
return (disp,getState) =>
{
return pokeAPI.getPokeAPI()
.then((response) => { disp(getPokemonSuccess(response))})
.catch((err)=> disp(getPokemonError(err)))
}
}
Store
const loggerMiddleware = createLogger()
const middleWare= applyMiddleware(thunkMiddleware,loggerMiddleware);
const store = createStore(rootReducer,preloadedState,
compose(middleWare, typeof window === 'object' && typeof window.devToolsExtension !== 'undefined'
? window.devToolsExtension() : (f) => f
))
const preloadedState=store.dispatch(pokemonActions.getPokemon())
export default store
in React component
function mapStateToProps(state) {
return {
pokemons:state.pokemons
}
}
class PokemonAbility extends React.Component {
render(){
return (
<div>
<div className="header">
<h1>Fetch Poke Api with axios</h1>
</div>
<main>
<h3> Display pokemons abilities </h3>
<p>{this.props.pokemons.data.count}</p>
</main>
</div>
)
}
}
export default connect(
mapStateToProps
)(PokemonAbility)
Api data example
{
"count": 292,
"previous": null,
"results": [
{
"url": "https://pokeapi.co/api/v2/ability/1/",
"name": "stench"
},
{
"url": "https://pokeapi.co/api/v2/ability/2/",
"name": "drizzle"
},
{
"url": "https://pokeapi.co/api/v2/ability/3/",
"name": "speed-boost"
}
],
"next": "https://pokeapi.co/api/v2/ability/?limit=20&offset=20"
}

You're rendering your component before the data has loaded. There are many strategies for dealing with this. In no particular order, here are some examples:
1. Short circuit the render
You can short circuit the render by returning a loading message if the data isn't there:
function mapStateToProps(state) {
return {
pokemons:state.pokemons
}
}
class PokemonAbility extends React.Component {
render(){
if (!this.props.pokemons.data) {
return (
<div>Loading...</div>
);
}
return (
<div>
<div className="header">
<h1>Fetch Poke Api with axios</h1>
</div>
<main>
<h3> Display pokemons abilities </h3>
<p>{this.props.pokemons.data.count}</p>
</main>
</div>
);
}
}
export default connect(mapStateToProps)(PokemonAbility);
2. Lift the data check to a parent component
You can move the mapStateToProps into a higher component, or abstract out the view component, and only render the view when the data is ready:
function mapStateToProps(state) {
return {
pokemons:state.pokemons
}
}
class SomeHigherComponent extends React.Component {
render(){
return (
this.props.pokemons.data ?
<PokemonAbility pokemons={this.props.pokemons} /> :
<div>Loading...</div>
);
}
}
3. Higher order component data checking
You could wrap your components in a "higher order component" (a function that takes a component class and returns a component class) to check if that prop exists before rendering:
function EnsurePokemon(ChildComponent) {
return class PokemonEnsureWrapper extends React.Component {
render() {
return (
this.props.pokemons.data ?
<ChildComponent {...this.props} /> :
<div>Loading...</div>
);
}
}
}
Usage:
export default connect(
mapStateToProps
)(EnsurePokemon(PokemonAbility))
And you can wrap any child component in this EnsurePokemon HOC to make sure it doesn't render until the data has loaded.

Related

Cannot read property 'some field in an object' of undefined in react-redux

in a component of my website with react and redux i'm using axios for to sent a request and the response is an array of objects.
and when i wanna use the property of objects in that array i get this error: Cannot read property 'courseName' of undefined
Dashboard.render
this is my component:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { getCourses } from '../../actions/coursesActions';
class Dashboard extends Component {
componentDidMount() {
this.props.getCourses();
}
render() {
const { user } = this.props.auth;
const { courses, loading } = this.props.course;
return (
<div>
<p>Dashboard: {user.name}</p>
<div className="card text-center">
{courses[1].courseTeacher}// i get the error here
</div>
</div>
);
}
}
Dashboard.propTypes = {
getCourses: PropTypes.func.isRequired,
auth: PropTypes.object.isRequired,
course: PropTypes.object.isRequired
};
const mapStateToProps = state => ({
course: state.course,
auth: state.auth
});
export default connect(mapStateToProps, { getCourses })(Dashboard);
when i console.log the courses array i get the array but when i console.log(courses.length) or console.log(courses[0]) i get errors
my courses array is like:
[
{
students: Array(1),
_id: "5d24aec630fe2799dd28b554",
courseName: "aaa",
courseTeacher: "aaa",
courseDetails: "review",
  …}
{
students: Array(0),
_id: "5d24af81e01d9599f4f4384a",
courseName: "a",
courseTeacher: "aaa",
courseDetails: "review",
  …}
length: 2
__proto__: Array(0)
I'm not sure about your initial state of the object but it seems like loading is not set inside the array. Also Axios returns it's value asynchronously, but the component will be rendered before the response was set inside redux. So you're accessing an empty object.
My approach is to have a loading state inside the component and use conditional rendering:
class Dashboard extends Component {
state = {
loading: true,
}
componentDidMount() {
this.initComponent()
}
initComponent = async () => {
await this.props.getCourses();
this.setState({loading: false})
}
render() {
const { user } = this.props.auth;
const { courses } = this.props.course;
const { loading } = this.state
return (
<div>
<p>Dashboard: {user.name}</p>
{!loading && (
<div className="card text-center">
{courses[1].courseTeacher}// i get the error here
</div>
)}
</div>
);
}
}
With this the courses first rendered when they are loaded. Maybe you're setting the loading flag inside your redux store. Then you don't need the local state and get it from the store but use it for conditional rendering anyways. Also you can show a spinner or an overlay while loading with {loading && (...)
You need to wait for the data to reach your component, you can display a message in the meantime. Have you tried something like this? It also displays errors?
class Dashboard extends Component {
componentDidMount() {
this.props.getCourses();
}
render() {
const { user } = this.props.auth;
const { courses, loading } = this.props.course;
return (
<div>
<p>Dashboard: {user.name}</p>
<div className="card text-center">
{!!(courses[1]) && courses[1].courseTeacher} // Just hide empty values
</div>
<div className="card text-center">
{courses.length > 0 ? courses[1].courseName : 'No data'} // Show a message
</div>
</div>
);
}
}

How to pass spinning bar to another component in ReactJS

I am in a scenario where I have to add a spinning bar in the component say,
List.js
class List extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
//spinning bar should be displayed here
</div>
);
}}
But the spinning bar should be displayed when another method in Actions(i.e redux) is called. So How will I pass this from actions.js to the render component in List.js
Actions.js
export const getList = (listInfo) => dispatch => {
//Spinning should start here
return application.getClientInfo(userInfo).then(
listInfo => {
//spinning should stop here
return dispatch(getListInfo(listInfo))
},
error => {
return dispatch(apologize('Error in getting application'))
}
)
}
getList and ListComponent is called in main.js
main.js
render() {
this.props.getClientApplication(this.props.user);
return (
<div>
<List />
</div>
);
}
So how will I add render method here that is actually to be displayed in list.js? Please help
In your reducer, keep a loading state and dispatch an action to set and clear loading states as and when you want
class List extends Component {
constructor(props) {
super(props);
}
render() {
const { isLoading } = this.props;
return (
<div>
//spinning bar should be displayed here
{isLoading && <Spinner>}
</div>
);
}
}
Actions.js
export const spinner = isLoading => {
return {
type: actionType.SPINNER, isLoading
}
}
export const getList = (listInfo) => dispatch => {
//dispatch loading action
dispatch(spinner(true));
return application.getClientInfo(userInfo).then(
listInfo => {
dispatch(spinner(false))
return dispatch(getListInfo(listInfo))
},
error => {
dispatch(spinner(false))
return dispatch(apologize('Error in getting application'))
}
)
}
Also make sure you aren't dispatching an action in render without using suspense
render() {
this.props.getClientApplication(this.props.user);
return (
<div>
<List isLoading={this.props.isLoading} />
</div>
);
}

Redux store updates in asynchronous function, but container does not get props changes

I' trying to make a real time application with react, redux and redux-thunk, that gets the objects from back-end through socket with STOMP over sockJS, and update redux store every time an object comes and finally updates the container when redux store updates.
My connect class through stomp over sockjs is this;
class SearcButtons extends Component {
render() {
return (
<div className="searchbuttons">
<RaisedButton className="bttn" label="Start" onClick={() => this.start_twitter_stream()} />
<RaisedButton className="bttn" label="Start" onClick={() => this.stop_twitter_stream()} />
</div>
);
}
start_twitter_stream() {
let stompClient = null;
var that = this;
let socket = new SockJS('http://localhost:3001/twitterStream');
stompClient = Stomp.over(socket);
stompClient.debug = null;
stompClient.connect({}, function () {
stompClient.subscribe('/topic/fetchTwitterStream', function (tokenizedTweet) {
let tweet = JSON.parse(tokenizedTweet.body);
let payload = {
data: {
tweets: that.props.state.reducer.tweets,
}
}
payload.data.tweets.push(
{
"username": tweet.username,
"tweet": tweet.tweet,
}
);
that.props.actions.update_tweets_data(payload);
});
stompClient.send("/app/manageTwitterStream", {}, JSON.stringify({ 'command': 'start', 'message': that.props.state.reducer.keyword }));
let payload = {
data: {
socketConnection: stompClient
}
}
that.props.actions.start_twitter_stream(payload);
});
}
stop_twitter_stream() {
var socketConnection = this.props.state.reducer.socketConnection;
socketConnection.send("/app/manageTwitterStream", {}, JSON.stringify({ 'command': 'stop', 'message': null }));
socketConnection.disconnect();
let payload = {
data: {
socketConnection: null
}
}
return this.props.actions.stop_twitter_stream(payload);
}
}
SearcButtons.propTypes = {
actions: PropTypes.object,
initialState: PropTypes.object
};
function mapStateToProps(state) {
return { state: state };
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(SearcButtons);
I'm calling tweet panel container inside App.js
import TweetPanel from './containers/TweetPanel';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
class App extends Component {
render() {
return (
<MuiThemeProvider>
<div className="main">
<TweetPanel />
</div>
</MuiThemeProvider>
);
}
}
export default App;
My container that listens redux-store is this;
class TweetPanel extends Component {
const TABLE_COLUMNS = [
{
key: 'username',
label: 'Username',
}, {
key: 'tweet',
label: 'Tweet',
},
];
render() {
console.log(this.props);
return (
<DataTables
height={'auto'}
selectable={false}
showRowHover={true}
columns={TABLE_COLUMNS}
data={
(typeof (this.props.state.reducer.tweets) !== "undefined" ) ?this.props.state.reducer.tweets : []
}
showCheckboxes={false}
onCellClick={this.handleCellClick}
onCellDoubleClick={this.handleCellDoubleClick}
onFilterValueChange={this.handleFilterValueChange}
onSortOrderChange={this.handleSortOrderChange}
page={1}
count={100}
/>
);
}
}
TweetPanel.propTypes = {
actions: PropTypes.object,
initialState: PropTypes.object
};
function mapStateToProps(state) {
return { state: state };
}
function mapDispatchToProps(dispatch) {
return {
actions: bindActionCreators(actions, dispatch)
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(TweetPanel);
My actions;
import {
BUILD_TWITTER_STREAM,
START_TWITTER_STREAM,
UPDATE_TWEETS_DATA,
} from '../actions/action_types';
export function build_twitter_stream(state) {
return {
type: BUILD_TWITTER_STREAM,
payload: state
};
}
export function start_twitter_stream(state) {
return {
type: START_TWITTER_STREAM,
payload: state
};
}
export function update_tweets_data(state) {
return {
type: UPDATE_TWEETS_DATA,
payload: state
};
}
My reducer;
import update from 'immutability-helper';
let initialState = {
socketConnection : null,
tweets : [ ]
}
export default function reducer(state = initialState, action) {
switch (action.type) {
case BUILD_TWITTER_STREAM:
return update(
state, {
socketConnection: { $set: action.payload.data.socketConnection }
}
);
case START_TWITTER_STREAM:
return update(
state, {
socketConnection: { $set: action.payload.data.socketConnection }
}
);
case UPDATE_TWEETS_DATA:
return update(
state, {
tweets: { $merge: action.payload.data.tweets }
}
);
default:
return state;
}
}
My observations are when I try to connect to socket through stomp over Sockjs, I need to pass the context as named "that" variable which you can see the first code block above and update redux store with that context in stompClient's connect function's callback, which means I update store in an asynchronou function, redux store updates very well when I look to Chrome' s extension of Redux devtools, but container doesn't update unless I press to the stop button which triggers an action which is not asynchronous.
Thanks in advance, your help is much appreciated :)
I can offer another approach, function delegate approach, since I have struggled by similar issiues. I create props in components, like your RaisedButton. For example I create BindStore props, such as:
<RaisedButton BindStore={(thatContext)=>{thatContext.state = AppStore.getState();}}... />
Also I can add subscriber props, such as:
<RaisedButton SubscribeStore={(thatContext) => {AppStore.subscribe(()=>{thatContext.setState(AppStore.getState())})}} ... />
At the RaisedButton.js, I can give thatContext easily:
...
constructor(props){
super(props);
if(this.props.BindStore){
this.props.BindStore(this);
}
if(this.props.SubscribeStore){
this.props.SubscribeStore(this);
}
}
...
Also by doing so, means by using props, one RaisedButton may not have BindingStore ability or SubscribeStore ability. Also by props I can call store dispatchers, such as:
in parent.js:
<RaisedButton PropsClick={(thatContext, thatValue) => {AppStore.dispacth(()=>
{type:"ActionType", payload:{...}})}} ... />
in RaisedButton.js
//as an example I used here dropDown, which is:
import { Dropdown } from 'react-native-material-dropdown';
//react-native-material-dropdown package has issues but the solutions are in internet :)
...
render(){
return(
<View>
<Dropdown onChangeText={(value) => {this.props.PropsClick(this, value);}} />
</View>
)
}
...
In many examples, for instance your parent is SearchButtons, the parent must be rendered, the parent must subscribe the store so when any child changes the store all component cluster is rerendered. But, by this approach, children components are subscribed and bound to the store. Even one child may dispatch an action and after that, other same type children subscribed function is called back and only the subscribed children is rerendered. Also, you will connect only one component to the redux store, the parent.
//parent.js
import { connect } from 'react-redux';
import AppStore from '../CustomReducer';
...
//you will not need to set your parent's state to store state.
I did not investigate very much about this approach but I do not use mapping functions. However, the child components will access all store datas, but also in mappings all store datas are also accessible.

React not reloading function in JSX

I am using react-redux.
I have the following JSX (only relevant snippets included):
getQuestionElement(question) {
if (question) {
return <MultiChoice questionContent={this.props.question.question} buttonClicked={this.choiceClicked} />
}
else {
return (
<div className="center-loader">
<Preloader size='big' />
</div>
)
}
}
render() {
return (
<div>
<Header />
{
this.getQuestionElement(this.props.question)
}
</div>
)
}
function mapStateToProps({ question }) {
return { question };
}
export default connect(mapStateToProps, questionAction)(App);
When the action fires, and the reducer updates the question prop
this.props.question
I expect
{this.getQuestionElement(this.props.question)}
to be reloaded and the new question rendered.
However this is not happening. Am I not able to put a function in this way to get it live reloaded?
My MultiChoice component:
import React, { Component } from 'react';
import ReactHtmlParser from 'react-html-parser';
import './questions.css';
class MultiChoice extends Component {
constructor(props) {
super(props);
this.state = {
question: this.props.questionContent.question,
answerArray : this.props.questionContent.answers,
information: null
}
this.buttonClick = this.buttonClick.bind(this);
}
createButtons(answerArray) {
var buttons = answerArray.map((element) =>
<span key={element._id} onClick={() => { this.buttonClick(element._id) }}
className={"span-button-wrapper-25 " + (element.active ? "active" : "")}>
<label>
<span>{element.answer}</span>
</label>
</span>
);
return buttons;
}
buttonClick(id) {
var informationElement;
this.props.buttonClicked(id);
var buttonArray = this.state.answerArray.map((element) => {
if (element._id === id ){
element.active = true;
informationElement = element.information;
return element;
}
else{
element.active = false;
return element;
}
});
this.setState({
answerArray: buttonArray,
information: informationElement
})
}
render() {
return (
<div className="question-container">
<div className="question-view">
<div className="icon-row">
<i className="fa fa-code" />
</div>
<div className="title-row">
{this.state.question}
</div>
<div className="button-row">
{this.createButtons(this.state.answerArray)}
</div>
<div className="information-row">
{ReactHtmlParser(this.state.information)}
</div>
</div>
</div>
);
}
}
export default MultiChoice;
QuestionAction.js
import axios from "axios";
import { FETCH_QUESTION } from "./types";
export const fetchQuestion = (questionId, answerId) => async dispatch => {
let question = null;
if (questionId){
question = await axios.get("/api/question/next?questionId=" + questionId + "&answerId=" + answerId);
}
else{
question = await axios.get("/api/question/next");
}
console.log("question", question);
dispatch({ type: FETCH_QUESTION, payload: question });
};
questionReducer.js
import {FETCH_QUESTION } from "../actions/types";
export default function(state = null, action) {
switch (action.type) {
case FETCH_QUESTION:
console.log("payload", action.payload.data);
return { question: action.payload.data, selected: false };
default:
return state;
}
}
index.js (Combined Reducer)
import { combineReducers } from 'redux';
import questionReducer from './questionReducer';
export default combineReducers({
question: questionReducer
});
and my entry point:
index.js
const store = createStore(reducers, {}, applyMiddleware(reduxThunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
registerServiceWorker();
requested console.log response:
render() {
console.log("Stackoverflow:", this.props.question)
.....
and after clicking the button (and the reducer updating, the console.log is updated, but the
this.getQuestionElement(this.props.question)
does not get re-rendered
MultiChoice Component shouldn't store his props in his state in the constructor, you have 2 options here :
Handle props changes in componentWillReceiveProps to update the state :
class MultiChoice extends Component {
constructor(props) {
super(props);
this.state = {
question: this.props.questionContent.question,
answerArray : this.props.questionContent.answers,
information: null
}
this.buttonClick = this.buttonClick.bind(this);
}
componentWillReceiveProps(nextProps) {
this.setState({
question: nextProps.questionContent.question,
answerArray : nextProps.questionContent.answers,
information: null
});
}
We have to keep using the constructor to set an initial state as from docs :
React doesn’t call componentWillReceiveProps() with initial props
during mounting.
2nd Option : Make it as a "dumb component" by having no state and only using his props to render something (some more deep changes in your component to do, especially to handle the "active" element, it will have to be handled by the parent component).

How can I make each child of component has a state? react redux

In my project, there are HomeIndexView and table component. So, when a user logs in to his account, in HomeIndexView, it shows all tables in the database. What I want to do is that make each table have a state so that it changes color of depends on its state(depends on child's state)... How can I do this?
My table component has a state like below.
const initialState = {
allTables: [],
showForm: false,
fetching: true,
formErrors: null,
};
EDIT ---1
HomeIndexView
class HomeIndexView extends React.Component {
componentDidMount() {
setDocumentTitle('Table_show');
}
componentWillunmount() {
this.props.dispatch(Actions.reset());
}
_renderAllTables() {
const { fetching } = this.props;
let content = false;
if(!fetching) {
content = (
<div className="tables-wrapper">
{::this._renderTables(this.props.tables.allTables)}
</div>
);
}
return (
<section>
<header className="view-header">
<h3>All Tables</h3>
</header>
{content}
</section>
);
}
_renderTables(tables) {
return tables.map((table) => {
return <Table
key={table.id}
dispatch={this.props.dispatch}
{...table} />;
});
}
render() {
return (
<div className="view-container tables index">
{::this._renderAllTables()}
</div>
);
}
}
EDIT--2
_handleClick () {
const { dispatch } = this.props;
const data = {
table_id: this.props.id,
};
if (this.props.current_order == null) {
dispatch(Actions.create(data));
Object.assign({}, this.state, {
tableBusy: true
});
}
else{
this.props.dispatch(push(`/orders/${this.props.current_order}`));
}
}
The state you shared above is part of the global state (where tableReducer use) not the table's component state, so what you need is to initialize component state in Table React component, so that you can check some values to render css differently something like this:
import React from "react";
class TableComponent extends React.Component {
componentWillMount() {
this.setInitialState();
}
setInitialState() {
this.setState({ isWhatever: false });
}
render() {
return (
<div>
<h1 classname={this.state.isWhatever ? 'css-class' : 'another-class'}>
{this.props.id}
</h1>
</div>
);
}
}

Resources