Monday, November 13, 2023

TypeScript function to set value by key

While researching React stores automation, I wondered whether it would be possible to have a TypeScript function to set the value of an object by the name of its property, with correctly typed parameters, of course.

Turns out, it is

export function setField<
	T extends object,
	K extends keyof T,
>(o: T, k: K, v: T[K]): void {
	o[k] = v;
}

The function above works perfectly with interfaces:

interface Person {
	name: string;
	age: number;
}

const p: Person = { name: 'Joe', age: 42 };
setField(p, 'name', 'foo');

Saturday, November 11, 2023

Jotai utilities for read-only and write-only atoms

Three months ago I wrote an utility function to deal with write-only atoms in Jotai. To go with it, today I wrote a function to deal with read-only atoms, so now I have:

import {Atom, useAtom, WritableAtom} from 'jotai';

/**
 * Syntactic sugar to useAtom() for read-only atoms.
 */
export function useReadAtom<V>(a: Atom<V>) {
	const [val] = useAtom(a);
	return val;
}

/**
 * Syntactic sugar to useAtom() for write-only atoms.
 */
export function useWriteAtom<V, A extends unknown[], R>(a: WritableAtom<V, A, R>) {
	const [_, setFunc] = useAtom(a);
	return setFunc;
}

It’s worth mentioning that useReadAtom also works for derived atoms, used for computed values.

Friday, November 10, 2023

Zustand computed values, pt. 2

I’ve been trying to find a way for computed values in Zustand for a while. The general recommended way is using a custom hook, according to Daishi Kato himself. The issue with this approach, however, is that your computed logic will be completely alienated from the store itself.

I just found a neat approach which seems to work: using a nested object inside the store itself, as pointed in this highly cheered comment. Below is a fully typed example:

import {create} from 'zustand';
import {combine} from 'zustand/middleware';
import {immer} from 'zustand/middleware/immer';

export interface Person {
	name: string;
	age: number;
}

const usePeople = create(immer(
	combine({
		people: [] as Person[],
	},
	(set, get) => ({
		$computed: {
			get count(): number {
				return get().people.length;
			},
		},
		addNew(name: string, age: number): void {
			set(state => {
				state.people.push({name, age});
			});
		},
	})),
));

export default usePeople;

The tradeoff, as benchmarked by myself, is that this $computed getters are not cached and will run every time the state changes, in addition to every time they are requested – which happens when the component re-renders. The ordinary custom hook approach runs only when they are requested. So, there is a performance penalty, which can be huge if the logic is too demanding.

Saturday, September 23, 2023

Cross-compiling Rust for x32

While analyzing a bug in WinSafe, I had to test its compilation on the i686 platform. Without knowing, I already had it installed together with my MSVC toolchain, and I just needed a few commands in order to pull it out:

List all available toolchains:

rustup toolchain list

Which gave me:

stable-i686-pc-windows-msvc
stable-x86_64-pc-windows-msvc (default)
nightly-x86_64-pc-windows-msvc

Then choose other than the default toolchain:

cargo +stable-i686-pc-windows-msvc c

After performing all the tests, it’s a good idea to clean all the build artifacts:

cargo clean

Note that, if the toolchain is not present, it may have to be installed.

Saturday, August 19, 2023

Calling write-only atoms in Jotai

Every atom in Jotai, when called through its useAtom primitive, returns a tuple of value + setter, just like React’s own useState. In a Jotai write-only atom, however, there is no value, so the canonical call is:

const [_, setVal] = useAtom(myValAtom);

In order to get rid of the verbose underscore, and thus the whole useless tuple, I wrote the following function:

import {WritableAtom} from 'jotai';

/**
 * Syntactic sugar to useAtom() for write-only atoms.
 */
export function useWriteAtom<V, A extends unknown[], R>(a: WritableAtom<V, A, R>) {
	const [_, setFunc] = useAtom(a);
	return setFunc;
}

The atom type was copied straight from Jotai’s TypeScript definitions, so the type inference works seamlessly. The example now can be written as:

const setVal = useWriteAtom(myValAtom);

Saturday, July 29, 2023

Delegating Display/Debug trait implementations

Suppose you want implement Display and Debug traits for your struct, but the output is the same. Instead of copy & paste the implementation, you can delegate the implementation by casting the Self type to the trait type:

impl std::fmt::Debug for MyStruct {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		write!(f, "MyStruct");
	}
}

impl std::fmt::Display for HRESULT {
	fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
		<Self as std::fmt::Debug>::fmt(self, f) // delegate
	}
}

As a reminder, the Self casting can be useful in other situations.

Monday, June 26, 2023

A generic hook for useState and Immer

While prospecting the use of useState with the great Immer, I ended up finding the small use-immer package, which unifies both things. However, it doesn’t have proper TypeScript typings.

So I wrote my own hook to perform this task: just like useState, it returns a value and a setter, but the setter’s argument is a mutable state, automatically wired to produce, and all that using a generic argument for proper typing:

import {useCallback, useState} from 'react';
import {Draft, produce} from 'immer';

export function useStateImmer<T>(initialState: T | (() => T)): [
	T,
	(updater: (draft: Draft<T>) => void) => void,
] {
	const [obj, setObj] = useState(initialState);
	const setObjProduce = useCallback((updater: (draft: Draft<T>) => void): void => {
		setObj(curState => {
			return produce(curState, draft => {
				updater(draft);
			});
		});
	}, [setObj]);
	return [obj, setObjProduce];
}

Note that the returned setter function is identity, since it’s guarded by an useCallback call.