0

所以我在下面有这个产品组件

  1. 显示组件列表
  2. 有一个按钮,可以打开一个模式来添加组件
  3. 每行都有一个编辑按钮,用于打开模式并更新该组件。

我正在为我的 api 调用使用 react-query,虽然我目前所拥有的东西正在工作,但感觉很糟糕,希望你们能提出更好的方法来把它放在一起。

所以这是我的组件:

import React from 'react';
import {  useGetProductsList, useGetProduct, useCreateProduct, prefetchProduct, usePutProduct, usePatchProduct } from '../api/ProductsApi';
import ModifyProducts from '../components/ModifyProducts'
import { compare } from 'fast-json-patch';

function Products(){
  const [productId, setProductId] = React.useState();
  const [product, setProduct] = React.useState();
  const [page, setPage] = React.useState(1);
  const [pageSize, setPageSize] = React.useState(8);
  const productsQuery = useGetProductsList(page, pageSize);
  const productQuery = useGetProduct(productId)
  const [addModalIsOpen, setAddModalIsOpen] = React.useState(false);
  const [editModalIsOpen, setEditModalIsOpen] = React.useState(false);
  const [createProduct, createProductInfo] = useCreateProduct();
  const [putProduct, putProductInfo] = usePutProduct();
  const [patchProduct, patchProductInfo] = usePatchProduct();

  const headerStyles = "px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider";
  const onPut = async (values) => {
    putProduct(values)
  }

  const onPatch = async (values) => {
    let patchDoc = compare(product, values)
    patchProduct({productId: values.productId, patchDoc})
  }

  React.useLayoutEffect(() => {
    let current = true;
    if(productId){
      if(current)
        setProduct(productQuery.data)
    }

    return () => {
      current = false
      setProductId(null)
    }
  },[productId, productQuery.data])

  // React.useLayoutEffect(() => {
  //   let current = true;
  //   if(productId){
  //     axios.get(`http://localhost:5000/api/products/${productId}`)
  //       .then((res) => {
  //         if(current)
  //           setProduct(res.data)
  //       })
  //   }

  //   return () => {
  //     current = false
  //   }
  // },[productId])

  function openEditModal(newProductId) {
    setProductId(newProductId)
    setEditModalIsOpen(true)
    putProductInfo.reset();
  }
  
  function openAddModal() {
    setAddModalIsOpen(true)
    createProductInfo.reset();
  }

  return (
    <>
      <div className="">
        <div className="pb-5 border-b border-gray-200 space-y-3 sm:flex sm:items-center sm:justify-between sm:space-x-4 sm:space-y-0">
          <h2 className="text-lg leading-6 font-medium text-gray-900">
            Products
          </h2>
          <div>
            <span className="shadow-sm rounded-md">
              <button onClick={() => openAddModal()} type="button" className="inline-flex items-center px-4 py-2 border border-transparent text-sm leading-5 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:shadow-outline-indigo focus:border-indigo-700 active:bg-indigo-700 transition duration-150 ease-in-out">
                Add new product
              </button>
            </span>
          </div>
        </div>
        
        { (productsQuery.isSuccess || productsQuery.isLoading) && (
          <div className="pt-5 flex flex-col">
            <div className="-my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
              <div className="py-2 align-middle inline-block min-w-full sm:px-6 lg:px-8">
                <div className="shadow overflow-hidden border-b border-gray-200 sm:rounded-t-lg">
                  <table className="min-w-full divide-y divide-gray-200">
                    <thead>
                      <tr>
                        <th className={headerStyles}>Name</th>
                        <th className={headerStyles}>Style</th>
                        <th className="px-6 py-3 bg-gray-50"></th>
                      </tr>
                    </thead>
                    <tbody className="bg-white divide-y divide-gray-200">
                      {productsQuery.products.map((product) => (
                        <tr>
                          <td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 font-medium text-gray-900">
                            {product.name}
                          </td>
                          <td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
                          {product.style}
                          </td>
                          <td className="px-6 py-4 whitespace-no-wrap text-right text-sm leading-5 font-medium">
                            <button onClick={() => openEditModal(product.productId)} onMouseEnter={() => prefetchProduct(product.productId)} className="text-indigo-600 hover:text-indigo-900">Edit</button>
                          </td>
                        </tr>
                      ))}
                    </tbody>
                  </table>
                  
                  <nav className="bg-white px-4 py-3 flex items-center justify-between border-t border-gray-200 sm:px-6">
                    <div className="hidden sm:block">
                      <p className="text-sm leading-5 text-gray-700 space-x-1">
                        Showing
                        <span className="px-1 font-medium">{productsQuery.products.length === 0 ? 0 : (page * productsQuery.currentPageSize)-productsQuery.currentPageSize + 1}</span>
                        to
                        <span className="pr-1 font-medium">{productsQuery.products.length === 0 ? 0 : ((page * productsQuery.currentPageSize)-productsQuery.currentPageSize + 1) + productsQuery.products.length - 1}</span>
                        of
                        <span className="pr-1 font-medium">{productsQuery.totalCount}</span>
                        results
                      </p>
                    </div>
                    <div className="flex-1 flex justify-between sm:justify-end">                      
                      <PageButtons page={page} setPage={setPage} hasPreviousPage={productsQuery.hasPrevious} hasNextPage={productsQuery.hasNext}></PageButtons>
                    </div>
                  </nav>
                </div>
              </div>
            </div>
          </div>
          )
        }
      </div>

      
      {(
        <>
          <ModifyProducts
            isOpen={addModalIsOpen}
            setIsOpen={setAddModalIsOpen}
            onSubmit={createProduct}
            clearOnSubmit
            createProductInfo
            submitText={
              createProductInfo.isLoading
                ? 'Saving...'
                : createProductInfo.isError
                ? 'Error!'
                : createProductInfo.isSuccess
                ? 'Saved!'
                : 'Add Product'
            }
          />
            
          {/* you can use either onPut or onPatch depending on if you want to update with patch or put */}
          <ModifyProducts
            isOpen={editModalIsOpen}
            setIsOpen={setEditModalIsOpen}
            initialValues={product}
            onSubmit={onPatch}
            editProductInfo = {patchProductInfo}
            submitText={
              patchProductInfo.isLoading
                ? 'Saving...'
                : patchProductInfo.isError
                ? 'Error!'
                : patchProductInfo.isSuccess
                ? 'Saved!'
                : 'Save Product'
            }
            />
        </>
      )}
    </>
  );
}


