Skip to content

Minjun1Kim/ReactNativeAppDemo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

20 Commits
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Creating your First React Native App

Table of Contents

Description

In this lab, you will learn how to build a simple React Native application and you will recreate a version of your own.

React Native is a framework for building native apps for IOS/Android using JavaScript.

There are two ways of building a React Native app: Expo CLI and React Native CLI. We will be using Expo CLI for simplicity and ease of use.

Please refer to React Native documentation for its Core Components and APIs: https://reactnative.dev/docs/components-and-apis

If there are any issues or inaccuracies, please contribute by raising issues, making pull requests, or asking on Piazza. Thanks!

Prerequisites

  1. Make sure you have Node.js installed. You can install from the official website: https://nodejs.org/en/download/
  2. An IDE. We will use VS Code.
  3. Expo Go installed on your mobile device. Available both on IOS and Android: https://expo.dev/client

Step 1: Setting up the new app

Open a terminal in VS Code and install Expo CLI with the following command:

npm install -g expo-cli

Now, create a new expo project

npx create-expo-app CounterApp
cd CounterApp

Replace CounterApp with your own app name.

Once complete, run the following command to start the server:

npm start

Alt text

It should create a QR code that you can scan with your phone. Make sure your phone is connected to the same network as your computer.

This is what you should see on your phone.

In case you face a loading problem, run the following commands:
npm install -g expo-cli
expo-cli start --tunnel

which globally installs the expo client in our application.

Note: UofT network blocks the live-previewing on ExpoGo. To test on the web, please run the following commands:

npx expo install react-dom react-native-web @expo/webpack-config
npx expo start --web

For more information, check: https://docs.expo.dev/workflow/web/

Designing the App

In App.js, change the text inside <Text> ... </Text> then save the file or reload the app. You should see the change on your device.

We will create a simple counter app.

Counter.js

Begin by creating a functional component Counter.js. In the root directory, make a sub directory called 'components'. Counter.js will be inside components.

Make sure you install the following packages on your terminal:

npm install react
npm install react-native
npm install uuid

For issues with uuid on Android, please check the common problems section: Common Problems

import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

export default Counter;

We will initally include the import statements and export statement for our functional component. Notice we are using a state hook and several components from React Native package.

import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

const Counter = ({ initialValues }) => {

};

export default Counter;

We will define the functional component Counter using arrow function syntax and destructing the initialValues prop.

import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

const Counter = ({ initialValues }) => {
  return (
    <View style={styles.container}>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    marginTop: 50,
  }
});

export default Counter;

Now, we have a return statment that defines the structure that the component will render. We use the StyleSheet api for creating styles. Check the documentation here: https://reactnative.dev/docs/stylesheet

To learn more about React Native's core components and apis, please refer to: https://reactnative.dev/docs/components-and-apis

import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

const Counter = ({ initialValues }) => {
  const [counters, setCounters] = useState(initialValues.map((value) => ({ id: uuidv4(), count: value })));

  return (
    <View style={styles.container}>
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    marginTop: 50,
  }
});

export default Counter;

The newly added line of code, we use the useState React hook with the state variable counters and state setter setCounters. We initialize an array of objects to counters, where each object is a counter with a unique id generated by uuidv4() and count that holds a value from the initialValues prop.

The map method takes each value in the array and map it to an id and a count value.

Modify App.js as follows:

import { StatusBar } from 'expo-status-bar';
import { StyleSheet, View } from 'react-native';
import Counter from './components/Counter';

