next-redux-wrapper
Я пишу приложение на next.js(pages dir) в связке с next-redux-wrapper. Проблема с диспатчем данных в стейт из клиентской части(например при нажатии на кнопку), данные записываются на клиенте и при переходе на другие страницы не записываются. Конфигурация стора:
const rootReducer = combineReducers({
productReducer,
cartReducer,
userReducer,
});
const makeStore = () =>
configureStore({
reducer: rootReducer,
});
export const store = makeStore();
export type RootState = ReturnType<typeof rootReducer>;
export type AppStore = ReturnType<typeof makeStore>;
export type AppState = ReturnType<AppStore["getState"]>;
export type AppDispatch = AppStore["dispatch"];
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
AppState,
unknown,
Action
>;
export const wrapper = createWrapper<AppStore>(makeStore);
Слайсер с которым возникают проблемы(все слайсера примерно одинаковые):
interface CartState {
products: IProductCart[];
isLoading: boolean;
error: string;
}
const initialState: CartState = {
products: [],
isLoading: false,
error: "",
};
export const cartSlice = createSlice({
name: "cart",
initialState,
reducers: {
firstAddProductToCart(state, action: PayloadAction<IProductCart[]>) {
state.products = action.payload;
},
addProductToCart(state, action: PayloadAction<IProductCart>) {
state.products = state.products.concat(action.payload);
},
},
extraReducers: {
[HYDRATE]: (state, action) => {
return {
...state,
...action.payload.cartReducer,
};
},
},
});
export const {
// все слайсеры
} = cartSlice.actions;
export default cartSlice.reducer;
_app.tsx:
const App = ({ Component, pageProps }: AppProps) => {
return (
<>
<Toaster toastOptions={{
style: {
background: 'rgb(51 65 85)',
color: '#FFFFFF',
}
}}
/>
<Component {...pageProps} />
</>
)
}
export default wrapper.withRedux(App);
Компонент в котором происходит запись данных в стейт:
interface ProductDetailsProps {
product: IProduct
}
export const ProductDetails: React.FC<ProductDetailsProps> = ({ product }) => {
const router = useRouter()
const dispatch = useDispatch()
return (
<div className='grid grid-cols-1 md:grid-cols-2 gap-12'>
{/*<ProductImage cartProduct={cartProduct} product={product} handleColorSelect={handleColorSelect} />*/}
<div className='flex flex-col gap-1 text-slate-500 text-sm'>
<h2 className='text-3xl font-medium text-slate-700'>{product.name}</h2>
<Horizontal />
<div>
<Rating value={rating} readOnly />
<div className='flex items-center gap-2 '>{product.product_reviews.length} Отзывы</div>
</div>
<Horizontal />
<div className='text-justify'>
{product.description}
</div>
<Horizontal />
<div>
<span className='font-semibold'>Категория: </span>
{product.category.name}
</div>
<div>
<span className='font-semibold'>Brand: </span>
{product.brand}
</div>
<div className={product?.on_stock ? 'text-teal-700' : 'text-rose-900'}>
{product?.on_stock ? 'Есть в наличии' : 'Нет в наличии'}
</div>
<Horizontal />
{productInCart ?
<>
<p className='mb-2 text-slate-500 flex items-center gap-1'>
<MdCheckCircle size={20} className='text-teal-400' />
<span>Добавлен в корзину</span>
</p>
<div className='max-w-[300px]'>
<CartButton label='Перейти в корзину' outline onClick={() => {
router.push('/cart')
}} />
</div>
</> :
<>
{/* <SetColor*/}
{/* cartProduct={cartProduct}*/}
{/* images={product.images}*/}
{/* handleColorSelect={handleColorSelect}*/}
{/*/>*/}
{/*<Horizontal /> */}
<div className='flex gap-8 items-center'>
<div className='font-semibold'>
Кол-во:
</div>
<div className='flex gap-4 items-center text-base'>
<button
onClick={() => setCartCounter(prev => {
if (prev > 1) {
return --prev
}
toast.error('Это минимум ...')
return prev
})}
className='border-[1.2px] border-slate-300 p-2 rounded'
>
-
</button>
<div>{cartCounter}</div>
<button
onClick={() => setCartCounter(prev => {
if (prev < product.quantity) {
return ++prev
}
toast.error('Это максимум ...')
return prev
})}
className='border-[1.2px] border-slate-300 p-2 rounded'
>
+
</button>
</div>
</div>
<Horizontal />
<div className='max-w-[300px]'>
<CartButton
label={"Добавить в корзину"}
onClick={() => {
const productCart: IProductCart = {
...product,
quantity: cartCounter,
maxQuantity: product.quantity
}
dispatch(addProduct(productCart))
toast.success(product.name + ' добавлен в корзину')
}}
/>
</div>
</>}
</div>
</div>
)
}
Страница, внутри которой используется компонент:
interface ProductParams {
product: IProduct
}
export default function Product({product}: ProductParams) {
if ('detail' in product)
return <>Product is not found</>
return (
<RootLayout>
<div className="p-8">
<Container>
<ProductDetails product={product} />
</Container>
<div className='flex flex-col mt-20 gap-4'>
<ListRating product={product} />
</div>
</div>
</RootLayout>
)
}
export const getStaticPaths: GetStaticPaths = async () => {
const res = await fetch('http://localhost:5000/products');
const products: IProduct[] = await res.json();
const paths = products.map((product) => {
return {
params: { id: product.id.toString() },
};
});
return {
paths,
fallback: true,
};
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
try {
const response = await fetch(`http://localhost:5000/products/${params?.id}`, {next: {revalidate: 300}});
const product = await response.json();
return {
props: { product },
};
} catch (e) {
console.log('Error: ', e);
return {
notFound: true
};
}
};