У меня есть интернет-магазин, в котором перечислены все продукты и указаны параметры фильтрации. Сегодня я добавил опции логина и регистрации. Итак, чтобы сохранить токен даже после обновления страницы 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);
});
}