Skip to content

Commit

Permalink
make hooks safe to use with SSR
Browse files Browse the repository at this point in the history
  • Loading branch information
konstantin-lukas committed Sep 14, 2024
1 parent f157c98 commit 765dfa1
Show file tree
Hide file tree
Showing 39 changed files with 5,644 additions and 2,393 deletions.
35 changes: 18 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,15 @@



Anzol provides very useful React hooks for very common tasks such as data fetching
or deferring the updating of values with a custom timeout. You can find working examples in the
[documentation](https://konstantin-lukas.github.io/anzol/) or if you just want the code in the examples/components
Anzol provides very useful client-side React hooks for very common tasks such as data fetching
or deferring the updating of values with a custom timeout. It supports both SSG and SSR. You can find working examples
in the [documentation](https://konstantin-lukas.github.io/anzol/) or, if you just want the code, in the examples/components
directory.

Anzol is built alongside automated tests to ensure quality.
Features:
- ✅ Fully Tested
- ✅ SSR and SSG compatible
- ✅ Detailed documentation

## Installation
Anzol is available on the NPM registry. To install it, just run:
Expand All @@ -28,23 +31,21 @@ npm install anzol
```

## Currently Available Hooks
- <b>useFetch:</b> Fetches the provided URL and optionally parses the response. Aborts requests when a new request is
- <b>useFetch:</b> Fetches the provided URL and optionally parses the response. Aborts requests when a new request is
started before the previous has finished to prevent flickering of stale responses by default.
- <b>useDefer:</b> Delays the update of a value until the input has stopped changing for a certain time. This is different
- <b>useDefer:</b> Delays the update of a value until the input has stopped changing for a certain time. This is different
from React's built-in useDeferredValue because you can set the delay yourself.
- <b>useFirstRender:</b> Returns true on first render; false otherwise.
- <b>useToggle:</b> Provides a boolean toggle that does not persist between page reloads.
- <b>useIntersectionObserver:</b> Provides a hook API that wraps the IntersectionObserver API. This hook is for use with a single element only. For
- <b>useFirstRender:</b> Returns true on first render; false otherwise.
- <b>useToggle:</b> Provides a boolean toggle that does not persist between page reloads.
- <b>useIntersectionObserver:</b> Provides a hook API that wraps the IntersectionObserver API. This hook is for use with a single element only. For
simplicity this is the recommended approach. If you have an extremely large number of objects to observe and want
to avoid creating an IntersectionObserver for each, refer to useIntersectionObserverArray to use a single observer
for multiple elements with a common root.
- <b>useIntersectionObserverArray:</b> Like useIntersectionObserver but for multiple elements.
- <b>useEvent:</b> Provides a wrapper around the EventListener API. Use the return value to define the event target.
- <b>useLazyLoad:</b> Provides a simple API for fetching data from a resource in batches.
- <b>useLocalStorage:</b> Provides access to local storage, with the additional option to update all usages of this hook
- <b>useIntersectionObserverArray:</b> Like useIntersectionObserver but for multiple elements.
- <b>useEvent:</b> Provides a wrapper around the EventListener API. Use the return value to define the event target.
- <b>useLazyLoad:</b> Provides a simple API for fetching data from a resource in batches.
- <b>useLocalStorage:</b> Provides access to local storage, with the additional option to update all usages of this hook
when local storage changes.
- <b>useClickOutside:</b> Provides a ref to attach to an HTML element and takes a callback function, and calls that
- <b>useClickOutside:</b> Provides a ref to attach to an HTML element and takes a callback function, and calls that
function when the user clicks anywhere outside the given element.
- <b>usePreferredScheme:</b> Listens for changes in the user's preferred scheme and returns it.
- <b>useDarkMode:</b> Similar to usePreferredScheme but allows setting the user scheme manually and automatically
updates it when the preferred scheme changes. Uses local storage to save the chosen scheme across reloads.
- ✅ <b>usePreferredScheme:</b> Listens for changes in the user's preferred scheme and returns it.
3 changes: 3 additions & 0 deletions examples/.eslintrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"extends": ["next/core-web-vitals", "next/typescript"]
}
36 changes: 36 additions & 0 deletions examples/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts
10 changes: 0 additions & 10 deletions examples/App.tsx

This file was deleted.

36 changes: 36 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import React from 'react';
import {useClickOutside} from "../../src";
import {useClickOutside} from "@/../src";

const DemoUseClickOutside = () => {
const Page = () => {
const ref = useClickOutside(e => (e.target as HTMLElement).style.backgroundColor = "black");
return (
<div style={{padding: '50px', backgroundColor: 'red'}}>
Expand All @@ -14,4 +16,4 @@ const DemoUseClickOutside = () => {
);
};

export default DemoUseClickOutside;
export default Page;
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import React from "react";
import useDarkMode from "../../src/hooks/useDarkMode";
import {useDarkMode} from "@/../src";

const DemoUseDarkMode = () => {
const Page = () => {
const { theme, setTheme, toggleTheme } = useDarkMode();
return (
<div style={{
Expand All @@ -16,4 +18,4 @@ const DemoUseDarkMode = () => {
);
};

export default DemoUseDarkMode;
export default Page;
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import React, {useState} from 'react';
import useDefer from "../../src/hooks/useDefer";
import {useDefer} from "@/../src";

const DemoUseDefer = () => {
const Page = () => {
const [value, setValue] = useState("");
const displayValue = useDefer(value, 500);
return (
Expand All @@ -18,4 +20,4 @@ const DemoUseDefer = () => {
);
};

export default DemoUseDefer;
export default Page;
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
"use client";

import React, {useEffect} from "react";
import { useEvent } from "../../src";
import { useEvent } from "@/../src";

const DemoUseEvent = () => {
const Page = () => {
const clickTarget = useEvent<HTMLDivElement>("click", (e) => {
const t = (e.target as HTMLDivElement);
t.style.backgroundColor = t.style.backgroundColor === "red" ? "green" : "red";
});

const windowTarget = useEvent("scroll", _ => console.log("scroll"));
const windowTarget = useEvent("scroll", () => console.log("scroll"));
useEffect(() => {
windowTarget(document);
}, [windowTarget]);
Expand All @@ -28,4 +30,4 @@ const DemoUseEvent = () => {
);
};

export default DemoUseEvent;
export default Page;
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
"use client";

import React, {useMemo, useState} from 'react';
import useFetch from "../../src/hooks/useFetch";
import {useFetch} from "@/../src";

const DemoUseFetch = () => {
const Page = () => {
const [value, setValue] = useState("");
const {loading, data} = useFetch<{
data: { title: string }[]
}>(
"https://api.artic.edu/api/v1/artworks/search?q=" + encodeURIComponent(value)
);
const list = useMemo(() => data?.data.map((e: any, i: number) => <li key={i}>{e.title}</li>), [data]);
const list = useMemo(() => data?.data.map((e: { title: string }, i: number) => <li key={i}>{e.title}</li>), [data]);
return (
<div>
<input
Expand All @@ -23,4 +25,4 @@ const DemoUseFetch = () => {
);
};

export default DemoUseFetch;
export default Page;
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import React, {useState} from 'react';
import useFirstRender from "../../src/hooks/useFirstRender";
import {useFirstRender} from "@/../src";

const DemoUseFirstRender = () => {
const Page = () => {
const isFirstRender = useFirstRender();
const [count, setCount] = useState(0);
if (isFirstRender) setCount(count => count + 1);
Expand All @@ -12,4 +14,4 @@ const DemoUseFirstRender = () => {
);
};

export default DemoUseFirstRender;
export default Page;
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import React from 'react';
import useIntersectionObserver from "../../src/hooks/useIntersectionObserver";
import {useIntersectionObserver} from "@/../src";

const DemoUseIntersectionObserver = () => {
const Page = () => {
const [ref, entry] = useIntersectionObserver<HTMLDivElement>();
return (
<>
Expand All @@ -22,4 +24,4 @@ const DemoUseIntersectionObserver = () => {
);
};

export default DemoUseIntersectionObserver;
export default Page;
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import React, {useMemo} from 'react';
import useIntersectionObserverArray from "../../src/hooks/useIntersectionObserverArray";
import {useIntersectionObserverArray} from "@/../src";

const DemoUseIntersectionObserverArray = () => {
const Page = () => {
const [ref, entries] = useIntersectionObserverArray<HTMLDivElement>();
const allInView = useMemo(
() => entries.length > 0 && entries.every(x => x?.isIntersecting),
Expand All @@ -19,18 +21,18 @@ const DemoUseIntersectionObserverArray = () => {
</h1>

<div ref={el => {
if (el) ref.current[0] = el;
if (el && ref.current) ref.current[0] = el;
}} style={{marginTop: "200vh", backgroundColor: "red"}}>
Hello, world!
</div>

<div ref={el => {
if (el) ref.current[1] = el;
if (el && ref.current) ref.current[1] = el;
}} style={{marginTop: "20vh", marginBottom: "200vh", backgroundColor: "yellow"}}>
Hello, world!
</div>
</>
);
};

export default DemoUseIntersectionObserverArray;
export default Page;
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
"use client";

import React, {ReactNode} from 'react';
import useLazyLoad from "../../src/hooks/useLazyLoad";
import {useLazyLoad} from "@/../src";

const DemoUseLazyLoad = () => {
const Page = () => {
const { loadMore, elements, reachedEnd, isFetching, clear } = useLazyLoad<ReactNode>(35, async (performedFetches) => {
const data = await fetch(`https://api.artic.edu/api/v1/artworks?page=${performedFetches + 1}&limit=10`);
const parsedData = await data.json();
Expand All @@ -21,4 +23,4 @@ const DemoUseLazyLoad = () => {
);
};

export default DemoUseLazyLoad;
export default Page;
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
"use client";

import React from "react";
import { useLocalStorage } from "../../src";
import { useLocalStorage } from "@/../src";

function ComponentOne() {
const [value, setValue] = useLocalStorage("value", { propagateChanges: true, listenForChanges: true });
const [value, setValue] = useLocalStorage("value", {
propagateChanges: true,
listenForChanges: true
});

return (
<div style={{ border: "1px solid black" }}>
<div>{value}</div>
<button onClick={() => setValue("Duck")}>Set value to "Duck"</button>
<button onClick={() => setValue("Duck")}>Set value to &quot;Duck&quot;</button>
</div>
);
}
Expand All @@ -16,14 +22,16 @@ function ComponentTwo() {
return (
<div style={{border: "1px solid black"}}>
<div>{value}</div>
<button onClick={() => setValue("Bear")}>Set value to "Bear"</button>
<button onClick={() => setValue("Bear")}>Set value to &quot;Bear&quot;</button>
</div>
);
}

function DemoUseLocalStorage() {
const [value, setValue] = useLocalStorage("value", { propagateChanges: true, listenForChanges: true });

function Page() {
const [value, setValue] = useLocalStorage("value", {
propagateChanges: true,
listenForChanges: true,
});
return (
<>
<ComponentOne/>
Expand All @@ -34,4 +42,4 @@ function DemoUseLocalStorage() {
);
}

export default DemoUseLocalStorage;
export default Page;
Loading

0 comments on commit 765dfa1

Please sign in to comment.