Skip to content

Commit 1576443

Browse files
authored
Merge pull request #105 from nikasepiskveradze/js-hook-to-ts
convert js hook to ts
2 parents c2f3a9e + 996938c commit 1576443

File tree

7 files changed

+382
-1
lines changed

7 files changed

+382
-1
lines changed

src/pages/useDebounce.md

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ title: useDebounce
44
date: "2018-11-09"
55
gist: https://gist.github.com/gragland/e50346f02e7edf4f81cc0bda33d3cae6
66
sandbox: https://codesandbox.io/s/711r1zmq50
7+
isMultilingual: true
78
---
89

910
This hook allows you to debounce any fast changing value. The debounced value will only reflect the latest value when the useDebounce hook has not been called for the specified time period. When used in conjunction with useEffect, as we do in the recipe below, you can easily ensure that expensive operations like API calls are not executed too frequently. The example below allows you to search the Marvel Comic API and uses useDebounce to prevent API calls from being fired on every keystroke. Be sure to check out the [CodeSandbox demo](https://codesandbox.io/s/711r1zmq50) for this one. Hook code and inspiration from [github.com/xnimorz/use-debounce](https://github.com/xnimorz/use-debounce).
@@ -105,3 +106,103 @@ function useDebounce(value, delay) {
105106
return debouncedValue;
106107
}
107108
```
109+
110+
```typescript
111+
import { useState, useEffect, useRef } from "react";
112+
113+
// Usage
114+
function App() {
115+
// State and setters for ...
116+
// Search term
117+
const [searchTerm, setSearchTerm] = useState<string>("");
118+
// API search results
119+
const [results, setResults] = useState<any[]>([]);
120+
// Searching status (whether there is pending API request)
121+
const [isSearching, setIsSearching] = useState<boolean>(false);
122+
// Debounce search term so that it only gives us latest value ...
123+
// ... if searchTerm has not been updated within last 500ms.
124+
// The goal is to only have the API call fire when user stops typing ...
125+
// ... so that we aren't hitting our API rapidly.
126+
// We pass generic type, this case string
127+
const debouncedSearchTerm: string = useDebounce<string>(searchTerm, 500);
128+
129+
// Effect for API call
130+
useEffect(
131+
() => {
132+
if (debouncedSearchTerm) {
133+
setIsSearching(true);
134+
searchCharacters(debouncedSearchTerm).then((results) => {
135+
setIsSearching(false);
136+
setResults(results);
137+
});
138+
} else {
139+
setResults([]);
140+
}
141+
},
142+
[debouncedSearchTerm] // Only call effect if debounced search term changes
143+
);
144+
145+
return (
146+
<div>
147+
<input
148+
placeholder="Search Marvel Comics"
149+
onChange={(e: React.FormEvent<HTMLInputElement>) => setSearchTerm(e.target.value)}
150+
/>
151+
152+
{isSearching && <div>Searching ...</div>}
153+
154+
{results.map((result) => (
155+
<div key={result.id}>
156+
<h4>{result.title}</h4>
157+
<img
158+
src={`${result.thumbnail.path}/portrait_incredible.${result.thumbnail.extension}`}
159+
/>
160+
</div>
161+
))}
162+
</div>
163+
);
164+
}
165+
166+
// API search function
167+
function searchCharacters(search: string): Promise<any[]> {
168+
const apiKey:string = "f9dfb1e8d466d36c27850bedd2047687";
169+
return fetch(
170+
`https://gateway.marvel.com/v1/public/comics?apikey=${apiKey}&titleStartsWith=${search}`,
171+
{
172+
method: "GET",
173+
}
174+
)
175+
.then((r) => r.json())
176+
.then((r) => r.data.results)
177+
.catch((error) => {
178+
console.error(error);
179+
return [];
180+
});
181+
}
182+
183+
// Hook
184+
// T is a generic type for value parameter, our case this will be string
185+
function useDebounce<T>(value: T, delay: number): T {
186+
// State and setters for debounced value
187+
const [debouncedValue, setDebouncedValue] = useState<T>(value);
188+
189+
useEffect(
190+
() => {
191+
// Update debounced value after delay
192+
const handler = setTimeout(() => {
193+
setDebouncedValue(value);
194+
}, delay);
195+
196+
// Cancel the timeout if value changes (also on delay change or unmount)
197+
// This is how we prevent debounced value from updating if value is changed ...
198+
// .. within the delay period. Timeout gets cleared and restarted.
199+
return () => {
200+
clearTimeout(handler);
201+
};
202+
},
203+
[value, delay] // Only re-call effect if value or delay changes
204+
);
205+
206+
return debouncedValue;
207+
}
208+
```

src/pages/useHover.md

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ title: useHover
44
date: "2018-10-30"
55
gist: https://gist.github.com/gragland/cfc4089e2f5d98dde5033adc44da53f8
66
sandbox: https://codesandbox.io/s/01w2zmj010
7+
isMultilingual: true
78
links:
89
- url: https://gist.github.com/gragland/a32d08580b7e0604ff02cb069826ca2f
910
name: useHover with callback ref
@@ -51,3 +52,42 @@ function useHover() {
5152
return [ref, value];
5253
}
5354
```
55+
56+
```typescript
57+
// Usage
58+
function App() {
59+
const [hoverRef, isHovered] = useHover<HTMLDivElement>();
60+
61+
return <div ref={hoverRef}>{isHovered ? "😁" : "☹️"}</div>;
62+
}
63+
64+
// Hook
65+
// T - could be any type of HTML element like: HTMLDivElement, HTMLParagraphElement and etc.
66+
// hook returns tuple(array) with type [any, boolean]
67+
function useHover<T>(): [MutableRefObject<T>, boolean] {
68+
const [value, setValue] = useState<boolean>(false);
69+
70+
const ref: any = useRef<T | null>(null);
71+
72+
const handleMouseOver = (): void => setValue(true);
73+
const handleMouseOut = (): void => setValue(false);
74+
75+
useEffect(
76+
() => {
77+
const node: any = ref.current;
78+
if (node) {
79+
node.addEventListener("mouseover", handleMouseOver);
80+
node.addEventListener("mouseout", handleMouseOut);
81+
82+
return () => {
83+
node.removeEventListener("mouseover", handleMouseOver);
84+
node.removeEventListener("mouseout", handleMouseOut);
85+
};
86+
}
87+
},
88+
[ref.current] // Recall only if ref changes
89+
);
90+
91+
return [ref, value];
92+
}
93+
```

src/pages/useKeyPress.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function App() {
4040
// Hook
4141
function useKeyPress(targetKey) {
4242
// State for keeping track of whether key is pressed
43-
const [keyPressed, setKeyPressed] = useState(false);
43+
const [keyPressed, setKeyPressed] = useState<boolean>(false);
4444
// If pressed key is our target key then set to true
4545
function downHandler({ key }) {
4646
if (key === targetKey) {

src/pages/useLockBodyScroll.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ title: useLockBodyScroll
44
date: "2019-01-15"
55
gist: https://gist.github.com/gragland/f50690d2724aec1bd513de8596dcd9b9
66
sandbox: https://codesandbox.io/s/yvkol51m81
7+
isMultilingual: true
78
links:
89
- url: https://jeremenichelli.io/2019/01/how-hooks-might-shape-design-systems-built-in-react/
910
name: How hooks might shape design systems built in React
@@ -61,3 +62,62 @@ function useLockBodyScroll() {
6162
}, []); // Empty array ensures effect is only run on mount and unmount
6263
}
6364
```
65+
66+
```typescript
67+
import { useState, useLayoutEffect } from "react";
68+
69+
// Usage
70+
function App() {
71+
// State for our modal
72+
const [modalOpen, setModalOpen] = useState<boolean>(false);
73+
74+
return (
75+
<div>
76+
<button onClick={() => setModalOpen(true)}>Show Modal</button>
77+
<Content />
78+
{modalOpen && (
79+
<Modal
80+
title="Try scrolling"
81+
content="I bet you you can't! Muahahaha 😈"
82+
onClose={() => setModalOpen(false)}
83+
/>
84+
)}
85+
</div>
86+
);
87+
}
88+
89+
90+
// Define modal props type
91+
type ModalProps = {
92+
title: string;
93+
content: string;
94+
onClose: () => void;
95+
}
96+
97+
function Modal({ title, content, onClose } : ModalProps) {
98+
// Call hook to lock body scroll
99+
useLockBodyScroll();
100+
101+
return (
102+
<div className="modal-overlay" onClick={onClose}>
103+
<div className="modal">
104+
<h2>{title}</h2>
105+
<p>{content}</p>
106+
</div>
107+
</div>
108+
);
109+
}
110+
111+
// Hook
112+
function useLockBodyScroll(): void {
113+
// useLaoutEffect callback return type is "() => void" type
114+
useLayoutEffect(() : () => void => {
115+
// Get original body overflow
116+
const originalStyle: string = window.getComputedStyle(document.body).overflow;
117+
// Prevent scrolling on mount
118+
document.body.style.overflow = "hidden";
119+
// Re-enable scrolling when component unmounts
120+
return () => (document.body.style.overflow = originalStyle);
121+
}, []); // Empty array ensures effect is only run on mount and unmount
122+
}
123+
```

src/pages/useOnScreen.md

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ title: useOnScreen
44
date: "2018-11-08"
55
gist: https://gist.github.com/gragland/d1175eb983772b077cb17ae0841c5329
66
sandbox: https://codesandbox.io/s/y7kr0vll4v
7+
isMultilingual: true
78
links:
89
- url: https://github.com/thebuilder/react-intersection-observer
910
name: react-intersection-observer
@@ -78,3 +79,67 @@ function useOnScreen(ref, rootMargin = "0px") {
7879
return isIntersecting;
7980
}
8081
```
82+
83+
```typescript
84+
import { useState, useEffect, useRef, MutableRefObject } from "react";
85+
86+
// Usage
87+
function App() {
88+
// Ref for the element that we want to detect whether on screen
89+
const ref: any = useRef<HTMLDivElement>();
90+
// Call the hook passing in ref and root margin
91+
// In this case it would only be considered onScreen if more ...
92+
// ... than 300px of element is visible.
93+
const onScreen: boolean = useOnScreen<HTMLDivElement>(ref, "-300px");
94+
95+
return (
96+
<div>
97+
<div style={{ height: "100vh" }}>
98+
<h1>Scroll down to next section 👇</h1>
99+
</div>
100+
<div
101+
ref={ref}
102+
style={{
103+
height: "100vh",
104+
backgroundColor: onScreen ? "#23cebd" : "#efefef",
105+
}}
106+
>
107+
{onScreen ? (
108+
<div>
109+
<h1>Hey I'm on the screen</h1>
110+
<img src="https://i.giphy.com/media/ASd0Ukj0y3qMM/giphy.gif" />
111+
</div>
112+
) : (
113+
<h1>Scroll down 300px from the top of this section 👇</h1>
114+
)}
115+
</div>
116+
</div>
117+
);
118+
}
119+
120+
// Hook
121+
function useOnScreen<T extends Element>(ref: MutableRefObject<T>, rootMargin: string = "0px"): boolean {
122+
// State and setter for storing whether element is visible
123+
const [isIntersecting, setIntersecting] = useState<boolean>(false);
124+
125+
useEffect(() => {
126+
const observer = new IntersectionObserver(
127+
([entry]) => {
128+
// Update our state when observer callback fires
129+
setIntersecting(entry.isIntersecting);
130+
},
131+
{
132+
rootMargin,
133+
}
134+
);
135+
if (ref.current) {
136+
observer.observe(ref.current);
137+
}
138+
return () => {
139+
observer.unobserve(ref.current);
140+
};
141+
}, []); // Empty array ensures that effect is only run on mount and unmount
142+
143+
return isIntersecting;
144+
}
145+
```

src/pages/useTheme.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ title: useTheme
44
date: "2019-01-07"
55
gist: https://gist.github.com/gragland/509efd16c695e7817eb70921c77c8a05
66
sandbox: https://codesandbox.io/s/15mko9187
7+
isMultilingual: true
78
links:
89
- url: https://medium.com/geckoboard-under-the-hood/how-we-made-our-product-more-personalized-with-css-variables-and-react-b29298fde608
910
name: CSS Variables and React
@@ -52,3 +53,49 @@ function useTheme(theme) {
5253
);
5354
}
5455
```
56+
57+
```typescript
58+
import { useLayoutEffect } from "react";
59+
import "./styles.scss"; // -> https://codesandbox.io/s/15mko9187
60+
61+
// Usage
62+
const theme = {
63+
"button-padding": "16px",
64+
"button-font-size": "14px",
65+
"button-border-radius": "4px",
66+
"button-border": "none",
67+
"button-color": "#FFF",
68+
"button-background": "#6772e5",
69+
"button-hover-border": "none",
70+
"button-hover-color": "#FFF",
71+
};
72+
73+
// This is type of "theme" object, kind of dynamic type
74+
interface Theme {
75+
[name: string]: string;
76+
}
77+
78+
function App() {
79+
useTheme(theme);
80+
81+
return (
82+
<div>
83+
<button className="button">Button</button>
84+
</div>
85+
);
86+
}
87+
88+
// Hook
89+
function useTheme(theme: Theme): void {
90+
useLayoutEffect(
91+
(): void => {
92+
// Iterate through each value in theme object
93+
for (const key in theme) {
94+
// Update css variables in document's root element
95+
document.documentElement.style.setProperty(`--${key}`, theme[key]);
96+
}
97+
},
98+
[theme] // Only call again if theme object reference changes
99+
);
100+
}
101+
```

0 commit comments

Comments
 (0)