React frontend patterns for Opik. Use when working in apps/opik-frontend, on components, state, or data fetching.
// β BAD
useEffect(() => {
fetch('/api/data').then(setData);
}, []);
// β
GOOD
const { data } = useQuery({
queryKey: ['data'],
queryFn: fetchData,
});
// β
USE useMemo for: complex computations, large data transforms
const filtered = useMemo(() =>
data.filter(x => x.status === 'active').map(transform),
[data]
);
// β
USE useCallback for: functions passed to children
const handleClick = useCallback(() => doSomething(id), [id]);
// β DON'T memoize: simple values, primitives, local functions
const name = data?.name ?? ''; // No useMemo needed
// β
GOOD - specific selector
const selectedEntity = useEntityStore(state => state.selectedEntity);
// β BAD - selecting entire store causes re-renders
const { selectedEntity, filters } = useEntityStore();
Many users auto-translate the page; the translator wraps text nodes in <font> elements, so React throws NotFoundError: removeChild when it reconciles a bare dynamic text node it re-parented. Wrap dynamic/conditional strings in their own element instead of rendering bare text.
// β bare dynamic text β crash under translation
<button>{icon}{label}</button>
// β
wrap it β React swaps a stable element, stays translatable
<button>{icon}<span>{label}</span></button>
For timer-driven text (typewriter/counter), also avoid per-tick setState β write into a ref'd node's textContent (React never reconciles it), or mark a decorative node translate="no". Ref: facebook/react#11538 (OPIK-7428, OPIK-7435).
ui β shared (one-way only)
ui β shared β v1/pages-shared β v1/pages (one-way only)
ui β shared β v2/pages-shared β v2/pages (one-way only)
src/components/ is BLOCKED (old structure, no longer exists)npm run deps:validateshowProjectSelector={true} not isV2={true})const Component: React.FC<Props> = ({ prop }) => {
// 1. State hooks
// 2. Queries/mutations
// 3. Memoization (only when needed)
// 4. Event handlers
if (isLoading) return <Loader />;
if (error) return <ErrorComponent />;
return <div>...</div>;
};
// Query with params
const { data } = useQuery({
queryKey: [ENTITY_KEY, params],
queryFn: (context) => fetchEntity(context, params),
});
// Mutation with invalidation
const mutation = useMutation({
mutationFn: updateEntity,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: [ENTITY_KEY] });
},
});
usePermissions() guard guidance for UI actions