2

I believe the nuance here that I need to understand is borne out of updating a nested state, if I could understand Wale's logic here, and how it applies to my example that'd be great.

I've tried this following code:

NewNotePage.js //where this.state.note.date is updated

class NewNotePage extends React.PureComponent {
  constructor(props) {
    super(props);
    this.state = {
      note: {
        title: "",
        body: "",
        createdAt: null,
        updatedAt: null,
        date:null
      }
    };
    this.handleDate = this.handleDate.bind(this);
    this.handleTime = this.handleTime.bind(this);
  }

  handleDate = date => this.setState({ date })
  handleTime = date => this.setState({ date })

  render() {
    console.log(this.state.note.date)
    const { note } = this.state;

    return (
      <div>
        <PickerDrawer 
        show={this.state.drawerOpen} 
        date={this.state.note.date} 
        handleTime={this.handleTime} 
        handleDate={this.handleDate} />
        {backdrop}
      </div>
    );
  }
}

PickerDrawer.js //where this.props.note.date is selected

class PickerDrawer extends React.Component {
  constructor(props) {
    super(props);

  render(props) {
    return (
      <div className={drawerClasses}>
        <MuiPickersUtilsProvider utils={DateFnsUtils}>
          <DatePicker
            label="Due Date"
            value={this.props.date}
            onChange={this.props.handleDate}
            animateYearScrolling
            showTodayButton={true}
            disablePast={true}
          />
          <TimePicker
            label="Time"
            value={this.props.date}
            onChange={this.props.handleTime}
            animateYearScrolling
          />
        </MuiPickersUtilsProvider>
      </div>
    );
  }
}

console.log(this.state.note.date) in NewNotePage throws null, as it is in initial state

  • I believe what you are looking for is this: https://stackoverflow.com/questions/43040721/how-to-update-nested-state-properties-in-react In summary, you should update the whole state, not only one property in the state object. Hope it helps :) – Bruno Monteiro Sep 17 '19 at 21:58
  • Possible duplicate of [How to update nested state properties in React](https://stackoverflow.com/questions/43040721/how-to-update-nested-state-properties-in-react) – Bruno Monteiro Sep 18 '19 at 03:46
  • thanks Bruno although Elder seems to be trying something different. `handleDate = child = setState({parent : { child : state } })`, `handleDate = date = setState({note : { date : date} })`I'm trying apart from Elder's method – Nick Carducci for Carface Bank Sep 18 '19 at 13:25

1 Answers1

2

You need to make a copy of your state first in your handlers using the spread operator:

const currentState = {...this.state};

After that, you can update your date by replacing it in the object:

currentState.note.date = event.target.value;

Finally, update your state again with this new state object:

this.setState(currentState);

Check it out the final code here:

Edit nifty-star-jixud

If you change your onChange on your DatePicker to:

 <DatePicker
        label="Due Date"
        value={this.props.date}
        onChange={(event, x) => {
          this.props.handleDate(event);
        }}
        animateYearScrolling
        showTodayButton={true}
        disablePast={true}
      />

and the handleDate in the NewNotePage to:

 handleDate = event => {
const currentState = { ...this.state };
currentState.note.date = event;

this.setState(currentState);

};

You should be able to get your state updated.

I did notice, however, that your state updates, but the datepicker does not.

I decided to print out the props in the PickerDrawer render method and notice that it's called twice and with different props.

PickerDrawer | render 
Object {show: true, date: null, handleDate: function bound ()}
PickerDrawer | render 
Object {className: "picker-drawer", close: undefined}

PickerDrawer:

import React from "react";
import DateFnsUtils from "@date-io/date-fns";
import ".././non-issues/PickerBackdrop.css";
import {
  DatePicker,
  TimePicker,
  DateTimePicker,
  MuiPickersUtilsProvider
} from "@material-ui/pickers";

class PickerDrawer extends React.Component {
  state = { date: null };
  constructor(props) {
    super(props);
  }

  dateHandler = date => {
    this.setState({ date: date });
    this.props.handleDate(this.state.date);
  };

  render() {
    console.log("Rendering Picker Drawer");
    let drawerClasses = "picker-drawer";
    if (this.props.show) {
      drawerClasses = "picker-drawer open";
    }
    return (
      <div className={drawerClasses}>
        <MuiPickersUtilsProvider utils={DateFnsUtils}>
          <DatePicker
            label="Due Date"
            value={this.state.date}
            onChange={(event, x) => {
              this.dateHandler(event);
            }}
            animateYearScrolling
            showTodayButton={true}
            disablePast={true}
          />
          <TimePicker
            label="Time"
            value={this.state.date}
            onChange={(event, x) => {
              this.props.handleDate(event);
            }}
            animateYearScrolling
            className=""
          />
        </MuiPickersUtilsProvider>
      </div>
    );
  }
}
export default PickerDrawer;
Elder
  • 1,475
  • 9
  • 17
  • 2
    this would still mutate the nested levels on state. The spread operator only does a shallow copy. – Chaim Friedman Sep 17 '19 at 22:16
  • @ChaimFriedman true, but for his initial state, it would work, check it out here: https://codesandbox.io/s/nifty-star-jixud?fontsize=14 – Elder Sep 17 '19 at 22:32
  • 1
    Yea it would absolutely work, but this approach could break any `shouldComponentUpdate`s or any `PureComponent`s since they compare the reference and in your case the reference would stay the same. – Chaim Friedman Sep 17 '19 at 22:38
  • I'll try to not use nested state with purecomponent & refs, thanks Chaim. Elder I cannot thank you enough for putting that together. However, the code still isn't a destination for value that Material UI accepts (can't console log it in the parent component). I forged this part of my project so you can see the error I'm getting. Know that the DatePicker accepts the value's destination when handleDate and state's date is set un-nested in the DatePicker component, but not when it is in your index.js/my NewPlan.js -it won't let us choose a note.date. https://codesandbox.io/s/awesome-pike-f4hh1 – Nick Carducci for Carface Bank Sep 18 '19 at 13:04
  • shoot Idk which you can see https://codesandbox.io/s/awesome-pike-f4hh1?fontsize=14, also now I'm trying to see if the comment to the question by Bruno's syntax is the problem when setting state – Nick Carducci for Carface Bank Sep 18 '19 at 13:21
  • Hi Nick, I did search here about Materialize and React and it seems that for you Date Picker component you need to change the onChange. I updated my answer. – Elder Sep 18 '19 at 13:22
  • Hi Elder, wow you're good. I'll have to study why the onChange needs to be like that in your longer answer for Materialize & React, I added these changes to https://codesandbox.io/s/awesome-pike-f4hh1 with your ideas. It seems to update when closing the drawer, (rendering NewNotePage where state is defined?). How can I update `this.props.date` while still on the `PickerDrawer.js` page in the `DatePicker`, before going to NewNotePage? Can I update state in `PickerDrawer.js` at the same time? Put them both in a function? (however that might look) I might try that – Nick Carducci for Carface Bank Sep 18 '19 at 14:04
  • Hey Nick, I did notice it was updating when closing the drawer, so, one thing you can do while you try to figure out why the state update in the PlanNew component is not set a rerender in the PickerDrawer and have a simple date state in your PickerDrawer just to control what is being displayed, then change that when a date is selected and call the this.props.handleDate(date) to actually save it in the parent's state. Check the updated answer to see how the new PickerDrawer would look. – Elder Sep 18 '19 at 14:30
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/199636/discussion-between-elder-patten-ferreira-and-nick-carducci). – Elder Sep 18 '19 at 14:31
  • 1
    Great, what you said in the chat & the latest `PickerDrawer.js `on your answer also works, in order to just display the state in child `PickerDrawer.js`, make a `dateHandler.js` in the `onChange={(event, x) => { this.dateHandler(event);}}` set state in child `PickerDrawer.js` component **as well as** `this.props.handleDate(this.state.date);` from the parent component. All done! – Nick Carducci for Carface Bank Sep 18 '19 at 14:50