Search code examples
reactjsreact-reduxmaterializeredux-thunk

Setting the default value of an input field after data is retrieved causes the content to overlap and the "onChange" event not to be triggered


I have an "edit category" component in my React application. The ID is passed through the URL. When the component is mounted, the action "fetchCategory" is called, which updates the props on the component with the current category.

I have a form which I want to be pre-populated, which I'm currently doing using the defaultValue on the input.

However, this isn't reflected on the state and the label for the text field overlaps the input field.

Any help would be greatly appreciated. I'll leave snippets of my code below which could help with understanding what I'm trying to do.

import React, { Component } from "react";
import { connect } from "react-redux";
import { fetchCategory } from "../../store/actions/categoryActions";

class AddOrEditCategory extends Component {
  componentDidMount() {
    this.props.fetchCategory(this.props.match.params.id);

    if (this.props.match.params.id) {
      this.setState({
        _id: this.props.match.params.id
      });
    }
  }

  handleSubmit = e => {
    e.preventDefault();
    console.log(this.state);
  };
  handleChange = e => {
    this.setState({
      [e.target.id]: e.target.value
    });
  };
  render() {
    const addingNew = this.props.match.params.id === undefined;

    return (
      <div className="container">
        <h4>{addingNew ? "Add category" : "Edit category"}</h4>
        <form onSubmit={this.handleSubmit}>
          <div className="input-field">
            <input
              type="text"
              id="name"
              defaultValue={this.props.category.name}
              onChange={this.handleChange}
            />
            <label htmlFor="name">Category name</label>
          </div>
          <div className="input-field">
            <input
              type="text"
              id="urlKey"
              onChange={this.handleChange}
              defaultValue={this.props.category.urlKey}
            />
            <label htmlFor="urlKey">URL Key</label>
          </div>
          <button className="btn">{addingNew ? "Add" : "Save"}</button>
        </form>
      </div>
    );
  }
}

const mapStateToProps = state => {
  return {
    category: state.categoryReducer.category
  };
};

export default connect(
  mapStateToProps,
  { fetchCategory }
)(AddOrEditCategory);

EDIT: Included whole component as requested


Solution

  • You need to replace the 'defaultValue' attribute with 'value' in the inputs.

    You are using a controlled vs uncontrolled component. You dont need to use defaultValue.

    You can set the initial values on the promise success for fetchCategory

    componentDidMount() {
      this.props.fetchCategory(this.props.match.params.id).then(response => { 
        // Set the initial state here 
      }
    }
    

    OR in

    componentWillReceiveProps(nextProps) {
      // Compare current props with next props to see if there is a change
      // in category data received from action fetchCategory and set the initial state 
    }
    

    React docs

        <form onSubmit={this.handleSubmit}>
          <div className="input-field">
            <input
              type="text"
              id="name"
              onChange={this.handleChange}
              value={this.state.name} //<---
            />
            <label htmlFor="name">Category name</label>
          </div>
          <div className="input-field">
            <input
              type="text"
              id="urlKey"
              onChange={this.handleChange}
              value={this.state.urlKey}
            />
            <label htmlFor="urlKey">URL Key</label>
          </div>
          <button className="btn">{addingNew ? "Add" : "Save"}</button>
        </form>