DEV Community

Cover image for Building a To-Do List with React-Native and Styled-Components.
Uzodike Oguejiofor
Uzodike Oguejiofor

Posted on • Updated on

Building a To-Do List with React-Native and Styled-Components.

Hello there, I would like to walk you through the process of building a basic to-do list in react-native. Before I begin, here is a snippet of what we will be building in this article.

Alt TextAlt Text

You should have a basic knowledge of React and how it works before engaging this article, as I would not be explaining some of these basic concepts

Getting Started

We would start by using Expo to create or initialize a new app from the command line. This app is created by Expo and a boilerplate on which we will be building our to-do app.

$ expo init todo-list

$ cd todo-list

$ expo start

Enter fullscreen mode Exit fullscreen mode

In the code above, an app called todo-list is created, then we change the directory to the todo-list folder that was created and start the app with the 'expo start' command. Go now to the App.js file and delete all the code there. Don't worry, we will write our own code, which is much better than what is already there. Now we have set up our environment, we can begin building.

Are you ready bud?

Sure you are.

Creating Components

Let's begin by creating a folder called Components, which will hold four files. These files are components that are serving different purposes. We will call these components AddInput.js, Header.js, Empty.js, and TodoList.js. These components will be the main attributes of our work here.

In AddInput.js we should have something like this:

//AddInput.js

import React, { useState } from "react";
import {View, TextInput, Text, TouchableOpacity} from 'react-native'


export default function AddInput() {
  const [value, setValue] = useState("");

  const onChangeText = (text) => {
    setValue(text);
  };

  return (
    <View>
      <View>
        <TextInput placeholder="Add Task..." onChangeText= 
         {onChangeText} />
      </View>
      <TouchableOpacity
        onPress={() => {
          alert('button clicked')
        }}
      >
        <Text>Submit</Text>
      </TouchableOpacity>
    </View>
  );
}
Enter fullscreen mode Exit fullscreen mode

This gives us a basic form where we write to-do items and a button to add them to the to-do list. Let's give this guy a bit of styling to make it look good.

styling AddInput.js
We would be using a library called styled-components to style the component. Firstly, we install styled-components

yarn add styled-components
Enter fullscreen mode Exit fullscreen mode

After installing it, we import it into the AddInput.js file so we can be able to use it.

//AddInput.js
 ...
import styled from "styled-components";

 ...

//beneath the AddInput component
//styles
const ComponentContainer = styled.View`
  flex-direction: row;
`;

const InputContainer = styled.View`
  flex-direction: row;
  border-radius: 10px;
`;

const Input = styled.TextInput`
  font-size: 20px;
  background-color: white;
  width: 300px;
  margin-right: 20px;
  padding: 10px;
  margin-bottom: 20px;
  border-radius: 10px;
`;

const SubmitButton = styled.TouchableOpacity`
  width: 50px;
  justify-content: center;
  align-items: center;
  background-color: whitesmoke;
  margin-bottom: 20px;
  border-radius: 50px;
`;

 ...

Enter fullscreen mode Exit fullscreen mode

In the AddInput component, we would now be updating the native components to custom semantics from styled-components. These native components will now be accessed via styled-components. This is to enable the styling to take place in the AddInput component.

 ...

export default function AddInput(}) {
 ...
  return (
    <ComponentContainer>
      <InputContainer>
        <Input placeholder="Add Task..." onChangeText={onChangeText} />
      </InputContainer>
      <SubmitButton
        onPress={() => {
           alert('button clicked')
        }}
      >
        <Text>Submit</Text>
      </SubmitButton>
    </ComponentContainer>
  );
}

//beneath the AddInput component
//styles

 ...
Enter fullscreen mode Exit fullscreen mode

As you can see, we updated the native components to a custom-styled component. In this case, we would not need to import these native components from react-native anymore as styled-components already let us access them.

Alright, that's enough for the AddInput.js for now. Let's quickly build up the Empty.js before we continue. The Empty.js file is rendered when the to-do list is empty. This component kind of makes the app look more appealing.

import React from "react";
import styled from "styled-components";

export default function Empty() {
  return (
    <ComponentContainer>
      <EmptyImage
        source={require("add an image here")}
      />
      <EmptyText>Add To-Do.</EmptyText>
    </ComponentContainer>
  );
}

