addons/isl/src/third-party/jotai-devtools/utils/useAtomDevtools.tsblame
View source
b69ab311import { useAtom } from 'jotai/react';
b69ab312import type { Atom, WritableAtom } from 'jotai/vanilla';
b69ab313import { useEffect, useRef } from 'react';
b69ab314import {
b69ab315 Connection,
b69ab316 createReduxConnection,
b69ab317} from './redux-extension/createReduxConnection';
b69ab318import { getReduxExtension } from './redux-extension/getReduxExtension';
b69ab319
b69ab3110type DevtoolOptions = Parameters<typeof useAtom>[1] & {
b69ab3111 name?: string;
b69ab3112 enabled?: boolean;
b69ab3113};
b69ab3114
b69ab3115export function useAtomDevtools<Value, Result>(
b69ab3116 anAtom: WritableAtom<Value, [Value], Result> | Atom<Value>,
b69ab3117 options?: DevtoolOptions,
b69ab3118): void {
b69ab3119 const { enabled, name } = options || {};
b69ab3120
b69ab3121 const extension = getReduxExtension(enabled);
b69ab3122
b69ab3123 const [value, setValue] = useAtom(anAtom, options);
b69ab3124
b69ab3125 const lastValue = useRef(value);
b69ab3126 const isTimeTraveling = useRef(false);
b69ab3127 const devtools = useRef<Connection>();
b69ab3128
b69ab3129 const atomName = name || anAtom.debugLabel || anAtom.toString();
b69ab3130
b69ab3131 useEffect(() => {
b69ab3132 if (!extension) {
b69ab3133 return;
b69ab3134 }
b69ab3135 const setValueIfWritable = (value: Value) => {
b69ab3136 if (typeof setValue === 'function') {
b69ab3137 (setValue as (value: Value) => void)(value);
b69ab3138 return;
b69ab3139 }
b69ab3140 console.warn(
b69ab3141 '[Warn] you cannot do write operations (Time-travelling, etc) in read-only atoms\n',
b69ab3142 anAtom,
b69ab3143 );
b69ab3144 };
b69ab3145
b69ab3146 devtools.current = createReduxConnection(extension, atomName);
b69ab3147
b69ab3148 const unsubscribe = devtools.current?.subscribe((message) => {
b69ab3149 if (message.type === 'ACTION' && message.payload) {
b69ab3150 try {
b69ab3151 setValueIfWritable(JSON.parse(message.payload));
b69ab3152 } catch (e) {
b69ab3153 console.error(
b69ab3154 'please dispatch a serializable value that JSON.parse() support\n',
b69ab3155 e,
b69ab3156 );
b69ab3157 }
b69ab3158 } else if (message.type === 'DISPATCH' && message.state) {
b69ab3159 if (
b69ab3160 message.payload?.type === 'JUMP_TO_ACTION' ||
b69ab3161 message.payload?.type === 'JUMP_TO_STATE'
b69ab3162 ) {
b69ab3163 isTimeTraveling.current = true;
b69ab3164
b69ab3165 setValueIfWritable(JSON.parse(message.state));
b69ab3166 }
b69ab3167 } else if (
b69ab3168 message.type === 'DISPATCH' &&
b69ab3169 message.payload?.type === 'COMMIT'
b69ab3170 ) {
b69ab3171 devtools.current?.init(lastValue.current);
b69ab3172 } else if (
b69ab3173 message.type === 'DISPATCH' &&
b69ab3174 message.payload?.type === 'IMPORT_STATE'
b69ab3175 ) {
b69ab3176 const computedStates =
b69ab3177 message.payload.nextLiftedState?.computedStates || [];
b69ab3178
b69ab3179 computedStates.forEach(({ state }: { state: Value }, index: number) => {
b69ab3180 if (index === 0) {
b69ab3181 devtools.current?.init(state);
b69ab3182 } else {
b69ab3183 setValueIfWritable(state);
b69ab3184 }
b69ab3185 });
b69ab3186 }
b69ab3187 });
b69ab3188
b69ab3189 return unsubscribe;
b69ab3190 }, [anAtom, extension, atomName, setValue]);
b69ab3191
b69ab3192 useEffect(() => {
b69ab3193 if (!devtools.current) {
b69ab3194 return;
b69ab3195 }
b69ab3196 lastValue.current = value;
b69ab3197 if (devtools.current.shouldInit) {
b69ab3198 devtools.current.init(value);
b69ab3199 devtools.current.shouldInit = false;
b69ab31100 } else if (isTimeTraveling.current) {
b69ab31101 isTimeTraveling.current = false;
b69ab31102 } else {
b69ab31103 devtools.current.send(
b69ab31104 `${atomName} - ${new Date().toLocaleString()}` as any,
b69ab31105 value,
b69ab31106 );
b69ab31107 }
b69ab31108 }, [anAtom, extension, atomName, value]);
b69ab31109}