Better handle falsy values in Select & GraphQL cells

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-02 10:47:40 +04:00
parent 57e267afe0
commit 519dd716b3
5 changed files with 17 additions and 13 deletions

View File

@@ -24,7 +24,7 @@ type Props<Q extends OperationType, T> = {
| { defaultValue: T[]; multiple: true } | { defaultValue: T[]; multiple: true }
); );
export function GraphQLCell<Q extends OperationType, T>(props: Props<Q, T>) { export function GraphQLCell<Q extends OperationType, T extends NonNullable<unknown>>(props: Props<Q, T>) {
const [value, setValue, valueRef] = useStateWithRef<T | T[] | undefined>( const [value, setValue, valueRef] = useStateWithRef<T | T[] | undefined>(
props.defaultValue, props.defaultValue,
); );

View File

@@ -1,9 +1,9 @@
import type { GraphQLTaggedNode } from "relay-runtime"; import type { GraphQLTaggedNode } from "relay-runtime";
import { useMutation } from "react-relay"; import { useMutation } from "react-relay";
export type MutationFieldUpdate<T> = ( export type MutationFieldUpdate<T extends Record<string, unknown>, TKey extends keyof T> = (
field: keyof T, field: TKey,
value: T[typeof field], value: T[TKey],
) => void; ) => void;
/** /**

View File

@@ -4,14 +4,18 @@ import { useCallback, useRef, useState } from "react";
* A useState hook that also returns a ref to the current state (usable in callbacks) * A useState hook that also returns a ref to the current state (usable in callbacks)
*/ */
export function useStateWithRef<T>(initialValue: T) { export function useStateWithRef<T>(initialValue: T) {
const [state, setState] = useState(initialValue); const [state, setState] = useState<T>(initialValue);
const ref = useRef(state); const ref = useRef(state);
return [ return [
state, state,
useCallback((v: T) => { useCallback((v: T | ((prevState: T) => T)) => {
setState(v); setState(prev => {
ref.current = v; const nextState = typeof v === "function"
? (v as (prevState: T) => T)(prev) : v;
ref.current = nextState;
return nextState;
});
}, []), }, []),
ref, ref,
] as const; ] as const;

View File

@@ -27,13 +27,13 @@ export const selectCell = tv({
}, },
}); });
export function SelectCell<T>(props: Props<T>) { export function SelectCell<T extends NonNullable<unknown>>(props: Props<T>) {
const [value, setValue, valueRef] = useStateWithRef<T | T[]>( const [value, setValue, valueRef] = useStateWithRef<T | T[]>(
props.defaultValue, props.defaultValue,
); );
const cellRef = useEditableCellRef(); const cellRef = useEditableCellRef();
const { __ } = useTranslate(); const { __ } = useTranslate();
const filteredValue = Array.isArray(value) ? value.filter(Boolean) : value ? [value] : []; const filteredValue = Array.isArray(value) ? value.filter(v => v !== undefined) : value ? [value] : [];
const usedKeys = new Set<string>(filteredValue.map(getKey).filter(Boolean) as string[]); const usedKeys = new Set<string>(filteredValue.map(getKey).filter(Boolean) as string[]);
const { onUpdate } = useEditableRowContext(); const { onUpdate } = useEditableRowContext();
@@ -103,12 +103,12 @@ export function SelectCell<T>(props: Props<T>) {
); );
} }
export function SelectValue<T>(props: { export function SelectValue<T extends NonNullable<unknown>>(props: {
itemRenderer: Props<T>["itemRenderer"]; itemRenderer: Props<T>["itemRenderer"];
onValueChange?: (value: T | T[]) => void; onValueChange?: (value: T | T[]) => void;
value: T | T[] | undefined; value: T | T[] | undefined;
}) { }) {
if (!props.value) { if (props.value === undefined) {
return ""; return "";
} }
if (!Array.isArray(props.value)) { if (!Array.isArray(props.value)) {

View File

@@ -24,7 +24,7 @@ export function TextCell(props: Props) {
if (props.required && inputValue === "") { if (props.required && inputValue === "") {
return; return;
} }
if (inputValue !== props.defaultValue) { if (inputValue !== value) {
setValue(inputValue); setValue(inputValue);
onUpdate(props.name, inputValue); onUpdate(props.name, inputValue);
} }