export default function App() {
  return (
    <View style={styles.container}>
        <Counter initialValues={[5, 10, 15]} />
        <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

We have imported the functional component Counter from components and included the Counter component in our render with the prop initialValues passed as an array [5, 10, 15]. It is important to note that this is where we pass in our prompt.

Now in Counter.js, we add the following code to display the Counters.

import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { v4 as uuidv4 } from 'uuid';

const Counter = ({ initialValues }) => {
  const [counters, setCounters] = useState(initialValues.map((value) => ({ id: uuidv4(), count: value })));

  return (
    <View style={styles.container}>
      {counters.map((counter) => (
        <View key={counter.id}>
          <Text style={styles.text}>Counter Value: {counter.count}</Text>
          <View style={styles.buttonContainer}>
            <Button title="Increment" onPress={() => increment(counter.id)} />
            <Button title="Decrement" onPress={() => decrement(counter.id)} />
          </View>
        </View>
      ))}
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    marginTop: 50,
  },
  text: {
    fontSize: 20,
    marginBottom: 20,
  },
  buttonContainer: {
    flexDirection: 'row',
  },
});

export default Counter;

Here, we use map to iterate over each element in the counters state variable and render React Native components for each counter.

We set key={counter.id} to provide an id for React to update and re-render components efficiently.

Using the Text component, we display the counter's value with its count. Then, we have a View for the two buttons: Increment and Decrement.

The onPress prop is used to specify the functions increment and decrement to be executed when the buttons are pressed.

We also updated the styles object to include stlying for text and buttonContainer. The flexDirection: 'row' makes the buttons go side by side horizontally.

This is what our App should look like at this stage:

Additionally, we include the following lines of code to set the default value of our prop if it's not explicitly provided.

Counter.defaultProps = {
  initialValues: [0],
};

This means there is one counter with the value 0.

Now, the next thing we have left is to implement the increment and decrement helper functions. We define the increment function as follows:

const increment = (id) => {
  setCounters((prevCounters) =>
    prevCounters.map((counter) =>
      counter.id === id ? { ...counter, count: counter.count + 1 } : counter
    )
  );
};

The array function takes an id and sets the state using setCounters defined earlier above. The prevCounters is the previous state that React keeps track of, and in fact, the naming is dependent on the developer. It doesn't have to be called setCounters. React will know and treat it as the previous state.

Now, we iterate over each counter in the previous state using map(). Once we find the id that was passed into the function, we update the counter's count by one. Otherwise, we return the counter unchanged.

Here are some thing to note:

We use the ternary operator to check for the condition.

condition ? action1 : action2

If the condition is true, we perform action1. Otherwise, we perform action2.

... used in ...counter is a spread operator that copies all properies of the current counter but with its count property incremented.

const decrement = (id) => {
  setCounters((prevCounters) =>
    prevCounters.map((counter) =>
      counter.id === id ? { ...counter, count: counter.count - 1 } : counter
    )
  );
};

The decrement function is implemented almost the same way, except we subtract 1 from counter.count.

Now, we'll add a new counter to the counters state.

To do that, we need to add the following addCounter array method.

const addCounter = (initialValue) => {
  const newCounter = { id: uuidv4(), count: initialValue };
  setCounters((prevCounters) => [...prevCounters, newCounter]);
};

First, we store the new object containing a uniquely generated id and the initialValue that is passed into the function. Then we update the previous state values prevCounters by using the spread operator to preserve the current states and adding the newCounter to it. And again, this is done using the 'setCounters' method, the state setter.

Then, we'll add the following line in the return statement.

<AddCounterForm onAddCounter={addCounter} />

This is a functional component that we will implement. It takes the prop onAddCounter, to which the method addCounter is assigned.

We'll see that we'll use addCounter to add a new counter to our state.

Below is the complete source code for the Counter.js component:

import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';
import { v4 as uuidv4 } from 'uuid';
import AddCounterForm from './AddCounterForm';

const Counter = ({ initialValues }) => {
  const [counters, setCounters] = useState(initialValues.map((value) => ({ id: uuidv4(), count: value })));

  const increment = (id) => {
    setCounters((prevCounters) =>
      prevCounters.map((counter) =>
        counter.id === id ? { ...counter, count: counter.count + 1 } : counter
      )
    );
  };

  const decrement = (id) => {
    setCounters((prevCounters) =>
      prevCounters.map((counter) =>
        counter.id === id ? { ...counter, count: counter.count - 1 } : counter
      )
    );
  };

  const addCounter = (initialValue) => {
    const newCounter = { id: uuidv4(), count: initialValue };
    setCounters((prevCounters) => [...prevCounters, newCounter]);
  };

  return (
    <View style={styles.container}>
      {counters.map((counter) => (
        <View key={counter.id}>
          <Text style={styles.text}>Counter Value: {counter.count}</Text>
          <View style={styles.buttonContainer}>
            <Button title="Increment" onPress={() => increment(counter.id)} />
            <Button title="Decrement" onPress={() => decrement(counter.id)} />
          </View>
        </View>
      ))}
      <AddCounterForm onAddCounter={addCounter} />
    </View>
  );
};

Counter.defaultProps = {
  initialValues: [0],
};

const styles = StyleSheet.create({
  container: {
    alignItems: 'center',
    marginTop: 50,
  },
  text: {
    fontSize: 20,
    marginBottom: 20,
  },
  buttonContainer: {
    flexDirection: 'row',
  },
});

export default Counter;

AddCounterForm.js

This is the beginning of the AddCounterForm.js component. We will add a component that prompts the user to enter a counter value using TextInput and adds it to the counters state once we click the button. This new counter will show up in the app on our device!

import React, { useState } from 'react';
import { View, TextInput, Button, StyleSheet, Keyboard } from 'react-native';

  const AddCounterForm = ({ onAddCounter }) => {
    const [initialValue, setInitialValue] = useState('');
    
    return (
      //we'll implement this soon
    );
  };

  export default AddCounterForm;

We begin by importing React and necessary components. We also declare the functional Component AddCounterForm as an array method that takes in the prop onAddCounter. Remember, this corresponds to the prop that we passed in the return statement of Counter.js for the AddCounterForm component. onAddCounter is assigned a method that handles adding a counter to the counters state.

Inside the method, we use useState to declare the initialValue state and the state setter setInitialValue, which is initially set as empty.

Now we'll render the TextInput form. We store the current value of the text input in the initialValue state and the state variable is updated with the user-input text on change. We only allow numeric input values and specify the return key as "done".

import React, { useState } from 'react';
import { View, TextInput, Button, StyleSheet, Keyboard } from 'react-native';

  const AddCounterForm = ({ onAddCounter }) => {
    const [initialValue, setInitialValue] = useState('');
    
    return (
      <View>
        <TextInput
          placeholder="Enter initial counter value"
          value={initialValue}
          onChangeText={(text) => setInitialValue(text)}
          keyboardType="numeric"
          returnKeyType="done"
        />
        <Button title="Add Counter" onPress={handleAddCounter} />
      </View>
    );
  };

  export default AddCounterForm;

Additionally, we add the "Add Counter" button that performs handleAddCounter when it's pressed. We'll implement this method.

const handleAddCounter = () => {
  if (initialValue.trim() !== '') {
    onAddCounter(Number(initialValue));
    setInitialValue('');
  }
};

Using the onAddCounter method prop, we update the initialValue of the counter by passing it in as a parameter if the input (stripped of white spaces) is not empty. We also convert the String input value to a number using the built-in method. Afterwards, we set the initialValue to empty for future use.

Adding styles to the components, this is what our source code looks like:

import React, { useState } from 'react';
import { View, TextInput, Button, StyleSheet } from 'react-native';

const AddCounterForm = ({ onAddCounter }) => {
  const [initialValue, setInitialValue] = useState('');

  const handleAddCounter = () => {
    if (initialValue.trim() !== '') {
      onAddCounter(Number(initialValue));
      setInitialValue('');
    }
  };

  return (
    <View style={styles.addCounterForm}>
      <TextInput
        style={styles.input}
        placeholder="Enter initial counter value"
        value={initialValue}
        onChangeText={(text) => setInitialValue(text)}
        keyboardType="numeric"
        returnKeyType="done"
      />
      <Button title="Add Counter" onPress={handleAddCounter} />
    </View>
  );
};

const styles = StyleSheet.create({
  addCounterForm: {
    marginVertical: 20,
  },
  input: {
    height: 40,
    borderColor: 'gray',
    borderWidth: 1,
    marginBottom: 10,
    paddingHorizontal: 10,
  },
});

export default AddCounterForm;

App.js

Finally, this is our App.js:

import { StatusBar } from 'expo-status-bar';
import { StyleSheet, View } from 'react-native';
import Counter from './components/Counter';

export default function App() {
  return (
    <View style={styles.container}>
        <Counter initialValues={[5, 10, 15]} />
        <StatusBar style="auto" />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#fff',
    alignItems: 'center',
    justifyContent: 'center',
  },
});

