keys in object are undefined but object is not (react-redux) - reactjs

So I have a strange issue with the data I'm fetching from an API. In my reducer I've set the initial state to an empty object and in my component's render method I'm accessing this fetched data using this.props.data.value.one.
The actual fetched data from the API look like this:
{data:
{value: {one: '1'}}
}
However when I try to console.log(this.props.data) it will log {value: {one: '1'}} after logging undefined a couple of times but will log this.props.data.value.one as undefined.
I'm doing similar stuff in my other reducers which are getting data from other API's and they work fine.
App.js Component:
#connect(store => store)
...
componentWillMount = () => {//API Call here}
render = () => {
return (
<div><p>{this.props.data.value.one}</p><div>
)
}
reducer.js:
...
export default function reducer(state={data: {}}, action) {
...
case 'FETCHED_DATA':
return {...state, data: action.data}
action.js:
...
fetchData = user => dispatch => {
//request.get...
.then(res => dispatch({type: 'FETCHED_DATA', data: res.body.data})
}

Since you set you initial state to be an empty object, and the data you get is though an API call which is async, so when the component renders first time this.props.data is undefined.
What you need to do is check if the data is available before using it
Add a conditional check like
this.props.data && this.props.data.value.one

Since you have defined initial state as data: {}, and not as data : {value : ''}, and thus when you fetched data from the api data object is not initialized as data.value to access it. Setting it to the latter should work.

There are 2 issues i see here:
As mentioned in other answers and comments, you should always
conditionally access objects that are fetched asynchronously.
this.props.myObj && this.props.myObj.myValue
By the way, don't fetch inside componentWillMount, instead do it
in componentDidMount. You can read about the reasons for this in
the DOCS.
Avoid introducing any side-effects or subscriptions in this method.
For those use cases, use componentDidMount() instead.
By the look of the shape of your reducer (state) it seems that the
data object is nested inside the reducer:
state={data: {}}
So you can't access it directly from props (unless you are changing
the shape in mapStateToProps).
So i would expect to see this:
this.props.reducerName.data.value.one
instead of this:
this.props.data.value.one

Related

How read array/object from get Axios in React function [duplicate]

I have recently moved from Angular to ReactJs. I am using jQuery for API calls. I have an API which returns a random user list that is to be printed in a list.
I am not sure how to write my API calls. What is best practice for this?
I tried the following but I am not getting any output. I am open to implementing alternative API libraries if necessary.
Below is my code:
import React from 'react';
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
UserList(){
return $.getJSON('https://randomuser.me/api/')
.then(function(data) {
return data.results;
});
}
render() {
this.UserList().then(function(res){
this.state = {person: res};
});
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">
{this.state.person.map((item, i) =>{
return(
<h1>{item.name.first}</h1>
<span>{item.cell}, {item.email}</span>
)
})}
<div>
</div>
)
}
}
In this case, you can do ajax call inside componentDidMount, and then update state
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
this.UserList();
}
UserList() {
$.getJSON('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
render() {
const persons = this.state.person.map((item, i) => (
<div>
<h1>{ item.name.first }</h1>
<span>{ item.cell }, { item.email }</span>
</div>
));
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{ persons }</div>
</div>
);
}
}
You may want to check out the Flux Architecture. I also recommend checking out React-Redux Implementation. Put your api calls in your actions. It is much more cleaner than putting it all in the component.
Actions are sort of helper methods that you can call to change your application state or do api calls.
Use fetch method inside componentDidMount to update state:
componentDidMount(){
fetch('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
This discussion has been for a while and #Alexander T.'s answer provided a good guide to follow for newer of React like me. And I'm going to share some additional know-how about calling the same API multiple times to refresh the component, I think it's probably a common question for beginners.
componentWillReceiveProps(nextProps), from official documentation :
If you need to update the state in response to prop changes (for
example, to reset it), you may compare this.props and nextProps and
perform state transitions using this.setState() in this method.
We could conclude that here is the place we handle props from the parent component, have API calls, and update the state.
Base on #Alexander T.'s example:
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
//For our first load.
this.UserList(this.props.group); //maybe something like "groupOne"
}
componentWillReceiveProps(nextProps) {
// Assuming parameter comes from url.
// let group = window.location.toString().split("/")[*indexParameterLocated*];
// this.UserList(group);
// Assuming parameter comes from props that from parent component.
let group = nextProps.group; // Maybe something like "groupTwo"
this.UserList(group);
}
UserList(group) {
$.getJSON('https://randomuser.me/api/' + group)
.then(({ results }) => this.setState({ person: results }));
}
render() {
return (...)
}
}
Update
componentWillReceiveProps() will be deprecated.
Here are only some methods (all of them in Doc) in the life cycle I think that they are related to deploying API in the general cases:
By referring to the diagram above:
Deploy API in componentDidMount()
The proper scenario to have API call here is that the content (from the response of API) of this component will be static, componentDidMount() only fire once while the component is mounting, even new props are passed from the parent component or have actions to lead re-rendering.
The component do check difference to re-render but not re-mount.
Quote from doc:
If you need to load data from a remote endpoint, this is a good place to
instantiate the network request.
Deploy API in static getDerivedStateFromProps(nextProps, prevState)
We should notice that there are two kinds of component updating, setState() in current component would not trigger this method but re-rendering or new props from parent component would.
We could find out this method also fires while mounting.
This is a proper place to deploy API if we want to use the current component as a template, and the new parameters to make API calls are props coming from parent component.
We receive a different response from API and return a new state here to change the content of this component.
For example:
We have a dropdown list for different Cars in the parent component, this component needs to show the details of the selected one.
Deploy API in componentDidUpdate(prevProps, prevState)
Different from static getDerivedStateFromProps(), this method is invoked immediately after every rendering except the initial rendering. We could have API calling and render difference in one component.
Extend the previous example:
The component to show Car's details may contain a list of series of this car, if we want to check the 2013 production one, we may click or select or ... the list item to lead a first setState() to reflect this behavior (such as highlighting the list item) in this component, and in the following componentDidUpdate() we send our request with new parameters (state). After getting the response, we setState() again for rendering the different content of the Car details. To prevent the following componentDidUpdate() from causing the infinity loop, we need to compare the state by utilizing prevState at the beginning of this method to decide if we send the API and render the new content.
This method really could be utilized just like static getDerivedStateFromProps() with props, but need to handle the changes of props by utilizing prevProps. And we need to cooperate with componentDidMount() to handle the initial API call.
Quote from doc:
... This is also a good place to do network requests as long as you
compare the current props to previous props ...
I would like you to have a look at redux
http://redux.js.org/index.html
They have very well defined way of handling async calls ie API calls, and instead of using jQuery for API calls, I would like to recommend using fetch or request npm packages, fetch is currently supported by modern browsers, but a shim is also available for server side.
There is also this another amazing package superagent, which has alot many options when making an API request and its very easy to use.
You can also fetch data with hooks in your function components
full example with api call: https://codesandbox.io/s/jvvkoo8pq3
second example: https://jsfiddle.net/bradcypert/jhrt40yv/6/
const Repos = ({user}) => {
const [repos, setRepos] = React.useState([]);
React.useEffect(() => {
const fetchData = async () => {
const response = await axios.get(`https://api.github.com/users/${user}/repos`);
setRepos(response.data);
}
fetchData();
}, []);
return (
<div>
{repos.map(repo =>
<div key={repo.id}>{repo.name}</div>
)}
</div>
);
}
ReactDOM.render(<Repos user="bradcypert" />, document.querySelector("#app"))
1) You can use Fetch API to fetch data from Endd Points:
Example fetching all Github repose for a user
/* Fetch GitHub Repos */
fetchData = () => {
//show progress bar
this.setState({ isLoading: true });
//fetch repos
fetch(`https://api.github.com/users/hiteshsahu/repos`)
.then(response => response.json())
.then(data => {
if (Array.isArray(data)) {
console.log(JSON.stringify(data));
this.setState({ repos: data ,
isLoading: false});
} else {
this.setState({ repos: [],
isLoading: false
});
}
});
};
2) Other Alternative is Axios
Using axios you can cut out the middle step of passing the results of
the http request to the .json() method. Axios just returns the data
object you would expect.
import axios from "axios";
/* Fetch GitHub Repos */
fetchDataWithAxios = () => {
//show progress bar
this.setState({ isLoading: true });
// fetch repos with axios
axios
.get(`https://api.github.com/users/hiteshsahu/repos`)
.then(result => {
console.log(result);
this.setState({
repos: result.data,
isLoading: false
});
})
.catch(error =>
this.setState({
error,
isLoading: false
})
);
}
Now you can choose to fetch data using any of this strategies in componentDidMount
class App extends React.Component {
state = {
repos: [],
isLoading: false
};
componentDidMount() {
this.fetchData ();
}
Meanwhile you can show progress bar while data is loading
{this.state.isLoading && <LinearProgress />}
Render function should be pure, it's mean that it only uses state and props to render, never try to modify the state in render, this usually causes ugly bugs and decreases performance significantly. It's also a good point if you separate data-fetching and render concerns in your React App. I recommend you read this article which explains this idea very well. https://medium.com/#learnreact/container-components-c0e67432e005#.sfydn87nm
This part from React v16 documentation will answer your question, read on about componentDidMount():
componentDidMount()
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. This method is a good place to set up
any subscriptions. If you do that, don’t forget to unsubscribe in
componentWillUnmount().
As you see, componentDidMount is considered the best place and cycle to do the api call, also access the node, means by this time it's safe to do the call, update the view or whatever you could do when document is ready, if you are using jQuery, it should somehow remind you document.ready() function, where you could make sure everything is ready for whatever you want to do in your code...
As an addition/update to Oleksandr T.'s excellent answer:
If you use class components, backend calls should happen in componentDidMount.
If you use hooks instead, you should use the effect hook
For example:
import React, { useState, useEffect } from 'react';
useEffect(() => {
fetchDataFromBackend();
}, []);
// define fetchDataFromBackend() as usual, using Fetch API or similar;
// the result will typically be stored as component state
Further reading:
Using the Effect Hook in the official docs.
How to fetch data with React Hooks? by Robin Wieruch
A clean way is to make an asynchronous API call inside componentDidMount with try/catch function.
When we called an API, we receive a response. Then we apply JSON method on it, to convert the response into a JavaScript object. Then we take from that response object only his child object named "results" (data.results).
In the beginning we defined "userList" in state as an empty array. As soon as we make the API call and receive data from that API, we assign the "results" to userList using setState method.
Inside the render function we tell that userList will be coming from state. Since the userList is an array of objects we map through it, to display a picture, a name and a phone number of each object "user". To retrieve this information we use dot notation (e.g. user.phone).
NOTE: depending on your API, your response may look different. Console.log the whole "response" to see which variables you need from it, and then assign them in setState.
UserList.js
import React, { Component } from "react";
export default class UserList extends Component {
state = {
userList: [], // list is empty in the beginning
error: false
};
componentDidMount() {
this.getUserList(); // function call
}
getUserList = async () => {
try { //try to get data
const response = await fetch("https://randomuser.me/api/");
if (response.ok) { // ckeck if status code is 200
const data = await response.json();
this.setState({ userList: data.results});
} else { this.setState({ error: true }) }
} catch (e) { //code will jump here if there is a network problem
this.setState({ error: true });
}
};
render() {
const { userList, error } = this.state
return (
<div>
{userList.length > 0 && userList.map(user => (
<div key={user}>
<img src={user.picture.medium} alt="user"/>
<div>
<div>{user.name.first}{user.name.last}</div>
<div>{user.phone}</div>
<div>{user.email}</div>
</div>
</div>
))}
{error && <div>Sorry, can not display the data</div>}
</div>
)
}}
As best place and practice for external API calls is React Lifecycle method componentDidMount(), where after the execution of the API call you should update the local state to be triggered new render() method call, then the changes in the updated local state will be applied on the component view.
As other option for initial external data source call in React is pointed the constructor() method of the class. The constructor is the first method executed on initialization of the component object instance. You could see this approach in the documentation examples for Higher-Order Components.
The method componentWillMount() and UNSAFE_componentWillMount() should not be used for external API calls, because they are intended to be deprecated. Here you could see common reasons, why this method will be deprecated.
Anyway you must never use render() method or method directly called from render() as a point for external API call. If you do this your application will be blocked.
You must try "axios" library for API call.
Instead of direct using jQuery.
Thanks.
It would be great to use axios for the api request which supports cancellation, interceptors etc. Along with axios, l use react-redux for state management and redux-saga/redux-thunk for the side effects.

React Redux and Fetch

I have React app with Redux that has following structure:
<ComponentParent>
<ComponentA></ComponentA>
<ComponentB></ComponentB>
</ComponentParent>
In component A an ComponentDidMount, a fetch is called and data is return async-ly. Reducer is then called to add data to the store.
Component B then accesses the store to access data added by A to the store.
Predictably Component B accesses the data before Component A had a change to write data to the store (because data is coming from aync fetch).
Question:
what is a proper way to design such interaction?
Do I need use
approach similar to
react redux with asynchronous fetch
? Note that in Reducer I just store data returned async-ly by
Component A, unlike in the link
Thanks
Set a default state to your componentB for it to load while awaiting results from your fetch.
In your fetch action, assuming you use redux-thunk:
let fetchData = () => async dispatch => {
let res = await fetchFromDataSource();
dispatch({
type: UPDATE_STATE,
payload: res
})
};
Your component B should be linked up to the store. Upon dispatch update, it should trigger your componentB to reload via ComponentDidUpdate.
I like the pattern of creating an initial state for the object in the reducer, so that any component accessing it gets that initial state first, and can later update based on a post-fetch state.
xReducer.js
const initState = {
// Initial state of object
};
export default function xReducer(state=initState, action) {
switch (action.type) {
case actionTypes.MY_POST_FETCH_ACTION_TYPE:
return {
...state,
// state override
};
default:
return state;
}
}

React Redux | setState using mapStateToProps

I want to set the local state of a component using props obtained from mapStateToProps. When I use this.setState in ComponentWillMount() method or ComponentDidMount() method, it is undefined.
componentWillMount() {
this.props.fetchBooks(this.props.id);
this.setState({
books:this.props.books
})
}
const mapStateToProps = (state, ownProps) => {
let id = ownProps.match.params.book_id;
return {
id: id,
books: state.library.books,
};
};
The 'books' data is retrieved using the fetchBooks() function obtained by mapDispatchToProps.
How can I set the sate using the props obtained from mapStateToProps?
The problem might be that this.props.fetchBooks is an asynchronous operation. In that case, this.setState is called before the operation is completed, hence the undefined value.
But why do you want to store the books in the local state? Why don't you use directly the books prop instead?
Using derived state is usually unnecessary, and might be a bad idea (see this blog post from the official React website for an in-depth explanation).
If you really need to store the books in the local state, you could:
if fetchBooks returns a promise, use then or await (and move the code in componentDidUpdate as suggested by #Baruch)
use getDerivedStateFromProps
Your componentWillMount lifecycle method will only fire once, so using it to call the function that fetches the data and also use the data will most likely not work. You should use componentWillReceiveProps instead.
constructor(props) {
props.fetchBooks(props.id);
}
componentWillReceiveProps(nextProps) {
if (nextProps.books != this.props.books) {
this.setState({books: nextProps.books});
}
}
You better use straight inside your render() {} this.props.assignedSupervisors instead of putting it in a class state.
Otherwise use
componentDidUpdate(prevProps, prevState) {
if (this.props.assignedSupervisors && this.props.assignedSupervisors !== prevProps.assignedSupervisors) {
this.setState({
supervisors:this.props.assignedSupervisors
})
}
}

componentWillReceiveProps not called after redux dispatch

I'm building a react native app and using redux to handle the state. I am running into a situation where one of my containers is not updating immediately when the redux state is changed.
Container:
...
class ContainerClass extends Component<Props, State> {
...
componentWillReceiveProps(nextProps: Object) {
console.log('WILL RECEIVE PROPS:', nextProps);
}
...
render() {
const { data } = this.props;
return <SubComponent data={data} />
}
}
const mapStateToProps = (state) => ({
data: state.data
};
export default connect(mapStateToProps)(ContainerClass);
Reducer:
...
export default function reducer(state = initalState, action) => {
switch(action.type) {
case getType(actions.actionOne):
console.log('SETTING THE STATE');
return { ...state, data: action.payload };
...
...
...
In a different random component, I am dispatching a call with the actionOne action, which I confirm prints out the relevant console.log. However, the console.log in the componentWillReceiveProps in the container is not printed.
The component that dispatches the call is a modal that has appeared over the Container, and closes automatically after the call is dispatched and the state is updated. What is weird is that although the Container isn't updated immediately, if I navigate to a different page and then back to the Container page, the state is in fact updated.
EDIT: Initial state is:
const initialState: Store = {
data: []
}
And the way I dispatch is in a different component which gets called as a new modal (using react-native-navigation) from Container:
fnc() {
...
setData(data.concat(newDatum));
...
}
Where setData and data are the redux dispatch action and the part of the store respectively that is passed in on props from the Container (which has setData and data through mapStateToProps shown above and a mapDispatchToProps which I didn't show).
I solved my problem by updating from react-native v0.56 to v0.57. Apparently there was a problem with react-redux v6 working properly in the react-native v0.56 environment.
Assuming you're using a recent version of React, componentWillReceiveProps is actually deprecated:
Using this lifecycle method often leads to bugs and inconsistencies
You can't really rely on that lifecycle hook in a number of situations. You may want to look at a slightly different approach with componentDidUpdate instead.
I think more important is to get the value after changing in state of redux rather than in which lifecycle you are getting the value . so for getting the value you can use subscribe method of redux in componentDidMount
store.subscribe( ()=> {
var updatedStoreState = store.getState();
})
I believe that getDerivedStateForProps would solve your problem.
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.data !== prevState.data) {
//Do something
} else {
//Do something else
}
}
You would check the state from the redux against the state from your component and then act accordingly.
Also, some info from the documentation that you might consider before using this method:
1. getDerivedStateFromProps is invoked right before calling the render method, both on the initial mount and on subsequent updates.
2. This method exists for rare use cases where the state depends on changes in props over time.
3. If you need to perform a side effect (for example, data fetching or an animation) in response to a change in props, use componentDidUpdate lifecycle instead.
You can read more at: https://reactjs.org/docs/react-component.html#static-getderivedstatefromprops

Event for when state is set

In React the this.setState method sets state. For instance below I set the state for name to Thomas.
this.setState({'name': 'Thomas'})
I need another object. A state property that comprises of other state properties. For instance below.
let name = 'Thomas'
this.setState({name})
console.log(this.state.data) // => undefined
let age = '26'
let data = {age}
this.setState(data)
console.log(this.state.data) {data: {age: '26'}}
this.stateChanged(state => {
let data = state.data || {}
if (state.name) data.name = state.name
this.setState({data})
})
this.setState({name})
console.log(this.state.data) // => {data: {age: '26', name: 'Thomas'}}
Is there any equivalent this.stateChanged method in React?
React gives us a couple explicit options to determine when the state's changed, or component has been updated. The third option being more of an implicit approach for your particular case:
Use the callback provided in setState(function|object nextState, [function callback]):
onClick(e) {
this.setState({
foobar: e.target.value
}, this.doAfter)
}
doAfter() {
console.log(`Just set FOOBAR to ${this.state.foobar}`)
}
Per the documentation, we can rely on this method to be invoked after the state was asynchronously set.
Use the componentDidUpdate lifecycle method:
componentDidUpdate() {
console.log(`Component updated: Foobar is ${this.state.foobar}`)
}
Per the documentation, this function will be invoked immediately after the component has rendered itself, thereby guaranteeing we have access to the latest state changes.
Compile all necessary state information in the render() method.
render() {
const compilation = {foobar: `${this.state.foobar}-bar`}
return (
<div>...</div>
)
}
Basically, just compose the object how you'd like it before returning a JSX object from render(), however the first two options above might suffice better for your needs.
Example
http://codepen.io/mikechabot/pen/KzyVVw?editors=0011

Resources