Create a todo app React Native for iOS, Android and web

Create a todo app React Native for iOS, Android and web

In this tutorial, I will be describing how to create a todo app with React Native for iOS, Android and web.

You will need Node and Yarn installed on your machine.

The application will be a Todo app but will also make use of Pusher Channels for realtime functionality. You can find a demo of the application below:

In the results of Stack Overflow’s 2019 developer survey, JavaScript happens to be the most popular technology. This is not by mere luck as it has proven we can write applications that can run almost anywhere - from web apps, desktop apps, android apps and iOS apps.

Prerequisites Directory setup

You will need to create a new directory called realtime-todo. In this directory, you will also need to create another one called server. You can make use of the command below to achieve the above:

    $ mkdir realtime-todo
    $ mkdir realtime-todo/server
Building the server

As you already know, we created a server directory, you will need to cd into that directory as that is where the bulk of the work for this section is going to be in. The first thing you need to do is to create a package.json file, you can make use of the following command:

$ touch package.json

In the newly created file, paste the following content:

    // realtime-todo/server/package.json
    {
      "name": "server",
      "version": "1.0.0",
      "description": "",
      "main": "index.js",
      "dependencies": {
        "body-parser": "^1.18.3",
        "cors": "^2.8.5",
        "dotenv": "^7.0.0",
        "express": "^4.16.4",
        "pusher": "^2.2.0"
      },
      "devDependencies": {},
      "scripts": {
        "test": "echo \"Error: no test specified\" && exit 1"
      }
    }

After which you will need to actually install the dependencies, that can be done with:

$ yarn

Once the above command succeeds, you will need to create an index.js file that will house the actual todo API. You can create the file by running the command below:

$ touch index.js

In the index.js, paste the following contents:

    // realtime-todo/server/index.js

    require('dotenv').config({ path: 'variable.env' });

    const express = require('express');
    const bodyParser = require('body-parser');
    const cors = require('cors');
    const Pusher = require('pusher');

    const pusher = new Pusher({
      appId: process.env.PUSHER_APP_ID,
      key: process.env.PUSHER_APP_KEY,
      secret: process.env.PUSHER_APP_SECRET,
      cluster: process.env.PUSHER_APP_CLUSTER,
      useTLS: true,
    });

    const app = express();

    app.use(cors());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

    app.post('/pusher/auth', function(req, res) {
      var socketId = req.body.socket_id;
      var channel = req.body.channel_name;
      var auth = pusher.authenticate(socketId, channel);
      res.send(auth);
    });

    const todos = [];

    app.get('/items', (req, res) => {
      res.status(200).send({ tasks: todos });
    });

    app.post('/items', (req, res) => {
      const title = req.body.title;

      if (title === undefined) {
        res
          .status(400)
          .send({ message: 'Please provide your todo item', status: false });
        return;
      }

      if (title.length <= 5) {
        res.status(400).send({
          message: 'Todo item should be more than 5 characters',
          status: false,
        });
        return;
      }

      const index = todos.findIndex(element => {
        return element.text === title.trim();
      });

      if (index >= 0) {
        res
          .status(400)
          .send({ message: 'TODO item already exists', status: false });
        return;
      }

      const item = {
        text: title.trim(),
        completed: false,
      };

      todos.push(item);

      pusher.trigger('todo', 'items', item);

      res
        .status(200)
        .send({ message: 'TODO item was successfully created', status: true });
    });

    app.post('/items/complete', (req, res) => {
      const idx = req.body.index;

      todos[idx].completed = true;

      pusher.trigger('todo', 'complete', { index: idx });

      res.status(200).send({
        status: true,
      });
    });

    app.set('port', process.env.PORT || 5200);
    const server = app.listen(app.get('port'), () => {
      console.log(`Express running on port ${server.address().port}`);
    });

In the above, we create an API server that has three endpoints:

  • /items : an HTTP GET request to list all available todo items.
  • /items : an HTTP POST request to create a new todo item.
  • /items/complete: used to mark a todo item as done.

Another thing you might have noticed in on Line 3 where we make mention of a file called variable.env. That file does not exists yet, so now is the time to create it. You can do that with the following command:

