I have a component that renders relative time
import React, { useEffect, useRef, useReducer } from 'react'
import { Text, TextProps } from 'react-native';
import { formatDistanceToNow } from 'date-fns';
type MomentTextProps = Omit<TextProps, 'children'> & {
date: Date | number;
pollTimeMs?: number;
}
export function MomentText({
date,
pollTimeMs = 60000,
...props}: MomentTextProps) : JSX.Element {
const [ formatted, update ] = useReducer(
() => formatDistanceToNow(Date.now())
);
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
useEffect(()=> {
timeoutRef.current = setTimeout(update, pollTimeMs);
return () => {
clearTimeout(timeoutRef.current!);
};
});
return <Text {...props}>{formatted}</Text>
}
Which works, but seems overkill that I am creating so many timeouts, I was wondering if there’s a way of doing this via a “React context” but only rerender this one specific child.
The way I am thinking of it (as I type this) is to have a subscribe/notify
hook like this…
/**
* This hook provides a simple subscription semantic to React components.
*/
export function useSubscription<T = unknown>(): SubscriptionManager<T> {
const subscribersRef = useRef<((data: T) => void)[]>([]);
function subscribe(fn: (data: T) => void) {
subscribersRef.current.push(fn);
return () => {
subscribersRef.current = subscribersRef.current.filter(
(subscription) => !Object.is(subscription, fn)
);
};
}
function notify(data: T) {
subscribersRef.current.forEach((fn) => fn(data));
}
function useSubscribeEffect(fn: (data: T) => void) {
useEffect(() => subscribe(fn), []);
}
return { subscribe, notify, useSubscribeEffect };
}
and have a useRef
and useEffect
that will call notify
on the MomentText
component.
Seems quite overkill but not really sure which is worse.
The first approach is easy to understand, but may be a performance hit
The second is a bit more complicated, but the usage is isolated since it’s just a context requirement and everything else can be handled by subscriptions within the component.