Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
.idea
**/*.xml
Comment thread
kireevmp marked this conversation as resolved.
Outdated

# Logs
logs
*.log
Expand Down
2 changes: 2 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import noWatch from "./rules/no-watch/no-watch"
import preferUseUnit from "./rules/prefer-useUnit/prefer-useUnit"
import requirePickupInPersist from "./rules/require-pickup-in-persist/require-pickup-in-persist"
import strictEffectHandlers from "./rules/strict-effect-handlers/strict-effect-handlers"
import useUnitDestructuring from "./rules/use-unit-destructuring/use-unit-destructuring"
import { ruleset } from "./ruleset"

const base = {
Expand All @@ -47,6 +48,7 @@ const base = {
"no-useless-methods": noUselessMethods,
"no-watch": noWatch,
"prefer-useUnit": preferUseUnit,
"use-unit-destructuring": useUnitDestructuring,
"require-pickup-in-persist": requirePickupInPersist,
"strict-effect-handlers": strictEffectHandlers,
Comment thread
kireevmp marked this conversation as resolved.
Outdated
},
Expand Down
145 changes: 145 additions & 0 deletions src/rules/use-unit-destructuring/use-unit-destructuring.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
# effector/use-unit-destructuring
Comment thread
kireevmp marked this conversation as resolved.
Outdated

[Related documentation](https://effector.dev/en/api/effector-react/useunit/)

Ensures that all units passed to useUnit are properly destructured to avoid unused subscriptions and implicit re-renders.

## Rule Details
This rule enforces that:
- All properties passed in an object to useUnit must be destructured to prevent implicit subscriptions;
- All elements passed in an array to useUnit must be destructured to prevent implicit subscriptions also.

### Object shape
When using useUnit with an object, you must destructure all keys that you pass. Otherwise, unused units will still create subscriptions and cause unnecessary re-renders.
TypeScript

```ts
// 👍 correct - all properties are destructured
const { value, setValue } = useUnit({
value: $store,
setValue: event,
});
```

```ts
// 👎 incorrect - setValue is not destructured but still creates subscription
const { value } = useUnit({
value: $store,
setValue: event, // unused but subscribed!
});
```

```ts
// 👎 incorrect - extra is destructured but not passed
const {
value,
setValue,
extra // extra is missing - will be undefined
} = useUnit({
value: $store,
setValue: event,
});
```

### Array shape
When using useUnit with an array, you must destructure all elements. Elements that are not destructured will still create subscriptions, leading to implicit re-renders.
TypeScript

```ts
// 👍 correct - all elements are destructured
const [value, setValue] = useUnit([$store, event]);
```

```ts
// 👎 incorrect - $store is not destructured but creates implicit subscription
const [setValue] = useUnit([event, $store]);
// Component will re-render when $store changes, even though you don't use it!
```

```ts
// 👎 incorrect - event and $anotherStore cause implicit subscriptions
const [value] = useUnit([$store, event, $anotherStore]);
// Component re-renders on $store, event, and $anotherStore changes
```

## Why is this important?
Implicit subscriptions can lead to:
- Performance issues: unnecessary re-renders when unused stores update
- Hard-to-debug behavior: component re-renders for unclear reasons
- Memory leaks: subscriptions that are never cleaned up properly

## Examples

### Real-world example

```tsx
import React, { Fragment } from "react";
import { createEvent, createStore } from "effector";
import { useUnit } from "effector-react";

const $store = createStore("Hello World!");
const event = createEvent();

// 👎 incorrect
const BadComponent = () => {
const { value } = useUnit({
value: $store,
setValue: event, // ❌ not used but subscribed!
});

return <Fragment>{value}</Fragment>;
};

// 👍 correct
const GoodComponent = () => {
const { value, setValue } = useUnit({
value: $store,
setValue: event,
});

return <button onClick={() => setValue("New value")}>{value}</button>;
};
```

```tsx
import React, { Fragment } from "react";
import { createEvent, createStore } from "effector";
import { useUnit } from "effector-react";

const $store = createStore("Hello World!");
const event = createEvent();

// 👎 incorrect - implicit subscription to $store
const BadComponent = () => {
const [setValue] = useUnit([event, $store]); // ❌ $store not used but subscribed!

return <button onClick={() => setValue("New value")}>Click</button>;
};

// 👍 correct - explicit destructuring
const GoodComponent = () => {
const [value, setValue] = useUnit([$store, event]);

return <button onClick={() => setValue("New value")}>{value}</button>;
};

// 👍 also correct - only pass what you need
const AlsoGoodComponent = () => {
const [setValue] = useUnit([event]); // ✅ no implicit subscriptions

return <button onClick={() => setValue("New value")}>Click</button>;
};
```

### When Not To Use It
If you intentionally want to subscribe to a store without using its value (rare case), you can disable this rule for that line:

```tsx
// eslint-disable-next-line effector/use-unit-destructuring
const { value } = useUnit({
value: $store,
trigger: $triggerStore, // intentionally subscribing without using
});
```

However, in most cases, you should refactor your code to avoid implicit subscriptions.
Loading