Избегайте Redux, постройте оптимальную систему добавления в корзину с UseReducer и UseContext в React NEXT.js 😎

Здравствуйте! Когда дело доходит до приложений электронной коммерции, единственное, чего мы не можем избежать — это управление состоянием с функцией «добавить в корзину»,
Сегодня я собираюсь показать вам, как построить его, а также оптимальный способ сделать это без использования Redux, который я вижу люди используют много, он поставляется вместе с большим количеством котельных пластин, которые вы хотели бы избежать любой ценой.
Все дело в оптимизации 😉 Давайте отправимся в путь.

Структура папки

внутренняя папка pages/

Главная страница pages/index.js

Ваша домашняя страница должна выглядеть примерно так в первый раз

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>
  )
}

Вход в полноэкранный режим Выход из полноэкранного режима

 Настройка продуктов

Теперь давайте создадим фиктивные товары, которые можно будет добавлять в нашу
корзину

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'
  },
]
...

Войти в полноэкранный режим Выход из полноэкранного режима

Разогрев

Хорошо, мы установили переменную DummyProducts с 4 объектами в массиве, но мы ничего не увидим, пока не сопоставим каждый объект и не отобразим отдельный элемент в массиве ‘DummyProducts’ на экране.

...
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>
  )
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Давайте посмотрим, что у нас получилось.

Да, для Next.js при использовании внешнего url во встроенном компоненте next’s image нам необходимо явно указать
домен url в файле next.config.js, вот как это делается
как это делается

inside the next.config.js file

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

module.exports = nextConfig

Вход в полноэкранный режим Выйти из полноэкранного режима

вот как это просто делается 😁 , теперь давайте посмотрим, что у нас на экране

Ура!!! 🥳

Но подождите, наша страница выглядит некрасиво, давайте добавим ей немного стилизации.

внутри 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;
}
Вход в полноэкранный режим Выйти из полноэкранного режима

Посмотрим, что у нас получилось

Теперь это минимально 😎

Переходим к реальной работе

мы закончили с визуализацией
переходим к реальной работе

Настройка useContext

Хук «useContext» используется для создания общих данных, которые могут быть доступны по всей иерархии компонентов без передачи реквизитов вручную на каждый уровень.

Для следующего js мы переходим в корень нашего приложения и устанавливаем там контекст, чтобы данные были доступны по всему нашему приложению.

внутри 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

Вход в полноэкранный режим Выйти из полноэкранного режима

Вот так, наш useContext устанавливается в экспортируемую переменную CartSystem и имеет готовый Provider с неопределенным значением, и именно здесь мы устанавливаем наш reducer и значение state.

Настройка объекта reducer и state

нам понадобится только одно состояние, которым будет cart
с начальным значением в виде пустого массива

Для простоты наши reducer и state будут находиться в одном файле.

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

Вход в полноэкранный режим Выход из полноэкранного режима

Теперь это обновление нашего файла pages/_app.js.
была создана структура функции Reducer, а также объект initialState.
Хук useReducer возвращает два значения, dispatch и state.

Я дам ссылку, чтобы прочитать больше о хуке useReducer, если вы новичок в этом.

Теперь давайте передадим эти данные как значение через наш контекст, чтобы
наше приложение могло получить доступ ко всем данным в любом месте.

...

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

...
Вход в полноэкранный режим Выйти из полноэкранного режима

С помощью этого мы можем получить доступ к тому, что находится в состоянии в любом компоненте нашего приложения.

Доступ к данным состояния из файла pages/index.js

Чтобы получить доступ к данным со страницы нашего продукта, нам нужно использовать функцию

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
мы импортируем useContext и CartSystem из react и _app.js соответственно.
Затем мы получаем данные с помощью хука useContext, передавая параметр

Поскольку наше начальное состояние — это пустой массив, мы не сможем ничего из него отобразить, попробуем создать функцию для добавления товаров в корзину.

...

export default function Home() {

  const {state,dispatch}  = useContext(CartSystem)

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

  return (

...
Вход в полноэкранный режим Выход из полноэкранного режима

Мы создаем функцию addToCart и назначаем диспетчер с типом действия 'ADD_TO_CART' и полезной нагрузкой элемента, который будет выбран, теперь внутри нашего редуктора в файле pages/_app.js создадим действие 'ADD_TO_CART', чтобы наша функция могла быть реализована.

...

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)
...

Вход в полноэкранный режим Выйти из полноэкранного режима

теперь функция reducer принимает два параметра state и action, мы используем оператор switch для проверки действия и выполнения задачи на основе действия, в нашем случае мы слушаем действие ADD_TO_CART.

Теперь, когда функция ADD_TO_CART установлена, давайте реализуем это действие на нашей кнопке Добавить в корзину и посмотрим, что у нас получилось

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>
  )
}

Вход в полноэкранный режим Выход из полноэкранного режима

Наконец

теперь мы сделали некоторые изменения в нашем файле pages/index
Я добавил две функции, которые находили общее количество и общую цену товаров

затем добавил функцию addToCart к кнопке добавления в корзину, и теперь у нас есть полностью рабочая система добавления в корзину.

* Теперь мы создали минимальную систему добавления в корзину без установки каких-либо библиотек или использования redux, наш код все еще находится в минимальном состоянии, та же цель может быть достигнута с помощью redux, но зачем напрягаться? *

дайте мне знать, если вы хотите получить полное продолжение этого урока
через комментарии

ниже приведена ссылка на полный репозиторий приложения
Полный репозиторий проекта

Оцените статью
Procodings.ru
Добавить комментарий