I've been working on an approach to selective context listening/dispatching, without using any 3rd party libraries. I think solution is working now but I wondered if any more experienced React developers had any thoughts? This is the main framework of the implementation. I have versions of it at the root of my app, for numbers/strings/string arrays. It's a handy way to bootstrap shared state when I'm building new UI sections, but still hinders performance in certain situations where there is a heavy update overhead (e.g. rending D3 graphs).
Manager:
import { MutableRefObject, useEffect, useReducer, useRef } from 'react';
export interface UpdateRefInterface<T> {
[key: string]: SelectiveListeners<T>;
}
export interface SelectiveListeners<T> {
[key: string]: (update: T) => void;
}
export interface LatestValueRef<T> {
[key: string]: T;
}
export interface UpdateAction<T> {
contextKey: string;
value: T;
}
export function useSelectiveContextManager<T>(
initialContext: LatestValueRef<T>
) {
const triggerUpdateRef = useRef({} as UpdateRefInterface<T>);
const latestValueRef = useRef(initialContext);
const dispatch = (action: UpdateAction<T>) => {
const { contextKey, value } = action;
const currentElement = latestValueRef.current[contextKey];
const listeners = triggerUpdateRef.current[contextKey];
if (!listeners) {
console.log('about to log an error:', listeners);
throw new Error(
`No listeners found for this context: ${contextKey} with value ${value}`
);
}
if (currentElement !== value) {
try {
Object.values(listeners).forEach((l) => {
l(value);
});
} catch (e) {
console.error(e);
}
latestValueRef.current[contextKey] = value;
// }
}
};
return { dispatch, triggerUpdateRef, contextRef: latestValueRef };
}
Listener:
export function useSelectiveContextListener<T>(
contextKey: string,
listenerKey: string,
fallBackValue: T,
updateRefContext: React.Context<
React.MutableRefObject<UpdateRefInterface<T>>
>,
latestValueRefContext: React.Context<
React.MutableRefObject<LatestValueRef<T>>
>
) {
const updateTriggers = useContext(updateRefContext);
const latestRef = useContext(latestValueRefContext);
let currentValue: LatestValueRef<T> | undefined = undefined;
try {
currentValue = latestRef.current;
} catch (e) {
console.error(e);
console.log(
'With: ',
contextKey,
listenerKey,
updateRefContext,
latestValueRefContext,
fallBackValue
);
}
const initialValue =
currentValue === undefined ||
currentValue === null ||
currentValue[contextKey] === undefined
? fallBackValue
: latestRef.current[contextKey];
const [currentState, setCurrentState] = useState<T>(initialValue);
let currentListeners = updateTriggers.current[contextKey];
if (currentListeners === undefined) {
updateTriggers.current[contextKey] = {};
currentListeners = updateTriggers.current[contextKey];
currentListeners[listenerKey] = setCurrentState;
}
useEffect(() => {
currentListeners[listenerKey] = setCurrentState;
setCurrentState(() => latestRef.current[contextKey]);
return () => {
if (currentListeners) {
delete currentListeners[listenerKey];
}
};
}, [currentListeners, contextKey, listenerKey, latestRef]);
return { currentState, latestRef, setCurrentState, updateTriggers };
}
Dispatcher:
export function useSelectiveContextDispatch<T>(
contextKey: string,
listenerKey: string,
initialValue: T,
UpdateTriggerRefContext: React.Context<
MutableRefObject<UpdateRefInterface<T>>
>,
dispatchUpdateContext: React.Context<(value: UpdateAction<T>) => void>,
latestValueRefContext: React.Context<MutableRefObject<LatestValueRef<T>>>
) {
const { currentState, latestRef, updateTriggers } =
useSelectiveContextListener(
contextKey,
listenerKey,
initialValue,
UpdateTriggerRefContext,
latestValueRefContext
);
const dispatchUpdate = useContext(dispatchUpdateContext);
const [isInitialized, setIsInitialized] = useState(false);
useEffect(() => {
if (latestRef.current[contextKey] === undefined) {
latestRef.current[contextKey] = initialValue;
}
}, [latestRef, initialValue, contextKey]);
useEffect(() => {
if (!isInitialized) {
dispatchUpdate({ contextKey: contextKey, value: initialValue });
if (currentState === initialValue) {
setIsInitialized(true);
} else {
}
}
}, [currentState, isInitialized, contextKey, initialValue, dispatchUpdate]);
return { currentState, dispatchUpdate };
}
type here