React useState option value 1 step behind onChange selection

送分小仙女□ 提交于 2021-01-28 14:40:08

问题


The size.value state is one option behind when the _updateData function triggers onChange of the <select>. For example, Let's say the starting defaultValue option of <select> is "All", locations is empty...the option is changed to "Medium", locations is "All"...the option is changed to "Large", locations is "Medium"...and so on. Initially I want the _updateData function to run onload with "All" selected which is not working either, it throws the error Cannot read property 'target' of undefined on setSize({value: event.target.value}). What am I doing wrong here? Thanks for the help.

const Map = () => {
    const [viewport, setViewport] = useState({longitude: -98.58, latitude: 39.83, zoom: 3.5})
    const [locations, setLocations] = useState([])
    const [geojson, setGeojson] = useState(null)
    const [size, setSize] = useState({value: "All"})

    useEffect(() => {
        setLocations(geodata)
        _updateData()
    }, []);

    const _updateViewport = viewport => {
        setViewport(viewport)
    }

    const _updateData = event => {
        setSize({value: event.target.value})
        const tempLocations = [];
        locations.forEach(function(res) {
            if (size.value === "All") {
                tempLocations.push(res);
            } else if (res.Size === size.value) {
                tempLocations.push(res);
            }
        });
        var data = {
            ...
        };
        setGeojson(data);
    }

    return (
        <ReactMapGL
            {...viewport}
            onViewportChange={_updateViewport}
            width="100%"
            height="100%"
            mapStyle={mapStyle}
            mapboxApiAccessToken={TOKEN}>
            <Source id="my-data" type="geojson" data={geojson}>
                <Layer {...icon} />
            </Source>
            <div style={navStyle}>
                <NavigationControl onViewportChange={_updateViewport} />
                <select onChange={_updateData} defaultValue={size}>
                    <option value="All">All</option>
                    <option value="Large">Large</option>
                    <option value="Medium">Medium</option>
                    <option value="Small">Small</option>
                    <option value="Very Small">Very Small</option>
                </select>
            </div>
        </ReactMapGL>
    );
}

export default Map;

回答1:


Yes, you are calling your select's onChange handler in the mounting useEffect hook with no event object to dereference a target property. I would factor out the rest of the updateData code so you can call it with the initial state value. This will allow you to update location details on mount using the initial size state date AND the select's onChange will remain as it was previously.

NOTE: You should note that updates to state won't take effect until the next render cycle, so in your code you call setSize with the new value but continue processing locations the current size value, so you need to forward the current value.

const Map = () => {
    const [viewport, setViewport] = useState({longitude: -98.58, latitude: 39.83, zoom: 3.5})
    const [locations, setLocations] = useState([])
    const [geojson, setGeojson] = useState(null)
    const [size, setSize] = useState({value: "All"}) // initial size state here

    useEffect(() => {
        setLocations(geodata);
        updateLocationData(size.value); // call the location updater on mount with the initial size state value
    }, []);

    const _updateViewport = viewport => {
        setViewport(viewport)
    }

    const _updateData = event => {
        setSize({value: event.target.value})
        updateLocationData(event.target.value); // forward current size value
    }

    const updateLocationData = (sizeValue) => { // forwarded size value
        const tempLocations = [];
        locations.forEach(function(res) {
            if (sizeValue === "All") { // forwarded size value for comparison
                tempLocations.push(res);
            } else if (res.Size === sizeValue) { // forwarded size value for comparison
                tempLocations.push(res);
            }
        });
        var data = {
            ...
        };
        setGeojson(data);
    };

    return (
        <ReactMapGL
            {...viewport}
            onViewportChange={_updateViewport}
            width="100%"
            height="100%"
            mapStyle={mapStyle}
            mapboxApiAccessToken={TOKEN}>
            <Source id="my-data" type="geojson" data={geojson}>
                <Layer {...icon} />
            </Source>
            <div style={navStyle}>
                <NavigationControl onViewportChange={_updateViewport} />
                <select onChange={_updateData} defaultValue={size.value}> // need to unpack the actual size value
                    <option value="All">All</option>
                    <option value="Large">Large</option>
                    <option value="Medium">Medium</option>
                    <option value="Small">Small</option>
                    <option value="Very Small">Very Small</option>
                </select>
            </div>
        </ReactMapGL>
    );
}

export default Map;



回答2:


First of all, useState function is asynchronous. Therefore takes some time to update. See Is useState synchronous?. And second, the useEffect function is called when the component mounts. So no onchange event has occurred yet. Onchange event occurs once you change the option in select tag. Just remove _updateData from your useEffect function.



来源:https://stackoverflow.com/questions/59550673/react-usestate-option-value-1-step-behind-onchange-selection

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!