Skip to main content

Upgrading from 1.x

Version 2.0 is a major release. Most apps only need to check the first two items below.

Breaking changes​

  • Controlled mode is now fully controlled. When you pass data, the tree renders exactly that data, so selections only appear once you pass the tree from onChange back in as data. In 1.x the tree kept its own copy and updated it even if you ignored onChange. If you don't want to manage the state, use defaultData.
  • Parents with disabled children are mixed, not checked. With chained selection, checking a parent whose disabled child is unchecked now makes the parent mixed (null) instead of checked, so its value always reflects its subtree. Clicking it again unchecks its enabled descendants. A parent whose descendants are all disabled is now shown as disabled, since it can never change (in 1.x it could be checked on its own). See Disabled nodes.
  • onChange receives a structurally shared tree, and is only called when something changed. Changed nodes and their ancestors are new objects; every other node is the same object as before. In 1.x every change produced a full deep copy. Your input data is still never modified, so treat the result as immutable.
  • Nodes no longer contain a _parentID property.
  • Expansion state is managed by TreeSelect. TreeNode now receives expanded, onExpandedChange, treeItemProps and its nested nodes as children, instead of keeping its own state and rendering its children itself. A 1.x-style custom node that renders its own children still works, but isn't part of keyboard navigation. If it spreads its remaining props onto a DOM element, destructure the new props first. See Custom nodes.
  • Changing defaultExpanded after the first render now expands or collapses every node. In 1.x it only affected nodes rendered afterward.
  • New DOM structure and ARIA roles. The root has role="tree", each node has role="treeitem", and nested nodes are wrapped in an element with role="group". The label no longer has role="button", the checkbox is removed from the tab order, and the expand icon is hidden from assistive technology. Update CSS selectors that relied on nested nodes being direct children of their parent.
  • The stylesheet no longer resets global styles. 1.x shipped Tailwind's Preflight reset, which changed styles across the whole page. If your app relied on it, add a reset yourself (for example Tailwind's @import "tailwindcss").
  • Styles are built with Tailwind CSS 4. The default styles use modern CSS (such as @property and individual transform properties) and need Safari 16.4+, Chrome 111+ or Firefox 128+.
  • Package exports are restricted. Only the main entry point and the stylesheet can be imported. Import styles from @attebyte/react-tree-select/style.css; the old dist/style.css path still works.
  • License changed from GPL-3.0 to MIT.

2.1.0​

  • data and defaultData accept a single root node as well as an array. onChange reports the tree in the same shape you passed in, so a controlled single-root tree works with onChange={setRoot}.
  • The stylesheet no longer affects elements outside the tree. In 2.0.0 its utility classes (such as basis-full) were unlayered and unprefixed, so they matched and overrode identically named classes in the consuming app, including Tailwind 4 responsive variants. Classes are now prefixed with rts: and live in the components cascade layer. See Customization.

New features​

  • Keyboard navigation and screen reader support following the WAI-ARIA tree view pattern.
  • getSelectedIDs and getSelectedNodes helpers.
  • Expansion state survives collapsing and reopening a parent.
  • Nodes can contain values that can't be cloned, such as functions or class instances.
  • Controlled data renders on the first render and on the server.
  • Much faster updates on large trees; see the large tree example.
  • A smaller package: the bundle no longer includes its dependencies (69 KB to 17 KB of JavaScript), and the stylesheet only contains the component's own styles (14 KB to 3 KB).
  • Type declarations work with "moduleResolution": "node16" and "nodenext", and the package can be loaded with require() in Node.js and Jest.