Home » Android » javascript – React Native: TextInput with state and AsyncStorage

javascript – React Native: TextInput with state and AsyncStorage

Posted by: admin May 14, 2020 Leave a comment

Questions:

When typing on the keyboard I was seeing some warnings about the input being ahead of the JS code..

Native TextInput(react native is awesome) is 4 events ahead of JS – try to make your JS faster.

So added the debounce and got this to “work”:

...
import { debounce } from 'lodash'
...
export default class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      data,
      indexRef: data.reduce((result, item, index) => {
        result[item.title] = index
        return result
      }, {}),
      ready: false,
    }

    this.updatePitch = this.updatePitch.bind(this)
    this.saveLocally = debounce(this.saveLocally, 300).bind(this)
  }
  ...
  updatePitch(id, text) {
    // Copy the data
    let data = [...this.state.data]
    const index = data.findIndex(obj => obj.id == id)
    data[index].pitch = text
    // Update the state
    this.setState({ data }, this.saveLocally(data))
  }

  saveLocally(data) {
    try {
      AsyncStorage.setItem('data', JSON.stringify(data))
      this.forceUpdate()
    } catch (error) {
      // Well..
    }
  }

  render() {
  ...

BTW: I’m doing some “prop drilling” for now – but planning to do use the Context API (react 16.3)

The warning seems to have gone by adding debounce (1).. But I’m seeing some strange issues – particularly on the iPhone 8 plus simulator (not seeing the same on iPhone 6 simulator or Android device)

Issues observed:

  • TextInput don’t expand – it just add scolling (expands on iPhone 6 and Android device)
  • Some layout issues in the FlatList – seems like it has problems finding correct height of list elements..

What is the best practice for fast JS code and saving to both state and AsyncStorage?

(1) One other way than using debounce could be to use getDerivedStateFromProps and add some sort of timer pushing the state to the parent component after some period of time.. But wasn’t sure that this would make the JS code faster. So didn’t try it.

UPDATE (again)

Open Source

I open sourced the entire code since it is too hard to give all the needed information in a SO post when the code is so nested.

The entire code is here:
https://github.com/Norfeldt/LionFood_FrontEnd

(I know that my code might seem messy, but I’m still learning..)

I don’t expect people to go in and fix my code with PR (even though it would be awesome) but just give me some code guidance on how to proper deal with state and AsyncStorage for TextInput.

I know I have some style issues – would love to fix them, but also comply with SO and keep it on topic.

Update II

I removed forceUpdate and replaced FadeImage with just vanilla react native Image.

but I’m still seeing some weird issues

Weird behavior

Here is my code

import React from 'react'
import {
  StyleSheet,
  SafeAreaView,
  FlatList,
  StatusBar,
  ImageBackground,
  AsyncStorage,
  Platform,
} from 'react-native'
import SplashScreen from 'react-native-splash-screen'
import LinearGradient from 'react-native-linear-gradient'
import { debounce } from 'lodash'

import Section from './Section'
import ButtonContact from './ButtonContact'

import { data } from '../data.json'

export default class App extends React.Component {
  constructor(props) {
    super(props)
    this.state = {
      data,
      indexRef: data.reduce((result, item, index) => {
        result[item.title] = index
        return result
      }, {}),
      ready: false,
    }
  }

  async componentDidMount() {
    SplashScreen.hide()
    try {
      let BusinessPlan = await AsyncStorage.getItem('BusinessPlan')
      if (BusinessPlan !== null) {
        // We have data!!
        let data = JSON.parse(BusinessPlan)
        data = this.state.data.map(item => {
          const index = data.findIndex(obj => obj.id == item.id)
          const pitch = index >= 0 ? data[index].pitch : ''
          return { ...item, pitch }
        })
        this.setState({ data, ready: true })
      } else {
        this.setState({ ready: true })
      }
    } catch (error) {
      // Error retrieving data
    }
  }

  updatePitch = (id, text) => {
    // Copy the data
    let data = [...this.state.data]
    const index = data.findIndex(obj => obj.id == id)
    data[index].pitch = text
    // Update the state
    this.setState({ data }, this.saveLocally(data))
  }

  saveLocally = data => {
    try {
      AsyncStorage.setItem('BusinessPlan', JSON.stringify(data))
    } catch (error) {
      // Well..
    }
  }

  render() {
    return (
      <LinearGradient
        style={{ flex: 1 }}
        start={{ x: 0.0, y: 0.25 }}
        end={{ x: 0.5, y: 1.0 }}
        colors={['#000000', '#808080', '#000000']}
      >
        <StatusBar
          barStyle={'light-content'}
          backgroundColor={Platform.OS == 'iOS' ? 'transparent' : 'black'}
        />
        <SafeAreaView>
          <ImageBackground
            source={require('../images/BackgroundImage.png')}
            style={{ width: '100%', height: '100%' }}
            resizeMode={'cover'}
          >
            <FlatList
              data={this.state.data}
              initialNumToRender="16"
              keyExtractor={item => item.id}
              renderItem={({ item }) => (
                <Section
                  id={item.id}
                  title={item.title}
                  pitch={item.pitch}
                  updatePitch={debounce(this.updatePitch, 1000)}
                  questions={item.questions}
                  ready={this.state.ready}
                />
              )}
              ListFooterComponent={<ButtonContact />}
              style={{
                backgroundColor: 'transparent',
                borderColor: '#000',
                borderWidth: StyleSheet.hairlineWidth,
              }}
            />
          </ImageBackground>
        </SafeAreaView>
      </LinearGradient>
    )
  }
}

const styles = StyleSheet.create({
  sectionHeader: {
    fontSize: 24,
    marginHorizontal: 5,
  },
})

(I also updated my git repo)

Update III

It seems that the setup I have for state and AsyncStorage works fine with a debounce. The issues I was seeing was because I’m draining the CPU (next step to fix).

How to&Answers:

I tried your code:

  • “I’m seeing some strange issues – particularly on the iPhone 8 plus
    simulator (not seeing the same on iPhone 6 simulator or Android
    device)” ==> I confirmed this

  • The app takes about 100% CPU.

After a while trying I figured out:

  • “I’m seeing some strange issues – particularly on the iPhone 8 plus
    simulator (not seeing the same on iPhone 6 simulator or Android
    device)” ==> doesn’t right, just wait a little TextInput will expand.

  • There are nothing wrong with state and AsyncStorage. I didn’t get any warning.

The root issue is the animation in FadeImage:

  • The app render many Carousel, and each Carousel has AngleInvestor, and FadeImage. The problem is FadeImage

  • FadeImage run Animated with duration 1000 => CPU is overloaded

    ==> That why TextInput add scroll then take a long time to expand, and FlatList look like has problem, but not. They are just slowly updated.

Solution:

  • Try to comment FadeImage, you will see the problem gone away.

  • Don’t start so many animations as the same time. Just start if it appears (Ex: the first card in Carousel)

UPDATE

I got your problem: typing fastly cause setState call so many times.
You use can debounce for this situation:

In App.js

render() {
    console.log('render app.js')
    ...
                <Section
                  id={item.id}
                  title={item.title}
                  pitch={item.pitch}
                  updatePitch={debounce(this.updatePitch, 1000)} // the key is here
                  questions={item.questions}
                  ready={this.state.ready}
                />

You can change the delay and watch the console log to see more. As I tried, delay about 500 can stop the warning.

P/s: You should try to remove forceUpdate