const ComponentContainer = styled.View`
  align-items: center;
  justify-content: center;
  height: 650px;
`;

const EmptyImage = styled.Image`
  width: 350px;
  height: 200px;
`;

const EmptyText = styled.Text`
  color: white;
  font-family: poppins-bold;
  margin-top: 30px;
  font-size: 30px;
`;

Enter fullscreen mode Exit fullscreen mode

Let's build what I consider the heartbeat of this app. This will happen in App.js

import React, { useState } from "react";
import { View, StatusBar, FlatList } from "react-native";
import styled from "styled-components";


export default function App() {
 const [data, setData] = useState([]);
 return (
      <ComponentContainer>
        <View>
          <StatusBar barStyle="light-content" 
             backgroundColor="midnightblue" />
        </View>

        <View>
          <FlatList
            data={data}
           renderItem={({ item }) => (
              console.log(item)
            )}
          />

        </View>
      </ComponentContainer>
    );
}

const ComponentContainer = styled.View`
  background-color: midnightblue;
  height: 100%;
  flex-direction: column;
  align-items: center;
  justify-content: center;
`;


Enter fullscreen mode Exit fullscreen mode

FlatList

At this point, I want to let you know that a to-do list is an array. Let me explain that with something called FlatList. FlatList is a component used to display large scrollable lists. Lists displayed with the FlatList can be infinite and unpredictable. FlatList has two major props which include data and the renderItem. data takes an array, in this case, an array called data which is an empty array and depends on the AddInput component for data to fill up the empty array. This array is the to-do list. On the other hand, we have the renderItem which is a callback function that renders each item of the array of the data prop.

Adding item to the list

Let us go back to our AddInput component and look at one of the native components called TouchableOpacity. This component takes in a prop called onPress. If you check out our above code on AddInput.js, you will see that the onPress prop is set to alert('hi') . onPress runs a function when a button is pressed. Now, the function it will be running is the one that will be adding items to the list(array called data).

In App.js we will create a method called SubmitHandler, which adds each item to the list. This is the function that will be passed into onPress.

//App.js
  ...

