Coder Social home page Coder Social logo

cxfunkyman / react-game-hub Goto Github PK

View Code? Open in Web Editor NEW
0.0 1.0 0.0 242 KB

My first React app with TypeScript, game hub with an API fetching games from Rawg.io

Home Page: https://react-game-hub-inky.vercel.app

HTML 0.95% CSS 1.65% TypeScript 97.40%

react-game-hub's Introduction

1- Create the project with npm

1.1- npm create vite@latest ([email protected])    

1.2- project name: react-game-hub

1.3- framework: React

1.4- variant: Typescript

2- Go inside the 'react-game-hub'

2.1- npm i (to install all dependencies)    

3- Run the npm server

3.1- npm run dev

4- Initialize Git repository (optional)

4.1- git init

4.2- git add .

4.3- git commit -m "Initial commit"

5- Install chakra from chakra-ui.com

5.1- on framework guide select chakra for vite

5.2- npm i @chakra-ui/react @emotion/react @emotion/styled framer-motion

5.3- add a provider
    
    5.3.1- on main.tsx
    
    5.3.2- import { ChakraProvider } from '@chakra-ui/react'
    
    5.3.3- wrap app component with chakra provider:
        <React.StrictMode>
            <ChakraProvider>
                <App />
            </ChakraProvider>
        </React.StrictMode>,

6- If you want to customize your theme (optional)

6.1- import { extendTheme } from '@chakra-ui/react'

6.2- must look like this:

6.2.1- Extend the theme to include custom colors, fonts, etc
    const colors = {
        brand: {
            900: '#1a365d',
            800: '#153e75',
            700: '#2a69ac',
        },
    }
    const theme = extendTheme({ colors })

6.3- in the provider add the theme like this:
    
    6.3.1- <React.StrictMode>
                <ChakraProvider theme={theme}>
                    <App />
                </ChakraProvider>
            </React.StrictMode>,

7- add components from chakra ui

7.1- can be found in he same chakra vite page

7.2- for example adding a button

7.3- import { Button, ButtonGroup } from '@chakra-ui/react'

7.4- And return <Button colorScheme='blue'>Button</Button>;

7.5- can customize size with size='xs','sm', 'md', 'lg'

8- delete everything inside index.css, it's not going to be use

9- commit your changes to git

10- if you want to see you git log

10.1- git log --oneline

11- create a responsive layout

11.1- use Grid from chakra ui

11.2- <Grid templateAreas={`"nav nav" "aside main"`}></Grid>

11.3- next inside Grid lets add GridItem

11.4 - <GridItem area='nav' bg='coral'>Nav</GridItem>
       <GridItem area='aside'bg='gold'>Aside</GridItem>
       <GridItem area='main' bg='dodgerblue'>Main</GridItem>

12- Build a navigation bar

12.1- create folder components in scr folder

12.2- inside create NavBar.tsx

12.3- create an horizontal stack <HStack></HStack>  

12.4- <HStack>
        <Image src={logo} boxSize='60px' />
        <Text>NavBar</Text>
      </HStack>  

13- Any image like logo goes inside assets folder

13.1- import images like any other component

13.2- import logo from '../assets/Logo/logo.webp';

14- Implementing dark mode (chakra ui: color mode)

14.1- in scr folder create file theme.ts

14.2- import { extendTheme, ThemeConfig } from "@chakra-ui/react";

14.3- const config: ThemeConfig = {
      initialColorMode: 'dark'
      };

14.4- export default this extendTheme({config}) with a   constant

14.5- add to main.tsx this on chakra provider and the color scriptmode

14.6- <ChakraProvider theme={theme}>
        <ColorModeScript initialColorMode={theme.config.initialColorMode} />
        <App />
      </ChakraProvider>

14.7- before refresh go to inspect->application->local and delete chakra-ui-color-mode which is set to light

14.8- now refresh

15- Build color mode switch

15.1- in components create file ColorModeSwitch.tsx

15.2- import { HStack, Switch, Text, useColorMode } from '@chakra-ui/react'

15.3- const {toggleColorMode, colorMode} = useColorMode();

15.4- <HStack>
        <Switch 
            colorScheme='green'
            isChecked={colorMode === 'dark'} 
            onChange={toggleColorMode}/>
        <Text>Dark Mode</Text>
      </HStack>

15.5- now add to navbar.tsx

15.6- import ColorModeSwitch from './ColorModeSwitch';

15.7- <HStack>
        <Switch 
            colorScheme='green'
            isChecked={colorMode === 'dark'} 
            onChange={toggleColorMode}/>
        <Text>Dark Mode</Text>
      </HStack>

16- Install axios for HTTP request

16.1- npm i axios

16.2- let's create a custom service

16.3- create folder services in scr folder

16.4- create file api-client.ts

16.5- import axios from "axios";

16.6- axios.create({
        baseURL: 'Your URL goes here',
        params: {                
            key: 'Your api key goes here'
        }
       })

17- Fetching games form rawg api

