react-spectrum icon indicating copy to clipboard operation
react-spectrum copied to clipboard

Export useControlledState from react-stately (and/or react-aria-components).

Open adriantrunzo opened this issue 2 months ago โ€ข 1 comments

Provide a general summary of the feature here

The useControlledState hook from @react-stately/utils is very helpful in creating reusable components, especially when building on top of React Aria Components. You can currently access useControlledState using import { useControlledState } from '@react-stately/utils';, but that import feels a bit like using undocumented internals. I am not sure what the intended separation is between the namespaced @react-* packages and the non-namespaced packages, but it would be convenient if they exposed the same exports.

๐Ÿค” Expected Behavior?

One of the following imports works:

import { useControlledState } from 'react-stately';
import { useControlledState } from 'react-aria-components';

๐Ÿ˜ฏ Current Behavior

useControlledState is only available using import { useControlledState } from '@react-stately/utils';.

๐Ÿ’ Possible Solution

Augment the react-stately or react-aria-components main index file (or both) to export useControlledState.

useControlledState is actually one example of a few hooks that I've found helpful, but I am not sure if there is a reason it's not exported from a non-namespaced package. useDescription from @react-aria/utils is another hook that I have found helpful.

I don't know if a goal of react-aria-components is to re-export everything available in react-aria and react-stately. For managing package versions in apps, it seems beneficial to only have to install one "monopackage", rather than worry about if the version of @react-stately/utils in package.json is different from the version used in react-aria-components, etc.

๐Ÿ”ฆ Context

One of my use cases for useControlledState is creating a wrapper around DateRangePicker that uses values that can be tagged as a preset date range. For example, something like:

// Details omitted here, but basically I am overwriting the value, defaultValue, and onChange props.
type PresetRangeValueProps<T extends DateValue, S extends string> = {
  defaultValue?: PresetRangeValue<T, S> | null;
  onChange?: (value: PresetRangeValue<T, S> | null) => void;
  presets: readonly PresetDateRange<T, S>[];
  value?: PresetRangeValue<T, S> | null;
};

// Merge from type-fest.
type PresetRangePickerProps<
  T extends DateValue,
  S extends string,
> = Merge<
  AriaDateRangePickerProps<T>,
  PresetRangeValueProps<T, S>
>;

function PresetRangePicker<T extends DateValue, S extends string>(
  {
    defaultValue,
    onChange,
    presets,
    value: propsValue,
    ...props
  }: PresetRangePickerProps<T, S>,
) {
    const [value, setValue] = useControlledState(
      propsValue,
      defaultValue || null,
      onChange,
    );

   // ...truncated

useControlledState allows me to make PresetRangePicker either controlled or uncontrolled for flexibility and more closely mimic the behavior of DateRangePicker.

๐Ÿ’ป Examples

No response

๐Ÿงข Your Company/Team

No response

๐Ÿ•ท Tracking Issue

No response

adriantrunzo avatar Apr 17 '24 20:04 adriantrunzo