Boost Reusability with MixProps — Examples and Code Snippets

Boost Reusability with MixProps — Examples and Code Snippets

Reusable UI components reduce duplication, simplify maintenance, and speed development. MixProps is a pattern for composing component behavior and appearance by merging small, focused prop objects (mixins for props). This article shows when to use MixProps, how to structure them, patterns for composition, and concrete examples in React (TypeScript) and plain JavaScript.

Why MixProps?

  • Separation of concerns: each MixProp focuses on one responsibility (layout, accessibility, styling).
  • Composability: combine small prop sets to form complex behavior.
  • Testability: smaller prop sets are easier to test.
  • Consistency: reuse the same prop mix across components to standardize behavior.

Core ideas

  • Define small, focused prop objects (MixProps) that encapsulate related props and default values.
  • Provide utilities to merge MixProps with component props, resolving conflicts predictably.
  • Keep MixProps pure and minimal; avoid embedding side effects.

Pattern 1 — Simple merging

Use a shallow merge where component props override MixProps defaults.

JavaScript example:

jsx

// mixprops/buttonProps.js export const baseButtonProps = { type: ‘button’, role: ‘button’, disabled: false, className: ‘btn’, }; // components/Button.jsx import React from ‘react’; import { baseButtonProps } from ’../mixprops/buttonProps’; export function Button(props) { const merged = { baseButtonProps, props }; const { className, children, rest } = merged; return ( <button className={className} {rest}> {children} </button> ); }

TypeScript with utility types:

tsx

// mixprops.ts export type BaseButtonProps = { type?: ‘button’ | ‘submit’ | ‘reset’; role?: string; disabled?: boolean; className?: string; }; export const baseButtonProps: Required<BaseButtonProps> = { type: ‘button’, role: ‘button’, disabled: false, className: ‘btn’, }; // Button.tsx import React from ‘react’; import { baseButtonProps, BaseButtonProps } from ’./mixprops’; type Props = BaseButtonProps & { children?: React.ReactNode }; export function Button(props: Props) { const merged = { baseButtonProps, props } as Required<Props>; const { className, children, rest } = merged; return ( <button className={className} {(rest as any)}> {children} </button> ); }

Pattern 2 — Deep merging for nested props

For props that are objects (e.g., style), deep-merge to preserve nested defaults.

Utility:

js

// utils/deepMerge.js export function deepMerge(target, source) { const out = { target }; for (const key in source) { if ( source[key] && typeof source[key] === ‘object’ && !Array.isArray(source[key]) ) { out[key] = deepMerge(target[key] || {}, source[key]); } else { out[key] = source[key]; } } return out; }

Usage:

jsx

const styleDefaults = { card: { padding: 12, background: ’#fff’ } }; const user = { card: { padding: 8 } }; const merged = deepMerge(styleDefaults, user); // result: { card: { padding: 8, background: ‘#fff’ } }

Pattern 3 — Priority rules & conflict resolution

Decide order of precedence and document it. Common rule: component props > MixProps provided by parent > global defaults. Implement explicit merge helpers to enforce.

Example helper:

ts

export function mergeProps(sources: Array<Record<string, any>>) { return Object.assign({}, sources); }

Pattern 4 — Behavior MixProps (event handlers)

Merge event handlers so multiple MixProps can respond to the same event.

js

function mergeHandlers(fns) { return (event) => { for (const fn of fns) { if (typeof fn === ‘function’) fn(event); } }; } // usage const onClick = mergeHandlers(mix.onClick, props.onClick);

Practical examples

1) Reusable Input with accessibility and validation MixProps

tsx

// mixprops/accessibility.ts export const a11yProps = { role: ‘textbox’, ‘aria-invalid’: false }; // mixprops/validation.ts export const validationProps = { validate: (v: string) => v.length > 0, ‘aria-invalid’: true, }; // components/TextInput.tsx import React from ‘react’; import { a11yProps } from ’./mixprops/accessibility’; import { validationProps } from ’./mixprops/validation’; import { mergeProps, mergeHandlers } from ’./utils’; export function TextInput(props) { const combined = mergeProps(a11yProps, validationProps, props); const onChange = mergeHandlers(validationProps.onChange, props.onChange); return <input {combined} onChange={onChange} />; }
2) Themed Card with layout and style MixProps

jsx

const layout = { padding: 16, borderRadius: 8 }; const style = { background: ’#fafafa’, boxShadow: ‘0 2px 6px rgba(0,0,0,0.08)’ }; function Card(props) { const merged = deepMerge({ layout, style }, props); const styleObj = { padding: merged.layout.padding, borderRadius: merged.layout.borderRadius, merged.style }; return <div style={styleObj}>{props.children}</div>; }

Testing MixProps

  • Unit-test merge utilities (shallow, deep, handler merging).
  • Snapshot components with different MixProp combinations.
  • Accessibility testing for a11y MixProps.

Best practices

  • Keep MixProps small and single-purpose.
  • Prefer composition over inheritance.
  • Document precedence and merging rules.
  • Avoid side effects; use MixProps for declarative prop values and handlers only.
  • Provide TypeScript types for MixProps to improve DX and prevent misuse.

Conclusion

MixProps offer a practical, scalable way to share prop-level behavior and styling across components. Use clear merging rules, utilities for deep merges and handler composition, and keep MixProps focused to maximize reuse and maintainability.

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *