React useState didn't catch previous state

I have some troubles with updating state in my component: i got few services which can be selected and selection, starts async loading this service, so i want to show some loading state, but when there is more than one service and i select both ( pretty fast ) i'm getting wrong previous state

when i'm fast click on first and second service, their both need to be loading but actual loading state when i click on second service === {0: false, 1: true} must be {0: true, 1: true} but i can't figure out why first service loading === false

there is code

generating initial state

    const [loadingState, setLoadingState] = React.useState(
        availableServices.reduce((loadingState, service) => {
            loadingState[service.id] = false;

            return loadingState;
        }, {})
    );

actual state

    useEffect(() => {
        console.log('actual loading state', loadingState);
    }, [Object.keys(loadingState).map(id => id)]);

handleSelect function

    const onSelect = useCallback(
        async (id: string) => {
            const selectedServicesIds = selectedServices.map(service => service.id);
            setAddingInProgress(true);
            console.log('set to true', loadingState);
            setLoadingState({ ...loadingState, ...{ [id]: true } });
            // set to true {0: false, 1: false}
            // actual loading state {0: true, 1: false}
            // click on second service
            // set to true {0: false, 1: false}
            // actual loading state {0: false, 1: true} <-- there is need to be {0: true, 1: true}

            if (selectedServicesIds.includes(id)) {
                selectedServicesIds.splice(selectedServicesIds.findIndex(serviceId => serviceId === id), 1);
                await updateServices(selectedServicesIds);
            } else {
                selectedServicesIds.push(id);
                await updateServices(selectedServicesIds);
            }
            setAddingInProgress(false);
            console.log('set to false', loadingState);
            setLoadingState({ ...loadingState, ...{ [id]: false } });
        },
        [selectedServices]
    );

Upvotes: 0

Views: 325

Answers (2)

Nappy
Nappy

Reputation: 3046

Since there is no question to answer I will just review real quick:

  1. Your map in [Object.keys(loadingState).map(id => id)] always creates a new object so your effect will be performed on each render which is not desireable
  2. Your callback returns a promise, which should work but its not normal to assing async handlers

Upvotes: 0

Jasper Bernales
Jasper Bernales

Reputation: 1681

I think this will work

    const onSelect = useCallback(
        async (id: string) => {
            const selectedServicesIds = selectedServices.map(service => service.id);
            setAddingInProgress(true);
            console.log('set to true', loadingState);
            setLoadingState(prev => ({ ...prev, ...{ [id]: true } }));
            // set to true {0: false, 1: false}
            // actual loading state {0: true, 1: false}
            // click on second service
            // set to true {0: false, 1: false}
            // actual loading state {0: false, 1: true} <-- there is need to be {0: true, 1: true}

            if (selectedServicesIds.includes(id)) {
                selectedServicesIds.splice(selectedServicesIds.findIndex(serviceId => serviceId === id), 1);
                await updateServices(selectedServicesIds);
            } else {
                selectedServicesIds.push(id);
                await updateServices(selectedServicesIds);
            }
            setAddingInProgress(false);
            console.log('set to false', loadingState);
            setLoadingState(prev => ({ ...prev, ...{ [id]: false } }));
        },
        [selectedServices]
    );

Upvotes: 2

Related Questions