Search code examples
reactjscomponentsextend

Child component sharing state with parent?


I'm pretty new to React and I'm struggling a bit with my first task.

I'm creating a generic TextBox (from MaterialUI) component and I've created a child component (NumberInput) that extends TextBox. The problem is that whenever I type any input into the NumberInput, the NumberInput state doesn't seem to be updated. Any tips would be appreciated!

Parent:

import React, { Component } from 'react';
import TextField from '@material-ui/core/TextField';
import { Validations } from './validations'
import PropTypes from 'prop-types';

class TextBox extends Component {
    constructor(props) {
        super(props);

        this.state = {
            value: null,
            error: false,
            errorMsg: null
        };

    this.setValue = this.setValue.bind(this);
    this.setError = this.setError.bind(this);
    this.setErrorMsg = this.setErrorMsg.bind(this);
    this.handleChange = this.handleChange.bind(this);
    this.checkForErrors = this.checkForErrors.bind(this);

}

// Handles changes upon each edit of the textbox. Updates the state value and checks for errors.
handleChange(event) {
    this.setValue(event);
}

setValue(event) {
    var value = document.getElementById(this.props.id).value;
    this.setState({ value: value }, () => this.checkForErrors());
}

setError(error, errorMsg) {
    this.setState({ error: error });
    this.setErrorMsg(errorMsg);
}

setErrorMsg(errorMsg) { this.setState({ errorMsg: errorMsg }) }

getValue() { return this.state.value }

getError() { return this.state.error }

getErrorMsg() { return this.state.errorMsg }

// Checks for input error
checkForErrors() {
    var input = this.getValue();
    console.log(input);
    var validations = Validations(this.props, input);
    this.setError(validations.error, validations.errorMsg);
}

render() {
    let {
        label,
        placeholder,
        maxLength,
        minLength,
        maxNumber,
        minNumber,
        disabled,
        required,
        type,
        multiline,
        id,
        name,
        allowSpecialCharacters,
        allowWhitespace
    } = this.props;

    return (
        <div>
            <br/>
            <TextField
                error = { this.getError() }
                label = { label }
                helperText = { this.getErrorMsg() }
                placeholder = { placeholder }
                inputProps = {{ maxLength: maxLength }}
                disabled = { disabled }
                required = { required }
                type = { type }
                multiline = { multiline }
                onChange = { this.handleChange }
                id = { id }
                name = { name }
            />
        </div>
    );
 }
};

TextBox.propTypes = {
    label: PropTypes.string,
    helperText: PropTypes.string,
    placeholder: PropTypes.string,
    maxLength: PropTypes.number,
    minLength: PropTypes.number,
    maxNumber: PropTypes.number,
    minNumber: PropTypes.number,
    disabled: PropTypes.bool,
    required: PropTypes.bool,
    type: PropTypes.oneOf(["button", "checkbox", "color", "date", "datetime-local", "email", "file",
    "hidden", "image", "month", "number", "password", "radio", "range", "reset", "search",
        "submit", "tel", "text", "time", "url", "week"]),
    multiline: PropTypes.bool,
    id: PropTypes.string,
    name: PropTypes.string,
    allowSpecialCharacters: PropTypes.bool,
    allowWhitespace: PropTypes.bool
}

TextBox.defaultProps = {
    label: '',
    helperText: '',
    placeholder: '',
    maxLength: 100000,
    minLength: 0,
    maxNumber: 100000,
    minNumber: -100000,
    disabled: false,
    required: false,
    type: 'text',
    multiline: false,
    id: 'input',
    name: '',
    allowSpecialCharacters: true,
    allowWhitespace: true   
}

export default TextBox;

Child:

import React, { Component } from 'react';
import TextBox from './textbox';
import { Validations } from './validations'
import PropTypes from 'prop-types';

// const NumberInput = props => {
//  return (
//      <TextBox {...props} type="number" />
//  )
// }

class NumberInput extends TextBox {

    constructor(props) {
        super(props);

        this.state = {
            value: null,
            error: false,
            errorMsg: null
        };  
    }

    render() {

        return (
            <div>
                <TextBox
                    label = { "NumberInput" }
                    type = { "number" }             
                />
            </div>
        )

    }
};

export default NumberInput;

App.js

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';
import TextBox from './widgets/textbox/textbox';
import TextArea from './widgets/textbox/textarea';
import NumberInput from './widgets/textbox/numberinput';

class App extends Component { 
  render() {
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <h1 className="App-title">Welcome to React</h1>
        </header>
        <p className="App-intro">
          To get started, edit <code>src/App.js</code> and save to 
  reload.
        </p>
        <TextBox type="text" label={"Textbox"} />
        <NumberInput label={"NumberInput"} />
      </div>
    );
  }
}

export default App;

Updated NumberInput:

const NumberInput = props => {
    return (
        <TextBox {...props} type="number" id="inp" />
    )
}

Solution

  • You don't need to use a class here for your child component and extend it from the parent. This process is very simple when you pass your handler function to your child component.

    class Parent extends React.Component {
      state = {
        value: "initial value",
      }
    
      handleChange = e => this.setState({ value: e.target.value });
    
      render() {
        return (
          <div>
            <p>Change the value:</p>
            <Child onChange={this.handleChange} value={this.state.value} />
            <p>{JSON.stringify(this.state.value)}</p>
          </div>
        );
      }
    }
    
    const Child = props => {
      return (
        <input onChange={props.onChange} value={props.value} />
      )
    }
    
    ReactDOM.render(<Parent />, document.getElementById("root"));
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
    <div id="root"></div>