render is called before the asynchronous method has finished in react native - react-native

hi everyone i am recently learning react native and i have a synchronization problem sending data from one screen to another screen.
I have the parent page (UserList) that wants to send its status to the child page (RecyclerViewPage) but the render is called before the data is available.
export default class UserList extends Component {
constructor(props){
super(props);
this.state={
comment_state: [],
datetime: []
}
}
//getPost is a network call which gets and store the result in the state of the class
async getPost(){
var sid=this.props.route.params.value_sid
var did=this.props.route.params.value_did
const utils=new Utils();
const responseJson = await utils.getPost(sid,did)
const comment = (responseJson?.posts ?? []).map((data) => data.comment)
this.setState({comment_state:comment})
console.log("now i change state with new value")
}
componentDidMount(){
this.getPost()
}
render(){
return(
<RecyclerViewPage
comment={this.state.comment_state}
/>
)
}
}
and RecyclerViewPage code:
export default class RecyclerViewPage extends Component {
constructor(props) {
super(props);
console.log("i am in recyclerviewPage : ",this.props.comment)
}
render(){}
}
I tried to put some logs and the result is:
Log: "i am in recyclerviewPage: []"
Log: "now i change state with new value"
It appears as if render () was called before the asynchronous getPost method has finished. how can i synchronize my code in order to get the data first and then send it to the RecyclerViewPage daughter schemata?

See you can wait for the results and then show recyclerviewpage:
You can add a loader state while its being fetched:)
Hope it helps. feel free for doubts
export default class UserList extends Component {
constructor(props){
super(props);
this.state={
comment_state: [],
datetime: []
}
}
//getPost is a network call which gets and store the result in the state of the class
async getPost(){
var sid=this.props.route.params.value_sid
var did=this.props.route.params.value_did
const utils=new Utils();
const responseJson = await utils.getPost(sid,did)
const comment = (responseJson?.posts ?? []).map((data) => data.comment)
this.setState({comment_state:comment})
console.log("now i change state with new value")
}
componentDidMount(){
this.getPost()
}
render(){
return(
{!!comment_state && <RecyclerViewPage
comment={this.state.comment_state}
/> }
{!comment_state && <Text> Please wait its loading </Text>}
)
}
}

Related

React native flatList scrollToItem not scrolling in componentDidMount

I have a really weird issue, I have two components (both navigation routes) which share the same higher-order component that keeps the posts as state.
One component can link to the other by passing a post slug to the route as a parameter, this component, in turn, scrolls a flaList to the correct index:
findIndexBySlug = memoizeOne(
(postsArray, selectedPostSlug) => postsArray.findIndex(
post => post.slug === selectedPostSlug,
),
);
constructor(props) {
super(props);
this.shouldScrollToPost = this.shouldScrollToPost.bind(this);
this.scrollToIndex = this.scrollToIndex.bind(this);
this.flatListRef = React.createRef();
}
componentDidMount() {
this.shouldScrollToPost();
}
componentDidUpdate() {
this.shouldScrollToPost();
}
shouldScrollToPost() {
const { navigation } = this.props;
const selectedPostSlug = navigation.getParam('selectedPostSlug');
console.log('shouldscrollto', selectedPostSlug);
if (selectedPostSlug) {
const { posts } = this.props;
const { postsArray } = posts;
const selectedPostIndex = this.findIndexBySlug(
postsArray, selectedPostSlug,
);
this.scrollToIndex(selectedPostIndex);
}
}
scrollToIndex(index) {
if (this.flatListRef.current) {
console.log('scrolling to ', index)
this.flatListRef.current.scrollToIndex({ index, animated: false });
}
}
In both the first mount (componentDidMount) and subsequent calls (componentDidUpdate) all the console.log fire (including the one that checks for the flatListref) but, when called the first time in componentDidMount no scrolling occurs, in componentDidUpdate it does actually scroll!
Why?
It's driving me insane, even read the Dan Abramov post about the availability of refs (componentDidMount called BEFORE ref callback) and the flatList is the only rendered component (in fact the ref is always available).
Any help greatly appreciated.
Thanks

Not understand on behavior

Currently, I am learning react native and still don't know why it happened.
I have a component A and component B.
In component A when handle success I will pass isSucceeded: true and false for the failure case.
but I don't know why when having success case it navigates to B screen and always show Fail text before showing Success text for 1 second
component A:
login() {
dataService.getService().then((response) => {
navigateService.navigate('B', {isSucceeded: true})
}).catch(error => {
navigateService.navigate('B', {isSucceeded: false})
});
}
In component B:
componentDidMount() {
this.state.isSucceeded = navigation.getParam('isSucceeded')
}
render() {
this.state.isSucceeded ? <View> <Text>Success</Text></View> :
<View> <Text>Fail</Text></View>
}
How can it show Success Text when having success case and fail for failed case
Thanks you.
You should do it in the constructor
constructor(props) {
super(props);
this.state = {
isSucceeded: props.navigation.getParam('isSucceeded'),
};
}
If you insist doing it in componentDidMount then you must use setState():
componentDidMount() {
const isSucceeded = this.props.navigation.getParam('isSucceeded');
this.setState({ isSucceeded });
}

how to manage component mount and unmount to update state values?

