0

I have a Reminder component comprising of a form where I am storing text and date onClick of a button using AsyncStorage.

Now, I want to display this stored data in Agenda Component.

I am using Agenda component from react-native-calendars library react-native-calendars

this is my reminder component

    class Reminder extends Component {
        constructor(props) {
            super(props);
            this.state = {
                input: '',
                chosenDate: new Date(),
            };
            this.setDate = this.setDate.bind(this);
            this.handleChangeInput = this.handleChangeInput.bind(this);
            this.saveData = this.saveData.bind(this);
        }

        setDate(newDate) {
            this.setState({
                chosenDate: newDate
            });
        }

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

        //save the input
        saveData() {
            AsyncStorage.setItem("key", JSON.stringify(this.state));
        }
        render() { 
            return ( 
                <View>
                    <Form style={styles.formContainer}>
                        <View style={styles.formView}>

                                < TextInput
                                placeholder = "Set your reminder"
                                onChangeText={this.handleChangeInput}
                                value={this.state.input}
                                />

                            <DatePicker
                                defaultDate={new Date()}
                                minimumDate={new Date(2018, 1, 1)}
                                maximumDate={new Date(2019, 12, 31)}
                                locale={"en"}
                                timeZoneOffsetInMinutes={undefined}
                                modalTransparent={false}
                                animationType={"fade"}
                                androidMode={"default"}
                                placeHolderText="Select date"
                                textStyle={{ color: "green" }}
                                placeHolderTextStyle={{ color: "#d3d3d3" }}
                                onDateChange={this.setDate}
                            />
                            <Text style={styles.datePicker}>
                                {this.state.chosenDate.toString().substring(0,10)}
                            </Text>
                        </View>
                        <View style={styles.footer}>
                            <Button block success style={styles.saveBtn} 
                            onPress={ () => 
                                {
                                  this.saveData()
                                  console.log('save data',this.state);
                                }
                            } 
                               >
                                <Icon type='MaterialIcons' name='done' />                        
                            </Button>
                        </View>
                    </Form>
                </View> 
            );
        }
    }

export default Reminder;

and this it's screen Reminder screen

import React, { Component } from 'react';
import { View, StatusBar } from 'react-native';
import PropTypes from 'prop-types';

import Reminder from '../components/Reminder';

const ReminderScreen = ({navigation}) => (
    <View >
        <Reminder navigation={navigation} >
            <StatusBar backgroundColor = "#28F1A6" />
         </Reminder >
    </View>
);

Reminder.propTypes = {
    navigation: PropTypes.object.isRequired
}

export default ReminderScreen;

and this is the component I want to display that data Agenda Component

class WeeklyAgenda extends Component {
    constructor(props) {
    super(props);
    this.state = {
      items: {},
      selectedDate: ''
    };
  }

  render() {
    return (
      <View style={{height:600}}>
            <Agenda
              items={this.state.items}
              loadItemsForMonth={this.loadItems.bind(this)}
              selected={this.props.day}
              renderItem={this.renderItem.bind(this)}
              renderEmptyData={this.renderEmptyDate.bind(this)}
              rowHasChanged={this.rowHasChanged.bind(this)}
              onRefresh = {() => { this.setState({refeshing : true})}}
              refreshing = {this.state.refreshing}
              refreshControl = {null}
              pastScrollRange={1}
              futureScrollRange = {3}
              theme = {
                {
                  agendaTodayColor: '#28F1A6',
                  agendaKnobColor: '#28F1A6',
                  dotColor: '#28F1A6',
                  selectedDayBackgroundColor: '#28F1A6',
                  todayTextColor: '#28F1A6',
                }
              }
          />
          <View >
              <Fab
                  active={!this.state.active}
                  direction="up"
                  style={{ backgroundColor: '#28F1A6'}}
                  position = 'bottomRight'
                  onPress={() => this.props.navigation.navigate('Reminder')}>
                  <Icon type='MaterialCommunityIcons' name="reminder" />
              </Fab>
          </View>
      </View>
    );
  }

  //On application loads, this will get the already saved data and set the state true when it's true.
    componentDidMount() {
        AsyncStorage.getItem("key").then((newItems) => {
            this.setState(JSON.parse(newItems));
        });
    }

