0

I am building a todo list app, and I want to long press individual todos, to change their color to green in order to mark them as finished.

I have a var color = 'white'; inside my App.js and I have another component named listItem for the list items.

I have this pretty basic function to change the color

const longPressHandler = () => {
    (color == 'white') ? color = 'green' : color = 'white';
  }

and I am sending the color via props of listItem

<ListItem item={item} longPressHandler = {longPressHandler} color={color} pressHandler = {pressHandler}/>

and I am using it as follows backgroundColor: props.color Check below:

const styles = StyleSheet.create({
        listItem:{
            padding: 8,
            margin:4,
            fontSize: 18,
            textAlignVertical:'center',
            borderColor:'gray',
            borderWidth: 3,
            borderStyle: 'solid',
            borderRadius:10,
            backgroundColor: props.color,
          }
    })

BUUT, it does not work... What am i doing wrong? Is there any simple solution that I am missing...

Here is the full code of App.js

import React, {useEffect, useState} from 'react';
import {Text, View, StyleSheet, FlatList, Alert, TouchableWithoutFeedback, Keyboard, Button, AsyncStorage } from 'react-native';
import ListItem from './components/listItem';
import AddItem from './components/addItem';

// npx react-native start // TO START
// npx react-native run-android // TO PROJECT INTO EMULATOR

//Hooks cant be used in class components, thus, switched from Class component structure => Function component structure

export default function TODOList() {

  const [todos, setTodos] = useState([
    {todo: 'do chores', key: '1'},
    {todo: 'do homework', key: '2'},
    {todo: 'go to grocery', key: '3'},
  ]);
 

  var color = 'white';

  //This handler DELETES the pressed list item from the list
  const pressHandler = (key) => {
    const newtodos = todos.filter(todo => todo.key != key);

    setTodos(newtodos);
    
    
  }

  //ADDS a new todo with the given text and a randomly generated key to the old todos list
  const inputSubmitHandler = (text) => {

    if(text.length > 0){
      const key = Math.random().toString();
      const newTodos = [{text, key}, ...todos];

      setTodos(newTodos);
      
    }else{
      Alert.alert('ERROR!', 'Text cannot be empty', [{text:'OK'}])
    }
  }

  //TODO Change color of the individual item in the list
  const longPressHandler = () => {
    (color == 'white') ? color = 'green' : color = 'white';
  }

      
  console.log('color', color);
  return (
    <TouchableWithoutFeedback onPress={() => {Keyboard.dismiss();}}>
      <View style={styles.mainPage}>
        <View style = {styles.header}>
            <Text style={styles.title}>TODO List</Text>
        </View>
        <View style={styles.content}>
          <AddItem inputSubmitHandler={inputSubmitHandler} />
          <View style={styles.list}>
            <FlatList
              data={todos}
              renderItem={( {item} ) => (
                <ListItem item={item} longPressHandler = {longPressHandler} color={color} pressHandler = {pressHandler}/>
              )}
            />
          </View>
        </View>
      </View>
    </TouchableWithoutFeedback>
  );
}

//The margins, paddings, etc. are given as pixel values, wont work same in other devices.
const styles = StyleSheet.create({
  mainPage: {
    flex: 1, // takes the whole background
    backgroundColor: 'white',
  },
  content: {
    flex: 1,
  },
  list:{
    margin: 10,
    flex:1,
  },
  header:{
    height: 50,
    paddingTop: 8,
    backgroundColor: 'orange'
},
  title:{
    textAlign: 'center',
    fontSize: 24,
    fontWeight: 'bold',
},
});

Here is the full code of listItem.js

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

export default function ListItem(props) {

    //Moved the style inside of the function since I want to use the color prop in 'backgroundCcolor'
    const styles = StyleSheet.create({
        listItem:{
            padding: 8,
            margin:4,
            fontSize: 18,
            textAlignVertical:'center',
            borderColor:'gray',
            borderWidth: 3,
            borderStyle: 'solid',
            borderRadius:10,
            backgroundColor: props.color,
          }
    })

    return (
        <TouchableOpacity onLongPress={() => props.longPressHandler()} onPress = {() => props.pressHandler(props.item.key)}>
              <Text style={styles.listItem}> {props.item.todo}</Text> 
        </TouchableOpacity>
    
    )
}
5
  • You should use a state to change the color in order to re render, and are you doing the styles creation inside the component ? Commented Nov 6, 2020 at 11:35
  • Could you please add some more code for better understanding? How are you managing the data and states? Commented Nov 6, 2020 at 11:42
  • @K.Raj. I edited my post and uploaded full codes. Commented Nov 6, 2020 at 11:51
  • @GuruparanGiritharan I tried to use state but then the color of every list item changes to green or white. Couldnt manage to target specific list item.. Commented Nov 6, 2020 at 11:52
  • 1
    @GuruparanGiritharan for the second question about styles creation: yes, because when did outside of the component, cant use props of the component. Commented Nov 6, 2020 at 11:54

2 Answers 2

1

There are few changes you can do to the code

  1. Move the color choice to the ListItem and pass a prop to decide that
  2. No need to create the whole style inside the item itself you can pass the ones that you want to override

So to do this you will have to start with your listitem

<TouchableOpacity
  onLongPress={() => props.longPressHandler(props.item.key)}
  onPress={() => props.pressHandler(props.item.key)}>
  <Text
    style={[
      // this will make sure that only one style object is created
      styles.listItem,
      { backgroundColor: props.marked ? 'green' : 'white' },
    ]}>
    {props.item.todo}
  </Text>
</TouchableOpacity>

And your long press handler should change like below, this will set the marked property in the state which you use to decide the color above

  const longPressHandler = (key) => {
    const updatedTodos = [...todos];
    const item = updatedTodos.find((x) => x.key == key);
    item.marked = !item.marked;
    setTodos(updatedTodos);
  };

You can refer the below snack https://snack.expo.io/@guruparan/todo

Sign up to request clarification or add additional context in comments.

Comments

1

Try this way

const longPressHandler = (index) => {
    
    const newTodos = [...todos];
    newTodos[index].color = (newTodos[index].color && newTodos[index].color == 'green') ? 'white' : 'green';
    setTodos(newTodos);
    
}


<FlatList
     data={todos}
     renderItem={( {item, index} ) => (
        <ListItem
       item={item}
       index={index} 
       longPressHandler = {longPressHandler} 
       color={item.color || 'white'} 
       pressHandler = {pressHandler}
    />
   )}
/>

export default function ListItem(props) {

    return (
        <TouchableOpacity onLongPress={() => props.longPressHandler(props.index)} >
             .....
        </TouchableOpacity>
    
    )
}

Note: You have to pass an index from renderItem to ListItem and also from ListItem to longPressHandler function

2 Comments

this did not work. " todos[index].color = (todos[index].color == 'white') ? 'green' : 'white'; " line fires up an 'undefined is not object error' pointing to the second 'todos'
I have updated my answer please check. Just change variable name

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.