1

So I'm working on this React native image storage app. The idea of the app is to be able to store tons of images into categorized boards. Right now I'm working on the board part and I want the app to store the boards. Problem right now is that I have this function which stores rows of the boards NOT the individual boards but a row of them. This function is called "syncBoardstoBoardRow".

The problem I'm having is that whenever I call this function it's after my AsyncStorage Asynchronus "getItems" function, which gets persisted JSON files of each boards, therefore my syncBoardstoBoardRow function has no boards to work with because the function before it still hasn't finished getting all the boards.

Does anyone know how make a function wait to execute until the previous async function finishes its work? This would preferably be within my componentDidMount function.

Important Code:

state = {
    homePageDisplay: 'block',
    board1PageDisplay: 'none',
    addBoardPageDisplay: 'none',

    newBoardName: '',
    newBoardImage: '',

    currentBoardName: '',

    boards: [],
    images: [],

    boardRows:[],
  };

  addBoard = () => {
    this.handleBackPress();
    this.state.boards.splice(this.state.boards.length, 0, {
      id: this.state.boards.length + 1,
      board: this.state.newBoardName,
      imageUrl: this.state.newBoardImage,
    });
    this.setState({
      newBoardImage: '',
      newBoardName: '',
    });
    AsyncStorage.setItem('boards', JSON.stringify(this.state.boards));
    this.syncBoardstoBoardRow();
  };

  syncBoardstoBoardRow = () => {
    for(var i=0; i < this.state.boards.length; i+2){
        if (this.state.boards.length/2 != 0) {
              //If boards are odd, and this is the last row && i+1==this.state.boards.length
            this.state.boardRows.splice(this.state.boardRows.length, 0, {
              id1: this.state.boards.id[i],
              board1: this.state.boards.board[i],
              imageUrl1: this.state.boards.imageUrl[i],      
              id2: -1,
              board2: '',
              imageUrl2: 'https://wallpaperaccess.com/full/1556608.jpg',
            });
        } else {
            this.state.boardRows.splice(this.state.boardRows.length, 0, {
              id1: this.state.boards.id[i],
              board1: this.state.boards.board[i],
              imageUrl1: this.state.boards.imageUrl[i],      
              id2: this.state.boards.id[i+1],
              board2: this.state.boards.board[i+1],
              imageUrl2: this.state.boards.imageUrl[i+1],
            });
        }
    }
  };

    async getData() {
      //AsyncStorage.removeItem('images'); // Use to remove
      let result = AsyncStorage.getItem('boards', (err, result) => {
        if (result !== null) {
          this.setState({
            boards: JSON.parse(result),
          })
        }
      });
      AsyncStorage.getItem('images', (err, result) => {
        if (result !== null) {
          this.setState({ 
            images: JSON.parse(result), 
          }) 
        }
      });
    }

    componentDidMount() {
       this.getData();
       this.syncBoardstoBoardRow();
    }

All Code:

import React, { Component } from 'react';
import {
  AppRegistry,
  Text,
  View,
  StyleSheet,
  Image,
  TextInput,
  ImageBackground,
  TouchableHighlight,
  Alert,
  Dimensions,
  ScrollView,
  AsyncStorage,
} from 'react-native';
import Constants from 'expo-constants';


let deviceHeight = Dimensions.get('window').height;
let deviceWidth = Dimensions.get('window').width;

export default class App extends Component {
  state = {
    homePageDisplay: 'block',
    board1PageDisplay: 'none',
    addBoardPageDisplay: 'none',

    newBoardName: '',
    newBoardImage: '',

    currentBoardName: '',

    boards: [],
    images: [],
    
    boardRows:[],
  };

  addBoard = () => {
    this.handleBackPress();
    this.state.boards.splice(this.state.boards.length, 0, {
      id: this.state.boards.length + 1,
      board: this.state.newBoardName,
      imageUrl: this.state.newBoardImage,
    });
    this.setState({
      newBoardImage: '',
      newBoardName: '',
    });
    AsyncStorage.setItem('boards', JSON.stringify(this.state.boards));
    this.syncBoardstoBoardRow();
  };

  syncBoardstoBoardRow = () => {
    for(var i=0; i < this.state.boards.length; i+2){
        if (this.state.boards.length/2 != 0) {
              //If boards are odd, and this is the last row && i+1==this.state.boards.length
            this.state.boardRows.splice(this.state.boardRows.length, 0, {
              id1: this.state.boards.id[i],
              board1: this.state.boards.board[i],
              imageUrl1: this.state.boards.imageUrl[i],      
              id2: -1,
              board2: '',
              imageUrl2: 'https://wallpaperaccess.com/full/1556608.jpg',
            });
        } else {
            this.state.boardRows.splice(this.state.boardRows.length, 0, {
              id1: this.state.boards.id[i],
              board1: this.state.boards.board[i],
              imageUrl1: this.state.boards.imageUrl[i],      
              id2: this.state.boards.id[i+1],
              board2: this.state.boards.board[i+1],
              imageUrl2: this.state.boards.imageUrl[i+1],
            });
        }
    }
  };

