3

I want to load from AsyncStorage and use it during my react native component rendering but my component renders immediately on app start and doesn't give AsyncStorage time to return data.

How can I use AsyncStorage so my components will either A) wait to be rendered until they have the correct information or B) change to reflect the correct information once the data has been loaded?

    import React from 'react';
    import { AsyncStorage } from 'react-native';

    // my wrapper object around AsyncStorage
    export default class StorageManager  {

    constructor() {
        this.data = {
            settings: { },
        }
        this.loadSettings();
    }

    async loadSettings() {
        await AsyncStorage.getItem('settings')
            .then( (settings) => this.data.settings = JSON.parse(settings) )
            .catch( (err) => console.log(err) );
    }

    /*
     * settings = {days: number, currency: string, costPerDay: number }
     */
    getDays() {
        return this.data.settings.days;
    }
}

And my component code. Here AchievementScreen is rendered first and the StorageManager doesn't have time to load anything. The other screens are rendered when I tab over to them so they have everything loaded correctly.

import React, {Component} from 'react';
import {AppRegistry, StyleSheet, Alert} from 'react-native';
import { Container, Header, Content, Body, List, Text} from 'native-base';
import { Col, Row, Grid } from "react-native-easy-grid";
import AchievementCard from '../../components/AchievementCard/AchievementCard'

export default class AchievementScreen extends Component {
    constructor(props) {
        super(props);
        // debugger shows props.storageManager as well-shaped but empty.
        this.state = {
            storageManager: this.props.storageManager,
            achievementCards: [ this.moneySavedAchievement(props.storageManager),
                               {header: "Header 2", body: "Body 2", icon: "money"},
                            ]
        }
    }

    moneySavedAchievement(storageManager) {
        return {
            header: "Money Saved",
            body:   storageManager.data.settings.currency + 
                    storageManager.data.settings.costPerDay *
                    storageManager.data.settings.days,
            icon: "money"
        }
    }

    render() {
        return (
            <Container>
            <Content>
            <Grid>
                <Col>
                    <List   dataArray={this.state.achievementCards.slice(0, this.state.achievementCards.length / 2)}
                            renderRow={ (item) => 
                                <AchievementCard achievementHeader={item.header}
                                    achievementBody={item.body}
                                    achievementIcon={item.icon} />
                            }
                    />
                </Col>
                <Col>
                    <List   dataArray={this.state.achievementCards.slice(this.state.achievementCards.length / 2)}
                            renderRow={ (item) => 
                                <AchievementCard achievementHeader={item.header}
                                    achievementBody={item.body}
                                    achievementIcon={item.icon} />
                            }
                    />
                </Col>
            </Grid>
            </Content>
        </Container>
    );
    }
}

AppRegistry.registerComponent('AchievementScreen', () => AchievementScreen);

My frontpage app code

import React, {Component} from 'react';
import {AppRegistry, StyleSheet} from 'react-native';
import {Tabs, Tab, Container, Header, Title, Body, TabHeading, Text, Right, Left, Button, TouchableOpacity} from 'native-base';
import Icon from 'react-native-vector-icons/FontAwesome';

import HomeScreen from './app/screens/HomeScreen/HomeScreen';
import MilestoneScreen from './app/screens/MilestoneScreen/MilestoneScreen';
import AchievementScreen from './app/screens/AchievementScreen/AchievementScreen';
import StorageManager from "./app/modules/StorageManager/StorageManager";

export default class my_react_app extends Component {
    constructor() {
        super();
        this.state = {
            storageManager: new StorageManager()
        }
    }

    render() {
        return (
            <Container>
            <Header hasTabs>
            <Left />
                <Body>
                    <Title>My App</Title>
                </Body>
                <Right>
                    <Icon active name="ellipsis-v" color="white"/>
                </Right>
            </Header>
            <Tabs initialPage={0}>
                <Tab heading="Status">
                    <AchievementScreen storageManager={this.state.storageManager} />
                </Tab>
                <Tab heading="Progress">
                    <HomeScreen storageManager={this.state.storageManager} />
                </Tab>
                <Tab heading="Milestones">
                    <MilestoneScreen storageManager={this.state.storageManager} />
                </Tab>
            </Tabs>
            </Container>
        );
    }
}

AppRegistry.registerComponent('my_react_app', () => My_React_App);

2 Answers 2

1

An immediate problem which I can see is that AchievementScreen component is dependent on storageManager, so the only way the component can re-render( from an external effect ) is if the storageManager prop changes, which does not happen.

This line -

<AchievementScreen storageManager={this.state.storageManager} />

Also if you want AchievementScreen to re-render on data fetches, a better design would be to pass the data to AchievementScreen rather than passing an object which in-turn gets data. AchievementScreen should be unaware of where the data is fetched from. ( For better side-effect management look at libraries like redux, redux-thunk, redux-saga )

So your Ideal implementation would be -

componentDidMount(){
  this.storageManager.getData().then((data) => 
   this.setState({moneySavedAchievement: data});
 );
}

<AchievementScreen moneySavedAchievement={moneySavedAchievement} />

Hope this helps!

Sign up to request clarification or add additional context in comments.

Comments

0

Like you stated in your question, your AchievementScreen component is being rendered before the storageManager has retrieved the data. You're actually explicitly setting the state in the constructor:

this.state = { storageManager: this.props.storageManager }

This sets the value for this.state.storageManager, and when the storageManager has updated data, the AchievementScreen component doesn't know what to do with it.

You can add a componentWillReceiveProps lifecycle hook into your component, this will get called every time your component receives new props. So, you could do:

In AchievementScreen - under the constructor:

componentWillReceiveProps(nextProps) {

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.