1
This commit is contained in:
+8
@@ -0,0 +1,8 @@
|
||||
type Updater<T> = (updater: T | ((origin: T) => T)) => void;
|
||||
/**
|
||||
* Similar to `useState` but will use props value if provided.
|
||||
* From React 18, we do not need safe `useState` since it will not throw for unmounted update.
|
||||
* This hooks remove the `onChange` & `postState` logic since we only need basic merged state logic.
|
||||
*/
|
||||
export default function useControlledState<T>(defaultStateValue: T | (() => T), value?: T): [T, Updater<T>];
|
||||
export {};
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { useState } from 'react';
|
||||
import useLayoutEffect from "./useLayoutEffect";
|
||||
/**
|
||||
* Similar to `useState` but will use props value if provided.
|
||||
* From React 18, we do not need safe `useState` since it will not throw for unmounted update.
|
||||
* This hooks remove the `onChange` & `postState` logic since we only need basic merged state logic.
|
||||
*/
|
||||
export default function useControlledState(defaultStateValue, value) {
|
||||
const [innerValue, setInnerValue] = useState(defaultStateValue);
|
||||
const mergedValue = value !== undefined ? value : innerValue;
|
||||
useLayoutEffect(mount => {
|
||||
if (!mount) {
|
||||
setInnerValue(value);
|
||||
}
|
||||
}, [value]);
|
||||
return [
|
||||
// Value
|
||||
mergedValue,
|
||||
// Update function
|
||||
setInnerValue];
|
||||
}
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
/** As `React.useEffect` but pass origin value in callback and not need care deps length change. */
|
||||
declare function useEffect(callback: (prevDeps: any[]) => void, deps: any[]): void;
|
||||
export default useEffect;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
|
||||
/** As `React.useEffect` but pass origin value in callback and not need care deps length change. */
|
||||
function useEffect(callback, deps) {
|
||||
const prevRef = React.useRef(deps);
|
||||
React.useEffect(() => {
|
||||
if (deps.length !== prevRef.current.length || deps.some((dep, index) => dep !== prevRef.current[index])) {
|
||||
callback(prevRef.current);
|
||||
}
|
||||
prevRef.current = deps;
|
||||
});
|
||||
}
|
||||
export default useEffect;
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
declare const useEvent: <T extends (...args: any[]) => any>(callback: T) => undefined extends T ? (...args: Parameters<NonNullable<T>>) => ReturnType<NonNullable<T>> | undefined : T;
|
||||
export default useEvent;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import * as React from 'react';
|
||||
const useEvent = callback => {
|
||||
const fnRef = React.useRef(callback);
|
||||
fnRef.current = callback;
|
||||
const memoFn = React.useCallback((...args) => fnRef.current?.(...args), []);
|
||||
return memoFn;
|
||||
};
|
||||
export default useEvent;
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
/** @private Note only worked in develop env. Not work in production. */
|
||||
export declare function resetUuid(): void;
|
||||
/**
|
||||
* Generate a valid HTML id from prefix and key.
|
||||
* Sanitizes the key by replacing invalid characters with hyphens.
|
||||
* @param prefix - The prefix for the id
|
||||
* @param key - The key from React element, may contain spaces or invalid characters
|
||||
* @returns A valid HTML id string
|
||||
*/
|
||||
export declare function getId(prefix: string, key: React.Key): string;
|
||||
declare const _default: (id?: string) => string;
|
||||
export default _default;
|
||||
+73
@@ -0,0 +1,73 @@
|
||||
import * as React from 'react';
|
||||
function getUseId() {
|
||||
// We need fully clone React function here to avoid webpack warning React 17 do not export `useId`
|
||||
const fullClone = {
|
||||
...React
|
||||
};
|
||||
return fullClone.useId;
|
||||
}
|
||||
let uuid = 0;
|
||||
|
||||
/** @private Note only worked in develop env. Not work in production. */
|
||||
export function resetUuid() {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
uuid = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a valid HTML id from prefix and key.
|
||||
* Sanitizes the key by replacing invalid characters with hyphens.
|
||||
* @param prefix - The prefix for the id
|
||||
* @param key - The key from React element, may contain spaces or invalid characters
|
||||
* @returns A valid HTML id string
|
||||
*/
|
||||
export function getId(prefix, key) {
|
||||
// React.Key can be string | number, convert to string first
|
||||
const keyStr = String(key);
|
||||
|
||||
// Valid id characters: letters, digits, hyphen, underscore, colon, period
|
||||
// Replace all invalid characters (including spaces) with hyphens to preserve length
|
||||
const sanitizedKey = keyStr.replace(/[^a-zA-Z0-9_.:-]/g, '-');
|
||||
return `${prefix}-${sanitizedKey}`;
|
||||
}
|
||||
const useOriginId = getUseId();
|
||||
export default useOriginId ?
|
||||
// Use React `useId`
|
||||
function useId(id) {
|
||||
const reactId = useOriginId();
|
||||
|
||||
// Developer passed id is single source of truth
|
||||
if (id) {
|
||||
return id;
|
||||
}
|
||||
|
||||
// Test env always return mock id
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return 'test-id';
|
||||
}
|
||||
return reactId;
|
||||
} :
|
||||
// Use compatible of `useId`
|
||||
function useCompatId(id) {
|
||||
// Inner id for accessibility usage. Only work in client side
|
||||
const [innerId, setInnerId] = React.useState('ssr-id');
|
||||
React.useEffect(() => {
|
||||
const nextId = uuid;
|
||||
uuid += 1;
|
||||
setInnerId(`rc_unique_${nextId}`);
|
||||
}, []);
|
||||
|
||||
// Developer passed id is single source of truth
|
||||
if (id) {
|
||||
return id;
|
||||
}
|
||||
|
||||
// Test env always return mock id
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return 'test-id';
|
||||
}
|
||||
|
||||
// Return react native id or inner id
|
||||
return innerId;
|
||||
};
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
import * as React from 'react';
|
||||
declare const useLayoutEffect: (callback: (mount: boolean) => void | VoidFunction, deps?: React.DependencyList) => void;
|
||||
export declare const useLayoutUpdateEffect: typeof React.useEffect;
|
||||
export default useLayoutEffect;
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
import * as React from 'react';
|
||||
import canUseDom from "../Dom/canUseDom";
|
||||
|
||||
/**
|
||||
* Wrap `React.useLayoutEffect` which will not throw warning message in test env
|
||||
*/
|
||||
const useInternalLayoutEffect = process.env.NODE_ENV !== 'test' && canUseDom() ? React.useLayoutEffect : React.useEffect;
|
||||
const useLayoutEffect = (callback, deps) => {
|
||||
const firstMountRef = React.useRef(true);
|
||||
useInternalLayoutEffect(() => {
|
||||
return callback(firstMountRef.current);
|
||||
}, deps);
|
||||
|
||||
// We tell react that first mount has passed
|
||||
useInternalLayoutEffect(() => {
|
||||
firstMountRef.current = false;
|
||||
return () => {
|
||||
firstMountRef.current = true;
|
||||
};
|
||||
}, []);
|
||||
};
|
||||
export const useLayoutUpdateEffect = (callback, deps) => {
|
||||
useLayoutEffect(firstMount => {
|
||||
if (!firstMount) {
|
||||
return callback();
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, deps);
|
||||
};
|
||||
export default useLayoutEffect;
|
||||
+1
@@ -0,0 +1 @@
|
||||
export default function useMemo<Value, Condition = any[]>(getValue: () => Value, condition: Condition, shouldUpdate: (prev: Condition, next: Condition) => boolean): Value;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
import * as React from 'react';
|
||||
export default function useMemo(getValue, condition, shouldUpdate) {
|
||||
const cacheRef = React.useRef({});
|
||||
if (!('value' in cacheRef.current) || shouldUpdate(cacheRef.current.condition, condition)) {
|
||||
cacheRef.current.value = getValue();
|
||||
cacheRef.current.condition = condition;
|
||||
}
|
||||
return cacheRef.current.value;
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
type Updater<T> = (updater: T | ((origin: T) => T), ignoreDestroy?: boolean) => void;
|
||||
/**
|
||||
* @deprecated Please use `useControlledState` instead if not need support < React 18.
|
||||
* Similar to `useState` but will use props value if provided.
|
||||
* Note that internal use rc-util `useState` hook.
|
||||
*/
|
||||
export default function useMergedState<T, R = T>(defaultStateValue: T | (() => T), option?: {
|
||||
defaultValue?: T | (() => T);
|
||||
value?: T;
|
||||
onChange?: (value: T, prevValue: T) => void;
|
||||
postState?: (value: T) => T;
|
||||
}): [R, Updater<T>];
|
||||
export {};
|
||||
+58
@@ -0,0 +1,58 @@
|
||||
import useEvent from "./useEvent";
|
||||
import { useLayoutUpdateEffect } from "./useLayoutEffect";
|
||||
import useState from "./useState";
|
||||
/** We only think `undefined` is empty */
|
||||
function hasValue(value) {
|
||||
return value !== undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Please use `useControlledState` instead if not need support < React 18.
|
||||
* Similar to `useState` but will use props value if provided.
|
||||
* Note that internal use rc-util `useState` hook.
|
||||
*/
|
||||
export default function useMergedState(defaultStateValue, option) {
|
||||
const {
|
||||
defaultValue,
|
||||
value,
|
||||
onChange,
|
||||
postState
|
||||
} = option || {};
|
||||
|
||||
// ======================= Init =======================
|
||||
const [innerValue, setInnerValue] = useState(() => {
|
||||
if (hasValue(value)) {
|
||||
return value;
|
||||
} else if (hasValue(defaultValue)) {
|
||||
return typeof defaultValue === 'function' ? defaultValue() : defaultValue;
|
||||
} else {
|
||||
return typeof defaultStateValue === 'function' ? defaultStateValue() : defaultStateValue;
|
||||
}
|
||||
});
|
||||
const mergedValue = value !== undefined ? value : innerValue;
|
||||
const postMergedValue = postState ? postState(mergedValue) : mergedValue;
|
||||
|
||||
// ====================== Change ======================
|
||||
const onChangeFn = useEvent(onChange);
|
||||
const [prevValue, setPrevValue] = useState([mergedValue]);
|
||||
useLayoutUpdateEffect(() => {
|
||||
const prev = prevValue[0];
|
||||
if (innerValue !== prev) {
|
||||
onChangeFn(innerValue, prev);
|
||||
}
|
||||
}, [prevValue]);
|
||||
|
||||
// Sync value back to `undefined` when it from control to un-control
|
||||
useLayoutUpdateEffect(() => {
|
||||
if (!hasValue(value)) {
|
||||
setInnerValue(value);
|
||||
}
|
||||
}, [value]);
|
||||
|
||||
// ====================== Update ======================
|
||||
const triggerChange = useEvent((updater, ignoreDestroy) => {
|
||||
setInnerValue(updater, ignoreDestroy);
|
||||
setPrevValue([mergedValue], ignoreDestroy);
|
||||
});
|
||||
return [postMergedValue, triggerChange];
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
/**
|
||||
* Hook to detect if the user is on a mobile device
|
||||
* Notice that this hook will only detect the device type in effect, so it will always be false in server side
|
||||
*/
|
||||
declare const useMobile: () => boolean;
|
||||
export default useMobile;
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import { useState } from 'react';
|
||||
import isMobile from "../isMobile";
|
||||
import useLayoutEffect from "./useLayoutEffect";
|
||||
|
||||
/**
|
||||
* Hook to detect if the user is on a mobile device
|
||||
* Notice that this hook will only detect the device type in effect, so it will always be false in server side
|
||||
*/
|
||||
const useMobile = () => {
|
||||
const [mobile, setMobile] = useState(false);
|
||||
useLayoutEffect(() => {
|
||||
setMobile(isMobile());
|
||||
}, []);
|
||||
return mobile;
|
||||
};
|
||||
export default useMobile;
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
type Updater<T> = T | ((prevValue: T) => T);
|
||||
export type SetState<T> = (nextValue: Updater<T>,
|
||||
/**
|
||||
* Will not update state when destroyed.
|
||||
* Developer should make sure this is safe to ignore.
|
||||
*/
|
||||
ignoreDestroy?: boolean) => void;
|
||||
/**
|
||||
* Same as React.useState but `setState` accept `ignoreDestroy` param to not to setState after destroyed.
|
||||
* We do not make this auto is to avoid real memory leak.
|
||||
* Developer should confirm it's safe to ignore themselves.
|
||||
*/
|
||||
declare const useSafeState: <T>(defaultValue?: T | (() => T)) => [T, SetState<T>];
|
||||
export default useSafeState;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import * as React from 'react';
|
||||
/**
|
||||
* Same as React.useState but `setState` accept `ignoreDestroy` param to not to setState after destroyed.
|
||||
* We do not make this auto is to avoid real memory leak.
|
||||
* Developer should confirm it's safe to ignore themselves.
|
||||
*/
|
||||
const useSafeState = defaultValue => {
|
||||
const destroyRef = React.useRef(false);
|
||||
const [value, setValue] = React.useState(defaultValue);
|
||||
React.useEffect(() => {
|
||||
destroyRef.current = false;
|
||||
return () => {
|
||||
destroyRef.current = true;
|
||||
};
|
||||
}, []);
|
||||
function safeSetState(updater, ignoreDestroy) {
|
||||
if (ignoreDestroy && destroyRef.current) {
|
||||
return;
|
||||
}
|
||||
setValue(updater);
|
||||
}
|
||||
return [value, safeSetState];
|
||||
};
|
||||
export default useSafeState;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
type Updater<T> = T | ((prevValue: T) => T);
|
||||
export type SetState<T> = (nextValue: Updater<T>) => void;
|
||||
/**
|
||||
* Same as React.useState but will always get latest state.
|
||||
* This is useful when React merge multiple state updates into one.
|
||||
* e.g. onTransitionEnd trigger multiple event at once will be merged state update in React.
|
||||
*/
|
||||
declare function useSyncState<T>(defaultValue?: T): [get: () => T, set: SetState<T>];
|
||||
export default useSyncState;
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
import * as React from 'react';
|
||||
import useEvent from "./useEvent";
|
||||
/**
|
||||
* Same as React.useState but will always get latest state.
|
||||
* This is useful when React merge multiple state updates into one.
|
||||
* e.g. onTransitionEnd trigger multiple event at once will be merged state update in React.
|
||||
*/
|
||||
function useSyncState(defaultValue) {
|
||||
const [, forceUpdate] = React.useReducer(x => x + 1, 0);
|
||||
const currentValueRef = React.useRef(defaultValue);
|
||||
const getValue = useEvent(() => {
|
||||
return currentValueRef.current;
|
||||
});
|
||||
const setValue = useEvent(updater => {
|
||||
currentValueRef.current = typeof updater === 'function' ? updater(currentValueRef.current) : updater;
|
||||
forceUpdate();
|
||||
});
|
||||
return [getValue, setValue];
|
||||
}
|
||||
export default useSyncState;
|
||||
Reference in New Issue
Block a user