ДАЛЕЕ. JS с постоянным сокращением, показывающим неожиданное поведение - PullRequest
1 голос
/ 14 июля 2020

У меня есть интернет-магазин, в котором перечислены все продукты и указаны параметры фильтрации. Сегодня я добавил опции логина и регистрации. Итак, чтобы сохранить токен даже после обновления страницы sh, я реализовал redux-persist. Теперь проблема в том, что если я обновляю sh страницу, дизайн ломается, а состояние не отображается. На странице со списком продуктов я поместил загрузчик перед загрузкой, а после загрузки продуктов загрузчик будет скрыт. Теперь каждый раз загрузчик отображается с другой анимацией. Пожалуйста, взгляните на мой код

store.js

import { createWrapper } from 'next-redux-wrapper';
import { applyMiddleware, createStore } from 'redux';
import logger from 'redux-logger';
import thunkMiddleware from 'redux-thunk';
import rootReducer from './reducers';

const bindMiddleware = (middleware) => {
    if (process.env.NODE_ENV !== "production") {
        const { composeWithDevTools } = require("redux-devtools-extension");
        return composeWithDevTools(applyMiddleware(...middleware));
    }
    return applyMiddleware(...middleware);
};

const makeStore = ({ isServer }) => {
    if (isServer) {
        //If it's on server side, create a store
        return createStore(rootReducer, bindMiddleware([thunkMiddleware, logger]));
    } else {
        //If it's on client side, create a store which will persist
        const { persistStore, persistReducer, autoRehydrate } = require("redux-persist");
        const storage = require("redux-persist/lib/storage").default;

        const persistConfig = {
            key: "nextjs",
            whitelist: ["authentication", "menu", "product"], // only counter will be persisted, add other reducers if needed
            storage, // if needed, use a safer storage
        };

        const persistedReducer = persistReducer(persistConfig, rootReducer); // Create a new reducer with our existing reducer

        const store = createStore(
            persistedReducer,
            {},
            bindMiddleware([thunkMiddleware, logger])
        ); // Creating the store again

        store.__persistor = persistStore(store); // This creates a persistor object & push that persisted object to .__persistor, so that we can avail the persistability feature

        return store;
    }
};

// Export the wrapper & wrap the pages/_app.js with this wrapper only
export const wrapper = createWrapper(makeStore);

[...slug].js

import { useRouter, withRouter } from 'next/router'
import { useDispatch, useSelector } from 'react-redux';
import { useEffect, useState } from 'react';
import { Layout } from '../../components/main/Index';
import { fetchproducts } from '../../store/actions/productAction'
import './styles/listing.scss';
import Link from 'next/link';
import {FontAwesomeIcon} from '@fortawesome/react-fontawesome'
import { faTimes, faChevronDown, faChevronUp, faHeart as heartBold, faShoppingCart } from '@fortawesome/fontawesome-free-solid'
import { faHeart as heartRegular } from '@fortawesome/fontawesome-free-regular'
import ReactPaginate from 'react-paginate';
import * as api from '../api'
import Head from 'next/head';

