Exploration
⛵ Primitives for creating high performance file explorers with React
Install / Use
/learn @jaredLunde/ExplorationREADME
Exploration
Primitives for creating high performance file explorers with React
npm i exploration
<a href="https://flexstack.com"><img src="https://flexstack.com/images/supported-by-flexstack.svg?v1" height="36" alt="Supported by FlexStack"></a>
<hr> <p> <a href="https://bundlephobia.com/result?p=exploration"> <img alt="Bundlephobia" src="https://img.shields.io/bundlephobia/minzip/exploration?style=for-the-badge&labelColor=24292e"> </a> <a aria-label="Types" href="https://www.npmjs.com/package/exploration"> <img alt="Types" src="https://img.shields.io/npm/types/exploration?style=for-the-badge&labelColor=24292e"> </a> <a aria-label="Code coverage report" href="https://codecov.io/gh/jaredLunde/exploration"> <img alt="Code coverage" src="https://img.shields.io/codecov/c/gh/jaredLunde/exploration?style=for-the-badge&labelColor=24292e"> </a> <a aria-label="NPM version" href="https://www.npmjs.com/package/exploration"> <img alt="NPM Version" src="https://img.shields.io/npm/v/exploration?style=for-the-badge&labelColor=24292e"> </a> <a aria-label="License" href="https://jaredlunde.mit-license.org/"> <img alt="MIT License" src="https://img.shields.io/npm/l/exploration?style=for-the-badge&labelColor=24292e"> </a> </p>Features
- [x] Zero-recursion, expandable tree
- [x] Virtualization, only render what is visible
- [x] Create/delete/move/rename actions
- [x] Drag and drop
- [x] Hotkeys
- [x] Multiselect
- [x] Traits, add class names to selections, focused elements, anything
- [x] Filtering/search
- [x] Tree snapshot restoration for persisting the expanded state of the tree between refreshes
- [x] Strongly typed so you can engineer with confidence
- [x] Concurrent mode safe, ready for React 18
The problem
File explorers in React tend to be large, slow, and opinionated. They usually peter out at a hundred nodes and aren't suitable for building a complex file explorer in the browser. Other solutions like Aspen aimed to solve this problem by using typed arrays (which don't seem to offer much benefit in performance) and event-driven models. They did a pretty job, however, the documentation was sparse and the code was verbose. All that said, Aspen and Monaco's tree were huge inspirations.
The solution
With this library I tried to solve all of those problems. It's built on plain JavaScript arrays, well-designed data structures, an event-driven model, and concurrent mode-safe React hooks. It does just enough to be extremely powerful without being opinionated about styles, hotkeys, or traits. It's also performant enough to be used in browser integrated development environments. It makes sure React only renders what changes without render thrashing.
Most importantly - it's easy to use. So check out the recipes below and give it a try!
Recipes
☀︎ Coming soon
- How to create a new file or directory to the tree
- How to delete a file or directory from the tree
- How to rename a file
- How to implement drag 'n drop with multiselect
- How to add traits to files/directories
- How to add your own hotkeys
- How to do perfom an action when a file is selected (opened)
- How to filter the list of visible files/directories
- How to write your own file tree plugin
- How to restore the state of the file tree from local storage
React API
createFileTree()
Create a file tree that can be used with the React API.
Arguments
| Name | Type | Required? | Description |
| -------- | ----------------------------- | --------- | --------------------------------------------------- |
| getNodes | GetNodes<Meta> | Yes | A function that returns the nodes of the file tree. |
| config | FileTreeConfig<Meta> | No | Configuration options for the file tree. |
GetNodes
type GetNodes<Meta> = {
/**
* Get the nodes for a given directory
*
* @param parent - The parent directory to get the nodes for
* @param factory - A factory to create nodes (file/dir) with
*/
(parent: Dir<Meta>, factory: Omit<FileTreeFactory<Meta>, "createPrompt">):
| Promise<FileTreeNode<Meta>[]>
| FileTreeNode<Meta>[];
};
FileTreeConfig
type FileTreeConfig<Meta> = {
/**
* A function that compares two nodes for sorting.
*/
comparator?: FileTree["comparator"];
/**
* The root node data
*/
root?: Omit<FileTreeData<Meta>, "type">;
/**
* Restore the tree from a snapshot
*/
restoreFromSnapshot?: FileTreeSnapshot;
};
Returns FileTree
useVirtualize()
A hook similar to react-window's FixedSizeList
component. It allows you to render only enough components to fill a viewport, solving
some important performance bottlenecks when rendering large lists.
Arguments
| Name | Type | Required? | Description |
| -------- | --------------------------------------------- | --------- | --------------------- |
| fileTree | FileTree<Meta> | Yes | A file tree |
| config | UseVirtualizeConfig | Yes | Configuration options |
UseVirtualizeConfig
| Name | Type | Required? | Description |
| -------------- | ---------------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| windowRef | WindowRef | Yes | A React ref created by useRef() or an HTML element for the container viewport you're rendering the list inside of. |
| nodeHeight | number | Yes | The fixed height (in px) of each node in your list. |
| nodeGap | number | No | Optionally set a gap (in px) between each node in your list. |
| nodes | number[] | No | When using a hook like useFilter you can supply the filtered list of nodes to this option. By default, useVirtualize() uses the nodes returned by useVisibleNodes() |
| overscanBy | number | No | This number is used for determining the number of nodes outside of the visible window to render. The default value is 2 which means "render 2 windows worth (2 * height) of content before and after the items in the visible window". A value of 3 would be 3 windows worth of grid cells, so it's a linear relationship. Overscanning is important for preventing tearing when scrolling through items in the grid, but setting too high of a value may create too much work for React to handle, so it's best that you tune this value accordingly. |
| ResizeObserver | ResizeObserver | No | This hook uses a ResizeObserver for tracking the size of the viewport. If you need to polyfill ResizeObserver you can provide that polyfill here. By defaul
Related Skills
node-connect
347.6kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
108.4kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
347.6kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
347.6kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