17.1- go to rawg.io login or sign up

17.2- if still free ask for api key

17.3- copy the api key and paste it on api-clients.ts

17.4- go to rawg.io/apidocs click on read documentation on top of get API Key

17.5- click on 'get /games' and copy the link

17.6- inside components create file GameGrid.tsx

17.7- const [games, setGames] = useState ([]);

17.8- const [error, setError] = useState('');

17.9- import apiClient from '../services/api-client';

17.10- useEffect(() => {
        apiClient.get('games')
        .then(res => setGames())
       })

17.11- use typescript to define an interface that represents the shape of the response object

17.12- interface Game {
        id: number;
        name: string;
       }
       interface FetchGamesResponse {
        count: number;
        results: Game[];
       }

17.13- const [games, setGames] = useState<Game[]> ([]);

17.14- const [error, setError] = useState('');

17.15- useEffect(() => {
        apiClient.get<FetchGamesResponse>('games')
        .then(res => setGames(res.data.results))
        .catch(err => setError(err.message));
       }, [])

17.16- inside the return
       <>
          {error && <Text>{error}</Text>}
          <ul>
            {games.map(game => <li key={game.id}>{game.name}</li>)}
          </ul>
        </>

17.17- in App.tsx on main GridItem add <GameGrid />

18- Create a custom hook for fetching

18.1- inside scr create folder hooks

18.2- inside hooks create file useGames.ts

18.3- cut interfaces and useState, useEffect from GameGrid.tsx
    const [games, setGames] = useState<Game[]> ([]);
    const [error, setError] = useState('');
    useEffect(() => {
        apiClient
        .get<FetchGamesResponse>('games')
        .then(res => setGames(res.data.results))
        .catch(err => setError(err.message));
    }, []);

18.4- put in useGames.ts

18.5- in GameGrid.tsx add 
        const {games, error} = useGames();

19- Build game cards

19.1- in components create file GameCard.tsx

19.2- create a Props interface

19.3- interface Props {
        game: Game;
      }

19.4- return <Card>
                <Image src={game.background_image}/>
                <CardBody>
                    <Heading>{game.name}</Heading>
                </CardBody>
             </Card>

19.5- on GameGrid.tsx replace the ul for SimpleGrid

19.6- add the column and spacing size

19.7- <SimpleGrid 
        columns={{ sm: 1, md: 2, lg: 3, xl: 5 }} 
        spacing={10}>
        {games.map((game) => (
            <GameCard key={game.id} game={game} />
        ))}
      </SimpleGrid>

19.8- on GameCard.tsx add to Card 2 properties

19.9- <Card borderRadius={10} overflow='hidden'>

19.10- fix heading size with predefined size on chakra

19.11- <Heading fontSize='1xl'>

20- Displaying icons

20.1- add the parent platform property to each interface from the game json list on game interface from useGames.ts

20.2- create a new interface

20.3- interface Platform {
        id: number;
        name: string;
        slug: string;
      }

20.4- add to game interface

20.5- parent_platforms: { platform: Platform }[];

20.6- for testing add in text format under heading from GameCards.tsx

20.7- {game.parent_platforms.map(({platform}) => <Text>{ platform.name }</Text> )}

20.8- is time to render the icons after the test, for that let's use react-icons

20.9- npm i [email protected] at the time of this project was created maybe is higher now

20.10- to render the icon we need a mapping between the name of the platform and the icon, will be done in a different component

20.11- in component folder, add file PlatformIconList.tsx

20.12- create interface props

20.13- interface Props {
            platforms: Platform[];
       }

20.14- exported from useGames.ts

20.15- export interface Platform

20.16- on PlatformIconList.tsx

20.17- import { Platform } from '../hooks/useGames';

20.18- add the props to PlatformIconList

20.19- const PlatformIconList = ({ platforms }: Props) => {

20.20- on GameCard.tsx cut the text line added before

20.21- {game.parent_platforms.map(({platform}) => <Text>{ platform.name }</Text> )}

20.22- paste it on PlatformIconList.tsx as a return statement

20.23- as we don't need anymore game.parent_platforms and don't need to destructure {platform}, will be end like this

20.24- {platforms.map((platform) => <Text>{ platform.name }</Text> )}

20.25- now on GameCard.tsx add after heading

20.26- <PlatformIconList platforms={game.parent_platforms.map(p => p.platform)} /> 

20.27- just for testing we were using labels now is time to change it for icons

20.28- on PlatformIconList.tsx

20.29- import the icon library from react-icons/fa

20.30- import { 
            FaWindows, 
            FaPlaystation, 
            FaXbox, 
            FaApple, 
            FaLinux, 
            FaAndroid  
       } from 'react-icons/fa';
       import { MdPhoneIphone } from 'react-icons/md';
       import { SiNintendo } from 'react-icons/si';
       import { BsGlobe } from 'react-icons/bs';

20.31- create an icon map with key index signature property

20.32 - const iconMap: { [key: string]: IconType } = {
            pc: FaWindows,
            playstation: FaPlaystation,
            xbox: FaXbox,
            nintendo: SiNintendo,
            mac: FaApple,
            linux: FaLinux,
            android: FaAndroid,
            ios: MdPhoneIphone,
            web: BsGlobe
        }

20.33- remove the text component and  put icon component

20.34- to render the icon dynamically

20.35- return <HStack marginTop={'10px'}>
                    {platforms.map((platform) => (
                    <Icon as={iconMap[platform.slug]} color='gray.500'/>
                    ))}
               </HStack>

21- Display Critics Scores

21.1- in useGames.ts add to game interface

21.2- metacritic: number;

21.3- on component folder create

21.4- CriticScore.tsx

21.5- create a new interface props with score: number and add it

21.6- const CriticScore = ({ score }: Props)

21.7- create badge from chakra and render the score

21.8- <Badge>{ score }</Badge>

21.9- on GameCard.tsx after PlatformIconList add

21.10- <CriticScore score={game.metacritic}/>

21.11- inside a horizontal stack HStack wrap PlatformIconList and CriticScore

21.12- <HStack justifyContent='space-between'>

21.13- add to badge font-size and border-radius

21.14- fontSize='14px' paddingX={2} borderRadius='5px'

21.15- create a constant to change the color of the badge

21.16- let color = score >= 90 ? 'green' : score >= 80 ? 'yellow' : score < 80 ? 'red'  : '';

21.17 add to badge 

21.18- colorScheme={color}

22- Getting optimized images

22.1- from this site rawg.io were the api link is taken images are to big to show inside cards, but this api support cropping images on the fly for example one image url is: https://media.rawg.io/media/games/562/562553814dd54e001a541e4ee83a591c.jpg but if we add to this link crop/600/400 we'll get a small image
https://media.rawg.io/media/crop/600/400/games/562/562553814dd54e001a541e4ee83a591c.jpg

22.2- create a new service to modify the url

22.2- on services folder create file

22.3- image-url.ts

22.4- create const getCroppedImageUrl

22.5- create a target to store 'media/

22.6- create index to store
    
    22.6.1- url.indexOf(target) + target.length;

22.7- return a url.slice
    
    22.7.1- slice frm the beginning to index (0, index), then add the crop accepted by the api in this case 'crop/600/400/', then add the rest of the url
    
    22.7.2- return url.slice(0, index) + 'crop/600/400' + url.slice(index);

22.8- on GameCard.tsx in Image src call the getCroppedImageUrl and pass the game.background_image as a parameter
    
    22.8.1- <Image src={getCroppedImageUrl(game.background_image)}/>

23- Improving User Experience with Loading Skeletons

23.1- on useGames.ts add 
    23.1.1- const [isLoading, setIsLoading] = useState(false);

23.2- in the useEffect in the response add new statement before the apiClient setIsLoading to true and in .then and .catch to false
    
    23.2.1- setIsLoading(true);
            apiClient
    
    23.2.2- .then(res => {
                setGames(res.data.results);
                setIsLoading(false);
            })

23.3- the same in the catch
    
    23.3.1- .catch(err => {
                if (err instanceof CanceledError) return;
                setError(err.message);
                setIsLoading(false);
            });

23.4- and in the return add isLoading
    
    23.4.1- return { games, error, isLoading };

23.5- in components folder create file GameCardSkeleton.tsx

23.6- in the return statement create a card inside a skeleton with height 200px for test, then create card body inside skeleton text
    
    23.6.1- <Card width='300px' borderRadius={15} overflow='hidden'>
                <Skeleton height='200px' />
                <CardBody>
                    <SkeletonText />
                </CardBody>
            </Card>

23.7- now add to GameGrid.tsx
    
    23.7.1- const {games, error, isLoading} = useGames();
    
    23.7.2- create an Array
        
        23.7.3- const skeletons = [1, 2, 3, 4, 5, 6, 7, 8, 9];

23.8- add a new line to render the skeletons when the page is loading
    
    23.8.1- {isLoading && skeletons.map(skeleton => <GameCardSkeleton key={skeleton}/>)}

24- Refactor - Removing Duplicated Styles

24.1- on components folder create new file
    
    24.1.1- GameCardContainer.tsx

24.2- return <Box></Box>

24.3- apply style to box
    
    24.3.1- <Box width='300px' borderRadius={15}      overflow='hidden'></Box>

24.4- create interface props with children reactnode
    
    24.4.1- interface Props {
                children: ReactNode
            } 

24.5- add to GameCardContainer
    
    24.5.1- const GameCardContainer = ({ children }: Props) => {

24.6- add to Box children
    
    24.6.1- { children }  

24.7- add to GameGrid.tsx wrap the gamecardskeleton component and gamecard component
    
    24.7.1- <GameCardContainer>
                <GameCardSkeleton key={skeleton} />
            </GameCardContainer>)}
    
    24.7.2- <GameCardContainer>
                <GameCard key={game.id} game={game} />
            </GameCardContainer>

25- Fetching the Genres

