-
-
Save a1300/eeb5b3978b444f9819e269263a416d0c to your computer and use it in GitHub Desktop.
To type a generic ReactNode
that guarantees a className
prop, you’ll want to constrain the type to a React element that accepts props — particularly one that includes className
. A plain ReactNode
is too flexible (it can be a string, fragment, boolean, etc.), so you’ll need a more refined approach.
Here’s a pattern that works well:
import React, { ReactElement, ComponentType } from 'react';
type WithClassNameProps = {
className?: string;
};
type Props<T extends ComponentType<WithClassNameProps>> = {
Component: T;
};
// Example usage
const Wrapper = <T extends ComponentType<WithClassNameProps>>({
Component,
}: Props<T>) => {
return <Component className="my-class" />;
};
🔍 What’s happening here:
ComponentType<P>
is a generic for a React component that accepts props of typeP
.- We're restricting it with
WithClassNameProps
so we know for sure it acceptsclassName
. - This ensures your passed-in
Component
(like adiv
,button
, or custom component) will support theclassName
prop at type level.
If you’re passing an actual element (like <div />
) rather than a component reference, you’d use ReactElement<WithClassNameProps>
instead.
Would you like an example with JSX-style children too? Or maybe you want to extend the prop checks even further?
React.cloneElement is a powerful—but often misunderstood—tool in React’s arsenal. It lets you create a new React element based on an existing one, while overriding or adding props and children. Here's why and when you'd use it:
🧩 Why Use React.cloneElement()
- Inject props into children: Useful when a parent component needs to modify or extend its children without changing their original implementation.
- Enhance reusability: You can build flexible, reusable UI components by dynamically customizing child elements.
- Avoid prop drilling: Instead of passing props through multiple layers, cloneElement lets you inject them directly.
📦 Common Use Cases
-
Customizing child components:
const cloned = React.cloneElement(<Button />, { color: 'red' });
This creates a new
<Button />
with thecolor
prop set to'red'
. -
Dynamic rendering in lists:
React.Children.map(children, (child, index) => React.cloneElement(child, { isActive: index === selectedIndex }) );
This is handy for components like tabs or selectable lists.
-
Wrapping components with additional behavior:
For example, adding event handlers or styling to children passed into a container.
⚠️ Caveats
- Can make code harder to trace: Since props are injected dynamically, it’s not always obvious where they come from.
- Not ideal for static typing: TypeScript may struggle with inferred props when using cloneElement.
- Better alternatives often exist: Consider using render props, context, or custom hooks for clearer data flow.
Would you like to see a real-world example or explore alternatives like render props or context?
``
const [selection, setSelection] = useState({ state: {}, items: [] });
const table = useReactTable({
data,
columns,
getRowId: row => row.id,
state: { rowSelection: selection.state },
onRowSelectionChange: updater => {
const newState = updater(selection.state);
const selectedItems = data.filter(item => newState[item.id]);
setSelection({ state: newState, items: selectedItems });
},
});
type OnlyArrays<T> = {
[K in keyof T]: T[K] extends any[] ? K : never
}[keyof T]
type ArrayItemType<T> = T extends (infer U)[] ? U : never;
function access<
T extends object,
K extends OnlyArrays<T>,
A extends (a: ArrayItemType<T[K]>) => boolean
>
(first: T, key: K, func: A): {
added: Array<ArrayItemType<T[K]>>
} {
const added = (first[key] as Array<ArrayItemType<T[K]>>).filter(func)
return {
added,
}
}
access(obj, "addresses", (a) => true);
improvement
function access<
T extends Record<K, any[]>,
K extends keyof T,
A extends (a: ArrayItemType<T[K]>) => boolean
>(
first: T,
key: K,
func: A
): {
added: ArrayItemType<T[K]>[]
} {
const added = first[key].filter(func);
return { added };
}
Great question! To efficiently sync a user's interests (stored as a JS array) with your Drizzle/Postgres backend when they update their interests via a mutation, you should:
1. Data Model
Assume:
persons
table (id, name, ...)interests
table (id, name)person_interests
table (person_id, interest_id) — many-to-many relationship2. Frontend Flow
["music", "coding", "cooking"]
) when they save.3. Backend Mutation Steps
a. Upsert interests
interests
table.insert
withonConflictDoNothing
(or similar) to avoid duplicates.b. Fetch Interest IDs
c. Sync Person's Interests
person_interests
.Sample Drizzle Code
Why this approach?
Tips
If your interests are not a separate table (i.e., you store them as a JSON array), the approach is simpler (just update the array), but you'll lose normalization, searchability, and de-duplication features.
Let me know if you want the code adapted for a different schema or want transaction handling!