Skip to content

Commit

Permalink
Merge pull request #2 from teclone/feat/add-suport-for-timeout-operat…
Browse files Browse the repository at this point in the history
…ions

feat: add support for setTimeout and setIntervals cleanup
  • Loading branch information
teclone authored Apr 17, 2023
2 parents d176749 + 77d606e commit 2470d51
Show file tree
Hide file tree
Showing 2 changed files with 83 additions and 4 deletions.
56 changes: 53 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@

Familiar with the `Can't perform a React state update on an unmounted component` error? this tiny package is to help solve that as well prevent stalled state updates in react components.

Execution of asynchronous requests in React useEffect hook has some quite interesting gotchas. First is the possibility of running state updates even when the component had been unmounted.
Execution of asynchronous/scheduled calls such as setTimeout and setIntervals in React useEffect hook has some quite interesting gotchas. First is the possibility of running state updates even when the component had been unmounted.

Second is the possibility of running state updates from an effect that has already stalled, because you failed to clean the effect properly.

Third is unoptimization of network requests. Why make network requests when the effect is stalled?

Finally, these things can result to memory leaks, infinite loop/lifetime execution of code in client applications.

These three problems can be seen in the codebase below

```tsx
Expand Down Expand Up @@ -43,9 +45,9 @@ Finally, could we abort a network request when the effect is no longer valid? fo
npm install @teclone/react-use-effect-cleaner
```

## Sample Usage
## Sample Usage with Api Request Cleanup

Below is how to utilize this module to solve the problems of the code show earlier.
Below is how to utilize this module to solve the problems of the code shown earlier.

```tsx
import { useState, useEffect } from 'react';
Expand Down Expand Up @@ -86,6 +88,52 @@ The module utilizes the Proxy web api to create a middleware for all state modif

AbortController is supported by popular request client libraries including Fetch and Axios.

## Sample Usage with SetTimeout/SetInterval Cleanup

Below is an example of how to use the createEffectCleaner to clean up setTimeouts or setInterval.

```tsx
import { useState, useEffect } from 'react';
import { createEffectCleaner } from '@teclone/react-use-effect-cleaner';

// sample asynchronous request
const getUserProfile = (userId, abortSignal) => Promise.resolve({ id: userId });

const Profile = ({ userId }) => {
const [count, setCount] = useState(0);

useEffect(() => {
const timoutIds = { count: 0 };
const intervalIds = { count: 0 };

// create effects
const effects = createEffectCleaner(
{
setCount,
},
{ timeoutIds, intervalIds }
);

timeoutIds.count = setTimeout(() => {
effects.setCount((count) => count + 1);
}, 10000);

intervalIds.count = setInterval(() => {
effects.setCount((count) => count + 1);
}, 1000);

// return the cleaner
return effects.clean();
}, [userId]);

return (
<div>
<p>Current Count: {count}</p>
</div>
);
};
```

### Interface

The package has only one export.
Expand All @@ -98,3 +146,5 @@ The package has only one export.

- `abortController` - axios, fetch and others
- `cancelTokenSource` - provide if your networking client is legacy axios
- `timeoutIds` - an object store containing timeout ids from `setTimeout`. Note that the object must be treated as a store and modifications to the timoutIds must be made on the store for this to work. (pass by reference).
- `intervalIds` - an object store containing interval ids from `setInterval`. Just like timeoutIds, the ids must be passed by reference.
31 changes: 30 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@ export interface CreateEffectCleanerOpts {
* abort axios based network requests
*/
cancelTokenSource?: CancelTokenSource;

/**
* object containing timeout ids
*/
timeoutIds?: {
[p: string]: number;
};

/**
* object containing intervalIds
*/
intervalIds?: {
[p: string]: number;
};
}

/**
Expand All @@ -21,7 +35,8 @@ export const createEffectCleaner = <T>(
opts: CreateEffectCleanerOpts
) => {
let _stalled = false;
const { abortController, cancelTokenSource } = opts || {};
const { abortController, cancelTokenSource, intervalIds, timeoutIds } =
opts || {};

const handler = {
apply(stateModifier, thisArg, argArray) {
Expand Down Expand Up @@ -58,6 +73,20 @@ export const createEffectCleaner = <T>(
} catch (ex) {
// do nothing
}

// abort set intervals
if (intervalIds) {
Object.keys(intervalIds).forEach((key) => {
clearInterval(intervalIds[key]);
});
}

// clear timeouts
if (timeoutIds) {
Object.keys(timeoutIds).forEach((key) => {
clearTimeout(timeoutIds[key]);
});
}
};

return proxies;
Expand Down

0 comments on commit 2470d51

Please sign in to comment.