Undgå Redux, byg et optimalt tilføjelsesvognssystem med UseReducer og useContext i React NEXT.js 😎

Hej ! Når det kommer til e-handelsapplikationer, er den ene ting, vi ikke kan undgå, statsadministration med "tilføj til kurv"-funktionalitet,
i dag vil jeg vise dig, hvordan du bygger en og også en optimal måde at gøre det på uden brug af Redux, som jeg ser folk bruger meget, det kommer sammen med en masse kedelplade, som du gerne vil undgå for enhver pris.
det handler om optimering 😉 lad os tage på vejen.

Mappestruktur

inde i pages/

Hjemmeside pages/index.js

din startside skulle se nogenlunde sådan ud for første gang

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'

export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Product Cart System</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
        <h1>this is our home page 🥺</h1>
    </div>
  )
}

 Konfigurer produkter

Lad os nu oprette en dummy-produkter for at kunne tilføje til vores
kurv

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'


const DummyProducts = [
  {
    id: 1,
    name: 'product1',
    price: 10,
    image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
    description: 'this is a dummy product description'
  },
  {
    id: 2,
    name: 'product2',
    price: 20,
    image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
    description: 'this is a dummy product description'

  },
  {
    id: 3,
    name: 'product3',
    price: 30,
    image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
    description: 'this is a dummy product description'

  },
  {
    id: 4,
    name: 'product4',
    price: 40,
    image: 'https://images.unsplash.com/photo-1518791841217-8f162f1e1131?ixlib=rb-1.2.1&ixid=eyJhcHBfaWQiOjEyMDd9&auto=format&fit=crop&w=800&q=60',
    description: 'this is a dummy product description'
  },
]
...

Opvarmning

godt, vi har sat en DummyProducts-variabel med 4 objekter i et array, men vi vil ikke se noget endnu, medmindre vi kortlægger hvert objekt og viser individuelt element i 'DummyProducts'-arrayet på vores skærm

...
const DummyProducts = [...] // contains a lot of data 😮‍💨


export default function Home() {
  return (
    <div className={styles.container}>
      <Head>
        <title>Product Cart System</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <h1 className={styles.heading}>Cart Quantity 🛒 (0)</h1>
      <div className={styles.products}>
        {DummyProducts.map(product => (
          <div className={styles.product} key={product.id}>
            <Image src={product.image} width={200} height={200} placeholder={'blur'} blurDataURL={product.image} />
            <h3 className={styles.name}>{product.name}</h3>
            <p className={styles.description}>{product.description}</p>
            <h4 className={styles.price}>${product.price}</h4>
            <button className={styles.addToCart}>Add to cart</button>
          </div>
        ))}
      </div>
    </div>
  )
}

lad os se, hvad vi har.

Ja til Next.js, når som helst vi bruger en ekstern url i den indbyggede næstes billedkomponent, skal vi udtrykkeligt angive
domænet for url'en i next.config.js fil, dette er
hvordan det gøres

inside the next.config.js file

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  images: {
    domains: ['images.unsplash.com'], // <--add this 
  },
}

module.exports = nextConfig

sådan er det simpelthen gjort 😁 , lad os nu se, hvad vi har på vores skærm

Hurra!! 🥳

Men vent, vores side ser grim ud, lad os tilføje lidt styling til den.

inde i styles/Home.module.css

.container {
  padding: 0 2rem;
}
.products{
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  grid-gap: 2rem;
  position: relative;
  z-index: 2;
  width: 70%;
  align-self: center;
  margin:0 auto;
  border:1px solid rgb(193, 193, 193);
  padding:30px;
  background-color: #fff;
  margin-top:100px;
}
.heading{
  font-size:2rem;
  font-weight:bold;
  text-align:center;
  margin-bottom:30px;
  padding:20px;
}
.name{
  font-size:1.5rem;
  font-weight:bold;
  text-align:center;
}
.description{
  font-size:0.9rem;
  text-align:center;
}
.price{
  font-size:1.2rem;
  font-weight:bold;
  text-align:center;
  color: green;
}
.addToCart{
  width:100%;
  padding:14px;
  background-color: #000000;
  color: white;
  border: none;
  cursor: pointer;
}
.addToCart:hover{
  background-color: rgb(69, 69, 69);
  color: #ffffff;
}

Lad os se, hvad vi har

Nu er det minimalt 😎

Lad os komme til den rigtige vare

vi er færdige med visualiseringsdelen
lad os komme til den rigtige vare

Opsætning af useContext

"useContext" hook bruges til at skabe fælles data, der kan tilgås i hele komponenthierarkiet uden at overføre rekvisitterne manuelt til hvert niveau.

til næste js går vi til roden af ​​vores applikation og sætter kontekst der, så der kan tilgås data i hele vores applikation.

inde i pages/_app.js

import '../styles/globals.css'
import React, { createContext } from 'react'

export const CartSystem = createContext()

function MyApp({ Component, pageProps }) {

  return (
    <CartSystem.Provider value={{}}>
      <Component {...pageProps} />
    </CartSystem.Provider>
  )
}

export default MyApp

ligesom dette er vores useContext sat til en eksporteret variabel CartSystem og har en klar Provider med udefineret værdi, og det er her, vi opsætter vores reduktions- og tilstandsværdi.

Opsætning af reduktions- og tilstandsobjektet

vi skal kun bruge én tilstand, som vil være cart
med startværdien af ​​et tomt array

import '../styles/globals.css'
import React, { createContext,useReducer } from 'react'

export const CartSystem = createContext()


const initailState = {
    cart: []
}

