Состояние обновлено, но компонент отображает предыдущие данные

#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. Да, я сохраняю в БД, я обновил вопрос своим действием