Skip to content

Instantly share code, notes, and snippets.

@adnanalbeda
Last active August 3, 2023 19:48
Show Gist options
  • Save adnanalbeda/736fb089e9d5db0c15d8d4380daf02cf to your computer and use it in GitHub Desktop.
Save adnanalbeda/736fb089e9d5db0c15d8d4380daf02cf to your computer and use it in GitHub Desktop.
React Utility Components
import { Component, ReactNode } from "react";
interface ErrorBoundaryProps {
errorChildren?: JSX.Element | ReactNode;
children?: JSX.Element | ReactNode;
reloadOnError?: boolean;
}
class ErrorBoundary extends Component<
ErrorBoundaryProps,
{ hasError: boolean }
> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: unknown) {
console.error(error); // Update state so the next render will show the fallback UI.
return { hasError: true };
}
// You can also log the error to an error reporting service here
componentDidCatch(error: unknown, errorInfo: unknown) {
console.error(error);
console.error(errorInfo);
}
render() {
if (this.state.hasError) {
if (this.props.reloadOnError) window.location.reload();
return (
this.props.errorChildren ?? (
<span>
An error occurred causing failure to load this component.
<br />
Check console for more details.
</span>
)
);
}
return this.props.children;
}
}
export default ErrorBoundary;
import { Fragment, useMemo } from "react";
type Nullable = null | undefined;
type AllowNullable<T, TNullable extends boolean> = TNullable extends true
? T
: T extends Nullable
? never
: T;
interface ForProps<T, TNullable extends boolean> {
each?: T[] | null;
keys: (of: AllowNullable<T, TNullable>, i: number) => string;
allowNullables?: TNullable;
children: (
prop: AllowNullable<T, TNullable>,
i: number
) => JSX.Element | JSX.Element[];
}
export function For<T, TNullable extends boolean = false>({
each,
keys,
allowNullables,
children,
}: ForProps<T, TNullable>) {
const items = useMemo<AllowNullable<T, TNullable>[]>(
() =>
((allowNullables ? each : each?.filter((x) => x != null)) ??
[]) as AllowNullable<T, TNullable>[],
[each, allowNullables]
);
if (!items.length) return null;
return (
<>
{items.map((x, i) => (
<Fragment key={keys(x, i)}>{children(x, i)}</Fragment>
))}
</>
);
}
export type XNode = JSX.Element | string | number | boolean | null | undefined;
export type XChild = XNode;
export type XC = XChild;
export type XCs = XChild[] | XChild;
export type ChildrenBuilder<T> = (options: T) => XC;
export type CB<T> = ChildrenBuilder<T>;
export type XC_CB<T> = ChildrenBuilder<T> | XCs;
export function buildChildren<T>(options: T, children?: XC_CB<T>) {
if (!children) return null;
if (typeof children === "function") return children(options);
return children;
}
export function BuildChildren<T>(props: { children?: XC_CB<T>; options: T }) {
return <>{buildChildren(props.options, props.children)}</>;
}
import { JSX, ReactNode } from "react";
// X: Static Return
// F: Functional Return
// define: MyComponent(props:FChildren<CustomProps>)
// return: return children(customProps)
// call: <MyComponent>{(props)=><>..rest..</>}</MyComponent>
// D: Dynamic Return (by 'as')
// define: MyComponent({as: As,...restProps}:DChildren<ElementsTags>)
// OR
// MyComponent({as: As,...restProps}:DChildren<"h1" | "h2">)
// return: return <As {...restProps} />;
// call: <MyComponent as="h1">..rest..</MyComponent>
// FD: Dynamic or Functional Dynamic Return (Same as D, but allow passing custom props)
// return: if (typeof As === "string") return <As {...restProps} />;
// return <As {...restProps} {...customProps} />;
// call: <MyComponent as={({children})=><>{children}</>}>..rest..</MyComponent>
// XF: Static, and optionally Functional Return
// return: return typeof children === "function" ? children(customProps) : children;
// XD: Static, and optionally Dynamic Return :: if (as) {/*do the D above*/}
// XFD: Static, and optionally Functional Dynamic Return :: if (as) {/*do the FD above*/}
export type ElementsTags = keyof JSX.IntrinsicElements;
export type XChildren<Props = Record<string, unknown>> = {
children?: ReactNode;
} & Props;
export type XElement<
T extends ElementsTags,
OM extends keyof JSX.IntrinsicElements[T] = never,
EXT = Record<string, unknown>
> = Omit<JSX.IntrinsicElements[T], OM> & EXT;
export type DElement<
T extends ElementsTags = ElementsTags,
OM extends keyof JSX.IntrinsicElements[T] = never,
EXT = Record<string, unknown>
> = { as: T } & Omit<JSX.IntrinsicElements[T], OM> & EXT;
export type FChildren<Param, Props = Record<string, unknown>> = {
children?: (value: Param) => ReactNode;
} & Props;
export type FDElement<
T extends ElementsTags = ElementsTags,
OM extends keyof JSX.IntrinsicElements[T] = never,
EXT = Record<string, unknown>
> = { as: T | FChildren<XElement<T, OM, EXT>> } & Omit<
JSX.IntrinsicElements[T],
OM
> &
EXT;
export type XFChildren<Param, Props = Record<string, unknown>> = {
children?: ((value: Param) => ReactNode) | ReactNode;
} & Props;
export type XDElement<
T extends ElementsTags = ElementsTags,
OM extends keyof JSX.IntrinsicElements[T] = never,
EXT = Record<string, unknown>
> = { as?: T } & Omit<JSX.IntrinsicElements[T], OM> & EXT;
export type XFDElement<
T extends ElementsTags = ElementsTags,
OM extends keyof JSX.IntrinsicElements[T] = never,
EXT = Record<string, unknown>
> = { as?: T | FChildren<XElement<T, OM, EXT>> } & Omit<
JSX.IntrinsicElements[T],
OM
> &
EXT;
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
type PortalProps = {
id?: string;
as?: keyof HTMLElementTagNameMap;
children: React.ReactNode | React.ReactNode[];
className?: string;
style?: Partial<CSSStyleDeclaration>;
};
function Portal({ id, as, children, className, style }: PortalProps) {
const [portal, setPortal] = useState<HTMLElement>();
useEffect(() => {
// create portal element
const el = document.createElement(as ?? "div");
// apply common attributes
if (id) el.id = id;
if (className) el.className = className;
if (style) {
for (const k in style) {
if (style[k]) el.style[k] = style[k]!;
}
}
// append element to body
document.body.appendChild(el);
// set portal element forcing a rerender to Portal Element so it shows the content
setPortal(el);
// remove on unmount
return () => {
el.remove();
};
}, [id, className, style]);
if (!portal) return null;
return createPortal(children, portal);
}
export default Portal;
import { useEffect } from "react";
type UseEffectProps = {
effect: () => void | (() => void);
} & (
| {
deps: unknown[];
infinite?: undefined;
}
| {
deps?: undefined;
infinite: true;
}
);
export default function UseEffect({ deps, effect }: UseEffectProps) {
useEffect(effect, deps);
return null;
}
import {
Dispatch,
SetStateAction,
useCallback,
useEffect,
useMemo,
useState,
} from "react";
import { BuildChildren, XC_CB } from "../../utils/JSXChildren";
type ChildrenAccessor<T> = {
state: [T, Dispatch<SetStateAction<T>>];
get: <K extends keyof T>(prop: K) => T[K];
set: <K extends keyof T>(prop: K, value: T[K]) => void;
};
type UseObjectStateProps<T extends Object | Record<string | number, unknown>> =
{
initialValue?: T;
reinitialize?: boolean;
children?: XC_CB<ChildrenAccessor<T>>;
};
export default function UseObjectState<
T extends Object | Record<string | number, unknown> = any
>({ initialValue, reinitialize, children }: UseObjectStateProps<T>) {
const [getter, setter] = useState<T>(initialValue ?? ({} as T));
const get = useCallback(
<K extends keyof T>(prop: K): T[K] => getter[prop],
[getter]
);
const set = useCallback(<K extends keyof T>(prop: K, value: T[K]) => {
setter((prev) => ({ ...prev, [prop]: value }));
}, []);
useEffect(() => {
if (reinitialize) setter(initialValue as T);
}, [initialValue, reinitialize]);
return (
<BuildChildren options={{ state: [getter, setter], get, set }}>
{children}
</BuildChildren>
);
}
import { useState, useRef } from "react";
type ProxyRecord = Record<string | symbol, unknown>;
type ProxyType<T extends ProxyRecord> = T & {
__isProxy: boolean;
__reinitialize: (value: T) => void;
};
const reactProxyHandler = <T extends ProxyRecord>(
render: () => void
): ProxyHandler<T> => ({
get: (target, key: keyof T) => {
if (key === "__isProxy") return true;
if (key === "__reinitialize")
return (value: T) => {
Object.keys(target).forEach((key) => delete target[key]);
Object.assign(target, value);
render();
};
const prop = target[key];
// return if property is not found, null or undefined
if (prop == null) return prop;
// set value as proxy if object
if (
typeof prop === "object" &&
!(prop as unknown as ProxyType<T>).__isProxy
) {
return (target[key] = new Proxy(
prop,
reactProxyHandler(render) as never
));
}
return prop;
},
set: (target, key, value) => {
// todo : call callback
target[key as keyof typeof target] = value;
render();
return true;
},
});
export default function useProxyState<T extends ProxyRecord>(
initValue: T
): ProxyType<T> {
const [, __] = useState(false);
const render = useRef(() => __((v) => !v)).current;
return useRef(new Proxy(initValue, reactProxyHandler(render)))
.current as ProxyType<T>;
}
import { Dispatch, SetStateAction, useEffect, useState } from "react";
import { BuildChildren, XC_CB } from "../../utils/JSXChildren";
type UseStateProps<T> = {
initialValue?: T;
reinitialize?: boolean;
children?: XC_CB<[T | undefined, Dispatch<SetStateAction<T | undefined>>]>;
};
export default function UseState<T>({
initialValue,
reinitialize,
children,
}: UseStateProps<T>) {
const [getter, setter] = useState(initialValue);
useEffect(() => {
if (reinitialize) setter(initialValue);
}, [initialValue, reinitialize]);
return <BuildChildren options={[getter, setter]}>{children}</BuildChildren>;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment