React don't change state after first method trigger - javascript

In my React app i have components structure:
-AllElements
--SingleElement
--SingleElementDetails
I am passing method See to SingleElement component where I invoke seefunc to invoke see method from AllElements component. The problem i my state (name) in AllElements not change after first onClick trigger, it changes after secund click. Could you tell my why ?
class AllElements extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
viewingElement: {
name:""
}
}
this.see = this.see.bind(this);
console.log('Initial Sate',this.state.viewingElement);
}
see(name) {
this.setState({
viewingElement: {
name:name
}
});
console.log('State after SEE',this.state.viewingElement);
}
render() {
const { myData, viewingElement } = this.state;
return (
<div>
{myData.map(se => (
<SingleElement
key={se.id}
name={se.name}
see={this.see}
/>
))}
<SingleElementDetails viewingElement={viewingElement}/>
</div>
);
}
}
class SingleElement extends Component {
constructor(props) {
super(props);
}
seefunc(name) {
this.props.see(this.props.name);
console.log('Name in seefunc props',this.props.name);
}
render() {
return (
<div onClick={this.seefunc.bind(this)}>
DIV CONTENT
</div>
)
}
}

The problem you have here is that setState is asynchronous. It does work the first time but you do not see it in your console.log because the console.log happens before the state is updated.
To see the updated state use the second argument of setState which is a callback function (https://reactjs.org/docs/react-component.html#setstate):
this.setState({
viewingElement: {
name:name
}
}, () => {
console.log('State after SEE',this.state.viewingElement);
});
And in SingleElement use the componentWillReceiveProps(nextprops) (https://reactjs.org/docs/react-component.html#componentwillreceiveprops) method from react lifecycle to see the updated props:
seefunc(name) {
this.props.see(this.props.name);
}
componentWillReceiveProps(nextprops) {
console.log('Name in props',nextProps.name);
}

It does change. However setState is an aync process so you're only logging the previous state to the console. setState does provide a callback that allows you to run code after the async process has finished, so you can do:
this.setState({
viewingElement: {
name:name
}
}, () => console.log('State after SEE',this.state.viewingElement));
DEMO

Related

Component sending undefined via props, because it is mounted before a database query ends

I have a problem related to the asynchronous world with react native.
I need to perform a database query, this query returns me a city vector, this vector should be sent to a picker via props.
The problem is: I perform the query within the ComponentWillUpdate function (by default it's the first function to call before mounting the screen). But even using componentWillMount the component (picker) is being assembled and sending undefined via props.
The data that should be sent is not being processed on time.
The flow of my program is being:
Starts the query (componentWillMount) -> rendering components (render) -> End of Query.
Is it possible to pause the screen mount until the query in ComponentWillUpdate ends?
I tried using async but dont work.
async componentWilldMount() {
try {
await axios.get(`${server}/getdata`)
.then(
//code
})
.catch(function (error) {
//handle error
})
} catch (err) {
// handle error
}
}
It's impossible to pause or stop the rendering the component. What you can do though is to set some property in your state like let's say data. So in your constructor you will have omething like that:
constructor(props) {
this.state = {
data: null
}
}
Then in your componentWillMount or even better componentDidMount you do:
componentDidMount() {
axios.get(`${server}/getdata`)
.then(response => {
this.setState({
data: response
})
)
.catch(function (error) {
//handle error
})
Last step is to render depending on your data so in your render method:
render() {
if(!state.data) {
return null;
}
<SomeComponent data={this.state.data} />
}
Solution : Use isReady flag in the parent component.
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
isReady: false,
}
}
componentDidMount() {
setTimeout(() => {
this.setState({value: "bbb", isReady: true});
}, 5000)
}
render() {
return (
<div>
{this.state.isReady && <Child value={this.state.value} />}
</div>
);
}
}
class Child extends React.Component {
constructor(props) {
super(props);
console.log(props.value);
}
render() {
return (
<div>{this.props.value}</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

React: Using `setState` with a function inside `componentWillReceiveProps`

Considering the following React Component snippet, I need to set a new component state based on the new props and the current state. It's preferred to use an "updater" function when defining new state which is based on the old one, now my confusion is that I have the nextProps parameter given to the componentWillReceiveProps lifecycle method, but the updater function also, gets a props parameter. Which one should I use?
Now I guess this should be specific to React 16.2; I'm guessing the introduction of the new getDerivedStateFromProps(nextProps, prevState) static method in React 16.3 should eliminate this confusion (right?)
class MyComponent extends Component {
constructor(props, ...args) {
super(props, ...args);
this.state = {
value: props.value,
};
}
componentWillReceiveProps(nextProps) {
this.setState((prevState, props) => {
return {
value: (/* `nextProps or props` */).value,
}
});
}
render() {
// ...
}
}
There are exactly the same thing. If your want to access previous props, the this.props remains untouched in componentWillReceiveProps method.
My intuition is that setState doesn't fire immediately in side componentWillReceiveProps.
Let's consider the example below.
After first click, the curProps will get 0, and props, nextProps both return 1.
class Test extends Component {
state = {
value: 0
};
componentWillReceiveProps(nextProps) {
const curProps = this.props;
this.setState((prevState, props) => {
console.log('[curProps]', curProps.num);
console.log('[props]', props.num);
console.log('[nextProps]', nextProps.num);
const value = prevState.value + nextProps.num;
return {
value
};
});
}
render() {
return <h4>{this.state.value}</h4>;
}
}
class App extends Component {
state = {
value: 0
};
render() {
return (
<div>
<Test num={this.state.value} />
<button onClick={this.onClick}>Add</button>
</div>
);
}
onClick = () => {
this.setState(prevState => ({ value: prevState.value + 1 }));
};
}
There is the codesandbox demo https://codesandbox.io/s/zxpovzkywx

TypeError: this.props.propName.map is not a function react js

I want to fetch JSON data, store it in state and then pass it to component through props. In the component I want to use the map function but it shows me this error :
TypeError: this.props.dataQueries.map is not a function.
This is my code:
class App extends Component {
constructor(props) {
super(props);
this.state = {
dataQueries: ''
}
}
fetchData() {
fetch('https://jsonplaceholder.typicode.com/posts', {method: "GET"}).
then(res => res.json()).
then(result => this.setState({ dataQueries: result }));
}
componentWillMount() {
this.fetchData();
}
render() {
return (
<div>
<ShowPosts dataQueries={ this.state.dataQueries } />
</div>
);
}
}
And this is my component :
class ShowPosts extends Component {
render() {
return (
<div>
{
this.props.dataQueries.map((query, index) => {
return index;
})
}
</div>
);
}
}
Initially, you're setting dataQueries to ''. Which has no map, as it's a string. It's only an array later, when your fetchData async call has completed. Nothing prevents render from being called before the async call completes.
Initialize it to [] instead, or modify render to avoid trying to use it as an array when it's not an array.
You should call this.fetchData() inside componentDidMount() lifecycle method. So when component is mounted only then you update the state with response from API. Also, you should render ShowPosts component when there are posts to render using conditional rendering.
render() {
return (
<div>
{this.state.dataQueries.length && <ShowPosts dataQueries={ this.state.dataQueries } />}
</div>
);
}
And your initial dataQueries should be an empty array. dataQueries = []

React component loops updating (GraphQL)

Good day!
I keep getting
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
which looks obvious, but i fail to see the loop in my component.
ComponentWillUpdate() shows that it calls lots of rerender with the same props and state in a short amount of time.
Thanks in advance.
src/TitleList.js
class TitleList extends Component {
constructor(props) {
super(props)
this.state = {'items': null}
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex),
});
};
render() {
if (this.props.allTitlesQuery && this.props.allTitlesQuery.loading){
return <div>Loading</div>
}
if (this.props.allTitlesQuery && this.props.allTitlesQuery.error) {
return <div>Error!</div>
}
const titlesToRender = this.props.allTitlesQuery.allTitles
this.setState({'items': titlesToRender})
return <SortableList
items={this.state.items}
onSortEnd={this.onSortEnd}
/>;
}
}
When you call this.setState it calls your renderagain. So if you call setState from render it goes to recursive loop.
You can try something as :-
class TitleList extends Component {
constructor(props) {
super(props)
this.state = {'items': null}
}
componentDidMount () {
this.updateState(props);
}
componentWillReceiveProps (nextProps) {
if (this.props.allTitlesQuery.allTitles !== nextProps.allTitlesQuery.allTitles) {
this.setState(nextProps);
}
}
updateState (props) {
this.setState({"items":props.allTitlesQuery.allTitles});
}
onSortEnd = ({oldIndex, newIndex}) => {
this.setState({
items: arrayMove(this.state.items, oldIndex, newIndex),
});
};
render() {
if (this.props.allTitlesQuery && this.props.allTitlesQuery.loading){
return <div>Loading</div>
}
if (this.props.allTitlesQuery && this.props.allTitlesQuery.error) {
return <div>Error!</div>
}
return <SortableList
items={this.state.items}
onSortEnd={this.onSortEnd}
/>;
}
}
Use componentDidMount method to render the data for first time and if data changes update using componentWillReceiveProps method
the loop is caused by this.setState({'items': titlesToRender}) in your render function
You shouldn't be calling setState inside of render, do it in another lifecycle method like componentDidMount or componentWillReceiveProps:
Render shouldn't modify state: https://reactjs.org/docs/react-component.html#render

Why does state not update correctly when calling setState() multiple times

When clicking the button, the text "second message" and "third message" should be added to the state through these functions:
class Button extends React.Component {
constructor(props) {
super(props);
this.addMessage = this.addMessage.bind(this);
}
addMessage() {
this.props.addMessage('second message'); // This is ignored
this.props.addMessage('third message'); // Only this message is added
}
render() {
return(
<button onClick={this.addMessage}>Add text</button>
)
}
}
But only one of them is firing.
I created a simple snippet that shows the problem.
class Button extends React.Component {
constructor(props) {
super(props);
this.addMessage = this.addMessage.bind(this);
}
addMessage() {
this.props.addMessage('second message'); // This is ignored
this.props.addMessage('third message'); // Only this message is added
}
render() {
return(
<button onClick={this.addMessage}>Add text</button>
)
}
}
class Messages extends React.Component {
constructor(props) {
super(props);
}
render() {
return(
<p>{this.props.message}</p>
)
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [{
text: 'first message'
}]
}
this.addMessage = this.addMessage.bind(this);
}
addMessage(message) {
let messages = this.state.messages.slice();
messages.push({text:message});
this.setState({messages: messages});
}
render() {
let messages = [];
this.state.messages.forEach((message) => {
messages.push(<Messages message={message.text}></Messages>);
});
return(
<div>
{messages}
<Button addMessage={this.addMessage} />
</div>
)
}
}
ReactDOM.render(
<App />,
document.getElementById('root')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
<div id="root"></div>
Thanks in advance!
State Updates May Be Asynchronous
Quoted from the reactjs docs about state updates:
React may batch multiple setState() calls into a single update for
performance.
Because this.props and this.state may be updated asynchronously, you
should not rely on their values for calculating the next state.
Your second call to addMessage() uses this.state to set the new state. But because setState() is asynchronous the state my not have been updated yet which leads to your second call overriding your first one:
addMessage(message) {
// here you access this.state which hasn't been updated yet in your second call
let messages = this.state.messages.slice();
messages.push({text: message});
this.setState({messages: messages});
}
To fix it, use a second form of setState() that accepts a function
rather than an object. That function will receive the previous state
as the first argument, and the props at the time the update is applied
as the second argument:
Use an arrow function that gets passed the previous state including changes of preceding calls to setState()to calculate the new state. That will fix your issue.
Using es6 spread syntax:
this.setState((prevState, props) => ({
messages: [...prevState.messages, {text: message}]
}));
Using slice():
this.setState((prevState, props) => {
let messages = prevState.messages.slice();
messages.push({text:message});
return {messages};
});

Categories