Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(docs): select dx #4078

Merged
merged 5 commits into from
Nov 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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