function MyApp({ Component, pageProps }) {

  const Reducers = ()=>{

  }

  const [state,dispatch] = useReducer(Reducers,initailState)

  return (
    <CartSystem.Provider value={{}}>
      <Component {...pageProps} />
    </CartSystem.Provider>
  )
}

export default MyApp

Nu er dette en opdatering af vores pages/_app.js file
strukturen af ​​Reducer funktion er blevet oprettet og en initialState objekt også.
useReducer hook returnerer to værdier , en dispatch og en state

Jeg vil slippe et link for at læse mere om useReducer Hook, hvis du er ny til det.

lad nu passere disse data som værdi gennem vores kontekst, så det
vores applikation kan få adgang til alle data overalt

...

 return (
    <CartSystem.Provider value={{state,dispatch}}>
      <Component {...pageProps} />
    </CartSystem.Provider>
  )

...

med dette kan vi få adgang til hvad der nogensinde er i tilstanden i enhver komponent i vores applikation

Adgang til tilstandsdata fra pages/index.js fil

for at få adgang til data fra vores produktside, skal vi bruge
useContext krog for at få fat i data, der kommer fra CartSystem.Provider i vores pages/_app.js fil

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'

import React,{useContext} from 'react'
import {CartSystem} from './_app'

const DummyProducts = [...]

export default function Home() {

  const {state,dispatch}  = useContext(CartSystem)

  return (
    <div className={styles.container}>
      <Head>
        <title>Product Cart System</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <h1 className={styles.heading}>Cart Quantity 🛒 (0)</h1>
      <div className={styles.products}>
        {DummyProducts.map(product => (

          ...


javascript
vi importerer useContext og CartSystem fra react og _app.js henholdsvis.
så griber vi dataene ved hjælp af useContext hook ved at videregive
CartSystem som et argument i useContext hook og destrukturering af værdierne state og dispatch fra det.

da vores oprindelige tilstand er et tomt array, vil vi ikke være i stand til at vise noget fra det, lad os prøve at oprette en funktion til at tilføje varer til indkøbskurven.

...

export default function Home() {

  const {state,dispatch}  = useContext(CartSystem)

  const addToCart =(product)=>{
      dispatch({type:'ADD_TO_CART',payload:product})
  }

  return (

...

vi opretter en funktion addToCart og tildel en afsendelse med handlingstypen 'ADD_TO_CART' og en nyttelast af den vare, der vil blive valgt , nu inde i vores reducer på pages/_app.js fil lader oprette 'ADD_TO_CART' handling, så vores funktion kan implementeres

...

function MyApp({ Component, pageProps }) {

  const Reducers = (state,action)=>{

    switch(action.type){
        case 'ADD_TO_CART':
           const {id, name, price,description} = action.payload
            const cartItem = state.cart.find(item => item.id === id)
            if (cartItem) {
                return {
                    ...state,
                    cart: state.cart.map(item => item.id === id ? {...item, quantity: item.quantity + 1} : item)
                }
            } else {
                return {
                    ...state,
                    cart: [...state.cart, {id, name, price, description, quantity: 1}]
                }
            }
        default:
        return state;
    }


  }

  const [state,dispatch] = useReducer(Reducers,initailState)
...

nu tager reduceringsfunktionen to parametre state og action , vi bruger en switch-sætning til at kontrollere handlingen og udføre en opgavebase på handlingen, i vores tilfælde lytter vi efter ADD_TO_CART handling.

nu hvor ADD_TO_CART funktionen er indstillet, lad os implementere denne handling på vores Tilføj til indkøbskurv-knap og se, hvad vi har fået

import Head from 'next/head'
import Image from 'next/image'
import styles from '../styles/Home.module.css'
import React,{useContext,useEffect} from 'react'
import {CartSystem} from './_app'

const DummyProducts = [...]

export default function Home() {

  const {state,dispatch}  = useContext(CartSystem)

  const addToCart =(product)=>{
      dispatch({type:'ADD_TO_CART',payload:product})
  }

  // add all the products price in cart
  const total = state.cart.reduce((total,item)=>{
    return total + item.price * item.quantity
  },0)

  // add all product quantity items
  const totalItems = state.cart.reduce((total,item)=>{
    return total + item.quantity
  },0)

  return (
    <div className={styles.container}>
      <Head>
        <title>Product Cart System</title>
        <meta name="description" content="Generated by create next app" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <h1 className={styles.heading}>Cart Quantity 🛒 ({totalItems}) | Total Price 💰 $({total})</h1>

      <div className={styles.products}>
        {DummyProducts.map(product => (
          <div onClick={()=>addToCart(product)} className={styles.product} key={product.id}>
            <Image src={product.image} width={200} height={200} placeholder={'blur'} blurDataURL={product.image} />
            <h3 className={styles.name}>{product.name}</h3>
            <p className={styles.description}>{product.description}</p>
            <h4 className={styles.price}>${product.price}</h4>
            <button className={styles.addToCart}>Add to cart</button>
          </div>
        ))}
      </div>
    </div>
  )
}

Endelig

nu har vi lavet nogle ændringer i vores pages/index fil
Jeg tilføjede to funktioner, der ville finde den samlede mængde og den samlede pris for produkterne

så tilføjede du endelig addToCart funktion til Tilføj til kurv-knappen, og nu har vi selv et fuldt fungerende tilføje til indkøbsvogn-system

*Nu har vi bygget et minimalt tilføjelsesvognssystem uden at installere nogen biblioteker eller bruge redux, vores kode er stadig i sin minimale tilstand, det samme mål kan opnås med redux, men hvorfor stresset? *