25.1- on components create GenreList.tsx

25.2- on hooks create useGenre.ts

25.3- because is the same return as useGame.ts you can either copy and modify or start from zero.

25.4- as in usGame.ts create 2 interfaces the same
    
    25.4.1- interface Genre {
                id: number;
                name: string;
                slug: string;
            }
    
    25.4.2- interface FetchGenresResponse {
                count: number;
                results: Genre[];
            }

25.5- on GenreList.tsx add const genre
    
    25.5.1- const {genres} = useGenres();

25.6- return a ul with the mapping of genres
    
    25.6.1- {genres.map(genre => <li key={genre.id}>    {genre.name}</li>)}

25.7- add GenreList component to app component to the aside area
    
    25.8- <GridItem area='aside'>
                <GenreList />
          </GridItem>

26- Creating a Generic Data Fetching Hook

26.1- in hooks folder create a new file
    
    26.1.1- useData.ts

26.2- copy all the code inside useGenres.ts and paste it in the new file

26.3- delete Genre interface

26.4- change [genre, setGenre] to [data, setData]

26.5- instead of Genre us a generic type <T>
    
    26.5.1- const useData = <T>() => {
            const [data, setData] = useState<T[]> ([]);

26.6- add generic type to fetchGenreResponse and change name to fetchResponse
    
    26.6.1- interface FetchResponse<T> {
                count: number;
                results: T[];
            }

26.7- add endpoint as a parameter
    
    26.7.1- const useData = <T>(endpoint: string) => {

26.8- add to .get fetchGenres
    
    26.8.1- .get<FetchResponse<T>>(endpoint, { signal: controller.signal })

26.9- last return data error and isLoading

26.10- in GenreList.tsx change genre for data and add
    
    26.10.1- const { data } = useData<Genre>('genres');

26.11- in useGenre.ts delete the fetchGenreResponse interface and delete the body of useGenres instead add
    
    26.11.1- const useGenres =() => useData<Genre>('genres');

26.12- as the GameList.tsx don't have to know about the endpoint after step 26.11.1 in this file add
    
    26.12.1- const { data } = useGenres();    

26.13- same process for fetching games, and in the GameGrid.tsx fix the error for games change it for data

27- Displaying the Genres

27.1- in useGenre.ts add background_image to interface
    
    27.1.1- background_image: string;

27.2- on GenreList.ts change the ul for List and the li for ListItem, both from chakra

27.3- remove the genre.name, and add an horizontal stack to renders the name and the icons horizontally, for image fetch use the getcroppedimageurl
    
    27.3.1- <ListItem key={genre.id}      paddingY="5px">
                <HStack>
                    <Image
                        boxSize="32px"
                        borderRadius={10}
                        src={getCroppedImageUrl(genre.image_background)}
                    />
                    <Text>{genre.name}</Text>
                </HStack>
            </ListItem>

27.4- in the App.tsx add horizontal padding to aside area
    
    27.4.1- <GridItem area='aside' paddingX={5}>

27.5- remove the fix card width of 300px

28- Showing a Spinner

28.1- on GameList.tsx add const isLoading and error

28.2- add if statement to check if is loading is true show spinner and if error is tru return null
    
    28.2.1- const { data, isLoading, error } = useGenres();
            if (isLoading) return <Spinner />;
            if (error) return null;

29- Filtering Games by Genre

29.1- on GenreList.tsx change the Text for a button

29.2- in App.tsx create a state hook for the genre filtering
    
    29.2.1- const [selectedGenre, setSelectedGenre] = useState<Genre | null>(null);

29.3- on GenreList.tsx add a prop for passing a callback function
    
    29.3.1- interface Props {
                onSelectGenre: (genre: Genre) => void;
            }
    
    29.3.2- add the parameter
        
        29.3.2.1- GenreList = ({ onSelectGenre }: Props)
    
    29.3.3- add event to button
        
        29.3.3.1- onClick={() => onSelectGenre(genre)}

29.4- in App.tsx aside area add the new props
    
    29.4.1- <GenreList onSelectGenre={(genre) => setSelectedGenre(genre)} />

29.5- on GameGrid.tsx add a new props interface
    
    29.5.1- interface Props {
                onSelectGenre: (genre: Genre) => void;
            }
    
    29.5.2- add the props parameter

29.6- on GameGrid add the selectedGenre to useGames
    
    29.6.1- const { data, error, isLoading } = useGames(selectedGenre);

29.7- now we have to modify useGames.ts to accept the parameter
    
    29.7.1- const { data, error, isLoading } = useGames(selectedGenre);
    
    29.7.2- on useData.ts modify const useData and add a second parameter optional requestConfig? type AxiosRequestConfig
        
        29.7.3- (endpoint: string, requestConfig?: AxiosRequestConfig)
    
    29.8- on useGames.ts add from AxiosRequestConfig params
        
        29.8.1- const useGames = (selectedGenre: Genre | null) => useData<Game>('games', { params: { genres: selectedGenre?.id} })
    
    29.9- now on useData.ts on fetchResponse after signal spread the requestConfig object
        
        29.9.1- (endpoint, { signal: controller.signal, ...requestConfig })
    
    29.10- let's test and se on network that we're a not sending any request to the server because we set empty array to the useEffect, making it to request only once to the server, we need to pass new dependencies.
        
        29.10.1 return () => controller.abort();
                }, []);
    
    29.11- in the parameters after requestConfig add deps:
    with an any array
        
        29.11.1- deps?: any[]
    
    29.12- now add the dependency to the use effect return, might have an error because the deps might be null or undefined so we must set a ternary if
        
        29.12.1- return () => controller.abort();
                }, deps ? [...deps] : []);
    
    29.13- on useGames.ts after params add the dependency
        
        29.13.1- { params: { genres: selectedGenre?.      id} }, [selectedGenre?.id]);
    
    29.14- back to App.tsx and pass the selected genre to our GameGrid
        
        29.14.1- <GameGrid selectedGenre={selectedGenre}/>

