use-fs
Version:
A React hook for integrating with the File System Access API. Enables web applications to seamlessly work with files on a user's local system.
200 lines (166 loc) • 7.45 kB
Markdown
# 🗂️ use-fs
A React hook for integrating with the [File System Access API](https://developer.mozilla.org/en-US/docs/Web/API/File_System_Access_API). Visit [**use-fs.com**](https://use-fs.com) to try it out in your browser.
The File System Access API enables web applications to seamlessly work with files on a user's local system. After a user grants permission, web apps can read, write, and manage files directly - eliminating the need for repeated file selection dialogs. This capability is ideal for creating powerful browser-based tools.
Unlike traditional file selection dialogs, the user will be prompted to select a directory, the hook will watch the files in that directory for changes - rerendering when changes are detected.
> ⚠️ Note: The File System API is not supported in all browsers. Works on Desktop in Chrome, Edge and Opera.
## 📡 Install
```console
npm install use-fs
yarn add use-fs
pnpm add use-fs
```
> 👋 Hello there! Follow me [@linesofcode](https://twitter.com/linesofcode) or visit [linesofcode.dev](https://linesofcode.dev) for more cool projects like this one.
## 🚀 Getting Started
```tsx
import { useFs } from "use-fs";
function App() {
const {
onDirectorySelection,
files,
isBrowserSupported,
onClear,
isProcessing,
writeFile,
deleteFile,
startPolling,
stopPolling,
isPolling
} = useFs({
// Optional array of filter functions to exclude files/directories. By default `commonFilters` is used to ignore .git, node_modules, etc.
filters: [
// Built-in filters available:
// - distFilter (excludes dist/, build/, node_modules/, etc.)
// - gitFilter (respects .gitignore)
// - miscFilter (excludes .DS_Store, etc.)
// Or use commonFilters which includes all of the above
],
// Called when new files are added to the watched directory
onFilesAdded: (newFiles, previousFiles) => {
console.log('Files added:', newFiles);
// newFiles: Map<string, string> - path -> content
// previousFiles: Map<string, string> - previous state
},
// Called when existing files are modified
onFilesChanged: (changedFiles, previousFiles) => {
console.log('Files changed:', changedFiles);
// changedFiles: Map<string, string> - path -> new content
// previousFiles: Map<string, string> - previous state
},
// Called when files are deleted from the watched directory
onFilesDeleted: (deletedFiles, previousFiles) => {
console.log('Files deleted:', deletedFiles);
// deletedFiles: Map<string, string> - path -> last known content
// previousFiles: Map<string, string> - previous state
},
});
if (!isBrowserSupported) {
return <div>Browser not supported</div>;
}
const handleSaveFile = async (path: string, content: string) => {
try {
await writeFile(path, content, { truncate: true });
console.log('File saved successfully');
} catch (error) {
console.error('Error saving file:', error);
}
};
const handleDeleteFile = async (path: string) => {
try {
await deleteFile(path);
console.log('File deleted successfully');
} catch (error) {
console.error('Error deleting file:', error);
}
};
return (
<div>
<button
onClick={onDirectorySelection}
disabled={isProcessing}
>
Select Directory
</button>
<button
onClick={onClear}
disabled={isProcessing}
>
Clear
</button>
<button
onClick={startPolling}
disabled={isProcessing || isPolling}
>
Start Polling
</button>
<button
onClick={stopPolling}
disabled={isProcessing || !isPolling}
>
Stop Polling
</button>
<div>
Status: {isPolling ? 'Polling Active' : 'Polling Stopped'}
</div>
{files.size > 0 && (
<div>
<h2>Files ({files.size}):</h2>
<div>
{Array.from(files.entries()).map(([path, content]) => (
<div key={path}>
<h3>{path}</h3>
<pre>{content}</pre>
<button onClick={() => handleSaveFile(path, 'New content')}>
Save Changes
</button>
<button onClick={() => handleDeleteFile(path)}>
Delete File
</button>
</div>
))}
</div>
</div>
)}
</div>
);
}
```
The hook provides several key features:
1. **File System Access**: Prompts users to select a directory and maintains access to it.
2. **File Writing**: Allows writing content to files with options for truncation and creation.
3. **File Deletion**: Enables safe removal of files from the selected directory.
4. **File Watching**: Continuously monitors selected directory for changes with automatic polling.
5. **Polling Control**: Manual control over when to start/stop monitoring for file changes.
6. **Content Management**: Provides access to file contents and updates in real-time.
7. **Filtering**: Built-in and custom filters to exclude unwanted files/directories.
8. **Performance Optimizations**:
- Batched file processing
- Content caching
- Debounced updates
- Efficient change detection
### Props
- `filters?: FilterFn[]` - Array of filter functions to exclude files/directories
- `onFilesAdded?: (newFiles: Map<string, string>, previousFiles: Map<string, string>) => void` - Callback when files are added
- `onFilesChanged?: (changedFiles: Map<string, string>, previousFiles: Map<string, string>) => void` - Callback when files change
- `onFilesDeleted?: (deletedFiles: Map<string, string>, previousFiles: Map<string, string>) => void` - Callback when files are deleted
- `pollInterval?: number` - How often to check for changes (default: 100ms)
- `batchSize?: number` - How many files to process in parallel (default: 50)
- `debounceInterval?: number` - Debounce interval for updates (default: 50ms)
- `fileCacheTtl?: number` - How long to cache file contents (default: 5000ms)
### Return Values
- `onDirectorySelection: () => Promise<void>` - Function to open directory picker
- `onClear: () => void` - Function to stop watching and clear state
- `files: Map<string, string>` - Current map of file paths to contents
- `isProcessing: boolean` - Whether files are being processed
- `isBrowserSupported: boolean` - Whether File System API is supported
- `writeFile: (path: string, data: string | ArrayBuffer | Blob, options?: FileWriteOptions) => Promise<void>` - Function to write to files
- `deleteFile: (path: string) => Promise<void>` - Function to delete files
- `startPolling: () => void` - Function to manually start polling for file changes
- `stopPolling: () => void` - Function to manually stop polling for file changes
- `isPolling: boolean` - Whether the hook is actively polling for changes
## 📚 Contributing
1. Navigate to the `docs` directory
2. Run `pnpm install` to install the dependencies
3. Run `pnpm dev` to start the development server
3. Navigate to `http://localhost:3000` to view the demo.
5. Modify the `Demo.tsx` file to make your changes.
If you're making changes to the `use-fs` package, you can run `pnpm build` to build the package and then run `pnpm link use-fs` to link the package to the `docs` directory for local development and testing.