export default function App() {
     ...
  const submitHandler = (value) => {
    setData((prevTodo) => {
      return [
        {
          value: value,
          key: Math.random().toString(),
        },
        ...prevTodo,
      ];
    });

     ...
  };

Enter fullscreen mode Exit fullscreen mode

The submitHandler method holds a function called setData that updates the state by adding items to the array.

To get the SubmitHandler method to work in the AddInput.js, we need to bring the AddInput component into the App.js and pass the SubmitHandler as a prop to the AddInput component.

//App.js
 ...
import AddInput from "./Components/AddInput";

export default function App() {
   ...

  const submitHandler = (value) => {
    setData((prevTodo) => {
      return [
        {
          value: value,
          key: Math.random().toString(),
        },
        ...prevTodo,
      ];
    });
  };


    return (
      <ComponentContainer>
        <View>
          <StatusBar barStyle="light-content" 
            backgroundColor="midnightblue" />
        </View>

        <View>
          //FlatList
          <View>
            <AddInput submitHandler={submitHandler} />
          </View>
        </View>
      </ComponentContainer>
    );
Enter fullscreen mode Exit fullscreen mode

The submitHandler method is passed in as a prop to the AddInput component to be used inside the AddInput component.

Back to AddInput.js, we pass in the props

  ...
export default function AddInput({ submitHandler }){
   ...
return(
  ...
 <SubmitButton
        onPress={() => {
          setValue(submitHandler(value));
        }}
      >
        ...
      </SubmitButton>
)
}
Enter fullscreen mode Exit fullscreen mode

Here, we are adding the value from our form input to the array being handled by the data prop in App.js with the help of onPress who calls the SubmitHandler function when pressed.

Displaying list to screen

Another component called TodoList is created at this point because we need it to render our to-do list items to the screen. Remember the renderItems prop from FlatList which helps to render items from the data prop to the screen? We are going to use it to render the TodoList component to the screen(this component holds our array items.)

Back to App.js

  ...
import TodoList from "./Components/TodoList";
  ...

<FlatList
            data={data}
            renderItem={({ item }) => (
              <TodoList item={item} />
            )}
          />
Enter fullscreen mode Exit fullscreen mode

Now moving to the TodoList.js, we build our component.

//TodoList.js
import React from "react";
import { View } from "react-native";
import styled from "styled-components";

export default function TodoList({ item }) {
  return (
    <ComponentContainer>
      <ListContainer>
        <View>
          <TextItem>{item.value}</TextItem>
          <TextTask> Task</TextTask>
        </View>
      </ListContainer>
    </ComponentContainer>
  );
}

const ListContainer = styled.TouchableOpacity`
  background-color: whitesmoke;
  height: auto;
  width: 350px;
  margin-bottom: 30px;
  border-radius: 10px;
  flex-direction: row;
  justify-content: space-between;
`;

const ComponentContainer = styled.View`
  flex-direction: row;
  justify-content: center;
  height: auto;
  width: auto;
`;

const TextItem = styled.Text`
  color: black;
  width: 260px;
  height: auto;
  font-size: 20px;
  margin-top: 10px;
  margin-right: 20px;
  font-family: poppins-regular;
`;

const TextTask = styled.Text`
  color: goldenrod;
  font-size: 15px;
  margin-right: 20px;
  font-family: poppins-regular;
  border-radius: 10px;
  width: 40px;
`;

Enter fullscreen mode Exit fullscreen mode

At this point, each item of our to-do list should be displayed on the screen when the function in onPress is called.

Before we continue, we need to make our list of items unique with a key. We do this by employing a prop called the keyExtractor prop in FlatList. This prop assigns a key to each of the items in the list.

  ...

<FlatList
            data={data}
            keyExtractor={(item) => item.key}
            renderItem={({ item }) => (
              <TodoList item={item} />
            )}
          />
Enter fullscreen mode Exit fullscreen mode

Deleting items from list

To delete items from the to-do list, we will create a method called DeleteItem in App.js and bring it in as a prop into TodoList.js

//App.js
  ...

export default function App() {
     ...
  const deleteItem = (key) => {
    setData((prevTodo) => {
      return prevTodo.filter((todo) => todo.key != key);
    });
  };
     ...
  };


Enter fullscreen mode Exit fullscreen mode

Now we pass deleteItem as a prop to the TodoList component

//App.js
  ...
import TodoList from "./Components/TodoList";
  ...

<FlatList
            data={data}
            keyExtractor={(item) => item.key}
            renderItem={({ item }) => (
              <TodoList item={item} deleteItem={deleteItem} />
            )}
          />
Enter fullscreen mode Exit fullscreen mode

For DeleteItem to work in TodoList.js, we need to update the component.

import React from "react";
import { View } from "react-native";
import { Entypo } from "@expo/vector-icons";
import { MaterialIcons } from "@expo/vector-icons";
import styled from "styled-components";

export default function TodoList({ item, deleteItem }) {
  return (
    <ComponentContainer>
      <ListContainer>
        <CirlceContainer>
          <Entypo name="circle" size={20} color="midnightblue" />
        </CirlceContainer>
        <View>
          <TextItem>{item.value}</TextItem>
          <TextDate> Task</TextDate>
        </View>
        <IconContainer onPress={() => deleteItem(item.key)}>
          <MaterialIcons name="delete" size={24} color="midnightblue" />
        </IconContainer>
      </ListContainer>
    </ComponentContainer>
  );
}

const ListContainer = styled.TouchableOpacity`
  background-color: whitesmoke;
  height: auto;
  width: 350px;
  margin-bottom: 30px;
  border-radius: 10px;
  flex-direction: row;
  justify-content: space-between;
`;

const ComponentContainer = styled.View`
  flex-direction: row;
  justify-content: center;
  height: auto;
  width: auto;
`;

const TextItem = styled.Text`
  color: black;
  width: 260px;
  height: auto;
  font-size: 20px;
  margin-top: 10px;
  margin-right: 20px;
  font-family: poppins-regular;
`;

const TextDate = styled.Text`
  color: goldenrod;
  font-size: 15px;
  margin-right: 20px;

  font-family: poppins-regular;
  border-radius: 10px;
  width: 40px;
`;

const IconContainer = styled.TouchableOpacity`
  align-items: center;
  justify-content: center;
  margin-right: 10px;
  margin-top: 15px;

  height: 40px;

  border-radius: 10px;
`;

const CirlceContainer = styled.View`
  align-items: center;
  justify-content: center;
  padding-left: 5px;
`;
Enter fullscreen mode Exit fullscreen mode

In the above code, we can delete an item from the list and we use icons to represent the delete button which removes items from the list when pressed.

Final Touches

The FlatList has other props attached to it. but we would focus on two more. These three include:
a. ListHeaderComponent
b. ListEmptyComponent

ListHeaderComponent will hold a component that contains the header of the list. Remember our Header.js file? It is time to use it.

//Header.js

import React from "react";
import styled from "styled-components";

let today = new Date().toISOString().slice(0, 10);

export default function Header() {
  return (
    <ComponentContainer>
      <HeaderText>To-Do.</HeaderText>
      <HeaderList>{today}</HeaderList>
    </ComponentContainer>
  );
}

const ComponentContainer = styled.View`
  height: 100px;
  flex-direction: row;
  justify-content: space-between;
  align-items: center;
`;

const HeaderText = styled.Text`
  color: white;
  font-family: poppins-bold;
  font-size: 30px;
`;

const HeaderList = styled.Text`
  color: white;
  font-family: poppins-bold;
  font-size: 20px;
  margin-right: 20px;
`;
Enter fullscreen mode Exit fullscreen mode

This component will be at the head of the screen. Let's make some changes to our App.js file.

//App.js
  ...
import Header from "./Components/Header";
  ...

<FlatList
            data={data}
            ListHeaderComponent={() => <Header />}
            keyExtractor={(item) => item.key}
            renderItem={({ item }) => (
              <TodoList item={item} deleteItem={deleteItem} />
            )}
          />
Enter fullscreen mode Exit fullscreen mode

Another prop is the ListEmptyComponent which renders a component when the to-do list is empty. Here, we are going to pass in the Empty component from Empty.js

//App.js
  ...
import Empty from "./Components/Empty";
  ...

<FlatList
            data={data}
            ListHeaderComponent={() => <Header />}
            ListEmptyComponent={() => <Empty />}
            keyExtractor={(item) => item.key}
            renderItem={({ item }) => (
              <TodoList item={item} deleteItem={deleteItem} />
            )}
          />
Enter fullscreen mode Exit fullscreen mode

Conclusion

Pheew! That was a lot of code. I hope you practice this and get your hands dirty. It is the best form of learning. I would love to see you tweak the code to your own taste and make something nice out of it. Good Luck in your endeavors and React-Native journey.

You can find the source code here https://github.com/ReenyDavidson/native-todo/tree/main

Top comments (5)

Collapse
 
wendilyn15 profile image
wendilyn15

Hi! can you help me with this error?

Collapse
 
wendilyn15 profile image
wendilyn15

_styledComponents.default.View is not a function
Evaluating Components/AddInput.js
Evaluating App.js
Loading App.js
TypeError: _styledComponents.default.View is not a function
at Object.eval (Components/AddInput.js.js:29:35

at eval (Components/AddInput.js.js

at eval (Components/AddInput.js.js

at eval (<anonymous>)
at [snack internals]
at Object.a ([snack internals]
at e.evaluate ([snack internals]
at Ze ([snack internals]
at [snack internals]
at https://snack-web-player.s3.us-west-1.amazonaws.com/v2/41/static/js/app.9bb29d3a.chunk.js:1:14898
Enter fullscreen mode Exit fullscreen mode
Collapse
 
uzodike profile image
Uzodike Oguejiofor

From what I can deduce from this snippet, you probably did not install the Styled-Components library.
You need install the library first

npm install --save styled-components
Enter fullscreen mode Exit fullscreen mode

After installing the library, then import it into AddInput.js

import styled from "styled-components";
Enter fullscreen mode Exit fullscreen mode

If this doesn't work, send in the full code, I would love to be of help.

Collapse
 
jaygustawo profile image
JayGustawo

Amazing article, though I'm having some issues and tried troubleshooting, do you have the code on a github repository?

Collapse
 
uzodike profile image
Uzodike Oguejiofor

Thank you for the feedback. You can review the code on my GitHub. github.com/ReenyDavidson/native-to...