Youtube Project Practice #challenge

De fiecare dată când ai un bloc (indiferent că-i if, for sau function) încearcă să ieși din el cât mai devreme (happy path).

Este o schimbare minoră, dar îți permite să „execuți” mental codul ceva mai ușor.

const durationStamp = (duration) => {
    const durationArray = duration.match(/\d+/g).map(Number);
    const [hour, min, sec] = durationArray;

    if (durationArray.length === 1) {
        if (sec <= 9) {
            return `0:0${sec}`;
        }

        return `0:${sec}`;
    }

    if (durationArray.length === 2) {
        if (sec <= 9) {
            return `${min}:0${sec}`;
        }

        return `${min}:${sec}`;
    }

    if (durationArray.length === 3) {
        if (min <= 9 && sec <= 9) {
            return `${hour}:0${min}:0${sec}`;
        }

        if (min <= 9 && sec >= 9) {
            return `${hour}:0${min}:${sec}`;
        }

        if (min >= 9 && sec <= 9) {
            return `${hour}:${min}:0${sec}`;
        }

        return `${hour}:${min}:${sec}`;
    }
};

Dacă ai folosit CRA, poți importa automat fișiere din src.

- import { SidebarContext } from '../../../context/SidebarContext';
+ import { SidebarContext } from 'context/SidebarContext';
1 Like

Nu stiu de ce dar nu reusesc sa-l fac sa mearga cu absolute imports.
am adaugat jsconfig.json in /src dar primesc eroare, nu-mi gaseste componenta.

Edit: Am adaugat jsconfig in root cu setarile, am instalat eslint-plugin-import la adaugat in .eslintrc.json la “extends” si tot acolo am adaugat:

 "settings": {
    "import/resolver": {
      "node": {
        "paths": ["src"]
      }
    }
  },

Am testat acum și pare în regulă (cu jsconfig.json din repo):

{
  "compilerOptions": {
    "baseUrl": "src"
  }
}

Singura problemă fiind aici:

src\components\Header\RightMenu\RgtMenu.jsx
  Line 6:1:  `components/Buttons/SignIn/SignInButton` import should occur before import of `./RgtMenu.module.css`  import/order

I.e. import-urile relative ar trebui să fie ultimele:

import { BsThreeDotsVertical } from 'react-icons/bs';
- import styles from './RgtMenu.module.css';
- import SignInButton from 'components/Buttons/SignIn/SignInButton';
+ import SignInButton from 'components/Buttons/SignIn/SignInButton';
+ import styles from './RgtMenu.module.css';

O chestie observată și care îți va creea probleme in the long run: modularizarea componentelor.

În momentul de față ai componente de genul:

Header/Logo
Header/Navigation
Sidebar/SidebarOpen/SidebarBox1
Sidebar/SidebarOpen/SidebarBox2

