ksenia
ksenia

Reputation: 177

set state in a function not working - reactJs

UPDATE: I have logged three lines of code, before sending the data to updateEvent function containing the endpoint. The following is the logs:

the new event date is 2019-01-01T01:00:00.000

the new SET event date is: 2019-01-01T01:00

the event detail is: {date: "2019-01-01T01:00" …}

The state is once again not set to the new format. Can anyone see what the error could be?

I am trying to render an event date to send as body to an endpoint.The user is able to input the date in the TextInput field, however before I send the event date, I want to modify its format, using moment.js inside updateEvent cb (YYYY-MM-DDTkk:mm:ss.SSS"), therefore I create a new variable

newDate

However, the setState inside of updateEvent doesn't actually set state and keeps the value of date as it is has been set in handleEventInputChange. I have a suspicion that it could be due to setting state to the same state variable twice, inside the handleEventInputChange and handleEvent. Can anyone confirm and/or propose a solution?

 //... import and styles 
class EditEvent extends Component {
    constructor(props) {
        super(props);

        this.state = {
            event: {
                date: '',
            },
        };

        this.handleEventInputChange = this.handleEventInputChange.bind(this);
        this.updateEvent = this.updateEvent.bind(this);
    }

    handleEventInputChange(event) {
        const target = event.target;
        const value = target.type === 'checkbox' ? target.checked : target.value;
        const name = target.name;

        this.setState({
            event: {
                ...this.state.event,
                [name]: value
            }
        });
    }

    updateEvent() {
        const { event } = this.state;
            let newDate = moment(this.state.event.date).format("YYYY-MM-DDTkk:mm:ss.SSS");

            this.setState(() => ({
                event: {
                    ...this.state.event,
                    date: newDate,
                }}))


            console.log("the new event date is ", newDate)
            console.log("the new SET event date is: ", event.date)
            console.log("the event detail is: ", event)

            this.props.updateEvent(this.props.event.id, event);
    }

    renderEvent() {
        const {
            event,
        } = this.state;

        return (
            <div>
                <Paper style={styles.paper}>
                    <TextField
                        name="date"
                        type="datetime-local"
                        onChange={this.handleEventInputChange}
                        value={event.date}/>
                </Paper>
            </div>
        );
    }

    render() {
        return (
            <ViewContainer
                title="Update Event"
                toolbarRight={
                    <Button
                        onClick={this.updateEvent}
                    > Save
                    </Button>
                }
            >
                {this.renderEvent()}
            </ViewContainer>
        );
    }
}

//... mapStateToProps, mapDispatchToProps and export default connect for EditEvent

Upvotes: 1

Views: 5786

Answers (2)

blaz
blaz

Reputation: 4068

This is a repost, the original answer is here:

From React's documentation (at the moment of this post)

setState() does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState() a potential pitfall.

So no, your approach will not get the updated value from this.state right away after calling setState, and you cannot catch the updated value in console.log for the same reason. According to the document, a better way is using componentDidUpdate

componentDidUpdate(prevProps, prevState) {
  if (this.event.date !== prevState.event.date) {
    this.props.updateEvent(this.props.event.id, event);
  }
}

updateEvent() {
  const { event } = this.state;
  let newDate = moment(this.state.event.date).format("YYYY-MM-DDTkk:mm:ss.SSS");

  this.setState(() => ({
      event: {
          ...this.state.event,
          date: newDate,
      }}))
}

If you still insist on keeping this.props.updateEvent inside updateEvent then there are 2 ways of doing it:

(1) Use newDate instead of this.state.event.date

updateEvent() {
  const { event } = this.state;
  let newDate = moment(this.state.event.date).format("YYYY-MM-DDTkk:mm:ss.SSS");

  this.setState(() => ({
      event: {
          ...this.state.event,
          date: newDate,
      }}))

  this.props.updateEvent(this.props.event.id, {
    ...this.state.event,
    date: newDate
  });
}

or (2) using callback of this.setState to correctly get updated value

updateEvent() {
  const { event } = this.state;
  let newDate = moment(this.state.event.date).format("YYYY-MM-DDTkk:mm:ss.SSS");

  this.setState(() => ({
      event: {
          ...this.state.event,
          date: newDate,
      }}),
      function callback() {
        this.props.updateEvent(this.props.event.id, this.state.event);
      }
  )
}

Upvotes: 1

Alexander Staroselsky
Alexander Staroselsky

Reputation: 38757

Based on the code you provided, you are attempting to set [name]: value inside setState() of handler handleEventInputChange(event), but you haven't set assigned a value from event argument to the value property. You'd need to add something along the lines of const value = target.value; before passing it to setState():

handleEventInputChange(event) {
    const target = event.target;
    const name = target.name;
    const value = target.value; // add this

    this.setState({
        event: {
            ...this.state.event,
            [name]: value
        }
    });
}

You could also consider using destructuring:

handleEventInputChange(event) {
    const target = event.target;
    const { name, value } = target;

    this.setState({
        event: {
            ...this.state.event,
            [name]: value
        }
    });
}

Update: onChange for an <input type="datetime-local" /> will only fire when all parts of the <input type="datetime-local" /> have been filled in including day, month, year, hour, minute and AM/PM. I've creating an example showing this functionality in action. Your code with the change to setting the value of value seems to work. If the goal is to always show the formatted date, you could consider creating another state property the holds the formatted value to avoid changing this.state.event.date when updateEvent() is triggered by the button click. This way you can avoid overwriting this.state.event.date constantly.

Update 2: Based on the comments, I'm piecing together that issue you are trying to resolve specifically is the value that is being passed to a mapped action dispatch. You could try using the setState() callback, 2nd argument, to call this.props.updateEvent(). It's key that you pass this.event.state into the 2nd argument of this.props.updateEvent() otherwise you would be passing the old/original event value. The example has been updated to reflect this.

updateEvent() {
    const { event } = this.state;
    let newDate = moment(this.state.event.date).format("YYYY-MM-DDTkk:mm:ss.SSS");

    this.setState({
      event: {
        ...this.state.event,
        date: newDate,
      }
    }, () => {
      this.props.updateEvent(this.props.event.id, this.state.event);
    });            
}

Hopefully that helps!

Upvotes: 3

Related Questions