1

I have a Modal component and a Form component. Modal is a functional component whereas Form is a class component because that's where I'm handling the form submission.

Modal [the parent] passes all of its props to Form. There are three values in Modal's props object, two strings and a number.

String values are as expected, but the number (meant to serve as an id) is coming as 1, instead of the expected 10 (in this case). This is a problem because I'm trying to save that value into state, and am not getting the value I'm expecting.

Peculiarly, if I console.log(this.props) inside render(), props object is printed twice; first time number value is 1, second time it's 10. This happens upon the component's initial render, without any changes happening to state.

Why is this happening and how do I get the actual value I'm expecting?


This is the Modal Component.

import React from 'react';
import Form from './Form';


const Modal = (props) => (
  <div className="modal fade" id="createWorkitem" tabIndex="-1" role="dialog" aria-labelledby="createWorkitemLabel" aria-hidden="true">
    <div className="modal-dialog" role="document">
      <div className="modal-content">
        <div className="modal-header">
          <h5 className="modal-title" id="createWorkitemLabel">
            {/* 10 */}
            Item #{ props.issueNumber }
          </h5>
          <button type="button" className="close" data-dismiss="modal" aria-label="Close">
            <span aria-hidden="true">&times;</span>
          </button>
        </div>
        <div className="modal-body">
          {/* Form needs props to POST */}
          <Form
            {...props}
          />
        </div>
      </div>
    </div>
  </div>
);

export default Modal;

And this is the Form Component

import React, { Component } from 'react';
import axios from 'axios';
import config from '../../../../../config';
const { create_issue_url } = config.init();

class Form extends Component {
  constructor(props) {
    super(props);
    this.state = {
      issueNumber: '',
      title: '',
      price: '',
      duration: '',
      description: ''
    }

    this.handleChange = this.handleChange.bind(this);
    this.submitForm = this.submitForm.bind(this);
    this.resetForm = this.resetForm.bind(this);
  }

  componentWillMount() {
    // this prints once with wrong value
    console.log(this.props);
  }

   componentDidMount() {
    // this prints once with wrong value
    console.log(this.props);
    // this prints once with right value inside props object
    console.log(this);
  }

  handleChange(e) {
    this.setState({[e.target.id]: e.target.value});
  }

  submitForm(e) {
    e.preventDefault();
    let endpoint = `${create_issue_url}/${this.props.repo}`;
    let msg = 'Are you sure you want to create this item?';
    // Make sure
    if(confirm(msg)) {
      axios.post(endpoint, this.state)
      .then(response => {
        console.log(response.data.workitem);
        // Clear form
        this.resetForm();
        // Show success alert
        document.getElementById('successAlert').style.display = '';
        // Hide it after 3 seconds
        setTimeout(function hideAlert(){
          document.getElementById('successAlert').style.display = 'none';
        }, 3000);
      })
      .catch(err => {
        console.log(err);
      });
    }
  }

  resetForm() {
    this.setState({
      title: '',
      price: '',
      duration: '',
      description: ''
    });
  }

  render() {
    let { title, price, duration, description } = this.state;
    // this prints twice
    {console.log(this.props.issueNumber)}
    return (
      <form onSubmit={this.submitForm}>
        <div id="successAlert" className="alert alert-success" role="alert"
          style={{display: 'none'}}
        >
          Item created.
        </div>
        <div className="form-row">
          <div className="form-group col-md-6">
            <label htmlFor="title">Title</label>
            <input onChange={this.handleChange} type="text" value={title} className="form-control" id="title" required/>
          </div>
          <div className="form-group col-md-3">
            <label htmlFor="price">Price</label>
            <input onChange={this.handleChange} type="number" value={price} className="form-control" id="price" required/>
          </div>
          <div className="form-group col-md-3">
            <label htmlFor="duration">Duration</label>
            <input onChange={this.handleChange} type="number" value={duration} className="form-control" id="duration"
              placeholder="days" required
            />
          </div>
        </div>
        <div className="form-group">
          <label htmlFor="description">Description</label>
          <textarea
            onChange={this.handleChange} 
            className="form-control"
            id="description"
            style={{overflow: 'auto', resize: 'none'}}
            value={description}
            required
          ></textarea>
        </div>
        {/* Using modal footer as form footer because it works */}
        <div className="modal-footer">
          <button type="submit" className="btn btn-primary">Submit</button>
          <button type="button" className="btn btn-secondary" data-dismiss="modal">Close</button>
        </div>
      </form>
    ); 
  }

}