Treaba asta aduce niște probleme:

  1. Legi componentele de layout. Dacă vrei să refolosești conținutul din SidebarBox1 în… footer, de exemplu, cum procedezi?
  2. Folosești state-ul în numele componentei. Componentele ar trebui să nu știe pe ce lume trăiesc (din același motiv ca mai sus: dacă vrei să folosești componenta în altă parte cum faci? Dacă vrei să le separi, ai putea să le spui widgets, de exemplu.
  3. Denumirea lucrurilor. Dacă ar fi să citești doar denumirea componentelor, ce nume ți se pare că este mai descriptiv? SidebarBox1 sau BestOf? SidebarBox2 sau YoutubeUpsells?

Altfel spus, primele două probleme le-ai putea rezolva dacă încetezi să te mai gândești „cum sunt așezate lucrurile în pagină” și pivotezi spre un raționament de genul „componenta asta nu trebuie să știe ce părinți are”


Funcția asta nu are ce căuta aici. Extrage-o într-un modul separat.

Adițional, aceeași problemă ca mai sus: dacă vrem să afișăm thumbs de video într-un carusel, de exemplu (sau related to, cum este în pagina de video) ce facem? Importăm HomePage/Videos în sidebar? :smiley:

Nu sunt foarte sigur de treaba asta, dar cred că aș extrage Video și Videos în două componente: una iterează, cealaltă doar afișează.


Ai făcut treabă bună la clonarea vizuală. Hai să vedem cum te descurci la interacțiuni:

  • categoriile de sus să filtreze ce se afișează jos (fă asta fără request-uri extra spre server, ca să exersezi interacțiunea între componente)
  • search-ul să conțină și sugestii (fă asta cu request-uri spre api, afișează un „loading” în zona thumbs dar ȘI în search box). (exersezi interacțiunea între componente și request-urile async)
  • meniul pentru fiecare video (atât ăsta de mai jos cât și „watch later” / „add to queue”)

image

1 Like

-Multumesc pentru sugestii Ionut. Am sa ma gandesc mai bine cum sa modularizez mai bine componentele probabil am sa fac restructurez acest proiesc dupa ce o sa il fac cat de cat functional.

-Functia am extras-o, este intr-un folder in src/helpers

-La partea de interactiune planul meu este urmatorul pentru ziua de azi si probabil ziua urmatoare.

—EDIT UPDATE:
-am facut update la folders structures, mai am sa umblu la Sidebar dar acolo trebuie sa dedic o parte buna de timp pentru ca am si ceva cod care se repeta si poate am sa refac tot ce tine de sidebar in viitor dupa ce fac site-ul mult mai interactiv.

Day 4:
Vreau ca videourile de pe prima pagina sa se deschida/randeze intr-o pagina separata ca sa poata fi urmarite iar in partea dreapta sa fie un sidebar cu related videos la acel video.

M-am gandit sa folosesc Routes pentru partea asta, am sa folosesc state in Videos.jsx iar atunci cand un user da click pe box-ul cu fiecare video eu vreau sa extrag id-ul acelui video care va fi id-ul pentru link dar si id-ul pentru video cand o sa-l randez in iframe.

1 Like

Salut, am nevoie de ajutorul vostru pentru ca nu resusesc sa rezolv o problema.

Am creact LinkContext, LinkProvider context/
Folosesc link context in components/Videos unde am o functie on click care imi ia id-ul link-ului

Bun pana aici toate bune si frumoase.

Eu ma gandeam ca in videos sa adaug ceva de genul:

<Link key={video.id} to={=/watch?v=${link}`}
    <div className={styles.videosContainer}>
      {videos.map((video) => (
        <div
          className={styles.videoContainer}
          tabIndex={0}
          role="button"
          onClick={() => handleId(video)}
          onKeyDown={handleId}
        >
          <div className={styles.thumbnailContainer}>
            <img
              className={styles.thumbnailImage}
              src={video.snippet.thumbnails.high.url}
              alt={video.snippet.title}
            />
            <div className={styles.timeStamp}>{durationStamp(video.contentDetails.duration)}</div>
          </div>
          <div className={styles.videoDetails}>
            <h3 className={styles.videoTitle}>{video.snippet.title}</h3>
          </div>
        </div>
</Link>

Cand cineva v-a da click pe container o sa se randeze componenta VideoPage un vreau sa apara un player-ul cu acel video pe care s-a dat click.

Problema este ca atunci cand dau click pe container ma duc la respectivul url path '/watch?v=eXamPleID" dar pe pagina respectiva (VideoaPage) nu primesc id-ul care este setat in context.

PS: M-am gandit sa iau id-ul folosind useLocation() de la router daca ma gandeam ca pot sa-l iau din state deodata ce-l setez pe onClick.

Sper ca intelegeti la ce ma refer.
Codul il am pe github daca vrea cineva sa arunce o privire si ma poate ajuta cu un sfat.

Eu recomand alta structura:

const durationStamp = (duration) => {
  const durationArray = duration.match(/\d+/g)?.map(Number);
  const durationLength = durationArray?.length;
  const [hour, min, sec] = durationArray;

  const underEqNineSec = sec <= 9;
  const underEqNineMinSec = min <= 9 && sec <= 9;
  const underEqNineMinOverEqNineSec = min <= 9 && sec >= 9;
  const overEqNineMinUnderEqNineSec = min >= 9 && sec <= 9;

  const durationString = {
    1: () => (underEqNineSec && `0:0${sec}`) || `0:${sec}`,
    2: () => (underEqNineSec && `${min}:0${sec}`) || `${min}:${sec}`,
    3: () => 
      (underEqNineMinSec && `${hour}:0${min}:0${sec}`) ||
      (underEqNineMinOverEqNineSec && `${hour}:0${min}:${sec}`) ||
      (overEqNineMinUnderEqNineSec && `${hour}:${min}:0${sec}`) ||
      `${hour}:${min}:${sec}`
  };

  return durationString[durationLength]();
};

Nu e foarte recomandat sa folosesti return-ul de mai multe ori daca nu cauti un cod cat mai optim posibil ci un cod cat mai clar si usor de testat/intretinut. Avantajul e ca daca ai un if complex nu trebuie sa descrii ce face cu comentarii fiindca ai extras logica in constante.

2 Likes

Cine nu recomandă asta? Isteții care fac code review dogmatic? Folosești break/continue în loops? Dacă da, de ce n-ai folosi return early?

Iar codul tău mi se pare mai greu de citit, mai ales ultima condiție…

2 Likes

Cred că ai nevoie de dynamic routing: React Router: Declarative Routing for React.js

ID-ul va fi apoi în props.

Uite aici un exemplu:


Referitor la metoda de mai sus: tu nu trebuie să implementezi ceva doar că zic eu sau altcineva. Folosește-ți propriul raționament, vezi dacă are sens ce ce unul sau celălalt, caută ce zic și alții. Nu aplica dogmatic chestii, încearcă să înțelegi.


Ceva îmi spune că ai învățat mai mult react în astea două zile decât ai învățat în zecile de ore petrecute în cursuri și tutoriale. Este? :smiley:

2 Likes

Lucrurile pe care le implementez au sens de aceea folosesc sugestile voastre, daca vad ca ceva nu are sens nu o sa le implementez.

Am invatat mult practicand, imi place sa practic si sa-mi bat capul dar parca ma simteam nepregatit sa incep pe cont propriu.

–Referitor la route am sa folosesc useLocation pentru link ma gandeam sa-l adaug in state si sa-l iau de acolo dar cred ca am implementat useContext gresit.

Astea se mai pot extrage in alte constante precum:

const durationStamp = (duration) => {
  const durationArray = duration?.match(/\d+/g)?.map(Number);
  const durationLength = durationArray?.length;
  const [hour, min, sec] = durationArray;

  const underEqNineSec = sec <= 9;
  const underEqNineMinSec = min <= 9 && sec <= 9;
  const underEqNineMinOverEqNineSec = min <= 9 && sec >= 9;
  const overEqNineMinUnderEqNineSec = min >= 9 && sec <= 9;

  const underEqNineSecFormat = (underEqNineSec && `0:0${sec}`) || `0:${sec}`;
  const underEqNineSecFormatLong =
    (underEqNineSec && `${min}:0${sec}`) || `${min}:${sec}`;
  const underEqNineMinSecFormat =
    underEqNineMinSec && `${hour}:0${min}:0${sec}`;
  const underEqNineMinOverEqNineSecFormat =
    underEqNineMinOverEqNineSec && `${hour}:0${min}:${sec}`;
  const overEqNineMinUnderEqNineSecFormat =
    overEqNineMinUnderEqNineSec && `${hour}:${min}:0${sec}`;

  const durationStringByLength = {
    1: () => underEqNineSecFormat,
    2: () => underEqNineSecFormatLong,
    3: () =>
      underEqNineMinSecFormat ||
      underEqNineMinOverEqNineSecFormat ||
      overEqNineMinUnderEqNineSecFormat ||
      `${hour}:${min}:${sec}`,
  };
  return duration && durationStringByLength[durationLength]();
};

Codul e mai greu de citit (daca e vorba de ceva simplu, daca e vorba de ceva complex te ajuta foarte mult sa ai totul declarat frumos) , dar scoate in evidenta logica ascunsa de if-uri, la testare e mai usor de vazut ca nu-i deloc usor de testat. Dupa cel mai important cu react e ca e declarativ, return-urile rapide si if-urile sunt cod imperativ, care ar trebui evitat cu react.

Bine pattern-ul nu e neaparat pentru codul de facut timestamp, dar e un pattern pentru logica complexa, in special cu redux si dynamic properties.

Day 4 UPDATE:
Aplicatia este acum putin mai interactiva, puteti da click pe video-uri si se va randa o pagina noua cu acel video. Pe acea pagina deocamdata este doar player-ul.

https://tender-fermat-e1769a.netlify.app/

Problema este ca nu pot sa accesez link-urile din browser asa:

EDIT: multumita lui @iamntz link-ul de mai jos este functional
https://tender-fermat-e1769a.netlify.app/watch?v=togmdDHG3Pw

Stiti cumva cum pot sa rezolv problema asta?

Ma gandesc sa adaug si titlul, like-uri etc. dar trebuie sa ma gandesc cum sa implementez asta avand toate datele de la video in Video/Videos. Ma gandesc sa folosesc useContext sper sa reusesc.

1 Like

https://sschannak.medium.com/netlify-and-react-router-1537aebe6256

3 Likes

Multumesc frumos pentru ajutor, am facut update si merge acum.

DAY 4 UPDATE:
Pe pagina videoclipului:
Am adaugat statisticile videoclipului: Titlu, Likes, Dislikes, Views
Inca nu am adaugat nici un styles acolo, urmeaza dar mi-a dat ceva batai de cap pana am reusit sa il implementez.

M-am gandit sa folosesc useContext dar nu am reusit sa stez state-ul de pe HomePage sa comunice cu Pagina Video-ului. In loc sa imi mai bat capul cu useContext am zis ca mai bine mai fac un API Request cate video pentru ca deja in PageVideo am o variabila cu ID-ul video-ului.

Ce urmeaza sa fac:

  • Incerc sa ma gandesc cum sa adaug related videos bazate pe id-ul video-ului
  • Dupa ce am related videos am sa ma ocup de UI mai mult
  • Edit: trebuie sa implementez search
  • Dupa ce termin cu UI-ul as vrea sa adaug Log In si sa fac functionale link-urile sau cel putin o parte din link-urile de pe prima pagina
1 Like

În Vue, se spune că răspunsul la întrebarea „când am nevoie de state management (vuex)” este „o să-ți dai seama singur”. Poate că se aplică și în React.

1 Like

@iamntz ma gandeam la redux. Am avut ceva “contact” cu redux din tutoriale bineinteles :laughing: si m-a lasat putin cam in ceata. Nu prea as vrea sa folosesc Redux la acest proiect, as vrea sa fac dupa acesta poate unul unde sa ma bazez mai mult pe Redux ca sa pot sa-l si invat.

Pentru acest proiect m-am gandit folosesc mai mult React Hooks si cred ca useContext() ar fi de ajuns doar ca posibil sa nu-l fi folosit eu cum trebuie. Sunt si destul de obosit ca sa-mi bat capul cu el acum cand pot alege varianta cu api request.

Update: Now Related videos on video page:
EDIT vad ca primesc ceva erori pe netlify. Incerc sa vad unde este problema.

https://tender-fermat-e1769a.netlify.app/watch?v=saqqO_uWxk4

Nimic frumos din punct de vedere vizual am vrut doar sa arat rezultatul.

–In VideoPage este adaugat tot codul doar pentru test urmeaza sa fac restructure si sa-l impart in doua componente VideoPlayer si RelatedVideos.

Cred ca am gasit problema: Eu primesc un array cu 10 obiecte dar unele videoclipuri nu au snippet in primul obiect.

Eu recomand zustand, e redux dar cum trebuia de la început. Dacă înțelegi cum să folosești useReducer-ul din react o să înțelegi și zustand

1 Like

Încearcă ori să pui video?.snippet?.title ori, mai bine, să pui un if înainte.

I.e. dacă este tot tag-ul, pui if. Dacă este doar atributul, folosește optional chaining.

1 Like

Am adaugat o conditie cat sa afisez paginile sa nu mai fie goale:

      <div className={styles.sidebarRelated}>
        {related.map((video) => (
          <div key={video.id} className={styles.videoContainer}>
            <div className={styles.videoThumbnail}>
              {video.snippet && (
                <img alt={video.snippet.id} src={video.snippet.thumbnails.medium.url} />
              )}
            </div>
          </div>
        ))}
      </div>

Se poate vedea pe unele pagini spatii goale unde acele obiecte nu au snippet.
Pana maine cred ca o sa las asa. Si dupa o sa adaug o conditie doar ca sa primesc obiecte cu snippet.

UPDATE: Related videos with link
Inca putina interactivitate, puteti daca click pe related videos si va face update la player cu video-ul pe care ati dat click.

–Trebuie sa ma gandesc cum dat update si la link-ul de la adresa pentru ca vreau sa se randeze si related videos atunci odata cu update-ul la clip.

EDIT: Video stats format, pentru asta folosesc Intl.NumberFormat

Like: 1.5M
Dislike: 15K
Views: 11,273,712

EDIT: API-ul nu mai raspunde, poate am depasit numarul de request-uri

–din cate vad related videos nu mai apar. posibil din cauza api-ului o sa verific maine, am terminat pe ziua de azi.
–am schimbat api-ul

Day 5
Lucrez putin la UI, am impartit in componente video page.
-quota de la youtube api se termina destul de repede cand lucrez la pagina, am incercat sa actualizez cu un nou api.
Daca exista probleme la afisarea paginii am sa pun mai jos un screenshot. Codul este updatat pe github
Imgur

  1. Salvează răspunsurile endpoint-uri în câteva JSON-uri. E.g. category.json, related.json etc.
  2. În loc să faci request-uri spre API-ul YT faci request-urile spre json-urile tale

Nu este nevoie să afișezi mereu date reale, poți folosi cu încredere mock data.

PS: nu uita că YT are două moduri de afișare a vide-ului: full width sau așa cum l-ai făcut tu, cu sidebar :wink: