0

I'm trying to change the background of only one item in this agenda but with my code, when I click on an item, it changes the background color of all of them and not just the one I clicked : screen before clicking on item screen after clicking

The problem amounts to knowing just how to change the style of just one item in the agenda and not the style of all of them.

Here is my code :

import React, { useState } from 'react';
import {
    StyleSheet,
    Text,
    View,
    TouchableOpacity,
} from 'react-native';
import moment from 'moment';
import { Avatar, Card } from 'react-native-paper';
import {Agenda} from "react-native-calendars";

const timeToString = (time) => {
    const date = new Date(time);
    return date.toISOString().split('T')[0];
};


export default function calendarScreen () {


    const [color, setColor] = useState(
        {backgroundColor: 'white', backgroundColor2: 'white', texte: 'Disponible', pressed: false}
    );

    const changeColor = () => {
        if (!color.pressed) {
            setColor({backgroundColor: 'lightgreen', backgroundColor2: 'white', texte: 'Réservé', pressed: true})
        } else {
            setColor({backgroundColor: 'white', backgroundColor2: 'green', texte: 'Disponible', pressed: false})
        }

    };

    const [items, setItems] = useState({});

    const loadItems = (day) => {
        setTimeout(() => {
            for (let i = -15; i < 85; i++) {
                const time = day.timestamp + i * 24 * 60 * 60 * 1000;
                const strTime = timeToString(time);

                if (!items[strTime]) {
                   items[strTime] = [];
                    const numItems = 1;
                    for (let j = 0; j < numItems; j++) {
                        items[strTime].push({
                            name: 'Disponible',
                            height: Math.max(50, Math.floor(Math.random() * 150)),
                            style: color.backgroundColor
                        });
                    }
                }
            }
            const newItems = {};
            Object.keys(items).forEach(key => {newItems[key] = items[key];});
            setItems(newItems);
        }, 1000);
    };


    
    const renderItem = (item, firstItemInDay) => {
        return (
            <TouchableOpacity style={{ marginTop: 17, marginRight: 10}} onPress={(changeColor)}>
                <Card style={ { backgroundColor : color.backgroundColor }}>
                    <Card.Content>
                        <View style={{
                            flexDirection: 'row',
                            justifyContent: 'space-between',
                            alignItems: 'center',
                        }}>
                            <Text>{item.name}</Text>
                            <Avatar.Text label="J" />
                        </View>
                    </Card.Content>
                </Card>
            </TouchableOpacity>
        )
    };
    return (
        <View style={{flex:1}}>
            <Agenda
                items={items}
                loadItemsForMonth={loadItems}
                selected={'2020-09-23'}
                renderItem={renderItem}
                />
        </View>
    )
}

Thanks a lot in advance for your help

Hadrien Jaubert
  • 154
  • 1
  • 3
  • 11

1 Answers1

0

The problem is that the color state is bound to every item you're rendering. Every item you're rendering should know about itself whether it is active or not, i.e. have its own state.

So you could do something like this:

const activeItemText = 'Réservé';
const activeItemStyles = {
  backgroundColor: 'lightgreen',
  backgroundColor2: 'white',
};
const inactiveItemText = 'Disponible';
const inactiveItemStyles = {
  backgroundColorPrimary: 'white',
  backgroundColorSecondary: 'white',
};

const timeToString = (time) => {
  const date = new Date(time);
  return date.toISOString().split('T')[0];
};

const CalendarItem = ({item, firstItemInDay}) => {
  const [active, setActive] = React.useState(false);
  const changeColor = () => {
    setActive(!active);
  };

  return (
    <TouchableOpacity
      style={{marginTop: 17, marginRight: 10}}
      onPress={changeColor}>
      <Card style={active ? activeItemStyles : inactiveItemStyles}>
        <Card.Content>
          <View
            style={{
              flexDirection: 'row',
              justifyContent: 'space-between',
              alignItems: 'center',
            }}>
            <Text>{active ? activeItemText : inactiveItemText}</Text>
            <Avatar.Text label="J" />
          </View>
        </Card.Content>
      </Card>
    </TouchableOpacity>
  );
};

const renderItem = (item, firstItemInDay) => {
  return <CalendarItem item={item} firstItemInDay={firstItemInDay} />;
};

const App = () => {
  const [items, setItems] = useState({});

  const loadItems = (day) => {
    setTimeout(() => {
      for (let i = -15; i < 85; i++) {
        const time = day.timestamp + i * 24 * 60 * 60 * 1000;
        const strTime = timeToString(time);

        if (!items[strTime]) {
          items[strTime] = [];
          const numItems = 1;
          for (let j = 0; j < numItems; j++) {
            items[strTime].push({
              name: inactiveItemStyles.texte,
              height: Math.max(50, Math.floor(Math.random() * 150)),
            });
          }
        }
      }
      const newItems = {};
      Object.keys(items).forEach((key) => {
        newItems[key] = items[key];
      });
      setItems(newItems);
    }, 1000);
  };

  return (
    <View style={{flex: 1}}>
      <Agenda
        items={items}
        loadItemsForMonth={loadItems}
        selected={'2020-09-23'}
        renderItem={renderItem}
      />
    </View>
  );
};

export default App;

Further explanation of the above approach

I put the style and possible text values outside of any component and I created a custom CalendarItem component that is passed to your renderItem function.

Because CalendarItem is a functional component it can have its own state. We don't have to hold an entire object in the state in this instance, since the only we really want to know is if an item is active or not. We can update the active state on press and then conditionally render CalendarItem based on your active and inactive styles and data.

5eb
  • 14,798
  • 5
  • 21
  • 65
  • Hello ! I am sorry but i have a new problem now... You solved my last problem so easily that I would be very glad if you could help with this one which is pretty much the same as this one. I'll leave the link here : https://stackoverflow.com/questions/64463002/react-native-calendars-how-to-change-the-background-color-of-only-one-item-usi – Hadrien Jaubert Oct 24 '20 at 12:24
  • If this answer solved the problem described in the original post be sure to accept it by pressing the checkmark to show the problem has been resolved. – 5eb Oct 24 '20 at 13:10
  • Ah yeah sorry I'm new on Stackoverflow – Hadrien Jaubert Oct 26 '20 at 06:16