  loadItems = (day) => {
    console.log('day',day);
    console.log('items', this.state.items);
    const {selectedDate} = this.state;

    setTimeout(() => {
      console.log('selected date', selectedDate);
      this.setState({selectedDate: day});
      console.log('selected date later', day);
      const newItems = {};
      Object.keys(this.state.items).forEach(key => {newItems[key] = this.state.items[key];});
      console.log('new items later', newItems);
      this.setState({
        items: newItems
      });
      console.log('new items later', this.state.newItems);
      console.log('items later', this.state.items);
      this.state.items;
    },1000);

  };

  renderItem(item) {
    return (
      <View style={[styles.item, {height: item.height}]}>
        <TouchableOpacity onPress={() => {this.props.navigation.navigate('Reminder')}}>
          <Text>{item.name}</Text>
        </TouchableOpacity>
      </View>
    );
  }

  renderEmptyDate() {
    return (
      <View style={styles.emptyDate}>
        <TouchableOpacity onPress={() => {this.props.navigation.navigate('Reminder')}}>
          <Text style={styles.emptyTextColor}> No Event or Reminder on this date </Text>
        </TouchableOpacity>
      </View>

    );
  }

  rowHasChanged(r1, r2) {
    return r1.name !== r2.name;
  }

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

export default WeeklyAgenda;

and this is it's screen Agenda Screen

import React, { Component } from 'react';
import { View, Text, StatusBar } from 'react-native';
import PropTypes from 'prop-types';

import WeeklyAgenda from '../components/Agenda';
class AgendaScreen extends Component {
    state = {  }
    render() { 
        const {navigation} = this.props;
        const { params } = this.props.navigation.state;
        return (
            <View style={{height: 100}}>     
                <WeeklyAgenda day={params["day"]} navigation={navigation}>
                    <StatusBar backgroundColor="#28F1A6" />
                </WeeklyAgenda >
            </View>
        );
    }
}

WeeklyAgenda.propTypes = {
    navigation: PropTypes.object.isRequired
}

export default AgendaScreen;

I am fairly new to react-native and still trying to figure out how to share data between components and screens.

project stucture

Nauman
  • 894
  • 4
  • 14
  • 45
  • do you mind providing details on the hierarchy? Does Agenda and Reminder have a common parent? Or are they completely unrelated? – Bens Steves Dec 08 '18 at 18:30
  • @BensSteves they are two different components. `Agenda` deals with the `weekly schedule` and `reminder` creates and stores a `reminder/event` – Nauman Dec 08 '18 at 18:47
  • are you using a back end db at all that you can fetch or query? – Bens Steves Dec 08 '18 at 18:52
  • @BensSteves I am using `AsyncStorage` which is react-native built-in `localStorage`. I using this is to store values in `reminder component` and then using `componentDidMount` in `Agenda component` to `get that data` and set it in `Agenda Component` – Nauman Dec 08 '18 at 18:55
  • 1
    @BensSteves I just edited my question with my project. If that helps or clarifies my question in anyway. – Nauman Dec 08 '18 at 18:59
  • 1
    I have saved data in `reminder component`. I want to display that data in `agenda component`. I am not sure how to do it. – Nauman Dec 08 '18 at 19:03
  • 1
    yes yes. I understand now. Sorry about that. Give me a sec so I can write up the solution for you. – Bens Steves Dec 08 '18 at 19:04

2 Answers2

0

So basically in react-native data flow between components involves props. In case you need to pass the entire state of one component to another, you can pass props to the second component by stringifying the state and recieve the prop by parsing it to json object again.

If you are using any navigation to navigate between components and screens, you can also use passProps to send state to next screen. Hope this helps.

