#reactjs
#reactjs
Вопрос:
У меня есть список продуктов и форма редактирования продукта. Проблема: 1. Я обновляю значения продукта и нажимаю кнопку отправки, чтобы сохранить новые данные и перенаправить в список продуктов. 2.My состояние обновляется: , но useEffect показывает предыдущие данные до моего обновления. Но иногда он показывает правильные обновленные данные.
Мой код для страницы списка продуктов:
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { Header } from '../components/Header';
import { Pagination } from '../components/Pagination';
import { LeftMenu } from '../components/LeftMenu';
import * as productsActions from '../store/actions/products';
import { Link, useHistory } from 'react-router-dom';
export const Products = () => {
let history = useHistory();
const dispatch = useDispatch();
const [searchValue, setSearchValue] = React.useState('');
const categoryId = undefined;
const sortBy = undefined;
const totalItems = useSelector(state => state.products.totalItems);
const perPage = 3;
const [currentPage, setCurrentPage] = React.useState(1);
const totalPages = Math.ceil(totalItems / perPage);
const products = useSelector(state => state.products.products);
const loading = useSelector(state => state.products.loading);
const [error, setError] = React.useState();
const [isLoading, setIsLoading] = React.useState(false);
const nextPage = (currentPage) => {
if (currentPage <= totalPages - 1) {
setCurrentPage(currentPage 1);
}
}
const previousPage = (currentPage) => {
if (currentPage > 1) {
setCurrentPage(currentPage - 1);
}
}
React.useEffect(() => {
dispatch(productsActions.fetchProducts(categoryId, sortBy, searchValue, currentPage));
}, [dispatch, categoryId, sortBy, searchValue, currentPage]);
const deleteProduct = React.useCallback((productId) => {
dispatch(productsActions.deleteProduct(productId));
}, [dispatch]);
const productSelectedHandler = (productId) => {
const URL = `/product/${productId}`;
history.push({ pathname: URL, id: productId });
}
return (
<div class="wrapper">
<Header />
<article class="main">
<div class="row">
<div class="search-bar">
<input name="search" searchData={searchValue} onChange={e => setSearchValue(e.target.value)} value={searchValue} className="searchOpen" placeholder="Search products..." />
<Link class="add-button" to="add-product">Add product</Link>
</div>
</div>
<div class="row">
<div class="item--4-4">
<div class="item-title">
{loading ? (
<table>
<thead>
<tr>
<th>ID</th>
<th>Image</th>
<th>Title</th>
<th>Category</th>
<th>Price</th>
<th>Status</th>
<th></th>
</tr>
</thead>
<tbody>
{products amp;amp; products.map(product => (
<tr>
<td onClick={() => productSelectedHandler(product._id)}>{product._id}</td>
<td onClick={() => productSelectedHandler(product._id)}><img class="product-image" src={`http://localhost:3000/${product.imageUrl}`} /></td>
<td onClick={() => productSelectedHandler(product._id)}>{product.title}</td>
<td>{product.categoryId.title}</td>
<td>{product.price}</td>
<td><span class="status">{product.status}</span></td>
<td><button onClick={() => deleteProduct(product._id)}><ion-icon name="close-outline"></ion-icon>
</button>
<button onClick={() => productSelectedHandler(product._id)}><ion-icon name="create-outline"></ion-icon></button></td>
</tr>
)
)}
</tbody>
</table>
) : 'Loading'}
</div>
<Pagination nextPage={nextPage} previousPage={previousPage} currentPage={currentPage} totalPages={totalPages} />
</div>
</div>
</article>
<LeftMenu />
</div>
)
}
Мой код для формы редактирования:
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
import FormData from 'form-data';
import { Header } from '../components/Header';
import { LeftMenu } from '../components/LeftMenu';
import * as categoriesActions from '../store/actions/categories';
import * as productsActions from '../store/actions/products';
import * as productActions from '../store/actions/product';
import { useHistory } from 'react-router-dom';
import { useForm } from "react-hook-form";
export const AddProduct = props => {
const dispatch = useDispatch();
let history = useHistory();
const getId = history.location.pathname.split('/');
const id = getId[2];
const userId = '5fe99c69488a0f6307df5990';
const categories = useSelector(state => state.categories.categories);
const productSelected = useSelector(state =>
state.products.products.find(prod => prod._id === id)
);
const productFetched = useSelector(state => state.product.product);
const [pickedImage, setPickedImage] = React.useState("");
const { register, handleSubmit, errors } = useForm();
const imageInput = React.useRef();
const handleClick = () => {
imageInput.current.click();
};
const handleChange = () => {
const file = imageInput.current.files[0];
const reader = new FileReader();
reader.onload = (event) => {
setPickedImage(event.target.result);
};
if (file) {
reader.readAsDataURL(file);
}
};
const onSubmit = product => {
const formData = new FormData()
formData.append("title", product.title)
formData.append("price", product.price)
formData.append("description", product.description)
formData.append("category", product.category)
formData.append("status", product.status)
formData.append("imageUrl", product.imageUrl[0])
if (id) {
formData.append("id", id);
}
if (id) {
dispatch(productsActions.editProduct(formData));
history.push('/products');
} else {
dispatch(productsActions.addProduct(formData));
history.push('/products');
}
}
const intialValues = {
title: productSelected ? productSelected.title : productFetched.title,
price: productSelected ? productSelected.price : productFetched.price,
description: productSelected ? productSelected.description : productFetched.description,
status: productSelected ? productSelected.status : productFetched.status,
// category: product.category
};
React.useEffect(() => {
if (!productSelected) {
dispatch(productActions.fetchProduct(id));
}
}, [dispatch, id]);
React.useEffect(() => {
dispatch(categoriesActions.fetchCategories());
}, [dispatch]);
return (
<div class="wrapper">
<Header />
<article class="main">
<form onSubmit={handleSubmit(onSubmit)}>
{pickedImage ? (
<img onClick={handleClick} src={pickedImage} alt="preview" />
) : (
<div onClick={handleClick} class="image-round-cover">
<ion-icon name="person-outline"></ion-icon>
</div>
)}
<input type="text" defaultValue={intialValues.title} placeholder="First name" name="title" ref={register({ required: true, maxLength: 80 })} />
<input type="text" defaultValue={intialValues.price} placeholder="Price" name="price" ref={register({ required: true, maxLength: 100 })} />
<input type="text" defaultValue={intialValues.status} placeholder="Status" name="status" ref={register({ required: true, maxLength: 100 })} />
<input ref={(e) => {
register(e, { required: true })
imageInput.current = e // you can still assign to ref
}}
onChange={handleChange} className="imageInput" type="file" defaultValue={intialValues.imageUrl} name="imageUrl" />
< textarea defaultValue={intialValues.description} name="description" ref={register({ min: 2 })} />
<select defaultValue={intialValues.category} name="category" ref={register({ required: true })}>
{categories.map((category) => (
<>
<option hidden>Choose category</option>
<option defaultValue={intialValues.category} key={category._id} value={category._id}>
{category.title}
</option>
</>
))}
</select>
<input type="submit" />
</form>
</article>
<LeftMenu />
</div>
)
}
Это мой редуктор:
import {
FETCH_PRODUCTS,
ADD_PRODUCT,
DELETE_PRODUCT,
EDIT_PRODUCT
} from '../actions/products';
const initialState = {
products: [],
totalItems: 0,
pageSize: 3,
currentPage: 1,
loading: false
};
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_PRODUCTS:
return {
...state,
products: action.products,
totalItems: action.totalItems,
loading: true
}
case ADD_PRODUCT:
return {
...state,
products: state.products.concat(action.product),
totalItems: state.totalItems 1,
pageSize: state.pageSize,
currentPage: state.currentPage,
loading: true
}
case EDIT_PRODUCT:
const elementsIndex = state.products.findIndex(element => element._id == action.product._id)
let newArray = [...state.products]
newArray[elementsIndex] = action.product;
return {
...state,
products: newArray,
totalItems: state.totalItems,
pageSize: state.pageSize,
currentPage: state.currentPage,
loading: true
}
case DELETE_PRODUCT:
return {
...state,
products: state.products.filter(items =>
items._id !== action.product._id),
totalItems: state.totalItems - 1,
pageSize: state.pageSize,
currentPage: state.currentPage,
}
}
return state;
}
Это мое действие:
export const fetchProducts = (categoryId, sortBy, title, page) => {
return async dispatch => {
try {
const response = await axios({ method: 'post', url: 'http://localhost:3000/api/products?page=' page, data: { categoryId, sortBy, title } });
const resData = await response.data.products;
const totalItems = await response.data.totalItems;
const loadedProducts = [];
for (const key in resData) {
loadedProducts.push(new Product(
resData[key]._id,
resData[key].title,
resData[key].imageUrl,
resData[key].description,
resData[key].price,
resData[key].status,
resData[key].categoryId,
))
}
dispatch({ type: FETCH_PRODUCTS, products: loadedProducts, totalItems: totalItems });
} catch (error) {
throw error;
}
}
}
Ответ №1:
ты можешь это сделать
reducer.js
export const actions = () => {
swicth() {
case EDIT_PRODUCT:
return {
...state,
products: state.products.map((x) => x.id === payload.product.id ? payload.product : x),
totalItems: state.totalItems,
pageSize: state.pageSize,
currentPage: state.currentPage,
loading: true
}
}
}
action.js
export const productActions = (data) => {
return (dispatch) => {
const response = await dispatch({
type: UpdateProduct,
payload: {
product: data
}
})
return response
}
}
Комментарии:
1. Не работает. Все еще можно увидеть старые данные о продукте. Только на странице обновления можно увидеть обновленный продукт.
2. Вы не сохраняете данные в базе данных?
3. Да, я сохраняю в БД, я обновил вопрос своим действием