We render the Counter component with [5, 10, 15] as the prop (3 counters with initial values 5, 10, 15).

This is our final product.

Your Task

  • Check the lab handout instructions on Quercus.
  • You are required to create a similar app, a To-Do List application.
  • There are two components ToDoList.js and AddTask.js
  • You are given the styles, so you don't have to implement them.
  • All instructions are in the handout, and it should be very similar to this demo.
  • Refer to https://reactnative.dev/docs/components-and-apis on how to use the React Native components and props they take.

The final product should look something like this.

Common Problems

Here are the common problems raised in the issues tab.

  1. For problems with uuidv4 on Android, please check the following solution: #1 (comment) https://stackoverflow.com/questions/61169746/crypto-getrandomvalues-not-supported

Install the following package:

npm install react-native-get-random-values

You need to import the following:

import 'react-native-get-random-values'
import { v4 as uuidv4 } from 'uuid';
  1. Unfortunately, ExpoGo does not work on UofT wifi due to the proxy settings.

To test on the web, please run the following commands:

npx expo install react-dom react-native-web @expo/webpack-config
npx expo start --web

For more information, check: https://docs.expo.dev/workflow/web/

Alternatively, you can try it outside of UofT with your computer and device connected to the same network.

Please test with the simulators if you can't work with the ExpoGo: https://docs.expo.dev/workflow/ios-simulator/ https://docs.expo.dev/workflow/android-studio-emulator/

  1. You might not have admin/root access or privileges:

run the command with 'sudo' on Mac/Linux or on Windows, open terminal as an admin:

Example:

sudo npm install -g expo-cli

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published