简体   繁体   中英

Transferring Props to Multi-Level Upward in React Child-Parent Components

I have 3 React components with such relationship:

  1. Parent
  2. Child
  3. ChildofChild

I want to have a button in ChildofChild component, when clicked to update state in Parent component. I can send it to Child component via props and run a fuction there.

ChildOfChild

// ChildOfChild Component
export class PlaceInfoWindow extends Component {

  render() {
    const {description, name, price} = this.props

    return(
      <InfoWindow onCloseClick={this.props.closeWindow}>
        <div>
          <h1>{name}</h1>
          <p>{description}</p>
          <span>${price}</span>
          <button type="button" onClick={this.props.onAdd} className="btn btn-primary btn-success m-2">Add</button>
        </div>
      </InfoWindow>
    );
  }
}

export default PlaceInfoWindow

Child

//Child Component
    export class PlaceMarker extends Component {
      constructor(props) {
        super(props);

        this.state = {
          showTooltip: false,
        };
      }

      clickTooltip() {
        this.setState({ showTooltip: !this.state.showTooltip });
      }

      closeWindow() {
        this.setState({ showTooltip: false });
      }

       render() {
        const { showTooltip } = this.state;
        const { lat, lng, name, price, description } = this.props;

        return (
          <Marker
            position={{
              lat: parseFloat(lat),
              lng: parseFloat(lng)
            }}
            onClick={this.clickTooltip.bind(this)}
            icon="https://image.ibb.co/cGPSW8/red_marker.png"
          >
            {showTooltip && (
              <PlaceInfoWindow
                description={description}
                name={name}
                price={price}
                closeWindow={this.closeWindow.bind(this)}
                onAdd={this.props.onAdd}
              />
            )}
          </Marker>
        );
      }
    }

    export default PlaceMarker;

Parent

// Parent Component
const AirbnbMap = withGoogleMap(props => (
  <GoogleMap
    defaultCenter={props.center}
    defaultZoom={props.zoom}
    defaultOptions={{
      styles: userMapStyle
    }}
  >
    {props.places.length > 0 &&
      props.places.map(place => (
        <PlaceMarker
          key={`place${place.id}`}
          id={place.id}
          lat={place.latitude}
          lng={place.longitude}
          description={place.description}
          name={place.name}
          price={place.price}
          onAdd={this.handleAdd}
        />
      ))}
  </GoogleMap>
));

export class Map extends Component {
  constructor(props) {
    super(props);

    this.zoom = 7;

    this.state = {
      lat: 50.0515918,
      lng: 19.9357531,
      places: [       
        {
        id: 1,
        latitude: 50,
        longitude: 20,
        description: "ABC",
        name: "City",
        price: 20
      }]
    };
  }


  handleAdd = () => {
    console.log("handle add called");
  };

  render() {
    const { lat, lng, places } = this.state;
    console.log(places);
    return (
      <div style={{ width: `100%`, height: `750px` }}>
        <AirbnbMap
          center={{
            lat: lat,
            lng: lng
          }}
          places={places}
          zoom={this.zoom}
          containerElement={<div style={{ height: `100%` }} />}
          mapElement={<div style={{ height: `100%` }} />}
        />
      </div>
    );
  }
}

export default Map;

But how can I send it to Parent (two levels up) component? In this way, Child component will only forward the props to the Parent component, which it took from its child ( ChildofChild ).

It's no problem to just forward props. This includes handlers:

class Parent extends Component {
    handle = event => { /* handle event */ };

    render() {
        return (
            <Child handler={this.handle} />
        );
    } 
}

const Child = ({handler}) => (
    <ChildOfChild handler={handler} />
);

const ChildOfChild = ({handler}) => (
    <button onClick={handler}>Click me!</button>
);

Your top Parent component is not AirbnbMap, is class Map. Also you are declaring handleAdd in your Map class, but your are not passing it to Airbnb component. On Map you should pass it on :

<AirbnbMap handleAdd={this.handleAdd}
    // Rest of props here
    />

Then on AirbnbMap class:

<PlaceMarker 
    ...
    onAdd={props.handleAdd} 
    />

The rest looks ok.

From React 16.3 on, you could use the new Context API so you don't need to pass down the props to every children and be able to lift up state easily and cleanly.

import React, { Component, createContext } from 'react';

const ParentContext = createContext({});

class Parent extends Component {
    handle = event => { console.log('Event', event) };

    render() {
        return (
          <ParentContext.Provider value={{
            onClick: this.handle,
          }}>
            <Child />
          </ParentContext.Provider>
        );
    } 
}

const Child = () => (<ChildOfChild />);

const ChildOfChild = () => ( 
  <ParentContext.Consumer>
    { (context) => <button onClick={context.onClick}>Click me!</button>}
  </ParentContext.Consumer>
);

export default Parent;

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