Trouble with state change between components

Viewed 147

I try to build the calendar with possibility of adding notes for the day. The structure of my components is as follows: App component is common component which renders main Build component. Build component uses CellBuild components to buils calendar's days and one DayEventBuilder which will show when the cell is clicked.

I try to change the Builder's "day" state in CellBuild component by click and after that to change DayEventBuilder's state to display actual day. I have the problem with changing DayEventBuilder's state because it isn't get the state by Builder's component.

The "day" state in CellBuild changes only after two clicks. DayEventBuilder component doesn't get any state from Builder.

App component

import 'bootstrap/dist/css/bootstrap.css';
import React from 'react';
import './App.css';
import Builder from './Components/Builder.js';


let helpDate = new Date();

class App extends React.Component {
  render() {
    return (
      <div className="App">
        <Builder helpDate={helpDate}/>
      </div>
    );
  }
}

export default App;

Builder component

          import React from 'react';
          import 'bootstrap/dist/css/bootstrap.css';
          import CellBuild from './CellBuild.js';
     import DayEventBuilder from './DayEventBuilder.js';
     import $ from 'jquery';



     let currentDate= new Date();
     let months =                ['January','February','March','April','May','June','July','August','September','October','November','December'];
     //let month = currentDate.getMonth(); //current month
     //let year = currentDate.getFullYear();   //current year



     //Builder calendar body
     class Builder extends React.Component {

         constructor(props) {
             super(props);
             this.state = {
                 dataState: this.props.helpDate,   //set date to build and display
             };
         }

         //help function to control date parameters in cell which is clicked
         clickCell=x=>{
             this.setState({day:x});
         };

         createTable =(data)=>{

             let helpDate = new Date(data.getFullYear(), data.getMonth(), data.getDate());    //help date for drawing
             let helpOther = new Date(data.getFullYear(), data.getMonth(), data.getDate());   //help date for build previous month days
             helpDate.setDate(1);
             helpOther.setDate(1);
             let table=[];    //create table container
             let rows=[];    //create rows container




             //outer loop for rows creating (filling rows container)
             for(let i=0;i<6;i++){
                 let cells=[];   //create empty cells container

                 //inner loop for cells creating in row (filling cells container)
                 for (let j=0;j<7;j++){

                     //loop for draw previous month days and padding current 1st days relative days of week
                     if(i===0&&j<helpDate.getDay()-1){
                         helpOther.setDate(-helpDate.getDay()+2+j);

                         cells.push(<CellBuild date={helpOther.getDate()} month={helpOther.getMonth()} isNowDate="numbers otherMonth"/>);
                     }
                     //continue drawing calendar
                     else{
                         //if current month
                         if(helpDate.getMonth()===data.getMonth()){

                             //checking for today
                                            if(helpDate.getDate()===currentDate.getDate()&&helpDate.getMonth()===currentDate.getMonth()&&helpDate.getFullYear()===currentDate.getFullYear()){

                                 cells.push(<CellBuild clickCell={this.clickCell} date={helpDate.getDate()} month={helpDate.getMonth()} isNowDate="numbers nowDate"/>); //join cell to cells container
                        }
                             else{

                            cells.push(<CellBuild clickCell={this.clickCell} date={helpDate.getDate()} month={helpDate.getMonth()} isNowDate="numbers" />); //join cell to cells container

                        }
                    }
                    //next month days
                    else{
                        cells.push(<CellBuild clickCell={this.clickCell} date={helpDate.getDate()} month={helpDate.getMonth()} isNowDate="numbers otherMonth" />); //join cell to cells container
                    }
                    helpDate.setDate(helpDate.getDate()+1);
                }

            }
            rows.push(<tr>{cells}</tr>);    //join filled cells  container to rows container (join a row)
        }

        table.push(<table className="main col-lg-12 col-md-12 col-sm-12 col-xs-12"><thead><tr><th colSpan={2}><button className="decrease" onClick=
            {() =>
            {
                data.setMonth(data.getMonth()-1);
                this.setState({dataState:data});
            }}>&larr;</button><div className="head">{months[data.getMonth()]}</div><div className="head">{data.getFullYear()}</div><button className="increase" onClick=
            {() =>
            {
                data.setMonth(data.getMonth()+1);
                this.setState({dataState:data});
            }}>&rarr;</button></th></tr></thead><tbody>{rows}</tbody></table>);  //join filled rows  container to table and make header

        return table;
    };



    render() {
        return (
            <div className="container">
                <div className="calendar">{this.createTable(this.state.dataState)}</div>
                <DayEventBuilder day={this.state.day}/>
            </div>
        )
    }
}


export default Builder;

CellBuild

import React from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import $ from 'jquery';

//holidays list

let holidays = [
    {id:0,
    name:"New year",
    date:'01.01'},
    {id:1,
    name:"Christmas",
    date:'01.07'},
    {id:2,
     name:"Men's day",
     date:'02.23'},
    {id:3,
     name:"Women's day",
     date:'03.08'},
    {id:4,
     name:"Labor day",
     date:'05.01'},
    {id:5,
     name:"Victory Day",
     date:'05.09'},
    {id:6,
     name:"Independence day",
     date:'07.03'},
    {id:7,
     name:"November revolution day",
     date:'11.07'}];


class CellBuild extends React.Component {


