Home » Android » android – How to Cache API data using AsyncStorage React Native

android – How to Cache API data using AsyncStorage React Native

Posted by: admin June 15, 2020 Leave a comment

Questions:

Fairly new to React native and its concepts. I have been playing with RN for a while to create an application to fetch API data from

http://jsonplaceholder.typicode.com/photos

I have been looking into the documentation of AsyncStorage to implement how i can cache the API data so that upon terminating the application, it doesn’t have to deal with fetching the data from web again and again, but wasn’t successfully able to implement it.

It will be great if you can provide me help/suggestion based on it. I have included my source code for the 2 important files in my application, along with the a Test.js file with how i was trying to achieve.

import React, {Component} from 'react';
import { FlatList, View, Text, AsyncStorage, ActivityIndicator } from 'react-native';
import axios from 'axios';
import GalleryDetail from './GalleryDetail';

class GalleryList extends Component {

state = { photos: []};

componentDidMount() {
    axios.get('http://jsonplaceholder.typicode.com/photos')
    .then(response => this.setState({ photos: response.data }))
    .catch((error)=> console.warn("fetch Error: ", error));
}

getPhotos = async()=> {
    try {
        photos = await AsyncStorage.getItem('GalleryPhotos');
    }
    catch (error) {
        console.error(error);
    }
}

savePhotos(){
    AsyncStorage.setItem('GalleryPhotos', this.state.photos);
    console.log('works !');
}

renderPhoto = ({item})=> {
    return <GalleryDetail photo={item}/>
}

keyExtractor = (photo, index) => photo.id;

render () {

    if(!this.state.photos){
        return <ActivityIndicator/>;
    }

    return (
            <FlatList
                data = {this.state.photos}
                keyExtractor={this.keyExtractor}
                renderItem={this.renderPhoto}
            />
    );
}
}

export default GalleryList;

and GalleryDetail linked with GalleryList-

import React, {Component} from 'react';
import { Text, View, Image } from 'react-native';
 import Card from './Card';
 import CardSection from './CardSection';

const GalleryDetail = (props)=> {
     return (
        <Card>
            <CardSection style = {styles.headerContentStyle}>
                <Image
                    style={styles.thumbnailStyle}
                    source = {{ uri: props.photo.thumbnailUrl}}/>
                <Text style= {styles.textStyle}>{props.photo.title}    </Text>
            </CardSection>
        </Card>
    );
};

const styles = {
   headerContentStyle: {
       flexDirection: 'column',
       justifyContent: 'space-around'
   },

   thumbnailStyle: {
       height: 60,
       width: 60
   },

   textStyle: {
       fontSize: 12,
       //textAlign: 'right',
       flexDirection: 'row',
       justifyContent: 'flex-end',
       flex: 1,
       flexWrap: 'wrap',
       marginLeft: 5,
       marginRight: 5,
    }
    }

    export default GalleryDetail;

My method of trying was that-
Upon launching the application, it will first look in asyncStorage, if it finds the data- it fetches from async otherwise going to the web,fetching and storing again for later use.
I tried to implement somewhat like this in a separate file since i dint wanted to breakdown my already running app. The weird broken syntax is

State = {
        photos: []
    }

    componentDidMount() {

        // just a variable acting to fetch data from the stored keyvalue pair

        check = AsyncStorage.getItem("PhotosKey").then((response) => {
                     this.setState({"PhotosKey": response});
                      }).done();

        if(check) {
            console.log('Data was fetched!!!!!');
            check();
        }

        else {
            console.log("Data was not fetched!");

            var Data = axios.get('http://jsonplaceholder.typicode.com/photos').
            then(response => this.setState({ photos: response.data })).
            catch((error)=> console.warn("fetch Error: ", error));



        }
    }

Thanks in advance!

How to&Answers:
async componentDidMount() {
    const photoStorage = await AsyncStorage.getItem('GalleryPhotos')
    if(photoStorage) {
      try {
        const photoResp = await axios.get('http://jsonplaceholder.typicode.com/photos')
        const photoData = await JSON.stringify(photoResp.data)
        await AsyncStorage.setItem('GalleryPhotos', photoData);
      } catch(e) {
        console.warn("fetch Error: ", error)
     }
    .then(response => this.setState({ photos: response.data }))
   }
 }

later

getPhotos = async()=> {
  try {
      photos = JSON.parse(await AsyncStorage.getItem('GalleryPhotos'));
  }
  catch (error) {
    console.error(error);
  }
}

Answer:

The approach from Subramanya is basically all you need to get started, I’m just going to introduce a state management approach with redux-persist where you can definitely appreciate when your app grows.

Redux Persist is performant, easy to implement, and easy to extend.

Let say you have your app hooked up with redux and implemented a fairly organised state tree, redux-persist stores the entire app state with AsyncStorage or any storage engine of your choice.

For instance, let’s assume that your API endpoint returns a collection of photos, all you need to do is update the store, and your users can expect their data is safe and saved with redux-persist.

I have not tested all the code below

Let’s define the store first,

import { AsyncStorage } from 'react-native';
import { createStore, compose, applyMiddleware, } from "redux";
import { persistStore } from "redux-persist";
import ReduxThunk from "redux-thunk";

import reducers from "../reducers"

const middleWare = [ReduxThunk]

const store = createStore(
  reducers, 
  {},
  compose(applyMiddleware(...middleWare))
)

// you can define more parameters, like blacklist or whitelist a reducer
// also, specify storage engine
persistStore(store, { storage: AsyncStorage });

export default store;

At your app’s entry point,

import React, { Component } from "react";
import { Provider } from "react-redux";
import Router from "./Router";
import store from './store';

export default class App extends Component {

  constructor(props) {
    super(props);
  }

  render() {
    return (
      <Provider store={store}>
        <Router />  // navigator
      </Provider>
    );
  }
}

Finally, your API logic.

// action creator
export storePhoto = photos => {
    return {
        type: 'STORE_PHOTOS',
        payload: photos
    }
}

// photos reducer
import { REHYDRATE } from 'redux-persist/constants';

export default (state = {}, action) => {
  switch (action.type) {
    case STORE_PHOTOS:
      return { ...state, photos: action.payload }
  // this is where `redux-persist` handles caching
    case REHYDRATE:
      var incoming = action.payload;
      if(incoming) return { ...state, ...incoming }
      return state;
    default:
      return state;
  }
};

To retrieve data, you will see that redux abstracts away all the excess logics and there is no more setItem, getItem because redux-persist does that automagically for your already.

import { connect } from "react-redux";
import { storePhotos } from "./actions";

class GalleryList extends Component {

    async componentDidMount() {
        const photos = await axios.get('http://jsonplaceholder.typicode.com/photos');
        storePhoto(photos)
    }

    renderPhoto = ({ item }) => <GalleryDetail photo={item}/>

    keyExtractor = (photo, index) => photo.id;

    render () {
        return (
            <FlatList
                data = {this.props.photos}
                keyExtractor={this.keyExtractor}
                renderItem={this.renderPhoto}
            />
        );
    }
}

// pull data from photos reducer
const mapStateToProps = ({ photos }) => {
    return {
        photos: photos.photos
    }
}

export default connect(mapStateToProps, { storePhotos })(GalleryList);

To summarise,

  1. Install redux-persist in you project.
  2. Import persistStore and autoRehydrate form redux-persist.
  3. Add autoRehydrate to your store.
  4. Pass your store to persistStore.
  5. Listen to the persist/REHYDRATE action on your reducer and populate state accordingly.

Hope my answer helps!