DEV Community

Discussion on: Clean Up Async Requests in `useEffect` Hooks

 
mav1283 profile image
Paolo

Hi sorry for the late reply, I followed your suggestions and here's what my app can do:

  1. GET request on initial load
  2. PATCH and PUT request but have to refresh the page to see the changes,
  3. I cannot POST, DELETE

Here's my updated custom hook that has all the factory requests:

const useApiReq = () => {
  const [state, dispatch] = useReducer(listReducer, initialState);

  const getRequest = async (cancelToken) => {
    dispatch(loading());
    try {
      const response = await axios.get('/list', {
        cancelToken,
      });
      dispatch(processingRequest(response.data));
    } catch (err) {
      if (!axios.isCancel(err)) {
        dispatch(handlingError);
      }
    }
  };

  const postRequest = async (entry, cancelToken) => {
    dispatch(loading());
    try {
      const response = await axios.post('/list', entry, {
        cancelToken,
      });
      dispatch(processingRequest(response.data));
    } catch (err) {
      if (!axios.isCancel(err)) {
        dispatch(handlingError);
      }
    }
  };

  const patchRequest = async (id, updated_entry, cancelToken) => {
    dispatch(loading());
    try {
      const response = await axios.patch(`/list/${id}`, updated_entry, {
        cancelToken,
      });
      dispatch(processingRequest(response.data));
    } catch (err) {
      if (!axios.isCancel(err)) {
        dispatch(handlingError);
      }
    }
  };

  const putRequest = async (id, updated_entry, cancelToken) => {
    dispatch(loading());
    try {
      const response = await axios.put(`/list/${id}`, updated_entry, {
        cancelToken,
      });
      dispatch(processingRequest(response.data));
    } catch (err) {
      if (!axios.isCancel(err)) {
        dispatch(handlingError);
      }
    }
  };

  const deleteRequest = async (id, cancelToken) => {
    dispatch(loading());
    try {
      const response = await axios.delete(`/list/${id}`, {
        cancelToken,
      });
      dispatch(processingRequest(response.data));
    } catch (err) {
      if (!axios.isCancel(err)) {
        dispatch(handlingError);
      }
    }
  };

  return [
    state,
    getRequest,
    postRequest,
    patchRequest,
    putRequest,
    deleteRequest,
  ];
};

export default useApiReq;

I tried negating the: axios.isCancel(err) but to no avail, here's my api request codes.

GET Request:

function Main() {
  const { state, getRequest } = useContext(AppContext);
  const cancelToken = useRef(null);
  const { isError, isLoading, data } = state;

  const getData = () => {
    if (cancelToken.current) {
      cancelToken.current.cancel();
    }

    cancelToken.current = axios.CancelToken.source();
    getRequest(cancelToken.current.token);
  };

  useEffect(() => {
    getData();
  }, []);

  useEffect(() => {
    /* axios cleanup */
    return () => {
      if (cancelToken.current) {
        cancelToken.current.cancel();
      }
    };
  }, []);

  return (
    <main className='App-body'>
      <Sidebar />
      <div className='list-area'>
        {isLoading && (
          <p className='empty-notif'>Loading data from the database</p>
        )}
        {isError && <p className='empty-notif'>Something went wrong</p>}
        {data.length == 0 && <p className='empty-notif'>Database is empty</p>}
        <ul className='parent-list'>
          {data.map((list) => (
            <ParentListItem key={list._id} {...list} />
          ))}
        </ul>
      </div>
    </main>
  );
}

export default Main;

POST Request:

const AddList = ({ exitHandler }) => {
  const { postRequest } = useContext(AppContext);
  const [newList, setNewList] = useState({});
  const cancelToken = useRef(null);
  const inputRef = useRef(null);

  /* On load set focus on the input */
  useEffect(() => {
    inputRef.current.focus();
  }, []);

  useEffect(() => {
    /* clean up axios */
    return () => {
      if (cancelToken.current) {
        cancelToken.current.cancel();
      }
    };
  }, []);

  const handleAddList = (e) => {
    e.preventDefault();
    const new_list = {
      list_name: inputRef.current.value,
      list_items: [],
    };
    setNewList(new_list);
  };

  const createNewList = (entry) => {
    if (cancelToken.current) {
      cancelToken.current.cancel();
    }

    /* create token source */
    cancelToken.current = axios.CancelToken.source();
    postRequest(entry, cancelToken.current.token);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    createNewList(newList);
    exitHandler();
  };

  return (
    <form onSubmit={handleSubmit} className='generic-form'>
      <input
        type='text'
        ref={inputRef}
        placeholder='List Name'
        onChange={handleAddList}
      />
      <input type='submit' value='ADD' className='btn-rec' />
    </form>
  );
};

export default AddList;

DELETE Request:

const DeleteList = ({ exitHandler }) => {
  const { state, deleteRequest } = useContext(AppContext);
  const { data } = state;
  const cancelToken = useRef(null);
  const selectRef = useRef();
  const [targetListId, setTargetListId] = useState();

  useEffect(() => {
    selectRef.current.focus();
  }, []);

  useEffect(() => {
    /* cleanup axios */
    return () => {
      if (cancelToken.current) {
        cancelToken.current.cancel();
      }
    };
  }, []);

  useEffect(() => {
    setTargetListId(data[0]._id);
  }, [data]);

  const deleteList = (entry) => {
    if (cancelToken.current) {
      cancelToken.current.cancel();
    }

    /* create token source */
    cancelToken.current = axios.CancelToken.source();
    deleteRequest(entry, cancelToken.current.token);
  };

  const handleDeleteList = (e) => {
    e.preventDefault();
    deleteList(targetListId);
    exitHandler();
  };

  const handleChangeList = (e) => {
    setTargetListId(e.target.value);
    console.log(targetListId);
  };

  return (
    <form onSubmit={handleDeleteList} className='generic-form'>
      <label>
        <select
          ref={selectRef}
          value={targetListId}
          onChange={handleChangeList}
          className='custom-select'
        >
          {data.map((list) => (
            <option key={list._id} value={list._id}>
              {list.list_name}
            </option>
          ))}
        </select>
      </label>
      <input type='submit' value='DELETE' className='btn-rec' />
    </form>
  );
};

export default DeleteList;
Thread Thread
 
pallymore profile image
Yurui Zhang

hi @paolo - sorry I just saw this. Could you setup a github repo or add me to your existing one? my github handle is @pallymore

alternatively could you set this up on codesandbox.io ? it'll be easier to read/write code there, thanks!

Thread Thread
 
mav1283 profile image
Paolo

Thanks so much, I added you on github :)