Home > Net >  createAsyncThunk - changing the state on pending doesn't cause immediate rerendering
createAsyncThunk - changing the state on pending doesn't cause immediate rerendering

Time:10-15

I'm using typescript, useSelector and @reduxjs/toolkit...

...When I dispatch the computeConfidenceIntervalsAsync action (see the code below), I can see the code running the following line immediatly: state.status = 'loading'

But the ViewComponent is re-rendered only after the payloadCreator has finished running the doSomeHeavyComputation function.

To explain it in another way: in the ViewComponent I would expect the 'loading' state rendered before the heavy computation but for some reason the heavy computation is run first, then I receive 'loading' and 'idle' in a row.

Any help on this one?

Reducer:

//...

export const computeConfidenceIntervalsAsync = createAsyncThunk(
  'forecast/getConfidenceIntervals',
  async (data: ConfidenceIntervalsParams) => {
    const response = await getConfidenceIntervals(data.totalRuns, data.itemsTarget, data.throughputs, new Date(data.startingDate));
    return [...response.entries()];
  }
);

export const forecastSlice = createSlice({
  name: 'forecast',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder
      .addCase(computeConfidenceIntervalsAsync.pending, (state) => {
        state.status = 'loading';
      })
      .addCase(computeConfidenceIntervalsAsync.fulfilled, (state, action) => {
        state.status = 'idle';
        state.confidenceIntervals = action.payload;
      });
  }
});

export const selectForecast = (state: RootState) => state.forecast;
//...

service:

//...
export function getConfidenceIntervals(totalRuns: number, itemsTarget: number, throughputs: number[], startingDate: Date) {
  return new Promise<Map<string, number>>((resolve) => {
    console.log('beginning');
    const outcomes = doSomeHeavyComputation(totalRuns, itemsTarget, throughputs, startingDate);
    console.log('ending');
    resolve(outcomes);
  });
}
//...

Component:

export function ViewComponent() {
  const forecast = useAppSelector(selectForecast);
  console.log(forecast)

  if (forecast.status === 'loading') {
    return (
      <div>Loading...</div>
    );
  }

  return (<div>...</div>);

useSelector hook

import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
import type { RootState, AppDispatch } from '../redux';

// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>();
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;

CodePudding user response:

Synchronous work is blocking - and not really what cAT is designed for. You can make this work by doing something like

export async function getConfidenceIntervals(totalRuns: number, itemsTarget: number, throughputs: number[], startingDate: Date) {
    await Promise.resolve() // this will defer this to the next tick, allowing React to render in-between
    console.log('beginning');
    const outcomes = doSomeHeavyComputation(totalRuns, itemsTarget, throughputs, startingDate);
    console.log('ending');
    return outcomes;
}

(I've taken the liberty of rewriting it to async/await)

Your promise would have started immediately with no pause in-before so I added an await Promise.resolve() which defers execution by a tick.

  • Related