I would like to know how to manage state property when the component mounts and unmounts.
I have a lot of different components in my application to maintain the application flow. I know about function componentdidmount and componentWillUnmount. and I also tried the solution about _isMounted=true on componentdidmount function and check _isMounted properties value when I update setState and then update _isMounted=false on componentWillUnmount function.
but this won't work when more two components come in the picture.
For example following links:
https://www.robinwieruch.de/react-warning-cant-call-setstate-on-an-unmounted-component/
Is there a way to check if the react component is unmounted?
as per the example, I have made a common class which will update the value of a component in setMounted function and will return value in getMounted function to validate component is mounted or not. These methods work correctly on a single screen when I call another screen from a stack and update some values then comes back on the previous page and refresh page it will ismount=false.
class Mount {
isMounted=false;
getMounted=()=>{
return isMounted;
}
setMounted=mounted=>{
isMounted=mounted;
}
}
var mount=new Mount();
export default mount;
class example extends component{
componentDidMount=async()=>{
mount.setMounted(true);
await this.loadScreen();
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
async() => {
await this.loadScreen();
}
);
}
loadScreen=async()=>{
//some other stuff
if(mount.getMounted()){//second time value is false
this.setState({value:'value'});
}
}
componentWillUnmount() {
mount.setMounted(false);
}
//renderview where i call example2 on buttonclick
}
class example2 extends component{
componentDidMount=async()=>{
mount.setMounted(true);
await this.loadScreen();
}
loadScreen=async()=>{
//some other stuff
if(mount.getMounted()){
this.setState({value:'value'});
this.props.navigation.goBack();
}
}
componentWillUnmount() {
mount.setMounted(false);
this.willFocusSubscription.remove();
}
}
It was showing following warning before using mount functions:
Can't perform a React state update on an unmounted component
You are creating only a single instance of your Mount class that is exported and shared across every instance of every component. You will need to create a new instance of Mount for each component instance:
class Mount {
...
}
// export the Mount class
export default Mount;
class example extends component{
constructor(props) {
super(props);
// create an instance of Mount for each component instance
this.mount = new Mount();
}
componentDidMount=async()=>{
this.mount.setMounted(true);
await this.loadScreen();
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
async() => {
await this.loadScreen();
}
);
}
loadScreen=async()=>{
//some other stuff
if(this.mount.getMounted()){//second time value is false
this.setState({value:'value'});
}
}
componentWillUnmount() {
this.mount.setMounted(false);
}
//renderview where i call example2 on buttonclick
}
Notice the addition of the constructor and the use of this.mount instead of mount throughout.

Validate React Native Component with Asynchronous Work

I have a basic component that calls a webservice during the componentDidMount phase and overwrites the contents value in my state:
import React, {Component} from 'react';
import {Text} from "react-native";
class Widget extends Component {
constructor() {
super();
this.state = {
contents: 'Loading...'
}
}
async componentDidMount() {
this.setState(...this.state, {
contents: await this.getSomeContent()
});
}
render() {
return (
<Text>{this.state.contents}</Text>
)
}
async getSomeContent() {
try {
return await (await fetch("http://someurl.com")).text()
} catch (error) {
return "There was an error";
}
}
}
export default Widget;
I would like to use Jest snapshots to capture the state of my component in each one of the following scenarios:
Loading
Success
Error
The problem is that I have to introduce flaky pausing to validate the state of the component.
For example, to see the success state, you must place a small pause after rendering the component to give the setState method a chance to catch up:
test('loading state', async () => {
fetchMock.get('*', 'Some Content');
let widget = renderer.create(<Widget />);
// --- Pause Here ---
await new Promise(resolve => setTimeout(resolve, 100));
expect(widget.toJSON()).toMatchSnapshot();
});
I'm looking for the best way to overcome the asynchronicity in my test cases so that I can properly validate the snapshot of each state.
If you move the asynchronous call out of setState, you can delay setState until the network call has resolved. Then you can use setState's optional callback (which fires after the state change) to capture the state.
So, something like this:
async componentDidMount() {
var result = await this.getSomeContent()
this.setState(...this.state, {
contents: result
},
// setState callback- fires when state changes are complete.
()=>expect(this.toJSON()).toMatchSnapshot()
);
}
UPDATE:
If you want to specify the validation outside of the component, you could create a prop, say, stateValidation to pass in a the validation function:
jest('loading state', async () => {
fetchMock.get('*', 'Some Content');
jestValidation = () => expect(widget.toJSON()).toMatchSnapshot();
let widget = renderer.create(<Widget stateValidaton={jestValidation}/>);
});
then use the prop in the component:
async componentDidMount() {
var result = await this.getSomeContent()
this.setState(...this.state, {
contents: result
},
// setState callback- fires when state changes are complete.
this.props.stateValidaton
);
}

Check state before render for react native

I need to get the user's name the first time the app is run. Then after I want to skip the first screen and go directly to the second screen.
I'm using AsyncStorage.getItem("first") in the first screen to check if this is the first boot. If not, navigate('SecondScreen').
The problem is that the first screen flash for half a second before going to the second screen. Is there a way to fix this?
Render a loader and check AsyncStorage until the data is loaded, then navitage or render your component conditionally like so:
constructor(){
super()
this.state = {
hasName: null, loaded: false,
}
}
componentDidMount(){
this.fetchName()
}
fetchName(){
AsyncStorage.getItem('first')
.then((e, s)=>{
this.setState({hasName: s ? true : false, loaded: true})
});
}
loading(){
return(<View><Text>Loading...</Text></View>)
}
renderLoaded(){
if (this.state.hasName){
return(...)
} else {
navigate('secondScreen')
return(...)
}
}
render(){
if (this.state.loaded){
return this.renderLoaded()
}
return this.loading()
}
Basicaly, you want to do something similar...
Render "loading" screen while loading data. When done, rerender with main application.
class Application extends React.Component {
state = {
ready: false,
user: null,
}
async componentWillMount() {
const user = await AsyncStorage.getItem("user");
this.setState({
user,
ready: true
})
}
render() {
if (this.state.ready === false) {
// render "booting" screen while reading data from storate or remote server
return <Boot />;
}
if (this.state.user === null) {
// render Login screen
return <Login />
}
// Render main navigation stack for app
return <NavigatorStack user={this.state.user} />
}
}