const Products = () => {
    const {products, loading} = useSelector(state => state.product);
    const [showMe, setShowMe] = useState([0]);
    const [showFilter, setFilter] = useState([]);
    const [yourSelection, setSelection] = useState([]);
    let [sortState, setSortState] = useState(0);
    let [pageState, setPageState] = useState(0);
    const router = useRouter()
    const dispatch = useDispatch();

    const slug = router.query.slug || []
    const sort = router.query.sort || []
    const page = router.query.page || []
    const pageInitial = router.query.page ? parseInt(page-1) : pageState;

    useEffect(() => {
        dispatch(fetchproducts(slug, sort, pageInitial+1, showFilter));
    }, [showFilter]);

    function toggle(index) {
        setShowMe(currentShowMe => currentShowMe.includes(index) ? currentShowMe.filter(i => i !== index) : [...currentShowMe, index]);
    }

    function filterClick (id, title) {
        const index = showFilter.indexOf(id);

        if (index > -1)
            setFilter(showFilter.filter(el=> el !== id));
        else
            setFilter(showFilter.concat(id));

        setPageState(0);
        setSortState(0);

        router.push(
            '/products/[...slug]',
            {
                pathname: '/products/'+slug.join('/')
            },
            '/products/'+slug.join('/'), { shallow: true }
        )
    }

    function sortBy(value) {
        setPageState(0);
        setSortState(value);
        dispatch(fetchproducts(slug, value, 1, showFilter));

        if (value === "0")
            router.push(
                '/products/[...slug]',
                {
                    pathname: '/products/'+slug.join('/')
                },
                '/products/'+slug.join('/'), { shallow: true }
            )
        else
            router.push(
                '/products/[...slug]',
                {
                    pathname: '/products/'+slug.join('/'),
                    query: { sort: value }
                },
                '/products/'+slug.join('/'), { shallow: true }
            )
    }

    return (
        <Layout title={products && products.title.length > 0 ? products.title : slug.join('-')}>
            <Head>
                <meta name="description" content={products && products.description.length > 0 ? products.description : slug.join('-')} />
            </Head>
            {!loading ?
                <div className="container-fluid mt-4 mb-4">
                    {products && products.data.length > 0 ?
                        <div className="row">
                            <div className="col-md-3">
                                <div className="filter">
                                    <div className="other">
                                        <h6>Refine</h6>
                                        <hr/>
                                        {products.filter.map((item, index) => (
                                            <div key={index}>
                                                <div className="single">
                                                    <div className="title" onClick={() => toggle(index)}>
                                                        <p className="float-left">{item.title}</p>
                                                        <p className="float-right"><FontAwesomeIcon icon={showMe.includes(index) ? faChevronUp : faChevronDown}/></p>
                                                    </div>
                                                    <ul style={{display: showMe.includes(index) ? "block" : "none"}}>
                                                        {item.items.map((single, index1) => (
                                                            <li key={index1}>
                                                                <label><input type="checkbox" name="checkbox" onClick={(e) => filterClick(e.target.value, item.title)} value={single.items_id} {...showFilter.includes(index) ? defaultChecked : "" }/> {single.items_value.charAt(0) === "#" ? <span className="color-filter" style={{backgroundColor: single.items_value}}></span> : single.items_value}</label>
                                                            </li>
                                                        ))}
                                                    </ul>
                                                </div>
                                                <hr/>
                                            </div>
                                        ))}
                                    </div>
                                </div>
                            </div>
                            <div className="col-md-9 mt-4 mt-md-0">
                                <div className="row align-items-center mb-4">
                                    <div className="col-md-9">
                                        <h4 className="m-0">{products && products.heading.length > 0 ? products.heading : slug.join('/')}</h4>
                                    </div>
                                    <div className="col-md-3">
                                        <select id="sort" className="sort-by form-control" onChange={(e) => sortBy(e.target.value)} value={sort.length > 0 ? sort : sortState}>
                                            <option value="0">Sort By</option>
                                            <option value="1">Price Low to High</option>
                                            <option value="2">Price High to Low</option>
                                            <option value="3">New Arrivals</option>
                                        </select>
                                    </div>
                                </div>
                                <div className="row">
                                    {products.data.map((items, index) => (
                                        <div className="col-sm-6 col-md-4" key={index}>
                                            <div className="single-box">
                                                <Link href={"/product/"+items.slug}>
                                                    <a title={items.name}>
                                                        {items.images.length > 0 ?
                                                            <img src={api.IMAGE_PRODUCTS+"/"+items.proId+"/"+items.images[0].image_name} alt={items.name} className="img-fluid" />
                                                        :
                                                            <img src="/images/noimage.png" alt={items.name} className="img-fluid" />
                                                        }
                                                        <div className="details">
                                                            <p className="title">{items.name}</p>
                                                            {items.ofprice !== null ?
                                                                <p>Rs.{items.ofprice} <span className="off-price">Rs.{items.oprice}</span> <span className="off">{parseInt(((items.oprice-items.ofprice)*100)/items.oprice)}% off</span></p>
                                                            :
                                                                <p>Rs.{items.oprice}</p>
                                                            }
                                                        </div>
                                                    </a>
                                                </Link>
                                                <p className={items.wishlist === 1 ? "wishlist w-active" : "wishlist"}><FontAwesomeIcon icon={items.wishlist === 1 ? heartBold : heartRegular}/></p>
                                            </div>
                                        </div>
                                    ))}
                                </div>
                            </div>
                        </div>
                    :
                        <div className="row justify-content-center">
                            <div className="col-12 col-md-6 col-lg-4">
                                <div className="empty-list">
                                    <FontAwesomeIcon icon={faShoppingCart}/>
                                    <h3>Empty</h3>
                                    <p>No products available in this category</p>
                                </div>
                            </div>
                        </div>
                    }
                </div>
            :
                <div className="loading"><div className="lds-ripple"><div></div><div></div></div></div>
            }
        </Layout>
    )
}

