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);
}
}