How to set a variable in a parent component from a child component

I have what I call a parent component. It is a modal. I am trying to update a variable from a child component, namely the ShippingBlock. I am trying to update shippingMethod.

import React from 'react';
import "./Checkout.scss"
import AddressBlock from './AddressBlock';
import PaymentBlock from './PaymentBlock';
import ShippingBlock from './ShippingBlock';
import TotalsBlock from './TotalsBlock';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';

class CheckoutModal extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: 'React'            
        };
    }    

    

    render() {
        let shippingMethod = null;

        function setShippingMethod(method) {
            shippingMethod = method;
        }

        const buttonStyles = {
            position: "absolute",
            top: "-35px",
            right: "10px",
            border: "0",
            background: "none"
        };        

        return (
            <Modal id="checkout"
                isOpen
                size='xl'>
                <button onClick={this.props.onRequestClose} style={buttonStyles}>x</button>
                <ModalHeader className="header">
                    Checkout                    
                </ModalHeader>
                <ModalBody>                    
                    <div className="row">
                        <AddressBlock />
                        <ShippingBlock setShippingMethod={setShippingMethod} shippingMethod={shippingMethod}/>
                    </div>
                    <div className="row">
                        <PaymentBlock />                    
                        <TotalsBlock />
                    </div>
                </ModalBody>
            </Modal>
        );
    }
}
export default CheckoutModal;
import React from 'react';
import Config from 'config';
import "./Checkout.scss"

class ShippingBlock extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: 'React',
            shippingMethods: [],
            defaultMethod: this.props.shippingMethod
        };
    }

    async componentDidMount() {
        console.log('Running componentDidMount')
        const tokenString = sessionStorage.getItem("token");
        const token = JSON.parse(tokenString);

        let headers = new Headers({
            "Accept": "application/json",
            "Content-Type": "application/json",
            'Authorization': 'Bearer ' + token.token
        });

        const response = await fetch(Config.apiUrl + `/api/Shipping/GetShippingMethods`, {
            method: "GET",
            headers: headers
        });
        const json = await response.json();
        console.log(json);
        this.setState({ shippingMethods: json });

        if (this.state.defaultMethod === null) {
            const mresponse = await fetch(Config.apiUrl + `/api/Users/GetDefaultShippingMethod`, {
                method: "GET",
                headers: headers
            });
            const mjson = await mresponse.json();
            console.log(mjson);
            this.setState({ defaultMethod: mjson });
        }        
    }

    setShippingMethod(e) {
        console.log('Shipping method ' + JSON.stringify(e.target.value));
        this.props.setShippingMethod(e.target.value);        
        this.state.defaultMethod = e.target.value;        
    }

    render() {
        const shippingMethods = this.state.shippingMethods;
        const defaultMethod = this.state.defaultMethod;        

        return (
            <aside id="checkout" className="block col-1">
                <h1>Shipping</h1>
                <div className="row">
                    <select id="comboMethod" onChange={this.setShippingMethod.bind(this)} value={defaultMethod === null ? null : defaultMethod.trim()}>
                        {shippingMethods.map(method => { return <option key={method.shipmthd.trim()} value={method.shipmthd.trim()}>{method.shipmthd.trim()}</option>} )}
                    </select>
                </div>
            </aside>
        );
    }
}
export default ShippingBlock;

Whenever I try to change the select in ShippingBlock my function onChange runs and the console log shows the correct selected value, but somehow the value gets set right back to the default. I haven’t been able pinpoint what I have done wrong. Any tips or help would be appreciated.

UPDATE

import React from 'react';
import "./Checkout.scss"
import AddressBlock from './AddressBlock';
import PaymentBlock from './PaymentBlock';
import ShippingBlock from './ShippingBlock';
import TotalsBlock from './TotalsBlock';
import { Modal, ModalHeader, ModalBody } from 'reactstrap';

class CheckoutModal extends React.Component {
    constructor(props) {
        super(props);
        this.state = {
            name: 'React',
            shippingMethod: null
        };
    }    

    setShippingMethod(method) {
        this.state.shippingMethod = method;
    }

    render() {
        const buttonStyles = {
            position: "absolute",
            top: "-35px",
            right: "10px",
            border: "0",
            background: "none"
        };        

        return (
            <Modal id="checkout"
                isOpen
                size='xl'>
                <button onClick={this.props.onRequestClose} style={buttonStyles}>x</button>
                <ModalHeader className="header">
                    Checkout                    
                </ModalHeader>
                <ModalBody>                    
                    <div className="row">
                        <AddressBlock />
                        <ShippingBlock setShippingMethod={this.setShippingMethod.bind(this)} shippingMethod={this.state.shippingMethod}/>
                    </div>
                    <div className="row">
                        <PaymentBlock />                    
                        <TotalsBlock />
                    </div>
                </ModalBody>
            </Modal>
        );
    }
}
export default CheckoutModal;

This is what I had before. It behaves the same.

Answer

What you’ve done wrong is that shippingMethod is not part of the parent component state.

You have defined it inside the render function, which runs everytime the parent component is rendered. This means that the value you set in there will reset back to what it was previously before the child is rendered i.e. null, therefore the child component will get the null when it is rendered.

Solution

Move the variable into the parent’s state, and make the setter a member of the parent class.


Your setShippingMethod should be using this.setState (similar to the useState hook in functional components):

setShippingMethod(method) {
    this.setState(state => ({...state, shippingMethod: method}));
}