30- Highlighting the Selected Genre

30.1- on GenreList.tsx add a new props selectedGenre type Genre or null
    
    30.1.1- selectedGenre: Genre | null;

30.2- add to GenreList parameters

30.3- in App.tsx add the parameter to GenreList

31- Building Platform Selector (build a platform dropdown list)

31.1- on components folder add new file PlatformSelector.tsx

31.2- add from chakra menu and menubutton righticon from bootstrap chevron down, add a menulist and many menuitem just to test our app
    
    31.2.1- <Menu>
                <MenuButton as={Button} rightIcon=  {<BsChevronDown />}>
                    Platforms
                </MenuButton>
                <MenuList>
                    <MenuItem>Item 1</MenuItem>        
                    <MenuItem>Item 2</MenuItem>        
                    <MenuItem>Item 3</MenuItem>        
                </MenuList>
            </Menu>
    
    31.3- add the platform selector on top of gamegrid in the app component main area
    
    31.4- let's render the menuitem dynamically, for that we'll need a different endpoint from rawg.io
    
    31.5- for platforms Rawg.io have 3 different endpoint
        
        1- Get a list of video game platforms
        2- Get a list of parent platforms
        3- Get details of the platform
    
    31.6- option number 2 will be used
        
        31.6.1- https://api.rawg.io/api/platforms/lists/parents
    
    31.7- in hooks folder create a new hook usePlatforms.ts
    
    31.8 the same as the other hooks but with platforms
        
        31.8.1- const usePlatforms = () => useData<Platform>('platforms/lists/parents');
    
    31.9- add error to usePlatforms
        
        31.9.1- const { data, error } = usePlatforms();
    
    31.10- add if statement in case of an error before return statement
        
        31.10.1- if (error) return null;
    
    31.11- render dynamically the MenuItem
        
        31.11.1- {data.map(platform => <MenuItem key={platform.id}>{platform.name}</MenuItem>)}

32- Filtering Games by Platform

32.1- using the same approach as before (filtering by genre)

32.2- in app component add new useState, the same as genre but for platforms
    
    32.2.1- const [selectedPlatform, setSelectedPlatform] = useState<Platform | null>(null);

32.3- on PlatformSelector.tsx add an interface prop
    
    32.3.1- interface Props{
                onSelectPlatform: (platform: Platform) => void;
            }

32.4- add parameter to PlatformSelector
    
    32.4.1- PlatformSelector = ({ onSelectPlatform }: Props)

32.5- to MenuItem add the onSelectPlatform
    
    32.5.1- {data.map(platform => <MenuItem onClick={() => onSelectPlatform(platform)} key={platform.id}>{platform.name}</MenuItem>)}

32.6- in GameGrid component interface props add selectedPlatform type Platform or null
    
    32.6.1- selectedPlatform: Platform | null;

32.7- app component on GameGrid main are add the selectedPlatform
    
    32.7.1- <GameGrid selectedPlatform={selectedPlatform} selectedGenre={selectedGenre}/>

32.8- the rest is the same process as genre filtering

33- Refactoring- Extracting a Query Object

33.1- till now we have too many variables and in the future we'll need some more like sort selector variables, searching an so on.
To fix that we'll pack related variables inside an object with the Query Object Pattern, that contains all the query we need to query the games, making the code cleaner and easy to understand

33.2- in the app component create an interface GameQuery, 2 parameters, one for genre type Genre or null, the other platform type Platform or null.
    
    33.2.1- interface GameQuery {
                genre: Genre | null;
                platform: Platform | null;
            }

33.3- delete the 2 useState for genre and platform and create a new useState with GameQuery interface
    
    33.3.1- interface GameQuery {
                genre: Genre | null;
                platform: Platform | null;
            }
    
    33.3.2- const [gameQuery, setGameQuery] = useState<GameQuery>({} as GameQuery);

33.4- now replace selectedGenre and selectedPlatform with gameQuery.genre and gameQuery.platform