  • this was my initial thought. But Reminder and Agenda have no relation. See the project heirarchy. And passingProps would not work because I believe the routes are being mapped. Even if they aren't I believe he would have to pass through a Link no? – Bens Steves Dec 08 '18 at 19:20
  • 2
    Yes. He must have to pass through a link to use passProps. My bad i didn't know the agend and reminder have no relation. Anyway another approach can be using the Context Api react provides. Basically it is similar to a shared state concept. Agenda and reminder can have some shared data to access using contexts. Here is an example: https://link.medium.com/KdjGtthkuS – Sourav Paul Dec 08 '18 at 19:34
  • @SouravPaul if you have a better solution to my update in my answer that would be great. my solution works (on my end) but it feels kind of hacky...do have any suggestions to make it better? thanks in advanced – Bens Steves Dec 09 '18 at 11:21
0

To do this you want to take your items and map them in your Agenda component. I don't know what props the item object contains so I just made up item.name and item.whatever. I also didn't know how you wanted to display this data. You can display it however you like in the return statement of the map function.

If you want a table just have to render a table in the return statement and dynamically add your item props accordingly. Does this make sense?

Also, the outermost element of your return in your map function must have a key prop that accepts unique keys only.

 state = { 
     items: [], 
     selectedDate: ''
 }
  render() {
      const { items } = this.state; // destructure state, so create a variable called items that is equal to this.state.items essentially takes the items in the state variable items and copies them to a new const variable called items
    return (
      <View style={{height:600}}>
            <Agenda
              items={items} //send those items to Agenda Component via prop
              loadItemsForMonth={this.loadItems.bind(this)}
              selected={this.props.day}
              renderItem={this.renderItem.bind(this)}
              renderEmptyData={this.renderEmptyDate.bind(this)}
              rowHasChanged={this.rowHasChanged.bind(this)}
              onRefresh = {() => { this.setState({refeshing : true})}}
              refreshing = {this.state.refreshing}
              refreshControl = {null}
              pastScrollRange={1}
              futureScrollRange = {3}
              theme = {
                {
                  agendaTodayColor: '#28F1A6',
                  agendaKnobColor: '#28F1A6',
                  dotColor: '#28F1A6',
                  selectedDayBackgroundColor: '#28F1A6',
                  todayTextColor: '#28F1A6',
                }
              }
          />
    );
  }

  //On application loads, this will get the already saved data and set the state true when it's true.

    componentDidMount() {
        AsyncStorage.getItem("key").then((newItems) => {
            //assuming JSON.parse returns an array of JSON objects, if not change
            //items to items: {} the way you had it originally in state
            this.setState({ items: JSON.parse(newItems) }) //add the items or data to state variable called items
        });
    }
}

//let's pretend that this is your Agenda Component

class Agenda extends Component {
    state = { 
        items: this.props.items, //get the items that were passed above using items={item} and assign them to a state variable items here in the Agenda component 
    }

    render() {
        const { items } = this.state; //destructure state
        return(
          <div>
            //put this where you want to render the data in the AgendaComponent you said you were using a Text Component so it would like this
            items.map(item => {
                return (
                  //assuming that item object comes with an id if not you must add a unique key so you can call a func that updates a count variable and returns it
                  <Text key={item.id}>{item.name} {item.date}</Text>
                )
            })
        )
      </div>
    }
}

remember. the map function is almost like a for loop. It will iterate through every element in the array and do what is in the return statement for each element of the array.

Hope this helps.

Update

The previous solution I wrote out had a bug. The Agenda component was setting the state before it actually received props and did not update the state when props updated.

For some reason, componentWillReceiveProps never got any props. However, componentDidUpdate received props after one update. The issue is that you cannot setState in componentDidUpdate() or you will get an infinite loop. So here is a work around. If anyone has a better solution to this please edit my answer or post a new answer with that.

