React Native Calendars : How to change the background of only one item in an agenda?

2.9k Views Asked by At

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

1

There are 1 best solutions below

3
On BEST ANSWER

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.