1

This seems a silly question, so I apologize in advance.

I have a page with several inputs and at the moment, for some reason, I can't just get the new value written in the input, so I can update the state and send this new data. Seems to me there is something wrong in with the onChange function, because I can get the previous state, but not the new value i'm saving on state.

EDIT: The submit button is outside the input form.

Here is the code:

constructor(props) {
    super(props)

    this.state = {
        editMode: false,
        data: {
            designation: '', 
            address: '', 
            description: ''
        }
    }
}

componentDidMount = () => {
    const dataInfo = data.get('url here');//fetching data here

    const data = {
        designation: dataInfo.designation , 
        address: dataInfo.address, 
        description: dataInfo.description 
    }

    this.setState({
        data: data
    })
}

handleInput = (e) => {
let value = e.target.value;
let name = e.target.name;

this.setState(
  prevState => ({
    data: {
      ...prevState.data,
      [name]: value
    }
  })
);
}

handleFormSubmit = (e) => {
    e.preventDefault();

    const { data } = this.state;

    console.log('hey', data.designation);

    this.setState({
        editMode: false
    })
 }

   render() {

   {!this.state.editMode 
? <button onClick={() => this.setState({ editMode: true })}>edit</button> 
: <div>
   <button 
   className={styles.buttonMargin} 
   onClick={() => this.setState({ editMode: false })}>cancel</button>
   <button onClick={this.handleFormSubmit}>Guardar</button>
  </div> 
    }

    <div>
      {this.state.editMode 
    ? <form onSubmit={this.handleFormSubmit}>
      <input 
        type='text'
        placeholder='Nome do Campo' 
        name='designation' 
        onChange={this.handleInput} 
        defaultValue={this.state.data.designation}
      />
      </form>
    : <p> {this.state.data.designation} </p> }
    </div>
 }
}
RCohen
  • 1,702
  • 10
  • 24
  • 44

2 Answers2

1

There are a number of changes that I would recommend, I have a working version of your code here: https://stackblitz.com/edit/react-vmeuxc

Binding your functions inside of constructor will allow you to write unit tests later and also access the functions from outside of your component if needed.

In my experience, using e.currentTarget.value is more stable than using e.target.value Difference between e.target and e.currentTarget

I didn't see why you were assigning this.state to a constant in handleFormSubmit so I removed that.

You were missing some markup, such as a submit button on your form and an edit button when you weren't in the edit mode. I also don't understand why you had the random this.setState({ editMode: false }) at the end of your render statement, so I removed that since it wasn't necessary.

Having a helper function such as this would help to examine the existing value to the new one.

  compareData(currentData, newData) {
    if(currentData === 'undefined' || (newData && currentData !== newData)) {
      return newData;
    } else {
      return false;
    }
  }

Here's the fully cleaned up version of your code. Note: I had to create a dummy dataInfo object since I don't have access to your API.

  constructor(props) {
    super(props);
    this.compareData = this.compareData.bind(this);
    this.handleInput = this.handleInput.bind(this);
    this.handleFormSubmit = this.handleFormSubmit.bind(this);
    this.state = {
      editMode: false,
      data: {
        designation: '', 
        address: '', 
        description: ''
      }
    }
  }

  componentDidMount = () => {
    const dataInfo = {
      designation: 'test designation',
      address: 'test address',
      description: 'test description'
    };
    // const dataInfo = data.get('url here'); //fetching data here
    const data = {
      designation: dataInfo.designation , 
      address: dataInfo.address, 
      description: dataInfo.description 
    }

    this.setState({
        data: data
    })
  }

  compareData(currentData, newData) {
    if(currentData === 'undefined' || (newData && currentData !== newData)) {
      return newData;
    } else {
      return false;
    }
  }

  handleInput(e) {
    let value = e.currentTarget.value;
    let name = e.currentTarget.name;

    if(this.compareData(this.state.data[name], value)) {
      this.setState({
        data: {
          ...this.state.data,
          [name]: value
        }
      });
    }

    console.log(this.state);
  }

  handleFormSubmit(e) {
    e.preventDefault();

    this.setState({
      editMode: false
    });
  }

  render() {
    return (
      <div>
        {!this.state.editMode ? (
          <button onClick={() => this.setState({ editMode: true })}>edit</button> 
        ) : (
          <div>
            <button onClick={() => this.setState({ editMode: false })}>cancel</button>
            <button onClick={this.handleFormSubmit}>Guardar</button>
          </div> 
        )}

        {this.state.editMode ? (
          <form>
            <input 
              type='text'
              placeholder='Nome do Campo' 
              name='designation' 
              onChange={this.handleInput} 
              defaultValue={this.state.data.designation}
            />
          </form>
        ) : (
          <p> {this.state.data.designation} </p>
        )}
      </div>
    )
  }
