Using useEffect to move react query state to useState

5.2k views Asked by At

So I have a table of salespeople that is populated using the map method. Each row in that table has an 'Edit' button that should 1) get the id of the row, 2) fetch the data for that record from the database using react query, and 3) open a modal and pass that the object data to the modal to populate the input fields.

At this point, I can do the above by passing the data to the modal directly using the react query response, but I want to put that response into a useState variable to track my state locally for client operations (e.g. when adding a new salesperson, I need to pass an empty object into the modal instead of the last query result).

I'm pretty sure to do this, I need to incorporate useEffect somehow, but I'm having trouble getting it to work properly. Would think it would be as easy as updating a usestate variable in the useeffect function, like so:

I tried doing something like this:


  const [salespersonToModify, setSalespersonToModify] = React.useState({})


  .....


  React.useEffect(() => {
      console.log(`effect salesperson:${idToEdit}`)
      setSalespersonToModify(fetchedSalesperson)
  }, [fetchedSalesperson])


  .....


  <ModifySalespersons
    isOpen={isOpen}
    setIsOpen={setIsOpen}
    salesperson={salespersonToModify}
    onSubmit={handleSubmit}
    />

But I'm not having any luck. Hoping someone can assist. Full code below

import React, { useState, useEffect, useRef } from 'react';
import { useQuery, useMutation, useQueryCache, QueryCache, ReactQueryCacheProvider } from 'react-query'
import ModifySalespersons from '../components/ModifySalespersons'


function Salespersons(props){
  const [page, setPage] = React.useState(1)
  const [hasNextPage, setHasNextPage] = React.useState(true)
  const [hasPreviousPage, setHasPreviousPage] = React.useState(false)
  const [isOpen, setIsOpen] = React.useState(false)
  const [idToEdit, setIdToEdit] = React.useState(0)

  // api calls to abstract out
  const fetchSalespersons = async (key, { page = 1 }) => {
    const res = await fetch(`http://localhost:5000/api/salespersons?pagenumber=${page}&pagesize=4`);
    let pagination = JSON.parse(res.headers.get("X-Pagination"));

    setHasNextPage(pagination.hasNext);
    setHasPreviousPage(pagination.hasPrevious);

    return res.json();
  }

  const fetchSalesperson = async (key, salespersonId) => {
    const res = await fetch(`http://localhost:5000/api/salespersons/${salespersonId}`);

    return res.json();
  }

  const { data: salespersons, status: salespersonsStatus } = useQuery(['salespersons', { page } ], fetchSalespersons)
  const { data: fetchedSalesperson, status: fetchedSalespersonStatus } = useQuery(['foundSalesperson', idToEdit], fetchSalesperson)

  function addSalesPerson(){ 
    // setFetchedSalesperson({});
    setIsOpen(true);
  }
  
  function editSalesPerson(salespersonId){    
    fetchSalesperson(salespersonId)
    .then((salesperson) => {
      setIdToEdit(salespersonId)
      setIsOpen(true);
    });    
  }

  React.useEffect(() => {
      console.log(`effect salesperson:${idToEdit}`)

  }, [idToEdit])

  function handleSubmit(newSalesperson) {
    console.log(newSalesperson);
    // call api to create or update record
    setIsOpen(false);
  }

  return (
    <>      
    {fetchedSalespersonStatus === 'success' && (
      <ModifySalespersons
        isOpen={isOpen}
        setIsOpen={setIsOpen}
        salesperson={fetchedSalesperson}
        onSubmit={handleSubmit}
        />
    )}
      {salespersonsStatus === 'loading' && (    
        <div class="fixed inset-0 transition-opacity flex items-center justify-center">
          <div class="absolute inset-0 bg-gray-500 opacity-75"></div>
          
          <button type="button" class="z-50 inline-flex items-center px-12 py-8 border border-transparent text-base leading-6 font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-500 focus:outline-none focus:border-indigo-700 focus:shadow-outline-indigo active:bg-indigo-700 transition ease-in-out duration-150 cursor-not-allowed" disabled="">
            <svg class="animate-spin -ml-1 mr-3 h-5 w-5 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
              <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
              <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
            </svg>
            <p className="text-xl">
              Loading
            </p>
          </button>
        </div>
      )}
      
      {salespersonsStatus === 'error' && (  
        <div>
          Error
        </div>
      )}

      {salespersonsStatus === 'success' && (  
        <div onKeyDown={(event) => {if(event.key === "Escape") setIsOpen(false)}} tabIndex="0">
          <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">
              Salespeople
            </h2>
            <div>
              <span className="shadow-sm rounded-md">
                <button onClick={ () => addSalesPerson() } 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 salesperson
                </button>
              </span>
            </div>
          </div>

          <div className="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-lg">
                  <table className="min-w-full divide-y divide-gray-200">
                    <thead>
                      <tr>
                        <th className="px-6 py-3 bg-gray-50 text-left text-xs leading-4 font-medium text-gray-500 uppercase tracking-wider">
                          Id
                        </th>
                        <th className="px-6 py-3 bg-gray-50"></th>
                      </tr>
                    </thead>
                    <tbody className="bg-white divide-y divide-gray-200">
                      
                      { salespersons.map((salesperson, index) => (
                      <tr>
                        <td className="px-6 py-4 whitespace-no-wrap text-sm leading-5 text-gray-500">
                          {salesperson.salespersonId}
                        </td>
                        <td className="px-6 py-4 whitespace-no-wrap text-right text-sm leading-5 font-medium">
                          <button onClick={ () => editSalesPerson(salesperson.salespersonId) } 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="flex-1 flex justify-between sm:justify-end">                      
                      <PageButtons page={page} setPage={setPage} hasPreviousPage={hasPreviousPage} hasNextPage={hasNextPage}></PageButtons>
                    </div>
                  </nav>
                </div>
              </div>
            </div>
          </div>
        </div>
      )}
    </>
  );
}

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 Salespersons;

1

There are 1 answers

2
wentris On

The algorithm can be like:

  1. Render list of your entities(salesmen) what you have done.
  2. Handle click on row and after this you need to store target person data:
  • put that data in useState
  • if you need only id - you can push in to react-router. latest solution is better imho because of you can send the link with person details to somebody which may be way useful.
  1. need to teach your modal parse query parameter, use it in useEfect for fetching data, and put that data in useState of modal.

Practically, your editSalesPerson could be

import {useHistory} rom 'react-router-dom';

...

const history = useHistory()
const editSalesPerson = useCallback((personId) => {
  history.push('/salespersons/' + personId);
}, [])

be sure that your comp Salespersons wrapped by:

  <Route path="/salespersons/:id" component={Salespersons} />

all work out of Modal has done. then you have to extract needed parameter from location.

inside your modal component(or just here) instead of your const [idToEdit, setIdToEdit] = React.useState(0)

you already have an edit id which is

const params = useParams(); // params.id : undefined | string
Also you do not need to use this

*const [isOpen, setIsOpen] = React.useState(false)*

because of isOpen now is

*const isOpen = params && params.id;*

and dialog would be closed by execution:

history.push('salespersons')

last part is to implement fetching inside your modal

const [data, setData] = useState()
const [loading, setLoading] = useState(false)

useEffect(() => {
  if (isOpen) {
    ;(async function() {
      setLoading(true)
      try {
        const response = await api.etchSalesmanById(params.id);
      
        setData(response.data); // or whatever, depends on how you set your API up.
      } catch (e) {
        console.log(e)
      } finally {
        setLoading(false)
      }
    })()
  }
}, [isOpen])