Search code examples
javascriptreactjsreact-hooksredux-toolkitrtk-query

React useState not rendering the current onClick


I have a quandary on the code I'm trying to execute. I'm using controlled inputs and I'm passing the response from an API to a child component, but it is rendering the last value.

Here is my code.

Ppassing the id of the data row I'm currently trying to display:

<AdminInvociceDetail row={invoiceRowClicked} />

Component receiving the id

import React, { useState, useEffect } from "react";
import {
 useFetchInvoiceQuery,
 useUpdateInvoiceMutation,
} from "../../features/Invoices/InvoiceSlice";
import { useNavigate, Link } from "react-router-dom";
import "./Admin.css";

const AdminInvociceDetail = ({ row }) => {
  const { data } = useFetchInvoiceQuery(row);
  const [UpdateInvoice] = useUpdateInvoiceMutation();

  //* state for the form
  const [Id, setId] = useState("");
  const [billtoname, setBillToName] = useState("");
  const [clientname, setClientName] = useState("");
  const [offc, setOffc] = useState("");
  const [matterNo, setMatterNo] = useState("");

  useEffect(() => {
    setId(data?.ID);
    setBillToName(data?.BillToName);
    setOffc(data?.OFFC);
    setClientName(data?.ClientName);
    setMatterNo(data?.MatterNo);
  }, [row]);

  function ButtonLink({ to, children, className }) {
    return (
      <Link to={to}>
        <button className={className}>{children}</button>
      </Link>
    );
  }

  return (
    <div>
      <div class="box">
        <form class="ui form" onSubmit={handleSubmit}>
          <h4 class="ui dividing header labels">Invoice Detail:</h4>
          <div class="two fields">
            <div class="field">
              <label className="labels">Bill To Name</label>
              <input
                type="text"
                name="BillToName"
                placeholder="Bill To Name"
                required
                onChange={(e) => setBillToName(e.target.value)}
                value={billtoname || ""}
              />
            </div>
            <div class="field">
              <label className="labels">Client Location</label>
              <select
                class="ui fluid dropdown"
                required
                onChange={(e) => setClientName(e.target.value)}
                value={clientname || ""}
              >
                <option value="">Location</option>
                <option value="Becker - Tampa">Becker - Tampa</option>
                <option value="Becker - Orlando">Becker - Orlando</option>
                <option value="Becker - West Palm Beach">
                  Becker - West Palm Beach
                </option>
                <option value="Becker - Fort Myers">Becker - Fort Myers</option>
                <option value="Becker - Coral Gables">
                  Becker - Coral Gables
                </option>
                <option value="Becker - Fort Lauderdale">
                  Becker - Fort Lauderdale
                </option>
                <option value="Becker - Morristown">Becker - Morristown</option>
                <option value="Becker - Bradenton">Becker - Bradenton</option>
                <option value="Becker - Stuart">Becker - Stuart</option>
                <option value="Becker - Naples">Becker - Naples</option>
                <option value="Becker - Sarasota">Becker - Sarasota</option>
                <option value="Becker - Fort Walton Beach">
                  Becker - Fort Walton Beach
                </option>
                <option value="Becker - New York">Becker - New York</option>
                <option value="Becker - Mount Laurel">
                  Becker - Mount Laurel
                </option>
                <option value="Becker - Red Bank">Becker - Red Bank</option>
                <option value="Becker - Tallahassee">
                  Becker - Tallahassee
                </option>
              </select>
            </div>
          </div>
          <div class="two fields">
            <div class="field">
              <label className="labels">Office</label>
              <select
                class="ui fluid dropdown"
                required
                onChange={(e) => setOffc(e.target.value)}
                name="offc"
                value={offc || ""}
              >
                <option value="">Office</option>
                <option value="TAL">TAL</option>
                <option value="ORL">ORL</option>
                <option value="WPB">WPB</option>
                <option value="FTM">FTM</option>
                <option value="MIA">MIA</option>
                <option value="FTL">FTL</option>
                <option value="MOR">MOR</option>
                <option value="STR">STR</option>
                <option value="NAP">NAP</option>
                <option value="BDT">BDT</option>
                <option value="SAR">SAR</option>
                <option value="FTW">FTW</option>
                <option value="TAM">TAM</option>
                <option value="MTL">MTL</option>
                <option value="MOR">MOR</option>
                <option value="RB">RB</option>
                <option value="NY">NY</option>
                <option value="DC">DC</option>
              </select>
            </div>
            <div class="field">
              <label className="labels">Matter No</label>
              <input
                type="text"
                name="MatterNo"
                placeholder="Matter No"
                onChange={(e) => setMatterNo(e.target.value)}
                value={matterNo || ""}
              />
            </div>
          </div>
          <ButtonLink to="/" className="back-button">
            Back to Invoice
          </ButtonLink>
          <button class="submit-button">Submit Invoice</button>
          <div class="fields"></div>
        </form>
      </div>
    </div>
  );

When I click on the rows, the state is lagging behind. Not sure what I'm missing here.


Solution

  • If I'm understanding correctly that the issue is that the local states are not updated correctly to matched the queried data by row value, then it seems the issue is that the useEffect hook to update the local state has incorrect dependencies.

    Instead of using row it should use data, so that when the queried data result updates, the effect can run and update the local state. You could also pass initial state values directly to the useState hooks in the case there is already cached query data.

    Example:

    const AdminInvociceDetail = ({ row }) => {
      const { data } = useFetchInvoiceQuery(row);
      const [UpdateInvoice] = useUpdateInvoiceMutation();
    
      // state for the form
      const [Id, setId] = useState(data?.ID);
      const [billtoname, setBillToName] = useState(data?.BillToName);
      const [clientname, setClientName] = useState(data?.ClientName);
      const [offc, setOffc] = useState(data?.OFFC);
      const [matterNo, setMatterNo] = useState(data?.MatterNo);
    
      useEffect(() => {
        setId(data?.ID);
        setBillToName(data?.BillToName);
        setOffc(data?.OFFC);
        setClientName(data?.ClientName);
        setMatterNo(data?.MatterNo);
      }, [data]); // <-- data is external dependency, not row
    
      ...
    

    When using React hooks it is strongly recommended to add the eslint-plugin-react-hooks plugin to your eslint configuration so missing and/or incorrect hook dependencies can be easily identified to you via your IDE.