33.5- replace setSelectedGenre and setSelectedPlatform with setGameQuery({ ...gameQuery ,genre }) and setGameQuery({ ...gameQuery ,platform })

33.5- now the same in the GameGrid component
    
    33.5.1- add gameQuery: GameQuery to props and delete the rest of the parameters
    
    33.5.2- replace selectedGenre and selectedPlatform with gameQuery
    
    33.5.3- now in useGames replace all the parameters with gameQuery
    
    33.5.4- now modify useGames.ts to accept the parameter
    
    33.5.5- add gameQuery to interface Props and delete the other 2, replace selectedGenre and selectedPlatform with gameQuery.genre and gameQuery.platform.
    
    33.5.6- for the dependencies, only need to pass gameQuery
    
    33.5.7- in the app component on main area GameGrid delete the passing objects and replace the with gameQuery={gameQuery}

34- Building Sort Selector

34.1- on component folder create a new file
    
    34.1.1- SortSelector.tsx

34.2- as SortSelector component is like PlatformSelector component we can copy the return statement and paste it inside the return of SortSelector component

34.3- add 6 menu item
    
    34.3.1- <MenuItem>Relevance</MenuItem>
            <MenuItem>Date Added</MenuItem>
            <MenuItem>Name</MenuItem>
            <MenuItem>Release Date</MenuItem>
            <MenuItem>Popularity</MenuItem>
            <MenuItem>Average Rating</MenuItem>

34.4- add SortSelector component to app component after PlatformSelector
    
    34.4.1- put SortSelector and PlatformSelector inside an horizontal stack HStack

35- Sorting Games

35.1- sort games use the same approach as filtering, the Rawg.io api gives a set of keywords for sorting(ordering):
    
    1- name
    2- released
    3- added
    4- created
    5- updated
    6- rating
    7- metacritic

35.2-  first lest create an array with values for rendering automatically the menuItem
    
    35.2.1- const sortOrders = [
                { value: '', label: 'Relevance' },
                { value: '-added', label: 'Date Added' },
                { value: 'name', label: 'Name' },
                { value: '-released', label: 'Released Date' },
                { value: '-metecritic', label: 'Popularity' },
                { value: '-rating', label: 'Average Rating' },
                { value: '-created', label: 'Created' },
                { value: '-updated', label: 'Updated' },
            ];

35.3- render the MenuItem dynamically
    
    35.3.1- <MenuList>
                {sortOrders.map((order) => (
                <MenuItem 
                    key={order.id} 
                    value={order.value}
                >
                    {order.label}
                </MenuItem>
                ))}
            </MenuList>

35.4- create interface props with select sort order, parameter sort order as string and return void
    
    35.4.1- interface Props {
                onSelectSortOrder: (sortOrder: string) => void;
            }

35.5- add props to SortSelector and implement onClick
    
    35.5.1- onClick={() => onSelectSortOrder(order.value)}

35.6- in app component add sortOrder type string to GameQuery interface

35.7- to SortSelector add the onSelectSortOrder event, this will make the app component to re-render
    
    35.8- onSelectSortOrder={(sortOrder) =>
          setGameQuery({ ...gameQuery, sortOrder })
          }

35.8- in the next render we'll pass the new game query object to GameGrid, go useGames hook and add to params 
    
    35.8.1- ordering: gameQuery.sortOrder
    
    35.8.2- return to app component and in GameGrid add gameQuery and pass gameQuery as an object

35.9- till now we'll have an issue with games with no image this will create the app to crash, so in image_url.ts on service folder add an if statement where if !url return an empty string

35.10.- now in the app component on SortSelector add sort order with gameQuery.sortOrder, to pass the name dynamically of the selected category 
    
    35.10.1- sortOrder={gameQuery.sortOrder}

35.11- add the sortOrder type string prop to SortSelector

35.12- create a new const to render the name of the selected filter
    
    35.12.1- const currentSortOrder = sortOrders.find(order => order.value === sortOrder);

35.13- add next to order by the current sort order
    
    35.13.1- Order by: {currentSortOrder?.label || 'Relevance'}

36- Handling Games without an Image

36.1- using an image to replace the image card when are not available, take one of your choice an put it inside assets folder

36.2- go to image-url.ts and import the image from assets
    
    36.2.1- import noImage from '../assets/NAME OF YOUR IMAGE';

36.3- now in the if statement done before to prevent crashes for cards with no image, instead or returning an empty string return noImage
    
    36.3.1- if (!url) return noImage;

37- Fixing the Issue with Chakra Menus

37.1- it seems we have a CSS issue noted in the console every time we make a filtering

37.2- to fix it replace HStack for Flex, but as Flex doesn't handle spacing we'll have to delete it

37.3- to fix this issue wrap one of the components inside a Box and give a marginRight or marginLeft depends on which one you selected and give a value of 5 
    
    37.3.1- <Box marginRight={5}>
                <PlatformSelector
                    selectedPlatform={gameQuery.platform}
                    onSelectPlatform={(platform) =>
                    setGameQuery({ ...gameQuery, platform })
                    }
                />
            </Box>