CWSites
  • 1,428
  • 1
  • 19
  • 34
  • Now that I see you code, I added the submit button to the code. The button is outside the form, if that is relevant to you. – RCohen Jan 25 '19 at 16:15
  • @RCohen, it wouldn't be proper markup to have a form submit button outside of the form, I don't even think that would work. If you want to have a regular button outside of the form and call the handleFormSubmit function that would be fine...but then just remove your onSubmit={handleFormSubmit} from your form since that is never called – CWSites Jan 25 '19 at 16:20
  • Okay, thanks for the advice. But hey, your solution worked with the button outside somehow. So, what the problem here? I just needed to check if the data existed?@CWSites – RCohen Jan 25 '19 at 16:23
  • You're welcome, I updated my code at https://stackblitz.com/edit/react-vmeuxc so that it matches where your buttons are located. I removed the styles from mine since I don't have access to yours. The button will work anywhere as long as you are calling the `handleFormSubmit` function. It's just markup semantics to keep in mind for having valid HTML – CWSites Jan 25 '19 at 16:25
0

i tweaked your code a little bit. In my case is working, although i suggest you to use value instead of defaultValue for setting the value content of the input component. I had to declare a dummy object instead of fetching the data, and declare your component as a standard HTML field. Also i added a button to enable edit mode since i don't get in which moment you do that. You can test it in codesandbox io (link to working example is https://codesandbox.io/s/n4lpz784wj):

import React from "react";
import ReactDOM from "react-dom";

import "./styles.css";

class App extends React.Component {
  constructor(props) {
    super(props)

    this.state = {
      editMode: false,
      data: {
        designation: '',
        address: '',
        description: ''
      }
    }
  }

  componentDidMount = () => {
    //const dataInfo = data.get('url here');//fetching data here
    const dataInfo = {
      designation: "Do something else",
      address: "5th Avenue #45",
      description: "This is a test description"
    }
    const data = {
      designation: dataInfo.designation,
      address: dataInfo.address,
      description: dataInfo.description
    }

    this.setState({
      data: data
    })
  }

  handleInput = (e) => {
    let value = e.target.value;
    let name = e.target.name;

    this.setState(
      prevState => ({
        data: {
          ...prevState.data,
          [name]: value
        }
      })
    );
  }

  toggleEditionMode = () => {
    this.setState({ editMode: true});
  }

  handleFormSubmit = (e) => {
    e.preventDefault();

    const { data } = this.state;

    console.log('hey', data.designation);

    this.setState({
      editMode: false
    })
  }

  render() {
    console.log(this.state.data.designation);
    return (
    <div>
      {this.state.editMode
        ? <form onSubmit={this.handleFormSubmit}>
          <input
            type='text'
            placeholder='Nome do Campo'
            name='designation'
            onChange={this.handleInput}
            defaultValue={this.state.data.designation}
          />

        </form>
          : <p> {this.state.data.designation} <button type="button" onClick={this.toggleEditionMode}>Enable edit</button> </p>}
    </div>
    );
  }

}

const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Oscar Calderon
  • 883
  • 2
  • 13
  • 30
  • What have you changed here exactly? besides the dummy data. @Oscar Calderon – RCohen Jan 25 '19 at 16:07
  • Now that I see you code, I added the submit button to the code. The button is outside the form, if that is relevant to you. – RCohen Jan 25 '19 at 16:15
  • @RCohen mainly syntax errors. In render() you didn't have a return statement, also you had a this.setState at the end of render (never ever update state inside of render, it will cause a render loop, render is only used for rendering components and not mutating state), also the logic for enabling edit mode which is not present in your example – Oscar Calderon Jan 25 '19 at 16:28
  • @RCohen regarding having submit button outside, that has nothing to do with the problem. In the event handler for button you just read state, which doesn't care if your html controls are located in the same form. – Oscar Calderon Jan 25 '19 at 16:29