export default withRouter(Products)

productAction.js

import * as types from '../types'
import axios from 'axios'
import * as api from '../../pages/api'

export const fetchproducts = (slug, sort = 0, page = 1, filter = null) => async dispatch => {
    const res = await axios.get(api.URL_PRODUCTS+"?slug="+slug+"&user=1&sort="+sort+"&page="+page+"&filter="+filter);
    dispatch({
        type: types.GET_PRODUCTS,
        payload: res.data
    })
}

rootReducer.js

import {combineReducers} from 'redux'
import { authReducer } from './authReducer';
import { menuReducer } from './menuReducer';
import { productReducer } from './productReducer';
import { HYDRATE } from 'next-redux-wrapper';

const reducer = (state = { app: 'init', page: 'init' }, action) => {
    switch (action.type) {
        case HYDRATE:
            if (action.payload.app === 'init') delete action.payload.app;
            if (action.payload.page === 'init') delete action.payload.page;
            return state;
        case 'APP':
            return { ...state, app: action.payload };
        case 'PAGE':
            return { ...state, page: action.payload };
        default:
            return state;
    }
};

const rootReducer = combineReducers({
    wrapper: reducer,
    authentication: authReducer,
    menu: menuReducer,
    product: productReducer
});

export default rootReducer;

_app.js

import App from 'next/app'
import React from 'react'
import {Provider} from 'react-redux'
import {createWrapper} from 'next-redux-wrapper'
import store from '../store/store'
import { PersistGate } from 'redux-persist/integration/react'

class MyApp extends App {
    render() {
        const {Component, pageProps} = this.props

        return (
            <Provider store={store}>
                <PersistGate persistor={store.__persistor} loading={<div>Loading...</div>}>
                    <Component {...pageProps}/>
                </PersistGate>
            </Provider>
        )
    }
}

const makestore = () => store;
const wrapper = createWrapper(makestore);

export default wrapper.withRedux(MyApp);

authReducer.js

import * as types from '../types'

export const authReducer = (state = { token: null }, action) => {
    switch (action.type) {
        case 'persist/REHYDRATE': {
            const data = action.payload;
            if (data) {
                return {
                    ...state,
                    ...data.auth
                }
            }
        }
        case types.AUTHENTICATE:
            return {
                ...state,
                token: action.payload
            };
        case types.DEAUTHENTICATE:
            return {
                token: null
            };
        default:
            return state;
    }
};

Есть ли способ исправить это. Я думаю, что мой код нужно изменить, чтобы это исправить. Я не знаю, как это исправить, так как я новичок в NEXT. JS и REDUX.

Пожалуйста, посмотрите видео ниже, чтобы получить представление о точной проблеме.

Ошибка видео

введите описание изображения здесь

authAction.js

import * as types from '../types'
import axios from 'axios'
import cookie from 'js-cookie';
import * as api from '../../pages/api'
import Router from 'next/router';

export const authenticate = user => async dispatch => {
    const res = await axios.post(api.URL_REGISTER, {user})
        .then(res => {
            if (res.data.response === 200) {
                setCookie('token', res.data.data.token);
                Router.push('/');

                dispatch({
                    type: types.AUTHENTICATE,
                    payload: res.data.data.token
                })
            }
            else
                dispatch({
                    type: types.AUTHENTICATE,
                    payload: res.data
                })
        }).catch(error => {
            console.log(error);
        });
}

