Connected component initial render fails because Redux state is not yet populated from server - javascript

I am using Redux thunk and axios to make server calls and modify the state depending on the result.
The problem is that when I use a connected component, and its initial state depends on data from the server, it does not render (the connected props are empty)
render () (<div>{this.props.someData}</data>) // empty, or error, if nested
...
const mapStateToProps = state => ({
someData: state.someData
})
I also tried this:
componentWillMount = () => {
this.setState({
someData: this.props.someData
})
}
And used state in render, but it didn't help.
Is there a way to wait for the server response before rendering or some other solution?

You can conditionally render that part. Use a property to indicate fetching status (property name is loading in my example).
class UserDetails extends React.Component {
state = {
loading: true,
data: null
}
fetch() {
this.setState({
loading: true
})
// an axios call in your case
setTimeout(() => this.setState({
loading: false,
data: {
nestedValue: 'nested value'
}
}), 500)
}
componentDidMount() {
this.fetch()
}
render() {
return <div>
{this.state.loading ? <span>loading...</span> : <div>nested prop value: {this.state.data.nestedValue}</div>}
</div>
}
}

Typically you would use the Component.defaultProps object to initialize your props. So in your case, something like this will set your someData prop to an initial value before axios receives the data.
class MyComponent extends React.Component {
// ... your implementation
}
MyComponent.defaultProps = {
someData: [] // empty array or whatever the data type is
};
Edit: docs

Related

React: Can't access array data in component state with indices

As a practice project, I've started building a small Pokedex app in React.
import React, { Component} from 'react';
import './App.css';
import Card from './components/card/Card.component';
class App extends Component{
constructor(){
super();
this.state = {}
}
componentDidMount(){
let pokeDataArr = []
const getPokemonData = async() => {
const dataResponse = await fetch(
'https://pokeapi.co/api/v2/pokemon?limit=10'
);
const dataArr = await dataResponse.json();
const dataArr2 = await dataArr.results.forEach(i => {
fetch(i.url)
.then(dataResponse => dataResponse.json())
.then(json => pokeDataArr.push(json))
})
this.setState({ pokeDataArr }, () => console.log(this.state))
}
getPokemonData();
}
render(){
return(
<div>Pokedex!</div>
)
}
}
I'm having trouble accessing data from a specific index in an array.
When I log the entire state object to the console, I can see all the data I have retrieved from the AJAX call.
this.setState({ pokeDataArr }, () => console.log(this.state))
And this is the result in the console:
console result
However, if I try to log out data from an index in the array with:
this.setState({ pokeDataArr }, () => console.log(this.state.pokeDataArr[0]))
I get "undefined" in the console:
console result 2
As far as I'm aware, whatever function you run in the this.setState method's callback, it should run after setState has finished.
My goal is to use the data from this.state.pokeDataArr to make cards that display the info of each individual pokemon, but it seems like I'm stuck until I find a way to extract the data from the array and I have no clue what I'm missing.
Thank you for your time.
I think you messed up with your react state.
Usually, what people do is they set up their react state as an object with other elements (arrays, objects, strings, whatever) inside it. This looks something like this:
constructor(){
super();
this.state = {
myObject: {},
somethingElse: "",
anArray: []
}
}
This enables you to access parts of your state like this: this.state.myObject for instance. (this would return {})
In your example, you defined your state as an empty object.
constructor(){
super();
this.state = {}
}
And later, you set this object to an object, with an array inside:
this.setState({ pokeDataArr });
This will set your state to this: {[(your array)]}
To prevent this initialize your state like this:
constructor(){
super();
this.state = { pokeDataArr : {} }
}
And set your values like this:
this.setState({ pokeDataArr: pokeDataArr }, () => console.log(this.state.pokeDataArr[0]))
read more here: https://reactjs.org/docs/state-and-lifecycle.html
You'll need to use updater to use the callback instead of plain state update:
this.setState(
() => ({ pokeDataArr }),
() => console.log(this.state.pokeDataArr[0])
)
Read the note from the docs in the linked example:
Subsequent calls will override values from previous calls in the same cycle, so the quantity will only be incremented once. If the next state depends on the current state, we recommend using the updater function form

getDerivedStateFromProps, change of state under the influence of changing props

