简体   繁体   中英

Lift state up between components in react

I have a problem with the communication between react components. I want to pass values between the components "ProductNameSave.js" to "readJson.js". The values from "ProductNameSave.js" came from the component "windowForm.js". Because of this strucutre ("windowForm.js" -> "ProductNameSave.js" -> "readJson.js"), I think "ProductNameSave.js" is the "source of thruth". Here are the components. "windowForm.js"

import React from 'react';
import Input from '@material-ui/core/Input';
import UploadPicture from './UploadPicture.js';

export default class FormWindow extends React.Component{
    state ={
        U_Factor:'',
        Solar_Heat_Gain_Coefficient:'',
        Visible_Transmittance:''
    };

    change = e =>{
        this.setState({
            [e.target.name]: e.target.value
        });
    };

    onSubmit= e =>{
        e.preventDefault();
        this.props.onSubmit(this.state);
        this.setState({
            U_Factor:'',
            Solar_Heat_Gain_Coefficient:'',
            Visible_Transmittance:''
        });
    };

    render(){
        return(
            <form className="productForm">
                <Input 
                    name="U_Factor"
                    placeholder="U-Factor" 
                    value={this.state.prodValue1} 
                    onChange={e => this.change(e)}
                />
                <br />
                <Input 
                    name="Solar_Heat_Gain_Coefficient"
                    placeholder="Solar Heat Gain Coefficient"
                    value={this.state.prodValue2} 
                    onChange={e => this.change(e)}
                />
                <br />
                <Input 
                    name="Visible_Transmittance"
                    placeholder="Visible Transmittance" 
                    value={this.state.prodValue3} 
                    onChange={e => this.change(e)}
                />
                <br />
                <UploadPicture/>
               <button onClick={e => this.onSubmit(e)}>Submit</button>
            </form>    
        )
    }
}

"ProductNameSave.js":

import React from 'react';
import FormWindow from './windowForm.js';


export default class ProductNameSave extends React.Component {

  constructor(props) {
    super(props);
    this.state = {
      field:'', 
      open: false
    };
  }

  state = {
    fields:{}
  };

  handleChance(fields){
    this.setState({fields})
  }

  onSubmit = (fields) => {
    this.setState({fields});
    console.log('App component got: ', fields);
  };

  render() {
    const fields = this.state.fields
    return (
      <div>
        <FormWindow  onSubmit ={fields => this.onSubmit(fields)} />
      </div>
    );
  }
}

"readJson.js":

import React from 'react';
import MyAppChild from './readJsonChild.js';
import data from '../../model/data/ProductData/Material.json';
import ProductNameSave from '../ProductList/ProductNameSave.js';

export default class MyApp extends React.Component {

  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {fields: ''};
  }

  handleChange(fields) {
    this.props.onValueChance({fields});
  }

  render() {
      const fields = this.props.fields;
      var json2 = data[0]      
      var arr = [];
      Object.keys(json2).forEach(function(key) {
        arr.push(json2[key]);
      });
    return 
      <ul>{arr.map(item => <MyAppChild key={item.Name} Name={item.Name} Roughness={item.Roughness} Thickness={item.Thickness} />)}</ul>;
      <ul><ProductNameSave onChance ={fields => this.handleChange(fields)}/></ul>;
  }
}

"readJson.js" gets it's values from a separate component. The aim is to pass the values from windowForm.js via ProductNameSave to readJson to overwright the values in reaJson with the new values from ProductNameSave. If I run the code, I get the following error: Error: MyApp.render(): A valid React element (or null) must be returned. You may have returned undefined, an array or some other invalid object.

Does anyone have an idea how I can solve this problem?

Thank you very much in advance!

You can also use <React.Fragment> if you are using React version > 16. This avoids adding another element in DOM just for grouping other JSX elements.

<React.Fragment>
    <ul>{arr.map(item => <MyAppChild key={item.Name} Name={item.Name} Roughness={item.Roughness} Thickness={item.Thickness} />)}</ul>;
    <ul><ProductNameSave onChance ={fields => this.handleChange(fields)}/></ul>;
</React.Fragment>

Reference: ReacJS Doc for Fragment .

There is also another short-hand provided by ReactJS which is not supported by other tools yet. The same can be read in above doc page.

The issue is in readJson.js. The way this component returning JSX is incorrect. In React 15 every component JSX elements must be enclosed either with div or span etc.

Check below updated code

import React from 'react';
import MyAppChild from './readJsonChild.js';
import data from '../../model/data/ProductData/Material.json';
import ProductNameSave from '../ProductList/ProductNameSave.js';

export default class MyApp extends React.Component {

  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {fields: ''};
  }

  handleChange(fields) {
    this.props.onValueChance({fields});
  }

  render(){
    const fields = this.props.fields;
      var json2 = data[0]      
      var arr = [];
      Object.keys(json2).forEach(function(key) {
        arr.push(json2[key]);
      });
    let children = [];
    arr.map((item, i) => {
        children.push(<MyAppChild key={item.Name} Name={item.Name} Roughness={item.Roughness} Thickness={item.Thickness} />);
    });
return(
    <div>
        <ul>
        {children}
        </ul>
        <ul><ProductNameSave onChance ={fields => this.handleChange(fields)}/></ul>
    </div>
    )
}
}

if your component returns single jsx element then they no need to be enclosed between div or span, if more than one jsx element then they has to be enclosed.

Don't try to lift a state up from windowForm.js via ProductNameSave to readJson, this is an antipatern. Instead, you can do the following:

  1. Define state in parent component (in your case this is probably readJson component) and also define a method that will change the state and will be passed to the child component (in your case windowForm.js). Once you define such a method and pass it into child component, you will be able to call this method inside child component and this method will change the state in the parent component.

OR

  1. Use Redux and keep your data in global state. These data will be available in all your components.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM