Skip to content

Commit

Permalink
Merge pull request #1 from merzainc/fix-state-issues
Browse files Browse the repository at this point in the history
Fix: consolidate gameQuery state using zustand
  • Loading branch information
Mataga Believe authored Dec 31, 2023
2 parents b9d9ce2 + 4e820e3 commit 0cd81e0
Show file tree
Hide file tree
Showing 13 changed files with 83 additions and 95 deletions.
Binary file modified bun.lockb
Binary file not shown.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.12.0",
"tailwind-merge": "^2.2.0"
"tailwind-merge": "^2.2.0",
"zustand": "^4.4.7"
},
"devDependencies": {
"@types/react": "^18.2.43",
Expand Down
43 changes: 7 additions & 36 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,28 +1,14 @@
import { useState } from 'react';
import GameGrid from './components/GameGrid';
import GameHeading from './components/GameHeading';
import GenreList from './components/GenreList';
import NavBar from './components/NavBar';
import PlatformSelector from './components/PlatformSelector';
import { Genre } from './hooks/useGenres';
import { Platform } from './hooks/useGames';
import SortSelector from './components/SortSelector';
import GameHeading from './components/GameHeading';

export interface GameQuery {
genre: Genre | null;
platform: Platform | null;
sortOrder: string;
searchText: string;
}

const App = () => {
const [gameQuery, setGameQuery] = useState({} as GameQuery);

return (
<div className="isolate">
<NavBar
onSearch={(searchText) => setGameQuery({ ...gameQuery, searchText })}
/>
<NavBar />
<div className="px-6 lg:px-8">
<div className="mx-auto flex items-start gap-x-12 ">
<aside className="hidden max-h-[calc(100dvh-theme(spacing.16))] w-[220px] shrink-0 overflow-y-auto pb-16 pt-12 lg:block">
Expand All @@ -33,32 +19,17 @@ const App = () => {
</h3>
</li>
<li className="mt-6">
<GenreList
selectedGenre={gameQuery.genre}
onSelectGenre={(genre) =>
setGameQuery({ ...gameQuery, genre })
}
/>
<GenreList />
</li>
</ul>
</aside>
<main className="min-w-0 lg:px-8 flex-1 gap-x-12 pb-16 pt-8 lg:pt-12">
<GameHeading gameQuery={gameQuery} />
<GameHeading />
<div className="flex items-center mb-6 space-x-3">
<PlatformSelector
selectedPlatform={gameQuery.platform}
onSelectPlatform={(platform) =>
setGameQuery({ ...gameQuery, platform })
}
/>
<SortSelector
sortOrder={gameQuery.sortOrder}
onSelectOrder={(sortOrder) =>
setGameQuery({ ...gameQuery, sortOrder })
}
/>
<PlatformSelector />
<SortSelector />
</div>
<GameGrid gameQuery={gameQuery} />
<GameGrid />
</main>
</div>
</div>
Expand Down
9 changes: 3 additions & 6 deletions src/components/GameGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,10 @@ import { Text } from './ui/text';
import useGames from '../hooks/useGames';
import GameCard from './GameCard';
import GameCardSkeleton from './GameCardSkeleton';
import { GameQuery } from '../App';
import useGameQueryStore from '../store';

interface Props {
gameQuery: GameQuery;
}

const GameGrid = ({ gameQuery }: Props) => {
const GameGrid = () => {
const gameQuery = useGameQueryStore((s) => s.gameQuery);
const { data, error, isLoading } = useGames(gameQuery);
const skeletons = [...Array(8).keys()].map((i) => i + 1);

Expand Down
16 changes: 8 additions & 8 deletions src/components/GameHeading.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { GameQuery } from '../App';
import useGenres from '../hooks/useGenres';
import useGameQueryStore from '../store';

interface Props {
gameQuery: GameQuery;
}
const GameHeading = () => {
const genreId = useGameQueryStore((s) => s.gameQuery.genreId);
const genre = useGenres().data.find((genre) => genre.id === genreId);
const platformId = useGameQueryStore((s) => s.gameQuery.platformId);
const platform = useGenres().data.find((genre) => genre.id === platformId);

const GameHeading = ({ gameQuery }: Props) => {
const heading = `${gameQuery.platform?.name || ''} ${
gameQuery.genre?.name || ''
} Games`;
const heading = `${platform?.name || ''} ${genre?.name || ''} Games`;
return (
<h2 className="text-2xl font-semibold my-5 text-default">{heading}</h2>
);
Expand Down
17 changes: 8 additions & 9 deletions src/components/GenreList.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import useGenres, { Genre } from '../hooks/useGenres';
import cn from '../lib/utils';
import getCroppedImage from '../services/image-url';
import useGameQueryStore from '../store';
import { Link } from './ui/link';
import cn from '../lib/utils';

interface Props {
onSelectGenre: (genre: Genre) => void;
selectedGenre: Genre | null;
}

const GenreList = ({ onSelectGenre, selectedGenre }: Props) => {
const GenreList = () => {
const { data } = useGenres();
const selectedGenreId = useGameQueryStore((s) => s.gameQuery.genreId);
const setSelectedGenreId = useGameQueryStore((s) => s.setGenreId);

return (
<ul
role="list"
Expand All @@ -19,10 +18,10 @@ const GenreList = ({ onSelectGenre, selectedGenre }: Props) => {
<li key={genre.id}>
<Link
href="#"
onClick={() => onSelectGenre(genre)}
onClick={() => setSelectedGenreId(genre.id)}
className={cn(
'relative inline-flex text-sm/6 gap-3 items-center hover:text-zinc-950 text-secondary dark:hover:text-white',
genre.id === selectedGenre?.id ? 'font-semibold' : 'font-normal'
genre.id === selectedGenreId ? 'font-semibold' : 'font-normal'
)}
>
<img
Expand Down
8 changes: 2 additions & 6 deletions src/components/NavBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import SearchBar from './SearchBar';
import { useTheme } from './ThemeProvider';
import { HiOutlineComputerDesktop, HiMoon } from 'react-icons/hi2';

interface Props {
onSearch: (searchText: string) => void;
}

const NavBar = ({ onSearch }: Props) => {
const NavBar = () => {
const { theme, setTheme } = useTheme();
return (
<div className="z-20 flex h-[60px] items-center lg:justify-between gap-4 border-b bg-default px-4 sm:px-6 lg:px-8 border-default">
Expand All @@ -16,7 +12,7 @@ const NavBar = ({ onSearch }: Props) => {
className="flex cursor-pointer mt-px max-md-gutters:mt-0 h-12"
/>

<SearchBar onSearch={(searchText) => onSearch(searchText)} />
<SearchBar />
<div className="flex items-center justify-center">
<button
onClick={() => {
Expand Down
17 changes: 8 additions & 9 deletions src/components/PlatformSelector.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import { HiChevronDown } from 'react-icons/hi2';
import {
Dropdown,
DropdownButton,
DropdownItem,
DropdownMenu,
} from '../components/ui/dropdown';
import { HiChevronDown } from 'react-icons/hi2';
import usePlatforms from '../hooks/usePlatforms';
import { Platform } from '../hooks/useGames';
import useGameQueryStore from '../store';

interface Props {
onSelectPlatform: (platform: Platform) => void;
selectedPlatform: Platform | null;
}

const PlatformSelector = ({ onSelectPlatform, selectedPlatform }: Props) => {
const PlatformSelector = () => {
const { data } = usePlatforms();
const setSelectedPlatformId = useGameQueryStore((s) => s.setPlatformId);
const platformId = useGameQueryStore((s) => s.gameQuery.platformId);
const selectedPlatform = data.find((platform) => platform.id === platformId);

return (
<Dropdown>
<DropdownButton outline>
Expand All @@ -25,7 +24,7 @@ const PlatformSelector = ({ onSelectPlatform, selectedPlatform }: Props) => {
{data.map((platform) => (
<DropdownItem
key={platform.id}
onClick={() => onSelectPlatform(platform)}
onClick={() => setSelectedPlatformId(platform.id)}
>
{platform.name}
</DropdownItem>
Expand Down
10 changes: 4 additions & 6 deletions src/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,18 @@ import { HiOutlineMagnifyingGlass } from 'react-icons/hi2';
import { Input } from '@headlessui/react';
import { useRef } from 'react';
import cn from '../lib/utils';
import useGameQueryStore from '../store';

interface Props {
onSearch: (searchText: string) => void;
}

const SearchBar = ({ onSearch }: Props) => {
const SearchBar = () => {
const setSearchText = useGameQueryStore((s) => s.setSearchText);
const ref = useRef<HTMLInputElement>(null);
return (
<div className="flex flex-1 lg:flex-initial gap-x-4 self-stretch lg:self-center lg:gap-x-6">
<form
className="relative flex flex-1"
onSubmit={(event) => {
event.preventDefault();
if (ref.current) onSearch(ref.current.value);
if (ref.current) setSearchText(ref.current.value);
}}
>
<label htmlFor="search-field" className="sr-only">
Expand Down
14 changes: 6 additions & 8 deletions src/components/SortSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,7 @@ import {
DropdownMenu,
DropdownItem,
} from './ui/dropdown';

interface Props {
onSelectOrder: (order: string) => void;
sortOrder: string;
}
import useGameQueryStore from '../store';

const sortOrders = [
{ value: '', label: 'Relevance' },
Expand All @@ -20,9 +16,11 @@ const sortOrders = [
{ value: '-rating', label: 'Average rating' },
];

const SortSelector = ({ onSelectOrder, sortOrder }: Props) => {
const SortSelector = () => {
const setSelectedOrderId = useGameQueryStore((s) => s.setSortOrder);
const sortOrder = useGameQueryStore((s) => s.gameQuery.sortOrder);
const currentSortOrder = sortOrders.find(
(order) => order.value === sortOrder
(order) => order.label === sortOrder
);

return (
Expand All @@ -34,7 +32,7 @@ const SortSelector = ({ onSelectOrder, sortOrder }: Props) => {
<DropdownMenu>
{sortOrders.map((order) => (
<DropdownItem
onClick={() => onSelectOrder(order.value)}
onClick={() => setSelectedOrderId(order.value)}
key={order.value}
value={order.value}
>
Expand Down
6 changes: 3 additions & 3 deletions src/hooks/useGames.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { GameQuery } from '../App';
import { GameQuery } from '../store';
import useData from './useData';

export interface Platform {
Expand All @@ -20,8 +20,8 @@ const useGames = (gameQuery: GameQuery) =>
'/games',
{
params: {
genres: gameQuery.genre?.id,
platforms: gameQuery.platform?.id,
genres: gameQuery.genreId,
platforms: gameQuery.platformId,
ordering: gameQuery.sortOrder,
search: gameQuery.searchText,
},
Expand Down
29 changes: 29 additions & 0 deletions src/store.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { create } from 'zustand';

export interface GameQuery {
genreId?: number;
platformId?: number;
sortOrder?: string;
searchText?: string;
}

interface GameQueryStore {
gameQuery: GameQuery;
setSearchText: (searchText: string) => void;
setGenreId: (genreId: number) => void;
setPlatformId: (platformId: number) => void;
setSortOrder: (sortOrder: string) => void;
}

const useGameQueryStore = create<GameQueryStore>((set) => ({
gameQuery: {},
setSearchText: (searchText) => set(() => ({ gameQuery: { searchText } })),
setGenreId: (genreId) =>
set(({ gameQuery }) => ({ gameQuery: { ...gameQuery, genreId } })),
setPlatformId: (platformId) =>
set((store) => ({ gameQuery: { ...store.gameQuery, platformId } })),
setSortOrder: (searchText) =>
set((store) => ({ gameQuery: { ...store.gameQuery, searchText } })),
}));

export default useGameQueryStore;
6 changes: 3 additions & 3 deletions vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react-swc'
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';

// https://vitejs.dev/config/
export default defineConfig({
plugins: [react()],
})
});

0 comments on commit 0cd81e0

Please sign in to comment.