export default Form;
jsdev17
  • 1,050
  • 2
  • 15
  • 25
  • Can we please see part of the code? – Oliver Ni Mar 25 '18 at 00:47
  • @OliverNi yes, I'll update my question with it – jsdev17 Mar 25 '18 at 00:48
  • @OliverNi updated – jsdev17 Mar 25 '18 at 00:59
  • even if I try logging props in `componentDidMount()`, it's the wrong value... – jsdev17 Mar 25 '18 at 01:10
  • curiously enough, the component DOES seem receive the correct value in props object... I tried `console.log(this)` inside `componentDidMount()`, and the props object has the correct value in it (10)... but as soon as I `console.log(this.props)`, I get the wrong value (1). What is this??? – jsdev17 Mar 25 '18 at 01:20
  • well if issuenumber is in form you need to create a getter in modal to get the value of issuenumber – Omar Mar 25 '18 at 01:21
  • Try console logging this.props in componentDidUpdate() method as well. Are you sure console.log(this) is printing10. ? I feel like Form component got mounted with 1 as prop value, later updated and re-rendered with 10 as prop value. – Subin Sebastian Mar 25 '18 at 01:42
  • @Subin yes... I even checked using the React developer tools and the value of the `issueNumber` prop on the Form component is = 10... – jsdev17 Mar 25 '18 at 01:50
  • `{ "issueNumber": 10, "user": "user-name", "repo": "repo-name", "get": "[function getter]" }`. I copied this straight out of the console – jsdev17 Mar 25 '18 at 01:51
  • I can't wrap my head around this one... – jsdev17 Mar 25 '18 at 01:53

1 Answers1

5

Behaviour is just correct. On load your modal component is receiving props as 1. and later it is changed to 10. So your components are updated once value is changed to 10. componentDidMount will be invoked only once during initial mounting. But componentDidUpdate and render will be called whenever component updates ie receives an updated prop(in your case issuenumber 10).

So render will be called twice initially with 1 as prop value and then 10. but componentDidMount will be called only once(when prop value is 1)

Now the problem of printing console.log(this) vs console.log(this.props) in componentDidMount. First case shows issuenumber prop is 10 and second case shows it as 1. I suspect this is because chrome developer tools is optimising the print using live update. When you are printing this obviously prop was 1, however I feel like console is live updating that print(as very soon that object is updated with new props)

Console.log showing only the updated version of the object printed

As suggested here instead of console.log(this) try console.log(JSON.parse(JSON.stringify(this))); this should print 1

Hope this clears the question.

Subin Sebastian
  • 10,870
  • 3
  • 37
  • 42
  • This was a tough nut to crack for me. Thank you! Your insight helped me solve this problem. Now, I have left wondering... why is issueNumber being as 1 first and then updated to actual value? Why isn't it 10 from the beginning, as it should be? Even though I was able to capture the value where I needed it in Form, this problem quickly became a huge inconvenience. – jsdev17 Mar 25 '18 at 04:38
  • For instance, I couldn't save the prop `issueNumber` in state on initial mounting of Form component because it wasn't the right value. And trying to call `setState` inside `componentDidUpdate`, a point by which `issueNumber` had in fact been updated to 10 (!?), just blew the stack (since on every `setState` the component is technically updated and thus continues to get re-rendered). And while React handles it gracefully and the program doesn't cash (though it does show an error in the console), it just doesn't seem ideal having to rely on React's ability to foresee and handle such a situation. – jsdev17 Mar 25 '18 at 04:56
  • I'm also wondering why all other props (there were 3 total) didn't have to be updated (they were received as expected and thus could be set into state during initial mount) but `issueNumber` did... is there documentation anywhere that you know of that explains this behavior? – jsdev17 Mar 25 '18 at 05:00
  • 1
    You have to check the parent component of Modal, its that component that is passing down issueNumber. I feel like 1 is just the initial default value. For eg: if you are using redux check the reducer corresponding to issueNumber, may be its initialValue is 1 – Subin Sebastian Mar 25 '18 at 06:16
  • Thank you again; this has given me an idea about why this may be happening. – jsdev17 Mar 25 '18 at 06:24