Lecture 56 - Clean YouTube Project | React Router Dom and API Bug Fixing

Lecture 56 - Clean YouTube Project | React Router Dom and API Bug Fixing

Introduction

আমরা আজ এপিআই থেকে যে ডাটাগুলো আসছে সেগুলো লোকালস্টোরেজে সেইভ করবো। এরপর রিয়্যাক্ট রাউটার নিয়ে কাজ করবো।

Save Data in localStorage

আমরা আমাদের প্রজেক্ট ডিরেক্টরিতে utils নামের একটা ফোল্ডার নিবো এবং সেখানে Storage.js নামে একটা ফাইল নিবো। এরপর সেখানে আমরা একটা ক্লাস নিবো Storage নামে। আমাদের মূলত লাগবে দুইটা মেথড। একটা এপিআই থেকে যে ডাটা আসবে তা সেইভ করার জন্য, আরেকটি লোকালস্টোরেজ থেকে ডাটা ফেচ করার জন্য। চলুন তাহলে লিখে ফেলা যাক।

class Storage {
    save(key, data) {
        localStorage.setItem(key, JSON.stringify(data));
    }

    get(key) {
        const json = localStorage.getItem(key);
        return JSON.parse(json);
    }
}

const storage = new Storage();
export default storage;

এখানে প্রথমে আমরা ক্লাসের মধ্যে সেই মেথডগুলো বানিয়ে নিলাম। এরপর আমরা একটা অবজেক্ট আকারে ক্লাসটাকে নিলাম, সেই অবজেক্টকেই আমরা এক্সপোর্ট করে দিলাম। এটা অনেকটা সিঙ্গেলটোন প্যাটার্নের মতো কাজ করবে। সিঙ্গেলটোন প্যাটার্নের কনসেপ্টটা হলো যদি কোনো ক্লাসের ইনস্ট্যান্স না থাকে তবে একটা ইনস্ট্যান্স ক্রিয়েট করবে। আর যদি থেকে থাকে তবে নতুন কোনো ইনস্ট্যান্স ক্রিয়েট করবে না, সেই একটা ইনস্ট্যান্সই সবখানে ব্যবহৃত হবে। এক্ষেত্রেও আমরা শুধু একটা অবজেক্ট ক্রিয়েট করে সেটাকেই সব জায়গায় ব্যবহার করছি সিঙ্গেলটোন প্যাটার্নের কনসেপ্ট ফলো করে।

Using Storage class to usePlaylists hook

আমাদের এখন ভাবতে হবে এই স্টোরেজ ক্লাসকে আমরা কোথায় ব্যবহার করতে পারি। যখন প্রথম অ্যাপ্লিকেশন লোড নেয়, তখন সবার প্রথমে লোড নেয় আমাদের main.jsx ফাইল। অর্থাৎ App.jsx ফাইল প্রথমে লোড নিবে। আরো নির্দিষ্ট করে বলতে গেলে লোড নিচ্ছে usePlaylists হুক। তাহলে আমাদের স্টোরেজ ক্লাস লাগবে এই হুকের মধ্যে।

import { useEffect, useState } from 'react';
import getPlaylist from '../api';
import storage from '../utils/Storage';

const STORAGE_KEY = 'cy__playlist__state';

const INIT_STATE = {
    playlists: {},
    recentPlaylists: [],
    favorites: [],
};

const usePlaylists = () => {
    const [state, setState] = useState(INIT_STATE);

    const [error, setError] = useState('');
    const [loading, setLoading] = useState(false);

    useEffect(() => {
        const state = storage.get(STORAGE_KEY);
        if (state) {
            setState({ ...state });
        }
    }, []);

    useEffect(() => {
        if (state !== INIT_STATE) {
            storage.save(STORAGE_KEY, state);
        }
    }, [state]);

    // Other codes...
}

একদম সবার প্রথমে আমরা আমাদের ডাটা লোড করে সেটাকে স্টেটের মধ্যে রাখবো। এরপর যদি state !== INIT_STATE হয় তবে আমরা স্টোরেজে ডাটা সেই করবো। মূলত এতটুকুই আমাদের মডিফাই করার ছিল।

Installing React-Router