$ touch variable.env

In the newly created file, paste the following content:

    // realtime-todo/server/variable.env

    PUSHER_APP_ID="PUSHER_APP_ID"
    PUSHER_APP_KEY="PUSHER_APP_KEY"
    PUSHER_APP_SECRET="PUSHER_APP_SECRET"
    PUSHER_APP_CLUSTER="PUSHER_APP_CLUSTER"
    PUSHER_APP_SECURE="1"

Please make sure to replace the placeholders with your original credentials

You can go ahead to run the server to make sure everything is fine. You can do that by running the command:

$ node index.js
Building the client

The client we will build in this section will run on the web. With the help of Expo and React Native, it will also run on Android and iOS. This is made possible via a library called [react-native-web]([https://github.com/necolas/react-native-web)](https://github.com/necolas/react-native-web)).

To get up to speed, we will make use of a starter pack available on GitHub. You will need to navigate to the project root i.e realtime-todo and clone the starter pack project. That can be done with the following command:

    # Clone into the `client` directory
    $ git clone [email protected]:joefazz/react-native-web-starter.git client

You will need to cd into the client directory as all changes to be made will be done there. You will also need to install the dependencies, that can be done by running yarn. As we will be making use of Pusher Channels and at the same time communicate with the server, you will need to run the following command:

$ yarn add axios pusher-js

The next step is to open the file located at src/App.js. You will need to delete all the existing content and replace with the following:

    // realtime-todo/client/src/App.js

    import React, { Component } from 'react';
    import {
      StyleSheet,
      Text,
      View,
      FlatList,
      Button,
      TextInput,
      SafeAreaView,
    } from 'react-native';
    import axios from 'axios';
    import Alert from './Alert';
    import Pusher from 'pusher-js/react-native';

    const APP_KEY = 'PUSHER_APP_KEY';
    const APP_CLUSTER = 'PUSHER_APP_CLUSTER';

    export default class App extends Component {
      state = {
        tasks: [],
        text: '',
        initiator: false,
      };

      changeTextHandler = text => {
        this.setState({ text: text });
      };

      addTask = () => {
        if (this.state.text.length <= 5) {
          Alert('Todo item cannot be less than 5 characters');
          return;
        }

        // The server is the actual source of truth. Notify it of a new entry so it can
        // add it to a database and publish to other available channels.
        axios
          .post('http://localhost:5200/items', { title: this.state.text })
          .then(res => {
            if (res.data.status) {
              this.setState(prevState => {
                const item = {
                  text: prevState.text,
                  completed: false,
                };

                return {
                  tasks: [...prevState.tasks, item],
                  text: '',
                  initiator: true,
                };
              });

              return;
            }

            Alert('Could not add TODO item');
          })
          .catch(err => {
            let msg = err;

            if (err.response) {
              msg = err.response.data.message;
            }

            Alert(msg);
          });
      };

      markComplete = i => {
        // As other devices need to know once an item is marked as done.
        // The server needs to be informed so other available devices can be kept in sync
        axios
          .post('http://localhost:5200/items/complete', { index: i })
          .then(res => {
            if (res.data.status) {
              this.setState(prevState => {
                prevState.tasks[i].completed = true;
                return { tasks: [...prevState.tasks] };
              });
            }
          });
      };

      componentDidMount() {
        // Fetch a list of todo items once the app starts up.
        axios.get('http://localhost:5200/items', {}).then(res => {
          this.setState({
            tasks: res.data.tasks || [],
            text: '',
          });
        });

        const socket = new Pusher(APP_KEY, {
          cluster: APP_CLUSTER,
        });

        const channel = socket.subscribe('todo');

        // Listen to the items channel for new todo entries.
        // The server publishes to this channel whenever a new entry is created.
        channel.bind('items', data => {
          // Since the app is going to be realtime, we don't want the same item to
          // be shown twice. Device A publishes an entry, all other devices including itself
          // receives the entry, so act like a basic filter
          if (!this.state.initiator) {
            this.setState(prevState => {
              return { tasks: [...prevState.tasks, data] };
            });
          } else {
            this.setState({
              initiator: false,
            });
          }
        });

        // This "complete" channel here is for items that were recently marked as done.
        channel.bind('complete', data => {
          if (!this.state.initiator) {
            this.setState(prevState => {
              prevState.tasks[data.index].completed = true;
              return { tasks: [...prevState.tasks] };
            });
          } else {
            this.setState({
              initiator: false,
            });
          }
        });
      }

      render() {
        return (
          // SafeAreaView is meant for the X family of iPhones.
          <SafeAreaView style={{ flex: 1, backgroundColor: '#F5FCFF' }}>
            <View style={[styles.container]}>
              <FlatList
                style={styles.list}
                data={this.state.tasks}
                keyExtractor={(item, index) => index.toString()}
                renderItem={({ item, index }) => (
                  <View>
                    <View style={styles.listItemCont}>
                      <Text
                        style={[
                          styles.listItem,
                          item.completed && { textDecorationLine: 'line-through' },
                        ]}
                      >
                        {item.text}
                      </Text>
                      {!item.completed && (
                        <Button
                          title="✔"
                          onPress={() => this.markComplete(index)}
                        />
                      )}
                    </View>
                    <View style={styles.hr} />
                  </View>
                )}
              />

              <TextInput
                style={styles.textInput}
                onChangeText={this.changeTextHandler}
                onSubmitEditing={this.addTask}
                value={this.state.text}
                placeholder="Add Tasks"
                returnKeyType="done"
                returnKeyLabel="done"
              />
            </View>
          </SafeAreaView>
        );
      }
    }

    const styles = StyleSheet.create({
      container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
        paddingTop: 20,
        height: '100%',
      },
      list: {
        width: '100%',
      },
      listItem: {
        paddingTop: 2,
        paddingBottom: 2,
        fontSize: 18,
      },
      hr: {
        height: 1,
        backgroundColor: 'gray',
      },
      listItemCont: {
        flexDirection: 'row',
        alignItems: 'center',
        justifyContent: 'space-between',
      },
      textInput: {
        height: 40,
        paddingRight: 10,
        borderColor: 'gray',
        width: '100%',
      },
    });

Please update Line 17 and 18 to contain your actual credentials.

While the above is pretty straight forward, perhaps the most interesting is the line that reads Alert('Could not add TODO item');. It is easy to think Alert.alert() should be used, while that is true, react-native-web doesn’t include support for the Alert component so we will have to roll out our own. Here is a list of all components react-native-web supports. Building functionality for making alerts on the web isn’t a herculean task. You will need to create a new file called Alert.js in the src directory.

$ touch src/Alert.js

In the newly created file Alert.js, paste the following contents:

    // realtime-todo/client/src/Alert.js

    import { Platform, Alert as NativeAlert } from 'react-native';

    const Alert = msg => {
      if (Platform.OS === 'web') {
        alert(msg);
        return;
      }

      NativeAlert.alert(msg);
    };

    export default Alert;

Simple right ? We just check what platform the code is being executed on and take relevant action.

With that done, you will need to go back to the client directory. This is where you get to run the client. Depending on the platform you want to run the app in, the command to run will be different:

  • Web : yarn web. You will need to visit [http://localhost:3000](http://localhost:3000).
  • Android/iOS : yarn start-expo

If you go with the second option, you will be shown a web page that looks like the following:

You can then click on the links on the left based on your choice.

Remember to leave the server running

If you open the project on the web and on iOS/Android, you will be able to reproduce the demo below:

Conclusion

In this tutorial, I have described how to build an application that runs on Android, iOS and the web with just one codebase. We also integrated Pusher Channels so as to make communication realtime.

As always, you can find the code on GitHub.

Thanks for reading

If you liked this post, share it with all of your programming buddies!

Follow us on Facebook | Twitter

Learn More

The Complete React Native and Redux Course

React Native - The Practical Guide

The complete React Native course ( 2nd edition )

React Native Web Full App Tutorial - Build a Workout App for iOS, Android, and Web

How to build a Chat App with React Native

React Native on Windows

Top Vue.js Developers in USA

Top Vue.js Developers in USA

Vue.js is an extensively popular JavaScript framework with which you can create powerful as well as interactive interfaces. Vue.js is the best framework when it comes to building a single web and mobile apps.

We, at HireFullStackDeveloperIndia, implement the right strategic approach to offer a wide variety through customized Vue.js development services to suit your requirements at most competitive prices.

Vue.js is an open-source JavaScript framework that is incredibly progressive and adoptive and majorly used to build a breathtaking user interface. Vue.js is efficient to create advanced web page applications.

Vue.js gets its strength from the flexible JavaScript library to build an enthralling user interface. As the core of Vue.js is concentrated which provides a variety of interactive components for the web and gives real-time implementation. It gives freedom to developers by giving fluidity and eases the integration process with existing projects and other libraries that enables to structure of a highly customizable application.

Vue.js is a scalable framework with a robust in-build stack that can extend itself to operate apps of any proportion. Moreover, vue.js is the best framework to seamlessly create astonishing single-page applications.

Our Vue.js developers have gained tremendous expertise by delivering services to clients worldwide over multiple industries in the area of front-end development. Our adept developers are experts in Vue development and can provide the best value-added user interfaces and web apps.

We assure our clients to have a prime user interface that reaches end-users and target the audience with the exceptional user experience across a variety of devices and platforms. Our expert team of developers serves your business to move ahead on the path of success, where your enterprise can have an advantage over others.

Here are some key benefits that you can avail when you decide to hire vue.js developers in USA from HireFullStackDeveloperIndia:

  • A team of Vue.js developers of your choice
  • 100% guaranteed client satisfaction
  • Integrity and Transparency
  • Free no-obligation quote
  • Portal development solutions
  • Interactive Dashboards over a wide array of devices
  • Vue.js music and video streaming apps
  • Flexible engagement model
  • A free project manager with your team
  • 24*7 communication with your preferred means

If you are looking to hire React Native developers in USA, then choosing HireFullStackDeveloperIndia would be the best as we offer some of the best talents when it comes to Vue.js.

What are hourly rates to hire React Native App Development Company?

What are hourly rates to hire React Native App Development Company?

HireFullStackDeveloperIndia is a top provider of React Native for Mobile App Development and cross-platform mobile app development.

Cross-platform mobile application development provides the user experience and functionality of native apps with the multi-platform compatibility of mobile apps. Explore all the details related to building your React Native mobile applications here and get the idea of the hourly hiring charges for React Native app development.

HireFullStackDeveloperIndia is a top provider of React Native for Mobile App Development and cross-platform mobile app development services in India & the USA. It specializes in full-service iOS/Android app development process right from requirements gathering, UX/UI design, coding, testing to product maintenance and support to build fully-featured & quality-rich mobile apps.

As a Top Mobile App Development Company in India provides end-to-end Apps Development Services to transform an idea into a successful mobile application that performs flawlessly in the entire Android ecosystem. Their app development covers connected devices, wearable, smart TVs and in-car infotainment systems. This mobile app development company is working in this business from last one and half decade offering the best result for the clients. They are also able to deliver customized, reliable and efficient mobile app development solutions to the clients.

Right from Mobile Strategy to Mobile app design and development to mobile app testing and deployment, they adhere to proven global standards and methodology to deliver top-ranking mobile apps. They develop and deploy mobility solutions that encompass strategy and industry-specific accelerators aimed towards transformation and building a future-proof organization.

Mobile App Development Services by Full Stack Developers:

  • iOS App Development(iPhone & iPad)
  • Android App Development
  • Windows App Development
  • Cross-platform App Development
  • React Native Mobile App Development

HireFullStackDeveloperIndia Top React Native Developers generally charge the range rates of $12- $18 per hour which depends on your requirement. The cost will be decided on the basis of requirement, product features, an experience level of the developer and many other factors.

To be precise, HireFullStackDeveloperIndia can be your mobile app development partner for the consistent growth of your business. Just get in touch with your Mobile App project requirements, as they can help you in creating an idea for project completion using excellent methodology.