Search code examples
javascriptreactjsstatereact-props

React - passing state down


I am building a CV creator in React. I have a form, a preview component and their closest parent component is Main. What I want to achieve is that when a user is typing inside form fields, the preview fields on the right get automatically updated with data from form but I just can't get it to work. This is my first project in React, and I cannot use hooks or functional components. I know that I am doing something wrong, but cannot pinpoint what.

Main component

import React, { Component } from "react";
import Form from "./form/Form";
import Preview from "./formpreview/Preview";

class Main extends Component {
  constructor(props) {
    super(props);
    this.state = {
      firstName: "",
      lastName: "",
      title: "",
      address: "",
      phoneNum: "",
      email: "",
      description: "",
    };

    this.handleSubmit = this.handleSubmit.bind(this);
    this.handleInputChange = this.handleInputChange.bind(this);
  }

  handleSubmit = (event) => {
    event.preventDefault();
  };

  handleInputChange = (event) => {
    this.setState = {
      [event.target.name]: event.target.value,
    };
  };

  render() {
    const { firstName } = this.state;
    return (
      <div className="main">
        <Form
          submitForm={this.handleSubmit}
          changeInput={this.handleInputChange}
          firstName={firstName} lastName={lastName} title= 
          {title} address={address} phoneNum={phoneNum}
          email={email} description={description}
        />
        <Preview onChange={this.handleInputChange} 
         firstName={firstName} lastName={lastName} title= 
          {title} address={address} phoneNum={phoneNum}
          email={email} description={description}/>
      </div>
    );
  }
}

Form component

import React, { Component } from "react";
import Personal from './buildingblocks/PersonalInfo';
import Experience from './buildingblocks/Experience';
import Education from './buildingblocks/Education';
import Buttons from './buildingblocks/FormButtons';

class Form extends Component {
   constructor(props) {
     super(props);
     this.state = {
       firstName: this.props.firstName,
       lastName: this.props.lastName,
       title: this.props.title,
       address: this.props.address,
       phoneNum: this.props.phoneNum,
       email: this.props.email,
       description: this.props.description
     }
   }

   submitForm = () => {
     this.props.submitForm()
   }

   changeInput = () => {
     this.props.changeInput()
   }

  
  render() {
    const {firstName, lastName, title, address, phoneNum, email, description} = this.state;
  
    return (
        <form className="cvForm" onSubmit={this.submitForm} onChange={this.changeInput}>
          <Personal firstName={firstName} lastName={lastName} title={title} address={address} phoneNum={phoneNum} email={email} description={description}/>

          <Experience />

          <Education />

          <Buttons />

        </form>
    );
  }
}

export default Form;

Preview Component

import React, { Component } from "react";

class Preview extends Component {

  constructor(props) {
    super(props);
    this.state = {
      firstName: this.props.firstName,
      lastName: this.props.lastName,
      title: this.props.title,
      address: this.props.address,
      phoneNum: this.props.phoneNum,
      email: this.props.email,
      description: this.props.description
    }
  }

  submitForm = () => {
    this.props.submitForm()
  }

  changeInput = () => {
    this.props.changeInput()
  }


  render() {
    const {firstName, lastName, title, address, phoneNum, email, description} = this.state;

    return (
      <div className="cvPreview">
        <div className="gridItem nameItem">
          <h1>{firstName} </h1>
          <h3>Data engineer</h3>
        </div>
       
...
    );
  }
}

I haven't included all of preview component because it is just elements. I know I am not doing this correctly, I am getting a TypeError "Cannot read property 'target' of undefined" and I am pretty sure I shouldn't be defining these props this many times, but after everything I have tried, this was my last shot. I am stuck, help.


Solution

  • Issues

    Main

    1. handleInputChange the this.setState should be a function call.

      handleInputChange = (event) => {
        const { name, value } = event.target;
        this.setState({
          [name]: value,
        });
      };
      

    Form

    1. Storing passed props in local component state is anti-pattern in React, just reference the prop values directly. If you store them in state then you must also implement componentDidUpdate so you can update the local cache saved in state when the props update (i.e. the state updated in parent), this is just extra unnecessary work though.

    2. The changeInput handler doesn't consume an onChange event nor pass it on to this.props.changeInput, but similar to the previous point, just attach this.props.changeInput to the elements needing it.

    3. The child component that needs the props.changeInput callback is the component rendering the inputs, i.e. in your case it seems is the Personal component.

      class Form extends Component {
        render() {
          const {
            address,
            changeInput,
            description,
            email,
            firstName,
            lastName,
            phoneNum,
            submitForm,
            title,
          } = this.props;
      
          return (
            <form className="cvForm" onSubmit={submitForm}>
              <Personal
                onChange={changeInput} // <-- pass change handler here
                firstName={firstName}
                lastName={lastName}
                title={title}
                address={address}
                phoneNum={phoneNum}
                email={email}
                description={description}
              />
      
              ...
            </form>
          );
        }
      }
      

    Preview

    1. All same comments as for Form component. Don't locally store the passed props and use the this.props.changeInput callback directly. Since this is a preview it likely doesn't need an onChange handler.

      class Preview extends Component {
        render() {
          const {
            firstName,
            lastName,
            title,
            address,
            phoneNum,
            email,
            description
          } = this.props;
      
          return (
            <div className="cvPreview">
              <div className="gridItem nameItem">
                <h1>
                  {title} {firstName} {lastName}
                </h1>
                <h3>Data engineer</h3>
                ... other fields
              </div>
            </div>
          );
        }
      }
      

    Demo

    Edit react-passing-state-down