Skip to content

Commit

Permalink
feat: useThrottledEffect
Browse files Browse the repository at this point in the history
  • Loading branch information
hosseinmd committed Jul 9, 2022
1 parent f440418 commit da66d10
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 0 deletions.
62 changes: 62 additions & 0 deletions src/core/src/hooks/useThrottledEffect/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { useEffect, useLayoutEffect, useRef } from "react";
import { useEvent } from "../useEvent";

const useThrottledEffect = (
callBack: () => void,
{
deps,
delay = 200,
ignoreInFirstCall = true,
callingAfterUnmount = false,
}: {
deps: any[];
/** Millisecond default is `200` */
delay?: number;
/** Default is `true` */
ignoreInFirstCall?: boolean;
/** Default is `false` */
callingAfterUnmount?: boolean;
},
) => {
const event = useEvent(callBack);
const timeout = useRef<ReturnType<typeof setTimeout>>();
const hasNextValue = useRef(false);
const isMounted = useRef(false);

useLayoutEffect(() => {
if (!isMounted.current && ignoreInFirstCall) {
isMounted.current = true;
return;
}
if (!timeout.current) {
event();

const timeoutCallback = () => {
if (hasNextValue.current) {
hasNextValue.current = false;
event();
timeout.current = setTimeout(timeoutCallback, delay);
} else {
timeout.current = undefined;
}
};
timeout.current = setTimeout(timeoutCallback, delay);
} else {
hasNextValue.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [delay, ...deps]);

const clear = useEvent(() => {
if (!callingAfterUnmount && timeout.current) {
clearTimeout(timeout.current);
}
});

useEffect(() => {
return clear;
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
};

export { useThrottledEffect };
74 changes: 74 additions & 0 deletions src/stories/src/hooks/useThrottledEffect/index.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { Meta, Story } from "@storybook/react";
import { useState } from "react";
import { useThrottledEffect } from "reactjs-view-core/src/hooks/useThrottledEffect";
import { RenderingControls } from "../../container/renderingControls";

type DemoProps = Omit<Parameters<typeof useThrottledEffect>[1], "deps">;

const Demo = ({ delay, ...rest }: DemoProps) => {
const [inputValue, setInputValue] = useState("");
const [throttledValue, setThrottledValue] = useState("");

const deps = [inputValue];
useThrottledEffect(
() => {
setThrottledValue(inputValue);
},
{ delay, deps, ...rest },
);

return (
<>
<pre>
<code>
{JSON.stringify({ throttledValue, deps, delay, ...rest }, null, 4)}
</code>
</pre>
<p>Input is dependency of useThrottledEffect</p>
<input
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
style={{
border: "1px solid #ccc",
padding: "5px",
fontSize: 16,
outline: "none",
}}
/>
</>
);
};

const DemoWithControls = (props: DemoProps) => (
<RenderingControls>
<Demo {...props} />
</RenderingControls>
);

const meta: Meta<DemoProps> = {
title: "Hooks/useThrottledEffect",
component: DemoWithControls,
argTypes: {
delay: {
description: "Delay to wait to throttle",
type: "number",
},
callingAfterUnmount: {
type: "boolean",
},
ignoreInFirstCall: {
type: "boolean",
},
},
parameters: {
controls: { expanded: true },
},
};

export default meta;

const Template: Story<DemoProps> = (args) => <DemoWithControls {...args} />;
export const Default = Template.bind({});
Default.args = {
delay: 5000,
};

0 comments on commit da66d10

Please sign in to comment.