আমরা এবার চাইছি Start Tutotrial বাটনে ক্লিক করলে যেন নতুন একটি পেইজ ওপেন হয়। আমরা যখন এইচটিএমএল, সিএসএস নিয়ে কাজ করতাম তখন আমরা প্রতি পেইজের জন্য একটা করে এইচটিএমএল ফাইল বানিয়ে নিতাম। কিন্তু এখন আমরা ব্যবহার করছি সিঙ্গেল পেইজ অ্যাপ্লিকেশন। এখানে তো একটাই এইচটিএমএল ফাইল। তাহলে কিভাবে আমরা বিভিন্ন পেইজে যেতে পারি? সেক্ষেত্রে আমাদের ব্রাউজারকে বোকা বানিয়ে কাজটা করতে হবে। যখন আমরা একটা পেইজে থাকবো তখন ঐ এইচটিএমএল ফাইলে থাকবে ঐ পেইজের কনটেন্ট। যখন আমরা ভিন্ন পেইজে যাবো তখন ফাইল থেকে আগের পেইজের সব মুছে গিয়ে নতুন পেইজের কনটেন্টগুলো কন্ডিশনালি রেন্ডারিং হবে। এজন্য আমাদের ব্যবহার করতে হবে ব্রাউজারের স্টোরেজ, ন্যাভিগেশন ইত্যাদি এপিআই। কিন্তু তা আমাদের করার দরকার নেই। কারণ আমাদের জন্য React-Router অলরেডি সেই কাজ করে রেখেছে। আমরা শুধু তা ব্যবহার করে সহজেই রাউটিং করতে পারি। প্রথমে আমাদের এটা ইনস্টল করতে হবে।

yarn add react-router-dom

এবার আমরা আমাদের App.jsx এর মধ্যে রিয়্যাক্ট রাউটারের BrowserRouter, Routes এবং Route ব্যবহার করবো।

import { BrowserRouter, Routes, Route } from 'react-router-dom';
import CssBaseline from '@mui/material/CssBaseline';
import Navbar from './components/navbar';
import PlaylistCardItem from './components/playlist-card-item';
import usePlaylists from './hooks/usePlaylists';

const App = () => {
    const { playlists, error, getPlaylistById } = usePlaylists();

    const playlistArray = Object.values(playlists);

    return (
        <BrowserRouter>
            <CssBaseline />
            <Navbar getPlaylistById={getPlaylistById} />
            <Routes>
                <Route
                    path='/'
                    element={<HomePage playlistArray={playlistArray} />}
                />
                <Route
                    path='/player/:playlistId'
                    element={<PlayerPage playlists={playlists} />}
                />
                <Route path='*' element={<NotFound />} />
            </Routes>
        </BrowserRouter>
    );
};

Routes হলো সবগুলো Route এর সমষ্টি। Route এর মাধ্যমে আমরা কোন পেইজে যাবো সেটা নির্ধারণ করে দিতে পারি। এখানে দেখা যাচ্ছে / এই রাউটের মাধ্যমে আমরা হোমপেইজে যেতে পারবো, /player/:playlistId এর মাধ্যমে আমরা প্লেয়ারপেইজে যেতে পারবো। এবং এই দুই রাউট ছাড়া বাকি যা আছে সেগুলোর ক্ষেত্রে নট ফাউন্ড পেইজ শো করবে। এখানে * মানে হলো যতো রাউট বাকি আছে সব। এই কম্পোনেন্টগুলো আমরা পরে বানাচ্ছি।

আমরা যে রাউটগুলো ডিফাইন করেছিলাম এবার ন্যাভবারে আমরা যে লোগো ব্যবহার করেছিলাম সেখানে গিয়ে হোমপেইজের রাউট লিংক করে দিবো। সেটা আমরা করবো React-Router এর Link এর মাধ্যমে। সেই সাথে MUI এর Link কম্পোনেন্টও আমরা ব্যবহার করবো।

import { useState } from 'react';
import { Link as RouterLink } from 'react-router-dom';
import AppBar from '@mui/material/AppBar';
import Link from '@mui/material/Link';
import Box from '@mui/material/Box';
import Toolbar from '@mui/material/Toolbar';
import Typography from '@mui/material/Typography';
import { Button, Stack } from '@mui/material';
import { Container } from '@mui/system';
import PlaylistForm from '../playlist-form';

const Navbar = ({ getPlaylistById }) => {
    // Previous codes...
    return (
        <Box sx={{ flexGrow: 1 }}>
            <AppBar position='fixed' color='default' sx={{ py: 2 }}>
                <Container maxWidth={'lg'}>
                    <Toolbar>
                        <Stack sx={{ flexGrow: 1 }}>
                            <Link
                                to='/'
                                component={RouterLink}
                                sx={{ textDecoration: 'none', color: 'black' }}
                            >
                                <Typography variant='h4'>
                                    Clean Youtube
                                </Typography>
                            </Link>
                            <Link
                                href='https://stacklearner.com'
                                target={'_blank'}
                                sx={{ textDecoration: 'none', color: 'black' }}
                            >
                                <Typography variant='body1'>
                                    By Stack Learner
                                </Typography>
                            </Link>
                        </Stack>
                        <Button variant='contained' onClick={handleClickOpen}>
                            Add Playlist
                        </Button>
                        <PlaylistForm
                            open={open}
                            handleClose={handleClose}
                            getPlaylistId={getPlaylistId}
                        />
                    </Toolbar>
                </Container>
            </AppBar>
        </Box>
    );
};
export default Navbar;