38- Building Search Input

38.1- in component folder create a new file SearchInput.tsx

38.2- create an input
    
    38.2.1- <Input borderRadius={25} placeholder='Search games...' variant='filled/>

38.3- in NavBar component add it to nav area after logo
    
    38.3.1- <SearchInput />

38.4- probably the dark mode label will be wrapped to fix that go to ColorModeSwitch component and add to the field Text a standard css attribute whiteSpace='nowrap'
    
    38.4.1- <Text whiteSpace='nowrap'>

38.5- on SearchInput component wrap the input inside an InputGroup and inside add InputLeftElement with children for bootstrap search icon
    
    38.5.1- <InputGroup>
                <InputLeftElement children={<BsSearch />} />
                <Input borderRadius={25} placeholder="Search games..." variant="filled" />
            </InputGroup>

39- Searching Games

39.1- in SearchInput component wrap everything inside a Form element.
note: select all the fields and bring the command pallet type wrap and then form.        

39.2- replace action for onSubmit and prevent default

    39.2.1- <form onSubmit={(event) => {
                event.preventDefault();
            }}>
                <InputGroup>
                    <InputLeftElement children={<BsSearch />} />
                    <Input borderRadius={25} placeholder="Search games..." variant="filled" />
                </InputGroup>
            </form>

39.3- now we can either use a ref hook or state hook, but because we have a simple input is easier to use a ref hook.

39.4- before the return statement add the useRef and add it to the input element
    
    39.4.1- const ref = useRef<HTMLInputElement>(null);

    39.4.2- <Input ref={ref} borderRadius={25} placeholder="Search games..." variant="filled" />

39.5- now inside form after event.preventdefault check if the ref is true (log into console to test if is working)

    39.5.1- for test: if (ref.current) console.log(ref.current.value); 

39.6- probably the size of the form is shorter to fixed go to index.css and make the form width 100%

    36.6.1- form {
                width: 100%;
            }

39.7- continuing with SearchInput component add a props interface with parameters onSearch with searchText type string returning void and add the props to SearchInput

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

39.8- replace the console log for onSearch

    39.8.1- for test: if (ref.current) onSearch(ref.current.value); 

39.9- now in app component add search text type string to the GameQuery interface.

39.10- in the NavBar component inside SearchInput element add onSearch

    39.10.1- <SearchInput onSearch={onSearch}/>

39.11- for now export the props from SearchInput and add it to the const NavBar as parameters

    39.11.1- const NavBar = ({ onSearch }: Props)

39.12- in the app component add it to nav bar in the nav area

    39.12.1- <NavBar onSearch={(searchText) => setGameQuery({ ...gameQuery, searchText})} />

39.13- now on useGames hooks add to params

    39.13.1- search: gameQuery.searchText

40- Adding a Dynamic Heading

40.1- on components folder create file GameHeading.tsx

40.2- we'll be returning a chakra Heading, you can return a react <H1> but we'll stick to chakra components

    40.2.1- <Heading as='h1'></Heading>

