2

I am trying to build a form, where you can pick a place at the end of the form. I am using places-autocomplete for this. The Locationpicker is a child of the Form-View Component. The state for the coordinates is set when I choose the place in handleSelect(), but if I want to add it to the form data in the parent, I can't access it.

Child component

class LocationPicker extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      address: "San Francisco, CA",
      lat: "",
      lng: ""
    };
    this.handleSelect = this.handleSelect.bind(this);
    this.handleChange = this.handleChange.bind(this);
  }
  handleSelect(address) {
    this.setState({
      address
    });
    geocodeByAddress(this.state.address)
      .then(results => getLatLng(results[0]))
      .then(latLng => this.setState({ lat: latLng.lat, lng: latLng.lng }))
      .catch(error => console.error("Error", error));
  }

  handleChange(address) {
    this.setState({
      address,
      geocodeResults: null
    });
  }

  render() {
    const inputProps = {
      value: this.state.address,
      type: "text",
      onChange: this.handleChange,
    };

    return (
      <div>
        <PlacesAutocomplete
          onSelect={this.handleSelect}
          onEnterKeyDown={this.handleSelect}
          inputProps={inputProps}
        />
        <p>{this.state.lat}</p>
        <p>{this.state.lng}</p>
      </div>
    );
  }
}

Now I would like to use lat and lng in my parent component, but I don't want to add the handleSelect function to my parent. How can I send lat and lng to my parent component?

Fabio ha
  • 553
  • 1
  • 8
  • 29
  • 1
    I think you should decide which component can be [`presentational and which one container component`](https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0). I dont like such approach as you want to try implement. You can have parent component which passes props & handlers down to child components [`here`](https://stackoverflow.com/questions/22639534/pass-props-to-parent-component-in-react-js) is some of ways how to handle such things. As far as i see it can be refactored in a way i suggested – The Reason Dec 29 '17 at 14:53
  • @TheReason My Child and Parent would both be a container component so that's probably not the best approach. How would you do it? – Fabio ha Dec 29 '17 at 14:59
  • Something similar to [`this`](https://jsfiddle.net/69z2wepo/96647/). in this case you have `LocationPicker` as presentational component and ParentComponent as a container. I'm not sure if you are using any libs like `redux` or `mobx` for managing you app state. And all your `this.state` can come from `redux state` – The Reason Dec 29 '17 at 15:20

1 Answers1

1

You can pass a function from the parent to the child that updates address in the parents state:

class LocationPicker extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            address: props.address || "San Francisco, CA",
            lat: "",
            lng: ""
        };
        this.handleSelect = this.handleSelect.bind(this);
        this.handleChange = this.handleChange.bind(this);
    }
    handleSelect(address) {
        this.props.updateAddress(address);
        geocodeByAddress(address)
            .then(results => getLatLng(results[0]))
            .then(latLng => this.setState({ lat: latLng.lat, lng: latLng.lng }))
            .catch(error => console.error("Error", error));
    }

    handleChange(address) {
        this.props.updateAddress(address);
        this.setState({
            geocodeResults: null
        });
    }

    componentWillReceiveProps(prevProps, nextProps) {
        if(prevProps.address !== nextProps.address){
            this.setState({
                address: nextProps.address
            });
        }
    }

    render() {
        const inputProps = {
            value: this.state.address,
            type: "text",
            onChange: this.handleChange,
        };

        return (
            <div>
                <PlacesAutocomplete
                    onSelect={this.handleSelect}
                    onEnterKeyDown={this.handleSelect}
                    inputProps={inputProps}
                />
                <p>{this.state.lat}</p>
                <p>{this.state.lng}</p>
            </div>
        );
    }
}

export default class Parent extends Component {
    state = {
        address: ''
    };
    updateAddress = address => {
        this.setState({
            address
        });
    }
    render() {
        return (
            <div>
                <LocationPicker address={address} />
            </div>
        )
    }
}

Or, you could move all of the address state to the parent (container) component and pass down everything to the Child component as props:

class LocationPicker extends React.Component {
    render() {
        const inputProps = {
            value: this.props.address,
            type: "text",
            onChange: this.props.handleChange,
        };
        return (
            <div>
                <PlacesAutocomplete
                    onSelect={this.handleSelect}
                    onEnterKeyDown={this.handleSelect}
                    inputProps={inputProps}
                />
                <p>{this.props.lat}</p>
                <p>{this.props.lng}</p>
            </div>
        );
    }
}

export default class Parent extends Component {
    state = {
        address: '',
        geocodeResults: null,
        lat: '',
        lng: ''
    }
    handleSelect = address => {
        this.setState({
            address
        });
        geocodeByAddress(this.state.address)
            .then(results => getLatLng(results[0]))
            .then(latLng => this.setState({ lat: latLng.lat, lng: latLng.lng }))
            .catch(error => console.error("Error", error));
    }
    handleChange = address => {
        this.setState({
            address,
            geocodeResults: null
        });
    }
    render() {
        return (
            <div>
                <LocationPicker address={this.state.address} handleSelect={this.handleSelect}/>
            </div>
        )
    }
}
Chase DeAnda
  • 15,963
  • 3
  • 30
  • 41