After reviewing the entire architecture again I figured that I need to manually set the initial state in the components. Now, the initial rendering is doing the useful work, and the second rendering will be ignored by the react change detection.
I still have the extra rendering cycles. However, I see that this is the state of affairs with change detection. A lot of things trigger a second rendering: the init, the router, the event handlers, the observables. As long as React is using the virtual dom for change detection to weed out values that do not actually change, there should be no real impact on performance. As they say: I'm barking at the wrong tree.
state.service.tsx
/** Access state changes as an observable stream */
export const store$ = new Observable<AppState>(observer => {
// All state store observable use `distinctUntilChanged()` operator.
// Without this initial state, `distinctUntilChanged()` will be unable to compare previous and current state.
// As a result, the webapi observable will miss the first response fron the server.
observer.next(appInitialState);
let appState: AppState;
store.subscribe( () => {
appState = store.getState();
observer.next(appState);
});
})
app.module.tsx
constructor(props: any) {
super(props);
DEBUG.construct && debug('Construct AppModule');
this.state = {
navigatorIsVisible: appInitialState.navigator.isVisible,
searchOverlayIsVisible: appInitialState.search.isVisible
} as State;
getAppSettings();
}
search.overlay.smart.tsx
searchOverlayIsVisible$().pipe(
takeUntil(this.destroyed$),
skip(1), // Ignore init state
)
.subscribe(searchOverlayIsVisible => {
DEBUG.subscribe && debug('Observe searchOverlayVisiblity$', searchOverlayIsVisible);
this.setState({ searchOverlayIsVisible });
this.state.searchOverlayIsVisible
});
search.overlay.service.tsx
export function toggleSearchOverlay(isVisible?: boolean) {
if (DEBUG.service && DEBUG.verbose) debug('Toggle search overlay', isVisible);
store.dispatch(
searchActions.toggleSearch(isVisible)
);
return searchOverlayIsVisible$();
}
export const searchOverlayIsVisible$ = () => store$.pipe(
map( state => SEARCH_VISIBILITY(state) ),
distinctUntilChanged()
);
Conclusions
- Pushing the initial state in the
store$
observable is necessary because we need all the state store observables to recieve their first state. Without this initial state distinctUntilChanged()
will not be able to run the comparison between previous and current state. If distictUntilChanged
is blocking the obsevables then we end up blocking responses from the webapi. This means we see empty pages even if the state store received the first set of data.
- Notice that we are using the component constructor to setup the initial state. Thus, we use the first rendering cycle for useful work. The second rendering will be inhibited by using
skip(1)
in all state store observables.
- Even if we setup init state in constructor we still keep the initial state in reducers as well. All the
TOGGLE
actions need an initial state to start from.
- Be aware that, a lot of processes trigger a second rendering: the init, the router, the event handlers, the observables. As long as React is using the virtual dom for change detection to weed out values that do not actually change, there should be no real impact on DOM rendering performance.
- This means it is close to impossible to have just one
componentDidUpdate
call per route change in LessonsPage
. This means we still need to filter out duplicate calls to handlRouteParams()
.
setState
? - aziumconnect()
method I need a way to push this data in the rendering pipeline. If I just store it in some props on the class instance than nothing will change on the screen. Right? Basically, I'd like to get rid of the first rendering and keep the second one which uses state store data. - Adrian Moisarender
gets run one extra time? - aziumcomponentDidUpdate()
to process route params and query string all together in one shot. If I have multiple renderings, then this step needs additional logic to filter out excessive renderings. I am forced to use this life cycle method because if I usethis.props.history.listen()
thenthis.props.match.params.foo
is lagging with 1 state behind. That's because the component has not updated the props yet. - Adrian MoisarenderedOnce
set to false initially, together with ashouldComponentUpdate
check? - azium