  handleBoard1Press = () =>
    this.setState((state) => ({
      homePageDisplay: 'none',
      board1PageDisplay: 'block',
      addBoardPageDisplay: 'none',
    }));

  handleBackPress = () =>
    this.setState((state) => ({
      homePageDisplay: 'block',
      board1PageDisplay: 'none',
      addBoardPageDisplay: 'none',
    }));

  handleAddBoardPress = () =>
    this.setState((state) => ({
      homePageDisplay: 'none',
      board1PageDisplay: 'none',
      addBoardPageDisplay: 'block',
    }));

    async getData() {
      //AsyncStorage.removeItem('images'); // Use to remove
      let result = AsyncStorage.getItem('boards', (err, result) => {
        if (result !== null) {
          this.setState({
            boards: JSON.parse(result),
          })
        }
      });
      AsyncStorage.getItem('images', (err, result) => {
        if (result !== null) {
          this.setState({ 
            images: JSON.parse(result), 
          }) 
        }
      });
    }

    componentDidMount() {
       this.getData();
       this.syncBoardstoBoardRow();
    }

  render() {
    return (
      <View style={styles.container}>
        {/*Home PAGE*/}
        <View style={{ display: this.state.homePageDisplay }}>
          <View style={styles.teamsPageView}>
            <View style={styles.topTab}>
              <Text style={styles.title}>IMG-ARCHIVE</Text>
              <View style={styles.addContainer}>
                <TouchableHighlight onPress={this.handleAddBoardPress}>
                  <Image
                    source={{
                      uri:
                        'https://codehs.com/uploads/a61d6279f306d4179aca7fe59bf96bc3',
                    }}
                    style={styles.addIcon}
                  />
                </TouchableHighlight>
              </View>
            </View>
            <ScrollView>
              {this.state.boardRows.map((boardMap) => (
                <View style={styles.boardRowContainer}>
                  <View style={styles.boardLeftContainer}>
                    <TouchableHighlight onPress={this.handleBoard1Press}>
                      <View>
                        <Image
                          source={boardMap.imageUrl}
                          style={styles.boardButton}
                        />
                      </View>
                    </TouchableHighlight>
                    <View>
                      <Text style={styles.boardLabel}>{boardMap.board}</Text>
                    </View>
                  </View>
                  <View style={styles.boardRightContainer}>
                    <TouchableHighlight onPress={this.USDToBitcoin}>
                      <View style={styles.boardButton}></View>
                    </TouchableHighlight>
                    <View>
                      <Text style={styles.boardLabel}>Board 2</Text>
                    </View>
                  </View>
                </View>
              ))}
            </ScrollView>
          </View>
        </View>

        {/*Template Board PAGE*/}
        <View style={{ display: this.state.board1PageDisplay }}>
          <View style={styles.teamsPageView}>
            <View style={styles.topTab}>
              <View style={styles.backContainer}>
                <TouchableHighlight onPress={this.handleBackPress}>
                  <Image
                    source={{
                      uri:
                        'https://codehs.com/uploads/7a886a695fdb890af1e2c2701aa392f2',
                    }}
                    style={styles.backIcon}
                  />
                </TouchableHighlight>
              </View>
              <Text style={styles.title}>All IMGs</Text>
              <View style={styles.menuContainer}>
                <TouchableHighlight onPress={this.handleBackPress}>
                  <Image
                    source={{
                      uri:
                        'https://codehs.com/uploads/ac9b7cf9eaf77f003530cb6ccde22cda',
                    }}
                    style={styles.menuIcon}
                  />
                </TouchableHighlight>
              </View>
            </View>
            <ScrollView>
              <View style={styles.imgRowContainer}>
                <View style={styles.imgLeftContainer}>
                  <TouchableHighlight onPress={this.USDToBitcoin}>
                    <View style={styles.image}></View>
                  </TouchableHighlight>
                </View>
                <View style={styles.imgMidContainer}>
                  <TouchableHighlight onPress={this.USDToBitcoin}>
                    <View style={styles.image}></View>
                  </TouchableHighlight>
                </View>
                <View style={styles.imgRightContainer}>
                  <TouchableHighlight onPress={this.USDToBitcoin}>
                    <View style={styles.image}></View>
                  </TouchableHighlight>
                </View>
              </View>
            </ScrollView>
          </View>
        </View>

        {/*Add Board PAGE*/}
        <View style={{ display: this.state.addBoardPageDisplay }}>
          <View style={styles.teamsPageView}>
            <View style={styles.topTab}>
              <View style={styles.backContainer}>
                <TouchableHighlight onPress={this.handleBackPress}>
                  <Image
                    source={{
                      uri:
                        'https://codehs.com/uploads/7a886a695fdb890af1e2c2701aa392f2',
                    }}
                    style={styles.backIcon}
                  />
                </TouchableHighlight>
              </View>
              <Text style={styles.title}>Add Board</Text>
            </View>
            <ScrollView>
              <View>
                <TextInput
                  value={this.state.newBoardName}
                  onChangeText={(newBoardName) =>
                    this.setState({ newBoardName })
                  }
                  style={styles.textInput}
                  placeholder="Name of New Board"
                  placeholderTextColor="Black"
                />
              </View>
              <View>
                <TextInput
                  value={this.state.newBoardImage}
                  onChangeText={(newBoardImage) =>
                    this.setState({ newBoardImage })
                  }
                  style={styles.textInput}
                  placeholder="Cover Image URL"
                  placeholderTextColor="Black"
                />
              </View>
              <View style={styles.submitBoardButton}>
                <TouchableHighlight onPress={this.addBoard}>
                  <Text style={styles.submitBoardText}>Add Board</Text>
                </TouchableHighlight>
              </View>
            </ScrollView>
          </View>
        </View>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    fontFamily: 'Helvetica',
    backgroundColor: '#000000',
  },
  topTab: {
    height: (2 * deviceHeight) / 20,
    width: deviceWidth,
    paddingTop: (1.5 * deviceHeight) / 40,
    flexDirection: 'row',
    justifyContent: 'center', //Centered vertically
    alignItems: 'center', // Centered horizontally
    color: '#ffffff',
  },
  teamsPageView: {
    height: deviceHeight,
    width: deviceWidth,
    alignItems: 'center',
    backgroundColor: 'white',
  },
  title: {
    color: 'black',
    fontSize: 20,
    alignSelf: 'center',
    marginBottom: 10,
    top: 10,
  },
  imgLeftContainer: {
    flexDirection: 'column',
  },
  imgMidContainer: {
    flexDirection: 'column',
    marginLeft: 2.5,
  },
  imgRightContainer: {
    flexDirection: 'column',
    marginLeft: 2.5,
  },
  imgRowContainer: {
    marginTop: 5,
    flexDirection: 'row',
  },
  image: {
    flexDirection: 'row',
    height: 7 * (deviceWidth / 20) - 0.5 * (deviceHeight / 20),
    width: 7 * (deviceWidth / 20) - 0.5 * (deviceHeight / 20),
    fontSize: 0.5 * (deviceHeight / 20),
    backgroundColor: '#1c1c1e',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: (0.1 * deviceWidth) / 10,
  },
  boardLeftContainer: {
    flexDirection: 'column',
  },
  boardRightContainer: {
    flexDirection: 'column',
    marginLeft: 10,
  },
  boardRowContainer: {
    marginTop: 5,
    flexDirection: 'row',
  },
  boardButton: {
    flexDirection: 'row',
    height: 10 * (deviceWidth / 20) - 0.5 * (deviceHeight / 20),
    width: 10 * (deviceWidth / 20) - 0.5 * (deviceHeight / 20),
    fontSize: 0.5 * (deviceHeight / 20),
    backgroundColor: '#1c1c1e',
    alignItems: 'center',
    justifyContent: 'center',
    borderRadius: (0.25 * deviceWidth) / 10,
  },
  boardLabel: {
    color: 'black',
    fontSize: 1 * (deviceWidth / 20),
    textAlign: 'center',
    fontWeight: '600',
    fontFamily: 'Helvetica Neue',
  },
  backIcon: {
    position: 'absolute',
    height: (1.5 * deviceWidth) / 20,
    width: (1.5 * deviceWidth) / 20,
  },
  backContainer: {
    left: 20,
    top: 45,
    position: 'absolute',
    height: (1.5 * deviceWidth) / 20,
    width: (1.5 * deviceWidth) / 20,
  },
  addIcon: {
    position: 'absolute',
    height: (1.5 * deviceWidth) / 20,
    width: (1.5 * deviceWidth) / 20,
  },
  addContainer: {
    right: 20,
    top: 45,
    position: 'absolute',
    height: (1.5 * deviceWidth) / 20,
    width: (1.5 * deviceWidth) / 20,
  },
  menuIcon: {
    position: 'absolute',
    height: (1 * deviceWidth) / 20,
    width: (1 * deviceWidth) / 20,
  },
  menuContainer: {
    right: 20,
    top: 50,
    position: 'absolute',
    height: (1.5 * deviceWidth) / 20,
    width: (1.5 * deviceWidth) / 20,
  },
  textInput: {
    width: deviceWidth - 60,
    height: 40,
    padding: 8,
    margin: 10,
    backgroundColor: 'White',
    borderBottomWidth: 2,
    borderBottomColor: 'black',
  },
  submitBoardButton: {
    borderWidth: 2,
    borderRadius: 10,
    width: deviceWidth * 0.5,
    height: 45,
    margin: 5,
    textAlign: 'center',
    alignItems: 'center',
    justifyContent: 'center',
  },
  submitBoardText: {
    color: 'black',
    fontSize: 20,
    alignSelf: 'center',
    marginBottom: 10,
    top: 10,
    margin: 5,
    fontFamily: 'Helvetica',
    textAlign: 'center',
  },
});
  • 1
    [`.splice` mutates](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) the current state array, which is [a bad practice with React](https://stackoverflow.com/q/37755997/1218980). – Emile Bergeron May 31 '21 at 19:56
  • @EmileBergeron what would you recommend instead? – John Carraher May 31 '21 at 19:58
  • [`filter` or `slice`](https://stackoverflow.com/q/36326612/1218980) depending on the use-case. – Emile Bergeron May 31 '21 at 20:01
  • Making it non-async would fix your problem i guess – Delice May 31 '21 at 20:31
  • @Delice, is that possible? getItems doesn't seem to work for me if I remove the async because it has all that JSON stuff in it. Whenever I do it gives me an error that "Cannot read property 'getItem' of undefined". Do you know of another way to go about this? – John Carraher May 31 '21 at 21:05

1 Answers1

1

As Emile said, using splice is not the appropriate way to update state. It is safe to add to an array with concat or with [...array, newItem]. Both of those return a new array and leave the original unchanged.

It looks like you are adding a new board based on the state properties newBoardName and newBoardImage and then clearing those properties. So try this:

this.setState((prevState) => ({
  boards: prevState.boards.concat({
    id: prevState.boards.length + 1,
    board: prevState.newBoardName,
    imageUrl: prevState.newBoardImage,
  }),
  newBoardImage: '',
  newBoardName: '',
}));

I'm not sure if I'm understanding your data flow properly, but I think that what you want to do is have the syncBoardstoBoardRow function run after the Promise from getData has resolved:

componentDidMount() {
  this.getData().then(() => this.syncBoardstoBoardRow());
}

In order for getData to resolve only when it actually has the data, you need to await the storage functions. It seems like the "empty" value for result is undefined rather than null, but the easy thing is to just see if result is truthy.


async getData() {
  await AsyncStorage.getItem('boards', (err, result) => {
    if (result) {
      this.setState({
        boards: JSON.parse(result),
      })
    }
  });
  await AsyncStorage.getItem('images', (err, result) => {
    if (result) {
      this.setState({ 
        images: JSON.parse(result), 
      }) 
    }
  });
}

Note that this will run them in sequence rather than in parallel because you await one before starting the next. You can run them in parallel with Promise.all.

async getData() {
  await Promise.all([
    AsyncStorage.getItem('boards', (err, result) => {
      if (result) {
        this.setState({
          boards: JSON.parse(result),
        });
      }
    }),
    AsyncStorage.getItem('images', (err, result) => {
      if (result) {
        this.setState({
          images: JSON.parse(result),
        });
      }
    }),
  ]);
}

You should be using import AsyncStorage from "@react-native-community/async-storage" since AsyncStorage has been deprecated from core.

Linda Paiste
  • 38,446
  • 6
  • 64
  • 102
  • Thank you for this! One question, I'm looking over my syncBoardstoBoardRow() and notice it uses splice as well. Would you recommend converting this into a setState prevState with concat? If so how would you go about this. Sorry about the questions by the way, I'm very new to React Native. – John Carraher Jun 01 '21 at 01:44
  • 1
    Yup you can use the same approach there. – Linda Paiste Jun 01 '21 at 02:50
  • Hmm for some reason the new addBoard function doesn't work. It worked fine before I changed to your above code using concat with prevstate, but now it isn't able to save properly. I think it might have to do with the last line "AsyncStorage.setItem('boards', JSON.stringify(this.state.boards));" But I'm not sure how to change it so it works. Do you have a recommendation? – John Carraher Jun 02 '21 at 06:23