import { memo, useMemo, useState } from 'react'; import './App.css'; type Person = { firstName: string; lastName: string; }; type WithInitial = Person & { initials: string; }; const initialPeople: Person[] = [ { firstName: 'John', lastName: 'Lennon' }, { firstName: 'Paul', lastName: 'McCartney' }, { firstName: 'George', lastName: 'Harrison' }, { firstName: 'Ringo', lastName: 'Starr' }, ]; function App() { const [theme, setTheme] = useState('light'); const [people, setPeople] = useState(initialPeople); const [counter, setCounter] = useState(0); return ( <div> <button onClick={() => setCounter((prev) => prev + 1)}> Increase {counter} </button> <button onClick={toggleTheme}>Toggle {theme}</button> <button onClick={addRandomPerson}>Add Random Person</button> <ListComponentNoMemo {...{ theme, people }} /> <ListComponentWithMemo {...{ theme, people }} /> <MemoListComponentWithMemo {...{ theme, people }} /> </div> ); function toggleTheme() { setTheme((prev) => (prev === 'light' ? 'dark' : 'light')); } function addRandomPerson() { setPeople((prev) => [ ...prev, { firstName: Math.ceil(Math.random() * 100).toString(), lastName: Math.ceil(Math.random() * 100).toString(), }, ]); } } function ListComponentNoMemo({ theme, people, }: { theme: string; people: Person[]; }) { console.log('ListComponentNoMemo'); const withInitials = processInitials('noMemo', people); return ( <> <p>Theme is {theme}</p> <ul> {withInitials.map((e, index) => ( <li key={index}> {e.firstName} {e.lastName} {e.initials} </li> ))} </ul> </> ); } function ListComponentWithMemo({ theme, people, }: { theme: string; people: Person[]; }) { console.log('ListComponenWithMemo'); const withInitials = useMemo( () => processInitials('withMemo', people), [people] ); return ( <> <p>Theme is {theme}</p> <ul> {withInitials.map((e, index) => ( <li key={index}> {e.firstName} {e.lastName} {e.initials} </li> ))} </ul> </> ); } const MemoListComponentWithMemo = memo(function ({ theme, people, }: { theme: string; people: Person[]; }) { console.log('MemoListComponenWithMemo'); const withInitials = useMemo( () => processInitials('memoWithMemo', people), [people] ); return ( <> <p>Theme is {theme}</p> <ul> {withInitials.map((e, index) => ( <li key={index}> {e.firstName} {e.lastName} {e.initials} </li> ))} </ul> </> ); }); function processInitials(calledFrom: string, people: Person[]): WithInitial[] { console.log({ calledFrom }); return people.map((e) => ({ firstName: e.firstName, lastName: e.lastName, initials: toInitials(e), })); function toInitials(p: Person) { console.log('emphasized expensiveness'); return p.firstName[0] + p.lastName[0]; } } export default App;Live: https://stackblitz.com/edit/vitejs-vite-drznsqwc?file=src%2FApp.tsx
class Programmer implements ANiceHumble, Person {
Code as we know it. Open source stuff goes here
Thursday, June 26, 2025
React: Memoized unchanged properties
Strongly-typed MUI Form Helper
import React from 'react'; type UnifiedChangeEvent = | React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement> | React.ChangeEvent<{ name?: string; value: unknown }> | (Event & { target: { name: string; value: unknown } }); export type ErrorObject<T> = Partial<Record<keyof T, string | boolean>>; function handleInputChange<T>( values: T, setValues: (o: T) => void, fieldName: keyof T ) { return { onChange(event: UnifiedChangeEvent) { const { name, value } = event.target; setValues({ ...values, [name!]: value }); }, name: fieldName, value: values[fieldName], }; } export function setupInputChange<T>(values: T, setValues: (o: T) => void) { return (fieldName: keyof T) => handleInputChange(values, setValues, fieldName); } function handleErrorChange<T>(errors: T, fieldName: keyof T) { return errors[fieldName] && { error: true, helperText: errors[fieldName] }; } export function setupErrorChange<T>(errors: T) { return (fieldName: keyof T) => handleErrorChange(errors, fieldName); } export function isValid<T>(errors: ErrorObject<T>): boolean { for (const v of Object.values(errors)) { if (v) { return false; } } return true; } export function hasErrors<T>(errors: ErrorObject<T>) { return !isValid(errors); } export function setupInputProps<T, U>( values: T, setValues: (o: T) => void, errors: ErrorObject<U>, setErrors: (o: ErrorObject<U>) => void ) { return { handleInputProps: (fieldName: keyof T & keyof U) => ({ ...handleInputChange(values, setValues, fieldName), ...handleErrorChange(errors, fieldName), }), checkValidators: (errors: ErrorObject<U>) => { setErrors(errors); return isValid(errors); }, }; }Example use:
import { FormEvent, useState } from 'react'; import { Button, TextField, } from '@mui/material'; import './App.css'; import { setupInputProps, type ErrorObject } from './utils/form'; class DonationCandidateDto { donationCandidateId = 0; fullname = ''; mobile = ''; email = ''; age = 0; bloodGroup = ''; address = ''; active = false; } const initialFieldValues: DonationCandidateDto = { donationCandidateId: 0, fullname: '', mobile: '', email: '', age: 0, bloodGroup: '', address: '', active: false, }; type FormType = typeof initialFieldValues; // out-of-band validations type OtherStates = { areZombiesInLab: boolean; day?: number; }; function App() { const [values, setValues] = useState(structuredClone(initialFieldValues)); const [errors, setErrors] = useState<ErrorObject<FormType & OtherStates>>({}); const { handleInputProps, checkValidators } = setupInputProps( values, setValues, errors, setErrors ); return ( <div> <h1>Hello world</h1> <form autoComplete="off" noValidate onSubmit={handleSubmit}> <div> <TextField label="Full name" required={true} {...handleInputProps('fullname')} /> </div> <br /> <div> <TextField label="Age" {...handleInputProps('age')} /> </div> <div> {errors.areZombiesInLab && ( <div style={{ color: 'red' }}>{errors.areZombiesInLab}</div> )} </div> <br /> <div> <Button color="primary" type="submit" variant="contained"> Submit </Button> <Button type="submit" color="inherit" variant="contained"> Reset </Button> </div> </form> <hr /> {/* {JSON.stringify(values)} */} </div> ); function handleSubmit(event: FormEvent<HTMLFormElement>) { event.preventDefault(); const isValid = checkValidators({ fullname: !values.fullname && 'Must have full name', age: !(values.age >= 18) && 'Must be an adult', // areZombiesInLab: true && 'Zombies in lab', }); if (!isValid) { return; } // POST/PUT here } } export default App;If the name is not existing from the DTO, it will not compile:
Monday, June 23, 2025
macOS vs Windows keys
Function | macOS | Windows |
Beginning of Page | Command+Up (Home if non-typing like browser page) | Ctrl+Home |
Ending of Page | Command+Down (End if non-typing like browser page) | Ctrl+End |
Beginning of line on screen | Command+Left (or fn+Left) | Home |
Ending of line on screen | Command+Right (or fn+Right) | End |
Beginning of line | Control+A | Not supported |
Ending of line | Control+E | Not supported |
Page Up | Fn+Up (Page Up if available) (Alt+Up if non-typing like browser page) | Page Up |
Page Down | Fn+Down (Page Down if available) (Alt+Down if non-typing like browser page) | Page Down |
macOS when on typing (e.g., IDE, word processor)
Keys | Function |
Home | Beginning of line on screen |
End | Ending of line on screen |
Alt+Up | VSCode: Move one line up. Word: Go to previous paragraph |
Alt+Down | VSCode: Move one line down. Word: Go to next paragraph |
JetBrains: | Beginning of Page |
JetBrains: | Ending of Page |