40.3- now to render the heading dynamically we should receive the game query object as a props in this component, define props gameQuery of type GameQuery and add the parameters

    40.3.1- interface Props {
                gameQuery: GameQuery
            }

    40.3.2- const GameHeading = ({ gameQuery }: Props) => {

40.4- before return statement create a heading const and add gameQuery platform and genre name, at the end Games

    40.4.1- const heading = `${gameQuery.platform?.name || ''} ${gameQuery.genre?.name || ''} Games`;

40.5- now add it to Heading

    40.5.1- <Heading as="h1">{heading}</Heading>

40.6- now in the app component and add our game heading just before main area flex component

    40.6.1- <GridItem area="main">
                <GameHeading gameQuery={gameQuery}/>
                <Flex paddingLeft={4} marginBottom={2}>

40.7- in the app component wrap the heading, platform filter, order by, and game cards into a chakra box component

    40.7.1- <Box paddingLeft={4}>
                <GameHeading gameQuery={gameQuery} />
                <Flex marginBottom={2}>
                    <Box marginRight={5}>
                        <PlatformSelector
                            selectedPlatform={gameQuery.platform}
                            onSelectPlatform={(platform) =>
                            setGameQuery({ ...gameQuery, platform })
                            }
                        />
                    </Box>
                    <SortSelector
                        sortOrder={gameQuery.sortOrder}
                        onSelectSortOrder={(sortOrder) =>
                        setGameQuery({ ...gameQuery, sortOrder })
                    }
                    />
                </Flex>
            </Box>

41- Cleaning Up the Genres

41.1- in the GenreList component at button add white space and set it to normal and text align and set to left

    41.1.1- whiteSpace='normal'
            textAlign='left'

41.2- now at Image put objectFit='cover'

41.3- now let's add a heading to the  genre list and wrap everything inside a container

    41.3.1- <Heading fontSize="2xl"     marginBottom={3}>
                Genres
            </Heading>

    41.3.2 <></>

42- Cleaning Up the Game Cards

42.1- let's move the game console icons and the rating on top of the games name

42.2- in the gameCards component move the heading after the horizontal stack

42.3- add a marginBottom of 3

42.4- let's add more space between cards, on GameGrid component and change spacing to 6 and change the columns xl from 5 to 4

43- Adding Emojis

43.1- open useGames.tsx from hooks folder and add a new game property, rating_top of type number

43.2- use the emoji of your like to put in the app

43.3- let's add a new component Emoji.tsx

43.4- we'll receive the rating as a prop, add a interface prop with rating of type number and add the props parameter

43.5- we're going to render the rating base on rating score, we're going to use a map object instead of multiples if statement, but first import the emoji images

43.6- add if statement for if less than 3 return null

43.7- create an emoji mapping for rating 

    1- 3 with src meh alt good 
    2- 4 with src thumbsUp alt Recommended 
    3- 5 with src bullsEye alt Recommended

    43.7.1- const emojiMap: { [key: number]: ImageProps } = {
                3: { src: meh , alt: 'Good'},
                4: { src: thumbsUp , alt: 'Recommended'},
                5: { src: bullsEye , alt: 'Exceptional'}
            }

43.8- at the end we return an image

    43.8.1- <Image { ...emojiMap[rating] } boxSize='25px' />

43.9- now add it to GameCard component, in the heading after the game name add the emoji

    43.9.1- <Heading fontSize="1xl">{game.name}<Emoji rating={game.rating_top} /></Heading>


43.10- to render the size if the emoji icons have different size go to emoji component.
in the emoji app to every parameter add a boxSize parameter and delete boxSize from Image component

    43.10.1- const emojiMap: { [key: number]: ImageProps } = {
                    3: { src: meh , alt: 'Good', boxSize: '25px'},
                    4: { src: thumbsUp , alt: 'Recommended', boxSize: '25px'},
                    5: { src: bullsEye , alt: 'Exceptional', boxSize: '30px'}
                }

43.11- in the image add a desire margin top     

    43.11.1- <Image { ...emojiMap[rating] } marginTop={2}/>

44- Shipping Static Data

44.1- when the app was created we render skeleton game cards for games and spinner for genres.
genres hardly change so we can make it static data and include it with the app
that way we don't have to send unnecessary request to the server for some data that won't change over time
the data will be available right away and don't need to wait for spinner

44.2- open up dev tools in networks (right click inside browser, network) in the genre request click, and select preview and copy the results

44.3- on scr create a folder data and create a file genres.ts

44.4- export default and paste the results

44.5- go to useGenres.ts, in const useGenres delete the use data and add the new parameters data set to null, isLoading set to false, error set to null

    44.5.1- ({ data: null, isLoading: false, error: null })

44.6- import genres, from data folder, set data to genres

44.7- go to GenreList component

45- Customizing the Chakra Theme

45.1- on theme.ts add a new property colors object with a gary object and map it from different values like 50, 100, 200, to 900

    45.1.1- const theme = extendTheme({
                config,
                colors: {
                    gray: {
                        50: '#f9f9f9',
                        100: '#ededed',
                        200: '#d3d3d3',
                        300: '#b3b3b3',
                        400: '#a0a0a0',
                        500: '#898989',
                        600: '#6c6c6c',
                        700: '#202020',
                        800: '#121212',
                        900: '#111'
                    }
                }
            });

45.2- you can search for the colors in different web sites, the base color for Rawg.io is #121212

    1- Themera
        1.1- https://themera.vercel.app/
    
    2- Smart Swatch (favorite)
        2.1- https://smart-swatch.netlify.app/
    
    3- Coolors
        3.1- https://coolors.co/d2f1e4-fbcaef-f865b0-e637bf-48304d
    
    4- Palx
        4.1- https://palx.jxnblk.com/

46- Refactoring Game Grid

46.1- in the Gamegrid component we have the error or we have the data.

46.2- it will be better to put the error outside and return just the SimpleGrid, lets delete the error element and the wrap and before the return statement create the if

46.2.1- if (error) return <Text>{error}</Text>;

47- Building for Production

47.1- we're ready to deploy our application but first we have to build it locally to make sure we don't have any issues, because is faster, safe and easily to discover and fix error locally than wait for deployment to finish

47.2- in the terminal type

    47.2.1- npm run build

47.3- if we have an error in build time typescript compiler will notify before building

47.4- shortcut ctrl+shift+p click on build, nets npm build
or ctrl + f9 all in windows

47.5- in the folder dist, everything all of our assets, images as well all the javascript and all the css files are include it in this folder

47.6- this is what we'll deploy as a production

48- Deploying to Vercel

48.1- we'll deploy at Vercel 

    48.1.1- https://vercel.com/

48.2- we need to install Vercel

    48.2.1- npm i -g vercel

    48.2.2- just type vercel

48.3- now connect the app to your github repository

react-game-hub's People

Contributors

cxfunkyman avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.