The following is only for your Agenda Component. Nothing else needs updating

  1. First, update your state declaration from this:

    state = { items: this.props.items, //get the items that were passed above using items={item} and assign them to a state variable items here in the Agenda component }

to this:

state = {
    items: null, //changed this to null bc the array gave a nested array later on making it more difficult to traverse through
    haveItems: false //this will be used later to check if there are items available in props to update state
  };
  1. In your render() function change this

    render() { const { items } = this.state; //destructure state return( //put this where you want to render the data in the AgendaComponent you said you were using a Text Component so it would like this items.map(item => { return ( //assuming that item object comes with an id if not you must add a unique key so you can call a func that updates a count variable and returns it {item.name} {item.date} ) }) ) } }

to this:

const { items, haveItems } = this.state; //destructure state
    if (this.props.items.length === 0) {
      this.setState({ haveItems: false }); //if there aren't any items in props then you have no items so set haveItems to false
    } else {
      //if you already have items then you already handled those items so do nothing
      if (haveItems) {
      } else {
        //handle the items
        this.setState({
          haveItems: true, //set this to true bc now you have items
          items: this.props.items //set items in state to the items in props, you will get one array with all of your objects 
        });
      }
    }
    return (
      <div>
        <div>
          {haveItems
            {* if you have items then map them the question mark means true, if not display nothing, the colon : means false
            ? items.map(item => {
                return (
                  <ul key={item.id}>
                    <li>{item.name}</li>
                    <li>{item.date}</li>
                  </ul>
                );
              })
            : null}
        </div>
      </div>
    );
  }
}

Sorry for a long answer but I hope this is what you needed.

Bens Steves
  • 2,633
  • 3
  • 17
  • 28
  • `items` will have the `name and date` from `reminder component`. As per the documentation of react-native-calendars library(link to which is in the description), `loadItems()` (which is mentioned in my code in the description) is the one responsible for loading `reminders/events` inside the `agenda screen`. so, I somehow need to link `loadItems()` and `componentWillMount()` or I don't know. Please correct me. I am very new to react-native. – Nauman Dec 08 '18 at 19:35
  • 1
    from my understanding the loadItems() functions is getting the data. In the code I typed I am taking that data and assigning it to the items state variable. Then I take that state variable and I pass to Agenda. So that info from Reminder is now in Agenda. To display the name and date for each item use the map function. Does that make sense? If it doesn't no worries. We will figure this out. :) – Bens Steves Dec 08 '18 at 19:40
  • okay. right. I think so yea, it does make some sense. If you don't mind, can you please put that in code. That would help me to understand it better like what I was missing out and what I needed to do. I have been searching this for a few hours now, and I am quite confused. I am just using element and some CSS. – Nauman Dec 08 '18 at 19:48
  • cool. thanks. wait, now I do not need `loadItems()` function? I am sorry, I am asking this because I do not see it in your snippet. – Nauman Dec 08 '18 at 20:03
  • If all the info you need to pass to Agenda is in the data that you are fetching, then no I don't think you will need it. To be safe, just safe that function somewhere in a text doc or just comment it out just in case. If you don't use it then your prop loadItemForMonth can be commented as well since that is what invokes loadItems(). Makes sense? and don't be sorry for asking a question. ask as much as you need to. – Bens Steves Dec 08 '18 at 20:09
  • @BenSteves I tried exactly as you suggested. no luck !! there are no errors. but unable to disable `newItems` values. I `console.log` `newItems` and it does show the stored values in the console. – Nauman Dec 09 '18 at 07:43
  • okay I figured out what is going wrong. The props are not updating before the component is rendered. so the state is empty...I have to figure out how we can get the props into the state. I tried componentWillReceiveProps() with no luck...my solution works...but it's a hacky way of doing it – Bens Steves Dec 09 '18 at 11:03
  • oh ok... you say 'componentWillRecieveProps() with no luck ' did it worked ? – Nauman Dec 09 '18 at 11:12
  • 1
    yea for whatever reason, componentWillRecieveProps() never got any props. very strange. So I made a work around. See update of my answer. I hope that helps. Lmk if it doesn't. But it worked on my end. – Bens Steves Dec 09 '18 at 11:17
  • so just to clarify, which one these lifecycle methods should I use and how? can you please put that in your answer as well? `componentDidMount()` or `componentWillRecieveProps()` or `componentDidUpdate()` – Nauman Dec 09 '18 at 11:37
  • 1
    lol none of them :) componentDidMount() has no props available yet. componentWillRecieveProps() for whatever reason received no props. componentDidUpdate() will result in an infinite loop. So my work around allows you to do it without any lifecycle method. Remember this is just for your Agenda Component. Everything else should be fine. – Bens Steves Dec 09 '18 at 11:44
  • I am getting an error Cannot read property length of undefined. apparently the length in this.props.iterms.length` is undefined. not sure why though. – Nauman Dec 09 '18 at 18:25
  • log your props to the console and lmk know what you get – Bens Steves Dec 09 '18 at 23:10