简体   繁体   中英

How to update state inside componentDidMount?

I'm using fetch API and I want update the const called state inside the componentDidMount() (with onChange) which are being using in a template string. How do I update this value with onChange?

import React, {Component} from 'react'

class Data extends Component {
    constructor() {
        super();
        this.state = {
            items: {},
            value: '',
            isLoaded: false
        }
    }

    handleChange(e) {
        this.setState({value: e.target.value});
    }


    componentDidMount() {

        const state = this.state.value

        fetch(`http://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${state}`)
        .then(res => res.json())
        .then(json => {
            this.setState({
                isLoaded: true,
                items: json,
            })
        });
    }


    render(){
        const {isLoaded} = this.state;

        if(!isLoaded) {
            return <div>Loading...</div>
        }

        return(
            <div>
                <select onChange={this.handleChange}>
                    <option value="America/Chicago">Chicago</option>
                    <option value="America/Sao_Paulo">São Paulo</option>
                </select>
            </div>
        )
    }
}


So, how can I update the value of the const state with onChange?

componentDidMount() is called when the React component has mounted, and it happens only once.

If I understand correctly, you want to call fetch on each change of the value stored under value state property, so the componentDidMount method is not a perfect place to put that kind of logic. You can create a separate method called fetchData and pass the value to it as an argument. Then you can call that method on componentDidMount as well as on each value property change (in our case - onChange event).

 import React, { Component } from "react"; class Data extends Component { constructor(props) { super(props); this.state = { items: {}, value: "America/Chicago", isLoaded: false }; this.handleChange = this.handleChange.bind(this); } componentDidMount() { const { value } = this.state; this.fetchData(value); } handleChange(event) { const value = event.target.value; this.setState({ value }); this.fetchData(value); } render() { const { isLoaded, value, items } = this.state; if (!isLoaded) { return <div>Loading...</div>; } return ( <div> <select onChange={this.handleChange} value={value}> <option value="America/Chicago">Chicago</option> <option value="America/Sao_Paulo">São Paulo</option> </select> {JSON.stringify(items)} </div> ); } fetchData(value) { fetch( `https://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${value}` ) .then(res => res.json()) .then(json => { this.setState({ isLoaded: true, items: json }); }); } } 

Working demo: https://codesandbox.io/embed/728jnjprmq

Tomasz's code has 2 mistakes: (1) it fetches resources w/o checking if the component has been unmounted; (2) it starts the request w/o updating the UI first.

I would do the following instead:

import React, {Component} from 'react'

class Data extends Component {
    constructor() {
        super();
        this.state = {
            items: {},
            value: '',
            isLoaded: false
        }

        this._isMounted = false;

        // don't forget to bind your methods
        this.handleChange = this.handleChange.bind(this);
    }

    componentDidMount() {
        this._isMounted = true;
    }

    componentWillUnmount() {
        this._isMounted = false;
    }

    handleChange(e) {
        const value = e.target.value;
        this.setState({ value }, () => {
            if (!this._isMounted) return;
            const url = `http://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${value}`
            fetch(url).then((res) => {
                if (!this._isMounted) return;
                const data = res.json();
                this.setState({ isLoaded: true, items: data });
            })
        });
    }

    render(){
        const { isLoaded } = this.state;

        if(!isLoaded) {
            return <div>Loading...</div>
        }

        return(
            <div>
                <select onChange={this.handleChange}>
                    <option value="America/Chicago">Chicago</option>
                    <option value="America/Sao_Paulo">São Paulo</option>
                </select>
            </div>
        )
    }
}

Assuming you want to refresh the value of this.state.items when the user changes the value of the select , you can do this in the onChange . However, your code is in a few (incorrect) pieces. Let's start from the top.

First of all, you're setting the value property of state to '' , so your componentDidMount function is going to see that value. I assume that's no good, so let's strip that out of componentDidMount entirely. We can move this code to the handleChange function instead, but it'll still need to be changed:

handleChange(e) {
    this.setState({value: e.target.value});

    fetch(`http://api.timezonedb.com/v2.1/get-time-zone?key=J9X3EOT2EM8U&format=json&by=zone&zone=${e.target.value}`)
    .then(res => res.json())
    .then(json => {
        this.setState({
            isLoaded: true,
            items: json,
        })
    });
}

Notice my change - we can't access the value from the state, because setState is asynchronous, and so the value hasn't been updated by this point. We know the value comes from the select though.

The other thing you could do to improve this functionality is to turn the select into a controlled component. To do this, you just have to set the value of the field to be controlled by the state of this component. Since you're using an onChange listener for this, it makes the field a controlled component (if you weren't using an onChange, it would be a read-only field.

The loading variable in state appears to be being used incorrectly, I'm guessing you just need to check if there's data in 'items'. I'll remove this for now, but you could come back to this.

render(){
    const {isLoaded} = this.state;

    if(!isLoaded) {
        return <div>Loading...</div>
    }

    return(
        <div>
            <select onChange={this.handleChange}>
                <option value="America/Chicago">Chicago</option>
                <option value="America/Sao_Paulo">São Paulo</option>
            </select>
        </div>
    )
}

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