Skip to content

Commit

Permalink
refactor(docs): select dx (#4078)
Browse files Browse the repository at this point in the history
* refactor(docs): select dx

* refactor(docs): select dx

* refactor(docs): select dx

* refactor(docs): select dx

* refactor(docs): select dx
  • Loading branch information
wingkwong authored Nov 27, 2024
1 parent ce610d7 commit 54b4a14
Show file tree
Hide file tree
Showing 66 changed files with 2,936 additions and 2,541 deletions.
98 changes: 98 additions & 0 deletions apps/docs/content/components/select/async-loading-items.raw.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import {Select, SelectItem} from "@nextui-org/react";
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";

export function usePokemonList({fetchDelay = 0} = {}) {
const [items, setItems] = React.useState([]);
const [hasMore, setHasMore] = React.useState(true);
const [isLoading, setIsLoading] = React.useState(false);
const [offset, setOffset] = React.useState(0);
const limit = 10; // Number of items per page, adjust as necessary

const loadPokemon = async (currentOffset) => {
const controller = new AbortController();
const {signal} = controller;

try {
setIsLoading(true);

if (offset > 0) {
// Delay to simulate network latency
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
}

let res = await fetch(
`https://pokeapi.co/api/v2/pokemon?offset=${currentOffset}&limit=${limit}`,
{signal},
);

if (!res.ok) {
throw new Error("Network response was not ok");
}

let json = await res.json();

setHasMore(json.next !== null);
// Append new results to existing ones
setItems((prevItems) => [...prevItems, ...json.results]);
} catch (error) {
if (error.name === "AbortError") {
// eslint-disable-next-line no-console
console.log("Fetch aborted");
} else {
// eslint-disable-next-line no-console
console.error("There was an error with the fetch operation:", error);
}
} finally {
setIsLoading(false);
}
};

React.useEffect(() => {
loadPokemon(offset);
}, []);

const onLoadMore = () => {
const newOffset = offset + limit;

setOffset(newOffset);
loadPokemon(newOffset);
};

return {
items,
hasMore,
isLoading,
onLoadMore,
};
}

export default function App() {
const [isOpen, setIsOpen] = React.useState(false);
const {items, hasMore, isLoading, onLoadMore} = usePokemonList({fetchDelay: 1500});

const [, scrollerRef] = useInfiniteScroll({
hasMore,
isEnabled: isOpen,
shouldUseLoader: false, // We don't want to show the loader at the bottom of the list
onLoadMore,
});

return (
<Select
className="max-w-xs"
isLoading={isLoading}
items={items}
label="Pick a Pokemon"
placeholder="Select a Pokemon"
scrollRef={scrollerRef}
selectionMode="single"
onOpenChange={setIsOpen}
>
{(item) => (
<SelectItem key={item.name} className="capitalize">
{item.name}
</SelectItem>
)}
</Select>
);
}
108 changes: 108 additions & 0 deletions apps/docs/content/components/select/async-loading-items.raw.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React from "react";
import {Select, SelectItem} from "@nextui-org/react";
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";

export type Pokemon = {
name: string;
url: string;
};

export type UsePokemonListProps = {
/** Delay to wait before fetching more items */
fetchDelay?: number;
};

export function usePokemonList({fetchDelay = 0}: UsePokemonListProps = {}) {
const [items, setItems] = React.useState<Pokemon[]>([]);
const [hasMore, setHasMore] = React.useState(true);
const [isLoading, setIsLoading] = React.useState(false);
const [offset, setOffset] = React.useState(0);
const limit = 10; // Number of items per page, adjust as necessary

const loadPokemon = async (currentOffset: number) => {
const controller = new AbortController();
const {signal} = controller;

try {
setIsLoading(true);

if (offset > 0) {
// Delay to simulate network latency
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
}

let res = await fetch(
`https://pokeapi.co/api/v2/pokemon?offset=${currentOffset}&limit=${limit}`,
{signal},
);

if (!res.ok) {
throw new Error("Network response was not ok");
}

let json = await res.json();

setHasMore(json.next !== null);
// Append new results to existing ones
setItems((prevItems) => [...prevItems, ...json.results]);
} catch (error) {
if (error.name === "AbortError") {
// eslint-disable-next-line no-console
console.log("Fetch aborted");
} else {
// eslint-disable-next-line no-console
console.error("There was an error with the fetch operation:", error);
}
} finally {
setIsLoading(false);
}
};

React.useEffect(() => {
loadPokemon(offset);
}, []);

const onLoadMore = () => {
const newOffset = offset + limit;

setOffset(newOffset);
loadPokemon(newOffset);
};

return {
items,
hasMore,
isLoading,
onLoadMore,
};
}
export default function App() {
const [isOpen, setIsOpen] = React.useState(false);
const {items, hasMore, isLoading, onLoadMore} = usePokemonList({fetchDelay: 1500});

const [, scrollerRef] = useInfiniteScroll({
hasMore,
isEnabled: isOpen,
shouldUseLoader: false, // We don't want to show the loader at the bottom of the list
onLoadMore,
});

return (
<Select
className="max-w-xs"
isLoading={isLoading}
items={items}
label="Pick a Pokemon"
placeholder="Select a Pokemon"
scrollRef={scrollerRef}
selectionMode="single"
onOpenChange={setIsOpen}
>
{(item) => (
<SelectItem key={item.name} className="capitalize">
{item.name}
</SelectItem>
)}
</Select>
);
}
178 changes: 3 additions & 175 deletions apps/docs/content/components/select/async-loading-items.ts
Original file line number Diff line number Diff line change
@@ -1,184 +1,12 @@
const usePokemonListTs = `export type Pokemon = {
name: string;
url: string;
};
export type UsePokemonListProps = {
/** Delay to wait before fetching more items */
fetchDelay?: number;
};
export function usePokemonList({fetchDelay = 0}: UsePokemonListProps = {}) {
const [items, setItems] = React.useState<Pokemon[]>([]);
const [hasMore, setHasMore] = React.useState(true);
const [isLoading, setIsLoading] = React.useState(false);
const [offset, setOffset] = React.useState(0);
const limit = 10; // Number of items per page, adjust as necessary
const loadPokemon = async (currentOffset: number) => {
const controller = new AbortController();
const {signal} = controller;
try {
setIsLoading(true);
if (offset > 0) {
// Delay to simulate network latency
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
}
let res = await fetch(
\`https://pokeapi.co/api/v2/pokemon?offset=\${currentOffset}&limit=\${limit}\`,
{signal},
);
if (!res.ok) {
throw new Error("Network response was not ok");
}
let json = await res.json();
setHasMore(json.next !== null);
// Append new results to existing ones
setItems((prevItems) => [...prevItems, ...json.results]);
} catch (error) {
if (error.name === "AbortError") {
console.log("Fetch aborted");
} else {
console.error("There was an error with the fetch operation:", error);
}
} finally {
setIsLoading(false);
}
};
React.useEffect(() => {
loadPokemon(offset);
}, []);
const onLoadMore = () => {
const newOffset = offset + limit;
setOffset(newOffset);
loadPokemon(newOffset);
};
return {
items,
hasMore,
isLoading,
onLoadMore,
};
}
`;

const usePokemonList = `export function usePokemonList({fetchDelay = 0} = {}) {
const [items, setItems] = React.useState([]);
const [hasMore, setHasMore] = React.useState(true);
const [isLoading, setIsLoading] = React.useState(false);
const [offset, setOffset] = React.useState(0);
const limit = 10; // Number of items per page, adjust as necessary
const loadPokemon = async (currentOffset) => {
const controller = new AbortController();
const {signal} = controller;
try {
setIsLoading(true);
if (offset > 0) {
// Delay to simulate network latency
await new Promise((resolve) => setTimeout(resolve, fetchDelay));
}
let res = await fetch(
\`https://pokeapi.co/api/v2/pokemon?offset=\${currentOffset}&limit=\${limit}\`,
{signal},
);
if (!res.ok) {
throw new Error("Network response was not ok");
}
let json = await res.json();
setHasMore(json.next !== null);
// Append new results to existing ones
setItems((prevItems) => [...prevItems, ...json.results]);
} catch (error) {
if (error.name === "AbortError") {
console.log("Fetch aborted");
} else {
console.error("There was an error with the fetch operation:", error);
}
} finally {
setIsLoading(false);
}
};
React.useEffect(() => {
loadPokemon(offset);
}, []);
const onLoadMore = () => {
const newOffset = offset + limit;
setOffset(newOffset);
loadPokemon(newOffset);
};
return {
items,
hasMore,
isLoading,
onLoadMore,
};
};`;

const App = `import {Select, SelectItem} from "@nextui-org/react";
import {useInfiniteScroll} from "@nextui-org/use-infinite-scroll";
import {usePokemonList} from "./usePokemonList";
export default function App() {
const [isOpen, setIsOpen] = React.useState(false);
const {items, hasMore, isLoading, onLoadMore} = usePokemonList({fetchDelay: 1500});
const [, scrollerRef] = useInfiniteScroll({
hasMore,
isEnabled: isOpen,
shouldUseLoader: false, // We don't want to show the loader at the bottom of the list
onLoadMore,
});
return (
<Select
className="max-w-xs"
isLoading={isLoading}
items={items}
label="Pick a Pokemon"
placeholder="Select a Pokemon"
scrollRef={scrollerRef}
selectionMode="single"
onOpenChange={setIsOpen}
>
{(item) => (
<SelectItem key={item.name} className="capitalize">
{item.name}
</SelectItem>
)}
</Select>
);
}`;
import App from "./async-loading-items.raw.jsx?raw";
import AppTs from "./async-loading-items.raw.tsx?raw";

const react = {
"/App.jsx": App,
"/usePokemonList.js": usePokemonList,
};

const reactTs = {
"/App.tsx": App,
"/usePokemonList.ts": usePokemonListTs,
"/App.tsx": AppTs,
};

export default {
Expand Down
Loading

0 comments on commit 54b4a14

Please sign in to comment.