Home > Blockchain >  React update state with nested promise in useEffect
React update state with nested promise in useEffect

Time:11-09

I am trying to update the state of a component with React.useState via useEffect. I want to use useEffect because the response of the API call (EmployeeState > init()) drives what is shown on the UI.

component:

import { EmployeeState } from './employee';

const myComp = React.memo(() => {
    const [employeeState, setEmployeeState] = React.useState(new EmployeeState())

    useEffect(() => {
        employeeState.init()
            .then((resp) => {
                console.log('init response', resp);
                setEmployeeState(resp);
            });

    }, []);
}

EmployeeState:

import { ConfigUtil } from './configUtil';

export class EmployeeState {
    public employeeId: string;
    public employeeFirstName: string;
    public employeeSurName: string;
    public employeeState: string;

    public async init() {

        console.log('Starting employee init()');

        return ConfigUtil.getUserConfig()
            .subscribe((resp: any) => {

                console.log('promise complete, setting data');

                this.setEmployeeId(resp);
                this.setEmployeeName(resp);
                this.setEmployeeState(resp);
            
                console.log('Ending employee init(true)');

                return await Promise.resolve(true); // <-- return response to call in useEffect
            },
            (err) => {
                console.log('Ending employee init(err)');                    
                return await Promise.reject(err);
            });
    }

    public setEmployeeId(data): void {
        console.log('setting employee id');
        this.employeeId = data.id;
    }

    public setEmployeeName(data): void {
        console.log('setting employee name');            
        this.employeeFirstName = data.fName;
        this.employeeSurName = data.sName;
    }

    public setEmployeeState(data): void {
        console.log('setting employee state');            
        this.employeeState = data.state;
    }
}

ConfigUtil:

import axios from 'axios';
import { Observable, of, from } from 'rxjs';

let config: Observable<Object>;

export function getConfig() {
    const promise = axios.get('http://mock.server:8080/user');
    const observable = from(promise);

    return observable
        .pipe(
            switchMap(rep => {                    
                return of(rep);
            }),
            catchError(() => {
                return config;
            })
        );            
}

export function getUserConfig() {
    if(!config) {
        config = getConfig()
            .pipe(
                first();
            );
    }

    return config;        
}

Problem: employeeState.init().then returns before these are called/ConfigUtil.getUserConfig is completed:

setEmployeeId()
setEmployeeName()
setEmployeeState()

As per console.log results:

'Starting employee init()'
'init response' > Subscriber {closed: false, ........}
'promise complete, setting data'
'setting employee id'
'setting employee name'
'setting employee sate'

I am looking to achieve:

'Starting employee init()'    
'promise complete, setting data'
'setting employee id'
'setting employee name'
'setting employee sate'
'init response' > Subscriber {closed: false, ........} // <- config call complete, setting data complete, finally returning to useEffect initial call

CodePudding user response:

You're going from promises to observables to promises again, why not stick with just one pattern? That's also the source of the bug. The init() function returns a subscription object, which is available instantly, not a promise resolved some time in the future.

Anyway: let's say we're going the promise route. It would be sth like this:

public async init() {
    console.log('Starting employee init()');
    const getUserConfigPromise = firstValueFrom(ConfigUtil.getUserConfig());
    try {
        const resp = await getUserConfigPromise;
        console.log('promise complete, setting data');

        this.setEmployeeId(resp);
        this.setEmployeeName(resp);
        this.setEmployeeState(resp);

        console.log('Ending employee init(true)');

        return true; 
    } catch (e) {
        console.log('Ending employee init(err)');                    
        throw new Error(e);
    }
}
  • Related