আমরা এখানে MUI এর Link কম্পোনেন্ট ব্যবহার করেছি। সেখানে component প্রপ্স হিসেবে পাস করেছি রিয়্যাক্ট রাউটারের Link যেটাকে আমরা নামকরণ করেছি RouterLink হিসেবে। এখন যদি আমরা Clean Youtube লেখার উপর ক্লিক করি তা আমাদেরকে হোমপেইজে নিয়ে যাবে।

এবার আমরা PlaylistCardItem কম্পোনেন্টের Start Tutorial বাটনকে ওয়ার্কেবল করবো।

import * as React from 'react';
import { Link } from 'react-router-dom';
import Card from '@mui/material/Card';
import CardMedia from '@mui/material/CardMedia';
import CardContent from '@mui/material/CardContent';
import CardActions from '@mui/material/CardActions';
import Typography from '@mui/material/Typography';
import { Box, Button, Stack } from '@mui/material';
import { PlayCircleOutline } from '@mui/icons-material';
const PlaylistCardItem = ({
    playlistThumbnail,
    playlistTitle,
    channelTitle,
    playlistId,
}) => {
    return (
        <Card
            sx={{
                height: '100%',
                display: 'flex',
                flexDirection: 'column',
                margin: 1,
            }}
        >
            <CardMedia
                component='img'
                image={playlistThumbnail.url}
                alt={playlistTitle}
            />
            <CardContent>
                <Typography variant='h6' color='text.primary'>
                    {`${
                        playlistTitle.length > 50
                            ? playlistTitle.substr(0, 50) + '...'
                            : playlistTitle
                    }`}
                </Typography>
                <Typography variant='body2' color='text.secondary'>
                    {channelTitle}
                </Typography>
            </CardContent>
            <Box sx={{ flexGrow: 1 }}></Box>
            <CardActions disableSpacing>
                <Button to={`/player/${playlistId}`} component={Link}>
                    <Stack direction={'row'} spacing={1} alignItems={'center'}>
                        <PlayCircleOutline />
                        <Typography variant='body2' fontWeight={600}>
                            Start Tutorial
                        </Typography>
                    </Stack>
                </Button>
            </CardActions>
        </Card>
    );
};
export default PlaylistCardItem;

Creating HomePage Component

আমরা আপাতত App.jsx এর মধ্যে এই কম্পোনেন্টগুলো ক্রিয়েট করবো।

import { Grid, Typography } from '@mui/material';
import { Container } from '@mui/system';
import PlaylistCardItem from './components/playlist-card-item';

const HomePage = ({ playlistArray }) => {
    return (
        <Container maxWidth={'lg'} sx={{ my: 16 }}>
            {playlistArray.length > 0 && (
                <Grid container alignItems='stretch'>
                    {playlistArray.map((item) => (
                        <Grid item xs={12} md={6} lg={4} mb={2}>
                            <PlaylistCardItem
                                key={item.playlistId}
                                playlistId={item.playlistId}
                                playlistThumbnail={item.playlistThumbnail}
                                playlistTitle={item.playlistTitle}
                                channelTitle={item.channelTitle}
                            />
                        </Grid>
                    ))}
                </Grid>
            )}
        </Container>
    );
};

Creating PlayerPage Component

import { Typography } from '@mui/material';
import { useParams } from 'react-router-dom';
import { Container } from '@mui/system';

const PlayerPage = ({ playlists }) => {
    const { playlistId } = useParams();
    const current = playlists[playlistId];
    console.log('Current Course -->', current);

    if (!current) return;

    return (
        <Container maxWidth={'lg'} sx={{ my: 16 }}>
            <Typography variant='h2' align='center'>
                {current.playlistTitle}
            </Typography>
            <Typography variant='body1'>
                {current.playlistDescription}
            </Typography>
        </Container>
    );
};

Creating NotFound Component

import { Typography } from '@mui/material';
import { Container } from '@mui/system';

const NotFound = () => (
    <Container maxWidth={'lg'} sx={{ my: 16 }}>
        <Typography variant='h2' align='center'>
            404 Page Not Found
        </Typography>
    </Container>
);

Conclusion

মোটামুটি এই লেকচারের সারসংক্ষেপ এতটুকুই ছিল। পরবর্তী লেকচারে অ্যাপ্লিকেশন স্টেট এবং রিডাক্সের গুরুত্ব নিয়ে একটা গুরুত্বপূর্ণ আলোচনা আসতে চলেছে। আশা করি মিস করবেন না।

Source Code

এই লেকচারের সোর্স কোড এই লিংক এ পাবেন।