    constructor() {
        super();
        this.state = {
            holidaysState: holidays,
        };
    }

    render(){
        //do default cell value if not holiday
        let cell=<td tabIndex="0" onClick={() => {
            console.log(this.props.date);
            this.setState({day:this.props.date});    //set day in state to render actual DayEventBuilderComponent
            console.log(this.state.day);
            //this.setState({month:this.props.month});    //set day in state to render actual DayEventBuilderComponent
            this.props.clickCell(this.state.day);
            $(function () {
                $('table.main').css('opacity','.5');
                $('table.dayEvents').css('display','table');
            })

        }
        }><div tabIndex="0" className={this.props.isNowDate}><p>{this.props.date}</p></div></td>;

        //check for holiday day
        this.state.holidaysState.map(function(holiday){
            //create temporary date object from date parameter of holiday
            let tempDate = new Date(holiday.date);

            //if current day is holiday change a cell value for this day in calendar
            if(tempDate.getMonth()===this.props.month&&tempDate.getDate()===this.props.date ){
                cell=<td tabIndex="0" onClick={() => {
                    $(function () {
                        $('table.main').css('opacity','.5');
                        $('table.dayEvents').css('display','table');
                    });
                }
                } className="holiday"><div tabIndex="0" className={this.props.isNowDate}><p>{this.props.date}</p></div><p className="holiday">{holiday.name}</p></td>;
            }

        },this);  //give CellBuilder as the context of map-function

        return cell;
    }
}


export default CellBuild;

DayEventBuilder

import React from 'react';
import 'bootstrap/dist/css/bootstrap.css';
import $ from 'jquery';


let events = [
    {id:0,
        date:"2019.01.20",
        name:"Event 1 for this day",
        time1:"15:00",
        time2:"19:00"
    },
 ];



class DayEventBuilder extends React.Component {



    constructor(props) {
        super(props);
        this.state = {
            day:this.props.day,             //take actual day and month of the clicked cell from CellBuild
            month:this.props.month,
        };
    }
    render(){
        let table=[];    //create table container
        let rows=[];    //create rows container
        let skip=0;   //skip <td> adding if needed

        //build table
        for (let i=0;i<25;i++){
            let cells=[];   //create empty cells container
            cells.push(<td>{i+':00'}</td>);



                if((i+':00')===events[0].time1){
                    let rowspan = events[0].time2.substring(0, 2)-events[0].time1.substring(0, 2);   //calculate how long will the event be
                    cells.push(<td rowSpan={rowspan} className="setEvent">{events[0].name}</td>);
                    skip=rowspan;   //set skip counter
                }

                if(skip<=0){   //if we finished skip <td> adding while event was
                    cells.push(<td onClick={() => {
                        $(function () {
                            $('table.main').css('opacity','.3');
                            $('table.dayEvents').css('opacity','.5');
                            $('.form').css('display', 'block');
                        })
                    }}/>);
                }
                --skip;

            rows.push(<tr>{cells}</tr>);
        }

        //create table and create exit button from events list
        //console.log('DayEventBuilder state.day ='+this.state.day);
        table.push(<table className="dayEvents col-lg-6 col-md-8 col-sm-10 col-xs-10"><thead><tr><th>{this.state.day}</th><th><button onClick={() => {
            $(function () {
                $('table.main').css('opacity','1');
                $('table.dayEvents').css('display','none');
            })
        }
        }>&#215;</button></th></tr></thead><tbody>{rows}</tbody></table>);


        return (<div>
                    <div>{table}</div>
                    <div className="form col-lg-6 col-md-6 col-sm-8 col-xs-8">
                        <button onClick={() => {
                            $(function () {
                                $('table').css('display', 'table');
                                $('.form').css('display', 'none');
                                $('table.dayEvents').css('opacity','1');
                                $('table.main').css('opacity','.5');
                            })
                        }}>&larr;</button>
                        <form>
                            <fieldset>
                                <select className="form-control">
                                    <option value="one">One</option>
                                    <option value="two">Two</option>
                                    <option value="three">Three</option>
                                    <option value="four">Four</option>
                                    <option value="five">Five</option>
                                </select>
                                <legend>Add the event</legend>
                                <p>Description <input name="login"/></p>
                                <p><input type="submit" value="Add"/></p>
                            </fieldset>
                        </form>
                    </div>
            </div>);
    }
}

export default DayEventBuilder;
2 Answers

It could be a problem in CellBuild click handler.

This code doesn't work as you expected because setState is async.

() => {
            this.setState({day:this.props.date});
            this.props.clickCell(this.state.day);    
        }

Try to change it to this.

() => {
            this.setState({day:this.props.date});
            this.props.clickCell(this.props.date);    
        }

Also it's a bad practice to use jquery with react.

1. The way you are getting values from props in the constructor in BuilderComponent and DayEventBuilder is wrong, change this.props to props.

Change

constructor(props) {
          super(props);
          this.state = {
          dataState: this.props.helpDate,   //this.props is not required here
     };
}

To

constructor(props) {
          super(props);
          this.state = {
          dataState: props.helpDate,   //props.helpDate is enough 
    };
 }

2. SetSate in BuilderComponent is setting a wrong variable in state.

Change:

clickCell=x=>{
             this.setState({day:x});
         };

To:

clickCell=x=>{
             this.setState({dataState:x});
         };
Related