1 Ответ

1 голос
/ 14 июля 2020

Мой рабочий магазин в Next. js app
Попробуйте исправить свой магазин

store. js

import { createWrapper } from 'next-redux-wrapper';
import { applyMiddleware, createStore } from 'redux';
import logger from 'redux-logger';
import thunkMiddleware from 'redux-thunk';
import rootReducer from './reducers/rootReducer';

const bindMiddleware = (middleware) => {
    if (process.env.NODE_ENV !== "production") {
        const { composeWithDevTools } = require("redux-devtools-extension");
        return composeWithDevTools(applyMiddleware(...middleware));
    }
    return applyMiddleware(...middleware);
};

const makeStore = ({ isServer }) => {
    if (isServer) {
        //If it's on server side, create a store
        return createStore(rootReducer, bindMiddleware([thunkMiddleware, logger]));
    } else {
        //If it's on client side, create a store which will persist
        const { persistStore, persistReducer, autoRehydrate } = require("redux-persist");
        const storage = require("redux-persist/lib/storage").default;

        const persistConfig = {
            key: "nextjs",
            whitelist: ["auth","cart"], // only counter will be persisted, add other reducers if needed
            storage, // if needed, use a safer storage
        };

        const persistedReducer = persistReducer(persistConfig, rootReducer); // Create a new reducer with our existing reducer

        const store = createStore(
            persistedReducer,
            {},
            bindMiddleware([thunkMiddleware, logger])
        ); // Creating the store again

        store.__persistor = persistStore(store); // This creates a persistor object & push that persisted object to .__persistor, so that we can avail the persistability feature

        return store;
    }
};

// Export the wrapper & wrap the pages/_app.js with this wrapper only
export const wrapper = createWrapper(makeStore);

Root редуктор

const reducer = (state = { app: 'init', page: 'init' }, action) => {
    switch (action.type) {
        case HYDRATE:
            if (action.payload.app === 'init') delete action.payload.app;
            if (action.payload.page === 'init') delete action.payload.page;
            return state;
        case 'APP':
            return { ...state, app: action.payload };
        case 'PAGE':
            return { ...state, page: action.payload };
        default:
            return state;
    }
};


const rootReducer = combineReducers({
    wrapper: reducer,
    cart: cartReducer,
    auth: authReducer
});

автори. js например

const authReducer = (state = initialState, action) => {
    switch (action.type) {
       ...
        case 'persist/REHYDRATE': {
            const data = action.payload;
            if (data) {
                return {
                    ...state,
                    ...data.auth
                }
            }
        }
       ...
    }
};

_app. js

import React from 'react';
import { useStore } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { wrapper } from '../store/store';

const Layout = ({ children }) => {
  return <>
    <Header />
    <div className="container">
      {children}
    </div>
  </>
}


const App = ({ Component, pageProps }) => {
  const store = useStore((state) => state)
  return (
    <Layout >
      <PersistGate persistor={store.__persistor} loading={<div>Loading...</div>}>
        <Component {...pageProps} />
      </PersistGate>
      <style jsx global>{`
        html,
        body {
          padding: 0;
          margin: 0;
          font-family: 'Montserrat', sans-serif;
        }
        .container { 
          width:1240px;
          margin: 0 auto;
        }
        
        img,li,ul,ol,span,h1,h2,h3,h4,h5,h6,main,footer {
          margin:0;
          padding:0;
        }

        main {
          width:100%
        }

        *,*::before,*::after {
          box-sizing: border-box;
        }
        a,
        button {
          cursor: pointer;
        } 

        a {
          text-decoration:none;
          color:#000
        }
        
      `}</style>
    </Layout>
  )
}

App.getInitialProps = async ({ Component, ctx }) => {
  // Keep in mind that this will be called twice on server, one for page and second for error page
  ctx.store.dispatch({ type: "APP", payload: "was set in _app" });
  return {
    pageProps: {
      // Call page-level getInitialProps
      ...(Component.getInitialProps
        ? await Component.getInitialProps(ctx)
        : {}),
      // Some custom thing for all pages
      appProp: ctx.pathname
    }
  };
};

export default wrapper.withRedux(App);
...