# functional-tools

Functional programming helpers for managing collections of side-effecting setup/teardown pairs.

---

## `mapEffect(collection, effect) → () => void`

Runs a setup *effect* function over each item in a collection and returns a single cleanup function that tears down all effects at once.

This is the functional equivalent of `useEffect` over an array — useful for setting up event listeners, subscriptions, or observers across a list of items.

### Parameters

| Name | Type | Default | Description |
|------|------|---------|-------------|
| `collection` | `ItemType[]` | *(required)* | The array of items to iterate over. |
| `effect` | `(item: ItemType) => (() => void) \| void` | *(required)* | A function called for each item. If it returns a function, that function will be called during cleanup. If it returns `undefined` / `void`, cleanup for that item is a no-op. |

### Returns

`() => void` — A cleanup function. When called, it invokes the cleanup function returned by each effect (if any), in the same order as the original collection.

### Example

```typescript
import { mapEffect } from '@yaoc1996/js-tools'

const elements = document.querySelectorAll('button')

const cleanup = mapEffect([...elements], (button) => {
    const handler = () => console.log('clicked', button)
    button.addEventListener('click', handler)
    return () => button.removeEventListener('click', handler)
})

// Later, remove all listeners at once:
cleanup()
```

```typescript
// Works well with React useEffect:
useEffect(() => {
    return mapEffect(items, (item) => item.subscribe(handler))
}, [items])
```

### Notes

- Effects that do not return a function (i.e. return `undefined` or `void`) are safely skipped during cleanup.
- An empty `collection` results in a no-op cleanup function.
- The effect function is called eagerly and synchronously during `mapEffect` — not lazily.
- Errors thrown by an individual effect are **not** caught; they propagate normally.
