React Native get initial state from async componentDidMount - javascript

i want to displaying state value to render() from async function that already called from componenDidMount, but in the first execution component it returned undefined, for second attempt it showing correct data
this is my state this.state.userData
my question is, what is priority processed function between render() and componentDidMount()?
This is my code
componentDidMount: function(){
store.get('userData').then((val) => {
if(typeof val != 'undefined'){
this.setState({'userData':val});
}
}).done();
},
render: function() {
return(
<View>
<Text>{this.state.userData}</Text> // displaying undefined
</View>
);
}

I'm not sure what you're asking.
As I understand it: you want to execute a function to set data before render() is called.
In that case: take a look at lifecycle methods -> link. It explains quite nicely that you can use componentWillMount() before render is called.
However: this doesn't guarantee that you have your data before render is called. I would suggest you set an additional state variable to know when the async call is complete and before that just call a loading spinner.
Something like:
constructor(props){
super(props);
this.state={
loading: true
}
}
in your function:
this.setState({userData:val, loading: false});
render:
render() {
if(this.state.loading){
return( <ActivityIndicator size='large' style={{height:80}} />
}
else{
return(
<View>
<Text>{this.state.userData}</Text> // displaying undefined
</View>
);
}
(should only give an idea - code is improvised.. but should solve the undefined problem)
have fun

Related

How do I prevent a function from being run before data is loaded into this.state (i.e. in componentDidMount)?

I have a function getRepresentativeName() which uses a piece of data (this.state.communityData.Representative) from this.state to index into a database and retrieve a string. My intent is that when the component mounts, all of the information in this.state, including this.state.communityData.Representative, will be populated and the representative's name will appear in the location set off by {...}. However, the function runs immediately when the first render is called and does not wait for the component to mount; as a result, since this.state is not populated yet, the getRepresentativeName function crashes.
I placed a ternary statement to check whether the communityData JSON object has been filled (the logic being that if it is not filled, i.e. === {}, just return null instead of executing getRepresenativeName()); Interestingly enough, the page crashes regardless (with the same issue: getRepresenativeName() is being called before this.state is populated in componentDidMount()).
Can someone explain to me why this is happening, and how I could resolve this situation without adding redundant data to this.state? I'm not sure if this is the way to solve it, but I thought that the problem could be solved if there was a way to make the getRepresentativeName() function wait until componentDidMount() finishes. Thank you!
Note: If I do not call the function and just replace the {...} with {this.state.communityData.Representative}, which fills the element with the Representative ID as opposed to the name, the page renders perfectly. In addition, I tried making getRepresentative name a callback, and that still didn't work (i.e. the page still crashed when it tried to render).
async getRepresentativeName() {
const representativeSnapshot = await database()
.ref('users')
.child(this.state.communityData.Representative)
.child('name')
.once('value');
return representativeSnapshot.val();
};
render() {
console.log('starting render');
return (
<View style={styles.containerScreen} testID="communityScreen">
{/*
This the container that holds the community Image
and its description,
*/}
<View style={styles.containerhorizontal}>
<View style={styles.containervertical}>
<Image
source={require('../../sample_images/va10.jpg')}
style={styles.image}
/>
</View>
<View style={styles.containervertical}>
<Text style={styles.containerhorizontal}>
Description: {this.state.communityData.Description}
</Text>
<Text style={styles.containerhorizontal}>
Representative: **{this.state.communityData !== {} ?this.getRepresentativeName() : null}**
</Text>
...
The typical pattern here is initialize your state with some kind of loading property. Then, asynchronously load data based on input from the user interface. That input could be from the user or it could be from lifecycle events. In this case, componentDidMount. Your component should know how to render when loading is true and when data is available.
class MyComponent extends React.Component {
state = {loading: true, error: null, representative: null}
componentDidMount() {
fetchData().then((representative) => {
this.setState({ loading: false, representative })
}).catch((error) => {
this.setState({ loading: false, error: error.message })
})
}
render() {
return this.state.error
? <span>woops</span>
: this.state.loading
? <span>busy</span>
: <div>Representative: {this.state.representative.name}</div>
}
}

React re-render conditional on state change without changing the conditional

I've got a conditional that displays an editor while a certain prop remains true. The thing is, the data with which that editor is rendered with should change every time I select another object with which to populate that editor.
However, because the prop responsible for the conditional rendering doesn't change, even though the data with which the editor is rendered does, it refuses to re-render on state change.
I'm not particularly good at React, so, hopefully someone can explain how I can get around this little hiccup.
Conditional render
{this.state.showEditor ? (<BlockEditor routine={this.state.editorObject} />) : null}
Method that is being called.
handleShowEditor = routine => {
this.setState({ showEditor: true });
this.setState({ editorObject: routine });
};
The editor component
export default class BlockEditor extends React.Component {
constructor(props) {
super(props);
this.state = {
routine: this.props.routine
};
}
render() {
return (
<div>
<Editor
autofocus
holderId="editorjs-container"
onChange={data => this.handleSave(data)}
customTools={{}}
onReady={() => console.log("Start!")}
data={this.props.routine.description}
instanceRef={instance => (this.editorInstance = instance)}
/>
</div>
);
}
}
Is there a reason for setting state separately? Why not
handleShowEditor = routine => {
this.setState({
showEditor: true,
editorObject: routine
});
};
Keep in mind that setState is asynchronous and your implementation could lead to such weird behaviour.
If you are still looking for an answer i have faced the same problem working with the same [Editor.JS][1] :).
This worked for me with functional component:
// on change fires when component re-intialize
onChange={async (e) => {
const newData = await e.saver.save();
setEditorData((prevData) => {
console.log(prevData.blocks);
console.log(newData.blocks);
if (
JSON.stringify(prevData.blocks) === JSON.stringify(newData.blocks)
) {
console.log("no data changed");
return prevData;
} else {
console.log("data changed");
return newData;
}
});
}}
// setting true to re-render when currentPage data change
enableReInitialize={true}
Here we are just checking if data changes assign it to editorData component state and perform re-render else assign prevData as it is which will not cause re-render.
Hope it helps.
Edit:
i am comparing editor data blocks change which is array.
of course you need to perform comparison of blocks more deeply than what i am doing, you can use lodash for example.
[1]: https://github.com/editor-js/awesome-editorjs
As setState is asynchronous you can make another call in its callback.
Try like this
handleShowEditor = routine => {
this.setState({
showEditor: true
}, () =>{
this.setState({
editorObject: routine
)}
});
};

Updating reactjs state with axios object returned from Alphavantage

I need to pass some stock data to a component named Chart. So far, the Chart component only returns the "test" value placed into state at the top of this code.
Rather than setting state and passing the new data to the Chart component, it gives the error, "TypeError: (intermediate value)(...) is not a function".
I think the getTimeSeries function needs to be defined outside of the render method because the function needs to update state. If so, where does it go?
I think I need to .bind(this), or something with binding, somewhere. I'm not sure.
export default class Main extends React.Component {
state = {
currentUser: null,
instr: null,
instrData: ['test']
};
constructor(props) {
super(props)
}
componentDidMount() {
const { currentUser } = firebase.auth()
this.setState({ currentUser, instr: 'NFLX' });
}
render() {
const { currentUser } = this.state;
var getTimeSeries = function(instr){
if(instr!=null){
const key = 'myKey';
var url = `https://www.alphavantage.co/query?function=TIME_SERIES_DAILY&symbol=${instr}&apikey=${key}`;
console.log(url);
axios.get(url)
.then((response) => {
(()=>{ console.log(response.data["Time Series (Daily)"]) })
// THIS IS NOT UPDATING STATE:
(()=>{ this.setState({ instrData: response.data["Time Series (Daily)"] }) })
(()=>{ console.log(this.state) })
})
.catch((error) => console.log(error));
};
}; // end getTimeSeries
return (
<View style={styles.container}>
<Text>
Hi {currentUser && currentUser.email}!
{ getTimeSeries(this.state.instr) }
</Text>
<Chart
instrument={this.state.instr}
instrData={this.state.instrData}
/>
<VotingButtons instrument={this.state.instr} />
</View>
);//end return
}// end render
}//end class
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
}
})
There's a lot of stuff that's not right here, I'm afraid. Here's a quick runthrough of the big problems:
You're triggering your axios call inside your render() method. A successful axios call will cause state to update (your setState() call), which will then cause another render() call - React components automatically re-render when their state changes. So you're setting up an infinite loop. Probably less dangerous to do this in the componentWillMount() lifecycle method, BUT...
Trying to store AJAX data in component state like this is a bit of an antipattern in React. It's not really supposed to act as a full-on MVC system, it's more like just a View. If you want to do this sort of thing, I strongly recommend using React alongside a robust state management system - Redux is very good and popular.
Your axios promise callbacks are all weird and I don't get what you're trying to do.
axios.get(url)
.then((response) => {
all fine so far, then we get...
(()=>{ console.log(response.data["Time Series (Daily)"]) })
OK, this is what's confusing me. What I expect to see here would be just:
console.log(response.data["Time Series (Daily)"]);
Instead of doing this, you've declared a function that does it. But you haven't called that function, you've just declared it and left it there. And because you haven't assigned it to anything, it's not really doing much.
// THIS IS NOT UPDATING STATE:
(()=>{ this.setState({ instrData: response.data["Time Series (Daily)"] }) })
(()=>{ console.log(this.state) })
These lines will also do nothing as you're not actually executing this.setState() or console.log(), you're declaring functions that will execute them:
foo(); //a call to function foo, no args
() => { foo(); } //a function that calls function foo with no args
Hope this helps!
Big thanks to Duncan Thacker.
Still needs to import the date, but this working code pulls in quotes and builds a candlestick chart:
Chart.js
Api.js

this.props.children.map at react native is not working

I want to render a child component from a parent component by passing to it one object from array of objects fetched from an api.
TypeError: this.props.posts.map is not a function
renderPosts() {
return this.props.posts.map(post =>
<HomeCard key={post.id} postData={post} />
);
}
All the component:
class Home extends Component {
componentWillMount() {
this.props.getUserPosts();
}
renderPosts() {
return this.props.posts.map(post =>
<HomeCard key={post.id} postData={post} />
);
}
render() {
return (
<View>
<View style={{ paddingBottom: 55 }}>
<SearchBar />
</View>
<ScrollView>
{this.renderPosts()}
</ScrollView>
</View>
);
}
}
const mapStateToProps = state => {
const posts = state.homePost;
console.log('posts', posts);
return { posts };
};
export default connect(mapStateToProps, { getUserPosts })(Home);
I suspect this is because this.props.posts is undefined (empty or whatever default you have it set to) when Home being mounted. Since you aren't giving us any log outputs, it's hard to tell but this is a very common mistake.
The immediate fix is to give it a default value either where you define your initial state for your reducer or in mapStateToProps. The latter looking something like this (adapting your code):
const mapStateToProps = state => {
const posts = state.homePost || [];
console.log('posts', posts);
return { posts };
};
While this will fix your error, another thing you need to correct is the common misconception that whatever is in componentWillMount will execute prior to mounting. This is not true and is one of the reasons that this lifecycle method (and componentWillReceiveProps and componentWillUpdate) will be deprecated in the future.
Your code here:
componentWillMount() {
this.props.getUserPosts();
}
is asynchronous since you mention fetching this data. getUserPosts will fire but isn't guaranteed to complete before mounting. So while you think this.props.posts will be set to some value before rendering, that is not going to be the case. Hence why you are getting the not a function error message.

"Cannot update during an existing state transition" error in React

I'm trying to do Step 15 of this ReactJS tutorial: React.js Introduction For People Who Know Just Enough jQuery To Get By
The author recommends the following:
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
return (
<div className="alert alert-warning">
<strong>Oops! Too Long:</strong>
</div>
);
} else {
return "";
}
},
render() {
...
{ this.overflowAlert() }
...
}
I tried doing the following (which looks alright to me):
// initialized "warnText" inside "getInitialState"
overflowAlert: function() {
if (this.remainingCharacters() < 0) {
this.setState({ warnText: "Oops! Too Long:" });
} else {
this.setState({ warnText: "" });
}
},
render() {
...
{ this.overflowAlert() }
<div>{this.state.warnText}</div>
...
}
And I received the following error in the console in Chrome Dev Tools:
Cannot update during an existing state transition (such as within render or another component's constructor). Render methods should be
a pure function of props and state; constructor side-effects are an
anti-pattern, but can be moved to componentWillMount.
Here's a JSbin demo. Why won't my solution work and what does this error mean?
Your solution does not work because it doesn't make sense logically. The error you receive may be a bit vague, so let me break it down. The first line states:
Cannot update during an existing state transition (such as within render or another component's constructor).
Whenever a React Component's state is updated, the component is rerendered to the DOM. In this case, there's an error because you are attempting to call overflowAlert inside render, which calls setState. That means you are attempting to update state in render which will in then call render and overflowAlert and update state and call render again, etc. leading to an infinite loop. The error is telling you that you are trying to update state as a consequence of updating state in the first place, leading to a loop. This is why this is not allowed.
Instead, take another approach and remember what you're trying to accomplish. Are you attempting to give a warning to the user when they input text? If that's the case, set overflowAlert as an event handler of an input. That way, state will be updated when an input event happens, and the component will be rerendered.
Make sure you are using proper expression. For example, using:
<View onPress={this.props.navigation.navigate('Page1')} />
is different with
<View onPress={ () => this.props.navigation.navigate('Page1')} />
or
<View onPress={ () => {
this.props.navigation.navigate('Page1')
}} />
The two last above are function expression, the first one is not. Make sure you are passing function object to function expression () => {}
Instead of doing any task related to component in render method do it after the update of component
In this case moving from Splash screen to another screen is done only after the componentDidMount method call.
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Button,
Image,
} from 'react-native';
let timeoutid;
export default class Splash extends Component {
static navigationOptions = {
navbarHidden: true,
tabBarHidden: true,
};
constructor(props) {
super(props)
this.state = { navigatenow: false };
}
componentDidMount() {
timeoutid=setTimeout(() => {
this.setState({ navigatenow: true });
}, 5000);
}
componentWillUnmount(){
clearTimeout(timeoutid);
}
componentDidUpdate(){
const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}
}
render() {
//instead of writing this code in render write this code in
componenetDidUdpate method
/* const { navigate,goBack } = this.props.navigation;
if (this.state.navigatenow == true) {
navigate('Main');
}*/
return (
<Image style={{
flex: 1, width: null,
height: null,
resizeMode: 'cover'
}} source={require('./login.png')}>
</Image>
);
}
}
Call the component props at each time as new render activity. Warning occurred while overflow the single render.
instead of
<Item onPress = { props.navigation.toggleDrawer() } />
try like
<Item onPress = {() => props.navigation.toggleDrawer() } />
You can also define the function overflowAlert: function() as a variable like so and it will not be called immediately in render
overflowAlert = ()=>{//.....//}

Categories

Resources