— by Hasan Karahan, MSc ETH Zurich, @notexeditor
Today, I’d like to present a React mixing that allows you to update a component’s state recursively. It’s primarily meant for software developers who still use class based components and who would like to apply a more sophisticated method instead of the standard setState
one, which only allows you to shallow update a component’s state.
The mixing below provides the update
method which enables you to set a new state and which at its core facilitates the jQuery.extend
function, that can deep merge multiple objects in a single invocation. It’s written in Typescript and offers full type safety while updating a component’s state:
import { DeepPartial } from 'redux';
import { Component } from 'react';
/**
* returns a generic constructor type
*/
export type Constructor<U = unknown> = {
new (...args: any[]): U;
}
/**
* @returns the *inferred* property type from a component's constructor
*/
type PropsOf<Ctor>
= Ctor extends Constructor<Component<infer P>> ? P : never;
/**
* @returns the *inferred* state type from a component's constructor
*/
type StateOf<Ctor>
= Ctor extends Constructor<Component<infer _, infer S>> ? S : never;
/**
* @returns a mixin with the `update` method to *deep* set the state
*/
export function Updatable<
TBase extends Constructor<Component<TProps, TState>>,
TProps = PropsOf<TBase>, TState = StateOf<TBase>
>(
Base: TBase
) {
return class extends Base {
protected update(
next_state: DeepPartial<TState>,
callback?: () => Promise<void>
) {
return new Promise<void>((resolve) => {
const state = $.extend(
true, {}, this.state, next_state
);
this.setState(state, async () => {
if (typeof callback === 'function') {
await callback();
}
resolve();
});
});
}
};
}
It’s usage is rather simple — by just wrapping the React component with the Updatable
mixin you gain access to the update
method:
type Props = {}
type State = {
my:{ deep:{ state:{ x:number; y:number; }}}
}
export class App extends Updatable(
React.Component<Props, State>
) {
constructor(props: Props) {
super(props); this.state = {
my:{ deep:{ state:{ x:0,y:0 }}}
};
}
set X(x: number) {
// old y is *not* overwritten but kept:
this.update(my:{ deep:{ state:{ x }}});
}
set Y(y: number) {
// old x is *not* overwritten but kept:
this.update(my:{ deep:{ state:{ y }}});
}
}
As you can see, the X
and Y
setters are only updating the respective x
and y
states while not explicitly copying the other one — which is handled by the jQuery.extend
function in the update
method!
Further, it’s also possible to combine the update
method with the async
and await
promises:
export class App extends Updatable(
React.Component<Props, State>
) {
constructor(props: Props) {
super(props); this.state = {
my:{ deep:{ state:{ x:0,y:0 }}}
};
}
async setX(x: number) {
await this.update(my:{ deep:{ state:{ x }}});
}
async setY(y: number) {
await this.update(my:{ deep:{ state:{ y }}});
}
}
The redux
and jQuery
dependencies are superficial and software developers who’d rather like to avoid them can replace the DeepPartial
type (3 lines of code) and the jQuery.extend
function with their own implementations.