Search code examples
javascriptreactjsasynchronousreact-hooksuse-effect

Update a Table with React Hooks when a Row is Added, Deleted and Modified? [Issue: Gets Before Post and Delete]


I'm using Axios to get, put, and delete values from our database and have them displayed in a table; however, I need to refresh the page to see my changes. To find answers, I've visited these posts: How to update the page after call Axios Successful ? React, refresh table after action in react, and How can I use React Hooks to refresh a table when a row is deleted or added? Unfortunately, I am still stuck and unsure how to dynamically update table rows upon response updates.

Update: I have noticed that the getValues function runs prior to the post and delete methods, which is why it is currently showing the previous values before the methods execute.

Axios.js - Where I am using get and delete methods. They work as I've had responses printed on the console.

import axios from "axios";

const getValues = async () => {
  const values = await axios
    .get("https://pokeapi.co/api/v2/type/")
    .then((response) => {
      return response.data;
    })
    .catch(function (error) {
      console.log(error);
    });
  return values;
};


const postValues = (values) => {
  axios
    .post("https://pokeapi.co/api/v2/type/")
    .then((response) => {
      console.log("Post Values: ", response.data);
      return response.data;
    });
};

const deleteValues = (id) => {
  console.log(id);
  const deleteValues = axios
    .delete(`https://pokeapi.co/api/v2/type/${id}`)
    .then((response) => {
      console.log("Delete Values: ", response);
    })
    .catch(function (error) {
      console.log(error);
    });
  return deleteValues;
};

export { getValues, postValues, deleteValues }

ValuesTable.js - Where the delete method executes

  import Axios from "./Axios";

  const [data, setData] = React.useState();

  useEffect(() => {
    Axios.getValues().then((result) => {
      setData(result.data);
    });
  }, [data]);

  return (
  {data.map((values) => {
    <TableRow/>
    <TableCell>{values.values}</TableCell>
    <TableCell>
      <Button
        onClick={() =>
           Axios.deleteValues(values.id);
        }
      />
    })};  
  )

Form.js - Where the post method executes

if (values.id === 0) {
  Axios.postValues(values);
} else {
  Axios.putValues(values, values.id);
}

UseState setData(result.data) loads all the existing values in the database. Method deleteValues deletes a value in an array. Method postValues adds a value into the database.


Solution

  • Well, you don't what to unconditionally call setData within an useEffect hook with data as a dependency as this will cause an infinite loop (render looping) to occur.

    Since the getValues utility already unpacks the response.data value there is likely no need to do it again in your UI. Also, remove the data dependency.

    useEffect(() => {
      Axios.getValues()
        .then((result) => {
          setData(result.results);
        });
    }, []);
    

    For the deleteValues utility, if console.log("Delete Values: ", response); is showing the correct values than I think you need to return this value from deleteValues.

    const deleteValues = (id) => {
      console.log(id);
      const deleteValues = axios
        .delete("https://pokeapi.co/api/v2/type/${id}`)
        .then((response) => {
          console.log("Delete Values: ", response);
          return response; // <-- new data values
        })
        .catch(function (error) {
          console.log(error);
        });
      return deleteValues;
    };
    

    Then in ValuesTable you need to update your data state with the new deleted values.

    {data.map((values) => {
      ...
        <Button
          onClick={() => {
            Axios.deleteValues(values.id)
              .then(data => setData(data));
          }}
        />
      ...
    })};
    

    Update

    Ok, since the deleteValues utility doesn't return the updated data from the backend you will need to maintain your local state manually. I suggest doing this work in a callback handler. Upon successful deletion, update the local state.

    const [data, setData] = React.useState();
    
    useEffect(() => {
      Axios.getValues().then((result) => {
        setData(result.data);
      });
    }, []);
    
    const deleteHandler = id => async () => {
      try {
        await Axios.deleteValues(id); // no error, assume success
        setData(data => data.filter((item) => item.id !== id));
      } catch(err) {
        // whatever you want to do with error
      }
    };
    
    return (
      ...
      {data.map((values) => {
        <TableRow/>
        <TableCell>{values.values}</TableCell>
        <TableCell>
          <Button onClick={deleteHandler(values.id)}>
            Delete
          </Button>
        })}; 
      ...
    )
    

    Note that I've written deleteHandler to be a curried function so you don't need an anonymous callback function for the button's onClick handler. It encloses the current id in an "instance" of the callback.

    Update 2

    If you are making a lot of different changes to your data in the backend it may just be easier to use a "fetch" state trigger to just refetch ("get") your data after each backend update. Anytime you make a call to update data in your DB, upon success trigger the fetch/refetch via a useEffect hook.

    const [data, setData] = React.useState();
    const [fetchData, setFetchData] = useState(true);
    
    const triggerDataFetch = () => setFetchData(t => !t);
    
    useEffect(() => {
      Axios.getValues().then((result) => {
        setData(result.data);
      });
    }, [fetchData]);
    
    const deleteHandler = id => async () => {
      try {
        await Axios.deleteValues(id); // no error, assume success
        triggerDataFetch(); // <-- trigger refetch
      } catch(err) {
        // whatever you want to do with error
      }
    };