I click Item -> I get data from url:https: // app / api / v1 / asset / $ {id}. The data is saved in loadItemId. I am moving loadItemId from the component Items to the component Details, then to the component AnotherItem.
Each time I click Item the props loadItemId changes in the getDerivedStateFromProps method. Problem: I'll click Element D -> I see in console.log 'true', then I'll click Element E --> It display in console.log true andfalse simultaneously, and it should display only false.
Trying to create a ternary operator {this.state.itemX ['completed'] ? this.start () : ''}. If {this.state.itemX ['completed'] call the function this.start ()
Code here: stackblitz
Picture: https://imgur.com/a/OBxMKCd
Items
class Items extends Component {
constructor (props) {
super(props);
this.state = {
itemId: null,
loadItemId: ''
}
}
selectItem = (id) => {
this.setState({
itemId: id
})
this.load(id);
}
load = (id) => {
axios.get
axios({
url: `https://app/api/v1/asset/${id}`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
this.setState({
loadItemId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render () {
return (
<div >
<Item
key={item.id}
item={item}
selectItem={this.selectItem}
>
<Details
loadItemId={this.state.loadTime}
/>
</div>
)
}
Item
class Item extends Component {
render () {
return (
<div onClick={() => this.props.selectItem(item.id}>
</div>
)
}
}
Details
class Details extends Component {
constructor() {
super();
}
render () {
return (
<div>
<AnotherItem
loadItemId = {this.props.loadItemId}
/>
</div>
)
}
}
AnotherItem
class AnotherItem extends Component {
constructor() {
super();
this.state = {
itemX: ''
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.loadItemId !== prevState.loadItemId) {
return { itemX: nextProps.loadItemId }
}
render () {
console.log(this.state.itemX ? this.state.itemX['completed'] : '');
{/*if this.state.loadX['completed'] === true, call function this.start()*/ }
return (
<button /*{this.state.loadX['completed'] ? this.start() : ''}*/ onClick={this.start}>
Start
</button>
);
}
}
here:
selectItem = (id) => {
this.setState({
itemId: id
})
this.load(id);
}
you call setState(), then 'Item' and 'Details' and 'AnotherItem' call their render method. so you see log for previous 'loadItemId'.
when 'load' method work done. here:
this.setState({
loadItemId: response.data
});
you setState() again, then 'Item' and 'Details' and 'AnotherItem' call their render method again. in this time you see log for new 'loadItemId'.
solution
setState both state in one place. after load method done, instead of:
this.setState({
loadItemId: response.data
});
write:
this.setState({
itemId: id,
loadItemId: response.data
});
and remove:
this.setState({
itemId: id
})
from 'selectItem' method.
Need some clarification, but think I can still address this at high level. As suggested in comment above, with the information presented, it does not seem that your component AnotherItem actually needs to maintain state to determine the correct time at which to invoke start() method (although it may need to be stateful for other reasons, as noted below).
It appears the functionality you are trying to achieve (invoke start method at particular time) can be completed solely with a comparison of old/new props by the componentDidUpdate lifecycle method. As provided by the React docs, getDerivedStateFromProps is actually reserved for a few 'rare' cases, none of which I believe are present here. Rather, it seems that you want to call a certain method, perhaps perform some calculation, when new props are received and meet a certain condition (e.g., not equal to old props). That can be achieved by hooking into componentDidUpdate.
class AnotherItem extends Component {
constructor(props) {
super(props);
this.state = {}
}
start = () => { do something, perform a calculation }
// Invoked when new props are passed
componentDidUpdate(prevProps) {
// Test condition to determine whether to call start() method based on new props,
// (can add other conditionals limit number of calls to start, e.g.,
// compare other properties of loadItemId from prevProps and this.props) .
if (this.props.loadItemId && this.props.loadItemId.completed === true) {
//Possibly store result from start() in state if needed
const result = this.start();
}
}
}
render () {
// Render UI, maybe based on updated state/result of start method if
// needed
);
}
}
You are encountering this behaviour because you are changing state of Items component on each click with
this.setState({
itemId: id
})
When changing its state, Items component rerenders causing AnotherItem to rerender (because that is child component) with it's previous state which has completed as true (since you've clicked element D before). Then async request completes and another rerender is caused with
this.setState({
loadItemId: response.data
});
which initiates another AnotherItem rerender and expected result which is false.
Try removing state change in selectItem and you'll get desired result.
I'd suggest you read this article and try to structure your code differently.
EDIT
You can easily fix this with adding loader to your component:
selectItem = (id) => {
this.setState({
itemId: id,
loading: true
})
this.load(id);
}
load = (id) => {
axios.get
axios({
url: `https://jsonplaceholder.typicode.com/todos/${id}`,
method: "GET"
})
.then(response => {
this.setState({
loading: false,
loadItemId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render() {
return (
<div >
<ul>
{this.state.items.map((item, index) =>
<Item
key={item.id}
item={item}
selectItem={this.selectItem}
/>
)
}
</ul>
{this.state.loading ? <span>Loading...</span> : <Details
itemId={this.state.itemId}
loadItemId={this.state.loadItemId}
/>}
</div>
)
}
This way, you'll rerender your Details component only when you have data fetched and no unnecessary rerenders will occur.

set multiple states, and push to state of array in one onClick function

I'm running into a recurring issue in my code where I want to grab multiple pieces of data from a component to set as states, and push those into an array which is having its own state updated. The way I am doing it currently isn't working and I think it's because I do not understand the order of the way things happen in js and react.
Here's an example of something I'm doing that doesn't work: jsfiddle here or code below.
import React, {Component} from 'react';
class App extends Component {
constructor(props) {
super(props);
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
}
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
setCategoryStates = (categoryTitle, categorySubtitle) => {
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
render() {
return (
<CategoryComponent
setCategoryStates={this.setCategoryStates}
categoryTitle={'Category Title Text'}
categorySubtitle={'Category Subtitle Text'}
/>
);
}
}
class CategoryComponent extends Component {
render() {
var categoryTitle = this.props.categoryTitle;
var categorySubtitle = this.props.categorySubtitle;
return (
<div onClick={() => (this.props.setCategoryStates(
categoryTitle,
categorySubtitle,
))}
>
<h1>{categoryTitle}</h1>
<h2>{categorySubtitle}</h2>
</div>
);
}
}
I can see in the console that I am grabbing the categoryTitle and categorySubtitle that I want, but they get pushed as null into this.state.categoryArray. Is this a scenario where I need to be using promises? Taking another approach?
This occurs because setState is asynchronous (https://reactjs.org/docs/state-and-lifecycle.html#using-state-correctly).
Here's the problem
//State has categoryTitle as null and categorySubtitle as null.
this.state = {
categoryTitle: null,
categorySubtitle: null,
categoryArray: [],
}
//This gets the correct values in the parameters
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
})
this.pushToCategoryArray();
}
//This method is using the state, which as can be seen from the constructor is null and hence you're pushing null into your array.
pushToCategoryArray = () => {
this.state.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
Solution to your problem: pass callback to setState
setCategoryStates = (categoryTitle, categorySubtitle) => {
//This is correct, you're setting state BUT this is not sync
this.setState({
categoryTitle: categoryTitle,
categorySubtitle: categorySubtitle,
}, () => {
/*
Add state to the array
This callback will be called once the async state update has succeeded
So accessing state in this variable will be correct.
*/
this.pushToCategoryArray()
})
}
and change
pushToCategoryArray = () => {
//You don't need state, you can simply make these regular JavaScript variables
this.categoryArray.push({
'categoryTitle': this.state.categoryTitle,
'categorySubtitle': this.state.categorySubtitle,
})
}
I think React doesn't re-render because of the pushToCategoryArray that directly change state. Need to assign new array in this.setState function.
// this.state.categoryArray.push({...})
const prevCategoryArray = this.state.categoryArray
this.setState({
categoryArray: [ newObject, ...prevCategoryArray],
)}

ReactJS calling function twice inside child component fails to set parent state twice

I'm having an issue where I want to save the data from a particular fieldset with the default values on componentDidMount().
The data saving happens in the parent component, after it is sent up from the child component. However, as React's setState() is asynchronous, it is only saving data from one of the fields. I have outlined a skeleton version of my problem below. Any ideas how I can fix this?
// Parent Component
class Form extends Component {
super(props);
this.manageData = this.manageData.bind(this);
this.state = {
formData: {}
}
}
manageData(data) {
var newObj = {
[data.name]: data.value
}
var currentState = this.state.formData;
var newState = Object.assign({}, currentState, newObj);
this.setState({
formData: newState, // This only sets ONE of the fields from ChildComponent because React delays the setting of state.
)};
render() {
return (
<ChildComponent formValidate={this.manageData} />
)
}
// Child Component
class ChildComponent extends Component {
componentDidMount() {
const fieldA = {
name: 'Phone Number',
value: '123456678'
},
fieldB = {
name: 'Email Address',
value: 'john#example.com'
}
this.props.formValidate(fieldA);
this.props.formValidate(fieldB)
}
render() {
/// Things happen here.
}
}
You're already answering you're own question. React handles state asynchronously and as such you need to make sure you use the current component's state when setState is invoked. Thankfully the team behind React is well-aware of this and have provided an overload for the setState method. I would modify your manageData call to the following:
manageData(data) {
this.setState(prevState => {
const nextState = Object.assign({}, prevState);
nextState.formData[data.name] = data.value;
return nextState;
});
}
This overload for the setState takes a function whose first parameter is the component's current state at the time that the setState method is invoked. Here is the link where they begin discussing this form of the setState method.
https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous
Change manageData to this
manageData(data) {
const newObj = {
[data.name]: data.value
};
this.setState(prevState => ({
formData: {
...prevState.formData,
...newObj
}
}));
}

React render method that depends on asynchronous request and state change

I am trying to learn ReactJS and Redux, and have come across a problem that I cannot seem to get over.
I have a React component, that gets data from an asynchronous request.
export class MyPage extends React.Component {
constructor(props) {
super(props)
this.state = {
enableFeature: false,
}
this.handleEnableFeatureChange = this.handleEnableFeatureChange.bind(this)
}
componentWillMount () {
this.fetchData()
}
fetchData () {
let token = this.props.token
this.props.actions.fetchData(token)
}
handleEnableFeatureChange (event) {
this.setState({ enableFeature: event.target.checked })
}
render () {
if (this.props.isFetching) {
return (
<div>Loading...</div>
)
} else {
return (
<div>
<label>Enable Feature
<input type="checkbox"
className="form-control"
checked={this.props.enableFeature}
onChange={this.handleEnableFeatureChange}
/>
</label>
</div>
)
}
}
}
So, my problem now is that, when I change the state of the checkbox, I want to update the state of my data. However, every time I update the state of my data, the react component method shouldComponentUpdate kicks in, and uses the current props to render the original data.
I would like to see how such cases are handled in general.
Thanks.
Try to do it like the following, i.e.
Use componentWillReceiveProps to assign props.enableFeature to state.enableFeature. From documentation
componentWillReceiveProps() is invoked before a mounted component receives new props. 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.
Note that React may call this method even if the props have not changed, so make sure to compare the current and next values if you only want to handle changes. This may occur when the parent component causes your component to re-render.
componentWillReceiveProps() is not invoked if you just call this.setState()
Use this state to load the value of checkbox
Manipulate this state (onchange) to update the value of checkbox
Following code can work in your case
export class MyPage extends React.Component {
static propTypes = {
isFetching: React.PropTypes.bool,
enableFeature: React.PropTypes.bool,
token: React.PropTypes.string,
actions: React.PropTypes.shape({
fetchData: React.PropTypes.func
})
};
state = {
enableFeature: false,
};
componentWillMount () {
this.fetchData();
}
/* Assign received prop to state, so that this state can be used in render */
componentWillReceiveProps(nextProps) {
if (this.props.isFetching && !nextProps.isFetching) {
this.state.enableFeature = nextProps.enableFeature;
}
}
fetchData () {
const { token } = this.props;
this.props.actions.fetchData(token)
}
handleEnableFeatureChange = (event) => {
this.setState({ enableFeature: event.target.checked })
};
render () {
return (<div>
{ this.props.isFetching && "Loading..." }
{
!this.props.isFetching && <label>
Enable Feature
<input
type="checkbox"
className="form-control"
checked={this.state.enableFeature}
onChange={this.handleEnableFeatureChange}
/>
</label>
}
</div>);
}
}
Note: The above code was not executed, but should work (babel's stage-0 code)
Change it to checked={this.state.enableFeature}

Categories

Resources