New Research: Supply Chain Attack on Axios Pulls Malicious Dependency from npm.Details →
Socket
Book a DemoSign in
Socket

@diskette/composed-props

Package Overview
Dependencies
Maintainers
1
Versions
20
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@diskette/composed-props

A TypeScript-first React hooks library that enables component libraries to provide render props functionality with type inference and runtime validation.

latest
Source
npmnpm
Version
0.15.0
Version published
Maintainers
1
Created
Source

@diskette/composed-props

A TypeScript-first React hooks library that enables component libraries to provide render props functionality with type inference and runtime validation.

📦 Installation

npm install @diskette/composed-props
yarn add @diskette/composed-props
pnpm add @diskette/composed-props

Key Features

  • 🏭 Hook Factory Pattern: Create customized useRenderProps hooks tailored to your component's needs
  • 🔒 Type Safety: Sophisticated TypeScript inference with runtime validation
  • 🎯 Component Library Focus: Built specifically for library authors to expose render props APIs
  • 🌟 Multi-State Support: Different props can respond to different state types
  • 🔧 Flexible: Support for static values, dynamic functions, defaults, and transforms

🧩 Core Concepts

ComposableProp<T, V>

The ComposableProp<T, V> type represents a prop that can be either:

  • A static value of type V
  • A function (state: T) => V that computes the value from component state
import type { ComposableProp } from '@diskette/composed-props'

interface ButtonState {
  isHovered: boolean
  isPressed: boolean
}

interface ButtonProps {
  // Must explicitly use ComposableProp for each prop
  backgroundColor: ComposableProp<ButtonState, string>
  opacity: ComposableProp<ButtonState, number>
  // Non-composable props remain normal
  onClick?: () => void
}

// Static usage
<Button backgroundColor="#007bff" opacity={1} />

// Dynamic usage
<Button
  backgroundColor={({ isHovered, isPressed }) =>
    isPressed ? '#0056b3' : isHovered ? '#0069d9' : '#007bff'
  }
  opacity={({ isHovered }) => isHovered ? 0.8 : 1}
/>

📚 API Reference

createUseRenderProps<Config>(config)

Creates a type-safe useRenderProps hook based on a configuration schema. This is the primary tool for component library authors.

Example: Creating a Custom Hook

import { createUseRenderProps } from '@diskette/composed-props'
import type { ComposableProp } from '@diskette/composed-props'

// Define your component's state shapes
interface TabsState {
  activeTab: string
  hoveredTab: string | null
}

interface ThemeState {
  theme: 'light' | 'dark'
}

// Props interface using ComposableProp explicitly
interface TabsProps {
  className?: ComposableProp<TabsState, string>
  style?: ComposableProp<ThemeState, CSSProperties> // Different state type!
  'data-active': ComposableProp<TabsState, boolean> // Required prop
  children?: ComposableProp<TabsState, ReactNode>
  // Non-composable props
  onTabChange?: (tab: string) => void
}

// Create the hook factory with validation
const useTabsRenderProps = createUseRenderProps({
  className: { type: 'string' },
  style: { type: (value): value is CSSProperties => typeof value === 'object' },
  'data-active': { type: 'boolean', required: true },
  children: { type: (value): value is ReactNode => true },
})

// Component library implementation
function Tabs(props: TabsProps) {
  const [activeTab, setActiveTab] = useState('tab1')
  const [hoveredTab, setHoveredTab] = useState<string | null>(null)
  const [theme, setTheme] = useState<'light' | 'dark'>('light')

  const tabsState: TabsState = { activeTab, hoveredTab }
  const themeState: ThemeState = { theme }

  const { composed, rest } = useTabsRenderProps(props)

  // Option 1: Use individual functions
  const className = composed.className?.(tabsState)
  const style = composed.style?.(themeState)

  return (
    <div
      className={className}
      style={style}
      data-active={composed['data-active']?.(tabsState)}
      {...rest}
    >
      {composed.children?.(tabsState)}
    </div>
  )

  // Option 2: Use props helper with multi-state support
  // const resolvedProps = composed.props({
  //   className: tabsState,    // TabsState for className
  //   style: themeState,       // ThemeState for style (different state!)
  //   'data-active': tabsState, // TabsState for data-active
  //   children: tabsState      // TabsState for children
  // })
  //
  // return <div {...resolvedProps} />  // rest is already included!
}

Individual Functions vs Props Helper

const { composed, rest } = useAccordionRenderProps(props, {
  // Options for defaults and transforms
  duration: {
    default: ({ isExpanded }) => (isExpanded ? 300 : 200),
  },
  className: {
    transform: (className, { isAnimating }) =>
      isAnimating ? `${className} accordion--animating` : className,
  },
})

// Approach 1: Individual functions (fine-grained control)
const className = composed.className?.(accordionState)
const style = composed.style?.(themeState)
const duration = composed.duration?.(accordionState)

return (
  <div className={className} style={style} data-duration={duration} {...rest}>
    {composed.children?.(accordionState)}
  </div>
)

// Approach 2: Props helper
const resolvedProps = composed.props({
  className: accordionState,
  style: themeState, // Different state type for style!
  duration: accordionState,
  children: accordionState,
})

return <div {...resolvedProps} /> // rest already included

Keywords

hooks

FAQs

Package last updated on 19 Sep 2025

Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts