2

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
ksenia
  • 187
  • 7
  • 23
  • 1
    while setting state '[name]: value', value is not defined, this might be the problem? – Ankush Sharma Oct 30 '18 at 21:12
  • @AnkushSharma Yes. – lux Oct 30 '18 at 21:17
  • @AnkushSharma, you noticed this before me. If you post an answer, I'll delete my question so you get credit. – Alexander Staroselsky Oct 30 '18 at 21:20
  • @ Alexander Staroselsky, no it is absolutely fine, you can keep it. i just randomly gave suggestion :-) – Ankush Sharma Oct 30 '18 at 21:23
  • Not sure this is causing your issue but it should be `this.setState((state) => ({ event: { ...state.event,` etc. I.e. `setState` should take a function with `state` as your first argument and replace `...this.state.event` with `...state.event`. [Don't use `this.state` to access state inside `setState()`](https://reactjs.org/docs/faq-state.html#why-is-setstate-giving-me-the-wrong-value). – SamVK Oct 31 '18 at 17:14
  • @ksneia https://stackoverflow.com/a/30783011/2404452 This should be the answer for your question. The mechanism of `setState` is still the same from 2015 according to official React document. – blaz Oct 31 '18 at 17:54

2 Answers2

3

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!

Alexander Staroselsky
  • 37,209
  • 15
  • 79
  • 91
  • thank you, @Alexander, that helped unfortunately did not resolve my problem of setting the newDate to state date – ksenia Oct 31 '18 at 14:43
  • 1
    @ksenia I've creating an [example](https://stackblitz.com/edit/react-xpvgdk). If you fill in **all** parts of the datetime-local input, the change event fires and the state updates which is demonstrated by `this.state.event.date` being printed/rendered. The date then get's formatted based on your moment format string after clicking the button. I had to simply the example because we don't know the rest of your application. If it's still not "working" you need to please update your question with exactly what the issue still is. Thanks! – Alexander Staroselsky Oct 31 '18 at 15:21
  • thanks @Alexander, you creating a stackblitz sample is super helpful, but I still cannot see where the problem lies and why the state event date is not being set to newDate. I have added some logs to show for clarity, hope this helps and not confuses – ksenia Oct 31 '18 at 17:08
  • 1
    @ksenia You are only expecting the formatted date to appear ONLY after the `Button`'s onClick event is clicked right? That is how you have it currently set up. Formatted date will not render/print when the input change event fires. Are you expecting the date to format with `onChange` also? Can you use something like StackBlitz or Code Sandbox to create a complete example of your functionality? – Alexander Staroselsky Oct 31 '18 at 17:13
  • no, I did not expect the date to format onChange, however I can see that that might be necessary as the button click formats the date, but nont fast enough to send the corrected data to the updateEvent function endpoint. – ksenia Oct 31 '18 at 17:51
  • I can see it in the console here: https://react-igzhmv.stackblitz.io. How do you reckon is the best way to get around this state setting issue? @Alexander – ksenia Oct 31 '18 at 17:53
  • 1
    I'm confused at this point. Are you trying to ALWAYS show the formatted date? Right now, when the user completes adds a complete datetime, it renders the value coming from the input. After a button click, the date is formatted and the formatted datetime is rendered. If the user enters a new datetime, it will overwrite the formatted value as it's a single state property being manipulated. Maybe a solution would be to have two properties in state, datetime that always comes from the input and the formatted datetime. Format the datetime each time onChange happens and use that to display. – Alexander Staroselsky Oct 31 '18 at 18:02
  • I think what I will try to do is to use async/await for the date to set to newDate before calling this.props.udateEvent() – ksenia Oct 31 '18 at 18:28
  • 1
    Wait, so the issue is passing this value to a Redux action? `setState()` has a callback, 2nd argument, that you could use to execute functionality such as `updateEvent()` or `this.props.updateEvent()` after state has been updated. – Alexander Staroselsky Oct 31 '18 at 18:31
  • exactly, this is the value to an action updateAction. Care to demo the setState() callback? – ksenia Oct 31 '18 at 18:33
  • 1
    I've updated the answer with an example of using the callback 2nd argument. – Alexander Staroselsky Oct 31 '18 at 18:36
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/182895/discussion-between-ksenia-and-alexander-staroselsky). – ksenia Oct 31 '18 at 19:12
1

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);
      }
  )
}
blaz
  • 3,981
  • 22
  • 37