function PageButtons(props) {
  return ( 
    <>
      { props.hasPreviousPage && (
        <button onClick={ () => props.setPage(props.page - 1) } className="relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
          Previous
        </button>
      )}
      { props.hasNextPage && (
        <button onClick={ () => props.setPage(props.page + 1) } className="ml-3 relative inline-flex items-center px-4 py-2 border border-gray-300 text-sm leading-5 font-medium rounded-md text-gray-700 bg-white hover:text-gray-500 focus:outline-none focus:shadow-outline-blue focus:border-blue-300 active:bg-gray-100 active:text-gray-700 transition ease-in-out duration-150">
          Next
        </button>
      )}
    </>
   );
}

export default Products;

这是我要导入的反应查询钩子:

// get individual
const fetchProduct = (productId) => {if(productId) axios.get(`${apiURL}/${productId}`).then((res) => res.data)}
function useGetProduct(productId) {
  const results = useQuery(
    ['product', { productId }],
    fetchProduct(productId),
    {
      // enabled: false
      // initialData: () =>
      //   queryCache.getQueryData('products')?.find((d) => d.productId === productId),
      // staleTime: 2000,
    })

    return { ...results, products: results.data ?? loadingProducts }
}

您会注意到开头的重要部分:

const [productId, setProductId] = React.useState();
  const [product, setProduct] = React.useState();
  const productQuery = useGetProduct(productId)

  const headerStyles = "px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider";

  React.useLayoutEffect(() => {
    let current = true;
    if(productId){
      if(current)
        setProduct(productQuery.data)
    }

    return () => {
      current = false
      setProductId()
    }
  },[productId, productQuery.data])

  // React.useLayoutEffect(() => {
  //   let current = true;
  //   if(productId){
  //     axios.get(`http://localhost:5000/api/products/${productId}`)
  //       .then((res) => {
  //         if(current)
  //           setProduct(res.data)
  //       })
  //   }

  //   return () => {
  //     current = false
  //   }
  // },[productId])

这里的重点是在 state 中有一个产品 id,当我单击表中的一行来编辑记录时,它会设置产品 id 并从 api 获取数据。这适用于注释掉的代码,但不使用 react-query。想要反应查询的力量,我努力重构它以使用我上面的内容。它确实有效。但感觉不对。

我遇到的最大问题是当我第一次设置它时,我的 useQuery 总是会使用未定义的 productId 调用初始渲染并失败。您可能已经注意到,我尝试将其添加enabled: false到我的 useQuery 中,因此我可以手动调用它,但由于某种原因它无法正常工作。也许你们还有其他想法。

不管怎样,珍惜时间。

4

1 回答 1

0

我想你可以使用 useEffect 来调用 useGetProductsList 和 useGetProduct 在安装组件时只调用一次,并且你可以在 productId 中绑定一个 useEffect 以防万一发生变化

而且我更喜欢单独调用函数而不是 React。

import React, { useEffect, useState, useLayoutEffect } from 'react';
// ...

function Products(){
  const [productId, setProductId] = useState();
  const [product, setProduct] = useState();
  const [page, setPage] = useState(1);
  const [pageSize, setPageSize] = useState(8);
  const [addModalIsOpen, setAddModalIsOpen] = useState(false);
  const [editModalIsOpen, setEditModalIsOpen] = useState(false);

  useEffect(()=>{
    // Will call once the component is mounted
    const productsQuery = useGetProductsList(page, pageSize);
    const [createProduct, createProductInfo] = useCreateProduct();
    const [putProduct, putProductInfo] = usePutProduct();
    const [patchProduct, patchProductInfo] = usePatchProduct();
  },[]);

  useEffect(()=>{
    // Will call when productId is changed
    const productQuery = useGetProduct(productId)
  },[productId]);

  // ...

Ofc,只是我的看法

于 2020-11-08T04:42:08.547 回答