How to cancel axios requests - javascript

I have api file with requests
import * as axios from "axios";
export const productAPI = {
getProducts() {
return axios({
method: 'get',
url: `/api/products`
});
}
};
which reaches to transport.js and sends request(i think that part is not important).
Method above is called from my component like this
useEffect(()=> {
setLoading(true);
productAPI.getProducts()
.then((response) => {
if(response.status === 200) {
history.push(`${pathWithLocation}${PAGES.newLoan}`);
}
})
.catch((error) => {
if (error.response.data.error.message) {
dispatch(addModal({
type: 'basic',
size: 'middle',
title: 'some title',
text: error.response.data.error.message,
buttons: [{ buttonLabel: 'ОК', onClick: ()=> dispatch(removeModal()) }]
}))
}
})
.finally(() => {
setLoading(false);
});
},[])
I want to cancel this specific request when component is unmounted. (switched route for example)

You can just use a isCurrent flag. (I have to admit that I have not considered what the benefit of using the axios.cancelToken mechanism would be here. Maybe it would make it cleaner, maybe it would just make it more convoluted.)
useEffect(() => {
const isCurrent = true;
setLoading(true);
productAPI.getProducts()
.then((response) => {
if(isCurrent && response.status === 200) {
history.push(`${pathWithLocation}${PAGES.newLoan}`);
}
})
.catch((error) => {
if (isCurrent && error.response.data.error.message) {
dispatch(addModal({/*...*/}))
}
})
.finally(() => {
if (isCurrent) setLoading(false);
});
return () => { isCurrent = false };
}, [])

getProducts(cancelToken) {
return axios({
method: 'get',
url: `/api/products`,
cancelToken
});
}
useEffect(()=> {
const source= CancelToken.source();
const isMounted= true;
setLoading(true);
productAPI.getProducts(source.token)
.then((response) => {
if(response.status === 200) {
history.push(`${pathWithLocation}${PAGES.newLoan}`);
}
})
.catch((error) => {
if (error.response.data.error.message) {
dispatch(addModal({
...
}))
}
})
.finally(() => {
isMounted && setLoading(false);
});
return ()=>{
isMounted= false;
source.cancel();
}
},[])
Or a bit magic way (Codesandbox demo):
import React, { useState } from "react";
import {
useAsyncEffect,
CanceledError,
E_REASON_UNMOUNTED
} from "use-async-effect2";
import cpAxios from "cp-axios";
export default function TestComponent(props) {
const [text, setText] = useState("");
const [loading, setLoading] = useState(true);
const cancel = useAsyncEffect(
function* () {
try {
const response = yield cpAxios(props.url);
setText(JSON.stringify(response.data));
setLoading(false);
if (response.status === 200) {
//history.push(`${pathWithLocation}${PAGES.newLoan}`);
}
} catch (err) {
CanceledError.rethrow(err, E_REASON_UNMOUNTED);
setLoading(false);
setText(err.toString());
//dispatch(addModal({})
}
},
[props.url]
);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>{text}</div>
<button onClick={cancel} disabled={!loading}>
Cancel request
</button>
</div>
);
}

Related

I am getting null as result in useSelector from react redux toolkit

and also useEffect also not working in my file. i tried many way but useEffect not working. if i comments everything file and try then useEffect working else not working.
and also i am getting null value from
const { currentVideo } = useSelector((state) => state.video);
this is my useEffect ->
useEffect(() => {
const fetchData = async () => {
try {
const videoRes = await axiosInstance.get(`/videos/find/${path}`, {
withCredentials: true,
});
// console.log(videoRes);
const updatedView = await axiosInstance.put(`videos/view/${path}`);
// console.log(updatedView.data, "view is updating");
const channelRes = await axiosInstance.get(
`/users/find/${videoRes.data.userId}`,
{ withCredentials: true }
);
setChannel(channelRes.data);
dispatch(fetchSuccess(videoRes.data));
} catch (err) {
console.log(err);
return "opps something went wrong!";
}
};
fetchData();
}, [path, dispatch]);
here i am getting null value
const Video = () => {
const { currentUser } = useSelector((state) => state.user);
const { currentVideo } = useSelector((state) => state.video);
const dispatch = useDispatch();
const path = useLocation().pathname.split("/")[2];
// console.log(currentVideo); // getting null
const [channel, setChannel] = useState({});
and my full video.jsx is
import React, { useEffect, useState } from "react";
import styled from "styled-components";
import ThumbUpOutlinedIcon from "#mui/icons-material/ThumbUpOutlined";
import ThumbDownOffAltOutlinedIcon from "#mui/icons-material/ThumbDownOffAltOutlined";
import ReplyOutlinedIcon from "#mui/icons-material/ReplyOutlined";
import AddTaskOutlinedIcon from "#mui/icons-material/AddTaskOutlined";
import ThumbUpIcon from "#mui/icons-material/ThumbUp";
import ThumbDownIcon from "#mui/icons-material/ThumbDown";
import Comments from "../components/Comments";
import { useDispatch, useSelector } from "react-redux";
import { useLocation } from "react-router-dom";
import axios from "axios";
import { fetchSuccess, like, dislike } from "../redux/videoSlice";
import axiosInstance from "../axios";
import { subscription } from "../redux/userSlice";
import Recommendation from "../components/Recommendation";
import { format } from "timeago.js";
const Video = () => {
const { currentUser } = useSelector((state) => state.user);
const { currentVideo } = useSelector((state) => state.video);
const dispatch = useDispatch();
const path = useLocation().pathname.split("/")[2];
// console.log(currentVideo); // getting null
const [channel, setChannel] = useState({});
/*
// console.log(path); ok hai video user id aa rahi hai.
// its working its getting all the data.
const test = async () => {
const isWorking = await axios.get(
"http://localhost:5000/api/videos/find/63931e44de7c22e61c4ffd6c"
);
console.log(isWorking.data);
console.log(isWorking.data.videoUrl);
};
const videoRes = test();
console.log(videoRes);
*/
// {withCredentials: true}
useEffect(() => {
const fetchData = async () => {
try {
const videoRes = await axiosInstance.get(`/videos/find/${path}`, {
withCredentials: true,
});
// console.log(videoRes);
const updatedView = await axiosInstance.put(`videos/view/${path}`);
// console.log(updatedView.data, "view is updating");
const channelRes = await axiosInstance.get(
`/users/find/${videoRes.data.userId}`,
{ withCredentials: true }
);
setChannel(channelRes.data);
dispatch(fetchSuccess(videoRes.data));
} catch (err) {
console.log(err);
return "opps something went wrong!";
}
};
fetchData();
}, [path, dispatch]);
const handleLike = async () => {
try {
await axiosInstance.put(`/users/like/${currentVideo._id}`, {
withCredentials: true,
});
dispatch(like(currentUser._id));
} catch (err) {
console.log(err);
return "opps something went wrong!";
}
};
const handleDislike = async () => {
try {
await axiosInstance.put(`/users/dislike/${currentVideo._id}`, {
withCredentials: true,
});
dispatch(dislike(currentUser._id));
} catch (err) {
console.log(err);
}
};
const handleSub = async () => {
currentUser.subscribedUsers.includes(channel._id)
? await axiosInstance.put(`/users/unsub/${channel._id}`, {
withCredentials: true,
})
: await axiosInstance.put(`/users/sub/${channel._id}`, {
withCredentials: true,
});
dispatch(subscription(channel._id));
};
if (!currentUser) return "Loading....";
return (
<Container>
<Content>
<VideoWrapper>
<VideoFrame src={currentVideo?.videoUrl} controls />
</VideoWrapper>
<Title>{currentVideo?.title}</Title>
<Details>
<Info>
{currentVideo?.views} views •{format(currentVideo?.createdAt)}
</Info>
<Buttons>
<Button onClick={() => handleLike()}>
{currentVideo?.likes.includes(currentUser._id) ? (
<ThumbUpIcon />
) : (
<ThumbUpOutlinedIcon />
)}{" "}
{currentVideo?.likes.length}
</Button>
<Button onClick={() => handleDislike()}>
{currentVideo?.dislikes.includes(currentUser._id) ? (
<ThumbDownIcon />
) : (
<ThumbDownOffAltOutlinedIcon />
)}
Dislike
</Button>
<Button>
<ReplyOutlinedIcon /> Share
</Button>
<Button>
<AddTaskOutlinedIcon /> Save
</Button>
</Buttons>
</Details>
<Hr />
<Channel>
<ChannelInfo>
<Image src={channel.img} />
<ChannelDetail>
<ChannelName>{channel.name}</ChannelName>
<ChannelCounter>{channel.subscribers} subscribers</ChannelCounter>
<Description>{currentVideo?.desc}</Description>
</ChannelDetail>
</ChannelInfo>
<Subscribe onClick={handleSub}>
{currentUser.subscribedUsers.includes(channel._id)
? "SUBSCRIBED"
: "SUBSCRIBE"}
</Subscribe>
</Channel>
<Hr />
<Comments videoId={currentVideo?._id} />
</Content>
<Recommendation tags={currentVideo?.tags} />
</Container>
);
};
export default Video;
and this is my redux videoslice file videoSlice.js
import { createSlice } from "#reduxjs/toolkit";
const initialState = {
currentVideo: null,
loading: false,
error: false,
};
export const videoSlice = createSlice({
name: "video",
initialState,
reducers: {
fetchStart: (state) => {
state.loading = true;
},
fetchSuccess: (state, action) => {
state.loading = false;
state.currentVideo = action.payload;
},
fetchFailure: (state) => {
state.loading = false;
state.error = true;
},
like: (state, action) => {
if (!state.currentVideo.likes.includes(action.payload)) {
state.currentVideo.likes.push(action.payload);
state.currentVideo.dislikes.splice(
state.currentVideo.dislikes.findIndex(
(userId) => userId === action.payload
),
1
);
}
},
dislike: (state, action) => {
if (!state.currentVideo.dislikes.includes(action.payload)) {
state.currentVideo.dislikes.push(action.payload);
state.currentVideo.likes.splice(
state.currentVideo.likes.findIndex(
(userId) => userId === action.payload
),
1
);
}
},
views: (state, action) => {
if (state.currentVideo.views.includes(action.payload)) {
state.currentVideo.views.push(action.payload);
}
},
},
});
export const { fetchStart, fetchSuccess, fetchFailure, like, dislike, views } =
videoSlice.actions;
export default videoSlice.reducer;
in browser i am also getting null
i am trying to call my api and render data. but useeffect not working in my code. also from useSelector i am getting null value.

How to manage component states (loading, error, data) in React

I have implemented the following code to fetch data and render a component if everything goes well along with checking loading, error states.
import { useEffect, useState } from "react";
function Posts() {
const [posts, setPosts] = useState([]);
const [loader, setLoader] = useState(false);
const [error, setError] = useState({ status: false, message: "" });
const fetchPosts = () => {
setLoader(true);
setTimeout(async () => {
try {
const response = await fetch(
"https://jsonplaceholder.typicode.com/posts"
);
// throw new Error("Some error occured");
const data = await response.json();
if (data.error) {
setError({ status: true, message: data.error });
} else {
setPosts(data);
}
setLoader(false);
} catch (error) {
console.log("error", error);
setError({ status: true, message: error.message });
setLoader(false);
}
}, 2000);
};
useEffect(() => {
fetchPosts();
}, []);
if (loader) return <h3>Loading...</h3>;
if (error.status) return <h3>Error: {error.message}</h3>;
return (
<div>
<h1>Posts</h1>
{posts.length === 0 && <h3>There are no posts</h3>}
{posts.length > 0 && (
<div>
{posts.map((post) => (
<Post post={post} key={post.id} />
))}
</div>
)}
</div>
);
}
export default Posts;
Is this the right way to handle loading, error and success states when fetching data? or is there a better and more elegant solution than repeating this for every component?
Instead of checking for data.error in the try block, you could check for response.ok; if it is true, call response.json(), otherwise throw an error.
Also move the setLoader call to the finally block to avoid the duplicate calls in try and catch blocks.
try {
const response = await fetch(...);
if (response.ok) {
let data = await response.json();
setPosts(data);
} else {
throw new Error(/* error message */);
}
} catch (error) {
console.log("error", error);
setError({ status: true, message: error.message });
} finally {
setLoader(false);
}
If you want to check for data.error property in a response, you can change the following if condition
if (response.ok) {
to
if (response.ok && !data.error) {
is there a better and more elegant solution than repeating this for
every component?
Make a custom hook to make the fetch request and use that in every component that needs to fetch data from the backend.
const useFetch = (apiUrl, initialValue) => {
const [data, setData] = useState(initialValue);
const [loader, setLoader] = useState(false);
const [error, setError] = useState({ status: false, message: "" });
useEffect(() => {
async function fetchData = (url) => {
setLoader(true);
try {
const response = await fetch(url);
if (response.ok) {
let responseData = await response.json();
setData(responseData);
} else {
throw new Error(/* error message */);
}
} catch (error) {
console.log("error", error);
setError({ status: true, message: error.message });
} finally {
setLoader(false);
}
}
fetchData(apiUrl);
}, [apiUrl]);
return [data, error, loader];
};
Your solution should be good enough to do, but, to me, I would prefer not to set timeout for getting data, and I will use .then and .catch for better readable and look cleaner to me
import { useEffect, useState } from "react";
function Posts() {
const [posts, setPosts] = useState([]);
const [loader, setLoader] = useState(false);
const [error, setError] = useState({ status: false, message: "" });
const fetchPosts = () => {
fetch("https://jsonplaceholder.typicode.com/posts")
.then(response => response.json())
.then(data => {
setPosts(data);
setLoader(false);
})
.catch(error =>{
console.log("error", error);
setError({ status: true, message: error.message });
setLoader(false);
});
};
useEffect(() => {
setLoader(true);
fetchPosts();
}, []);
if (loader) return <h3>Loading...</h3>;
if (error.status) return <h3>Error: {error.message}</h3>;
return (
<div>
<h1>Posts</h1>
{posts.length === 0 && <h3>There are no posts</h3>}
{posts.length > 0 && (
<div>
{posts.map((post) => (
<Post post={post} key={post.id} />
))}
</div>
)}
</div>
);
}
export default Posts;

How to use custom component multiple times in same component

Basically i've created one custom component for api calling
import React, {useState, useEffect} from 'react';
import axios from 'axios';
export const useFetch = config => {
const [Response, setResponse] = useState({});
const [Error, setError] = useState({});
const [ShowModal, setShowModal] = useState(false);
const [ShowLoader, setShowLoader] = useState(false);
useEffect(() => {
callAPI();
}, []);
const callAPI = () => {
setShowLoader(true);
axios(config)
.then(res => {
console.log('==>>', res);
if (res.status == 200) {
setShowLoader(false);
setResponse(res.data);
}
})
.catch(err => {
console.log('==>>', err.response);
setError(err.response.data);
setShowLoader(false);
setShowModalErrorMessage(err.response.data.error);
setShowModal(true);
});
};
return {Response, Error, ShowModal, ShowLoader};
};
with the help on this i can call api and get response if i use it with useEffect/componentDidMount in component. But how to use same for calling different api on Button click. is it possible?
i followed this=> post
Add setUrl method (can expand to setConfig) in useFetch.
Here working demo for this in stackblitz
import React, {useState, useEffect} from 'react';
import axios from 'axios';
const useFetch = ({}) => {
const [Response, setResponse] = useState({});
const [Error, setError] = useState({});
const [ShowModal, setShowModal] = useState(false);
const [ShowLoader, setShowLoader] = useState(false);
const [url, setUrl] = useState("");
useEffect(() => {
if (url) {
console.log('making request ', url);
callAPI();
}
}, [url]);
const callAPI = () => {
setShowLoader(true);
axios(url)
.then(res => {
console.log('==>>', res);
if (res.status == 200) {
setShowLoader(false);
setResponse(res.data);
}
})
.catch(err => {
console.log('==>>', err.response);
setError(err.response.data);
setShowLoader(false);
setShowModalErrorMessage(err.response.data.error);
setShowModal(true);
});
};
return {Response, Error, ShowModal, ShowLoader, setUrl};
};
export default useFetch;
On the button click, set url (expand to config)
import React, {useState, useEffect} from 'react';
import useFetch from './use-fetch';
export default ({ name }) => {
const {Response, Error, ShowModal, ShowLoader, setUrl } = useFetch({});
return (<div>
<button key={'1'} onClick={() => setUrl("http://foo/items")}> Request 1 </button>
<button key={'2'} onClick={() => setUrl("http://foo/other")}> Request 2 </button>
</div>)
};
Common Request.js file using the fetch method
Request("POST","http://localhost/users/user",{'name':"",'add':""})
export default function Request(reqMethod, endPointUrl, bodyData) {
if (reqMethod === "POST") {
return fetch(endPointUrl, {
method: reqMethod,
body: JSON.stringify(bodyData),
})
.then((response) => {
return response.json();
})
.catch(() => {
localStorage.clear();
});
} else if (reqMethod === "GET") {
return fetch(endPointUrl, {
method: reqMethod,
})
.then((response) => {
return response.json();
}).catch(() => {
localStorage.clear();
});
}
}

How can i write "setTimeOut" function in react hooks?

I am working on displaying a "message" on the component based on the server response, and i wanted that message to disappear after 5 second. I tried my best with setTimeout but no luck, can you help me?
Here is my code:
import React, { useState } from "react";
import { Form, Button, Container, Row, Col} from 'react-bootstrap'
import axios from 'axios'
export default function Users() {
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const [message, setMessage] = useState("")
function handleSubmit(e){
e.preventDefault()
const credential = { email, name };
axios
.post('/', credential)
.then(response => {
if(response.status === 201) {
resetInputs()
setMessage(response.data.message)
}
})
.catch(error => {
if (error.response.status === 409) {
setMessage(error.response.data.message)
}
})
}
function resetInputs(){
setEmail("")
setName("")
}
return (
<div className="form">
<div className="hero-container">
<h1>Welcome to <span className="hi">my</span><span>website</span></h1>
<h5>Enter your name and your email to join our waiting list!</h5>
<p></p>
<div>
{message}
</div>
<p></p>
</div>
)
}
You call setTimeout after setting the message, telling it to fire after five seconds, and then clear the message:
function handleSubmit(e){
e.preventDefault()
const credential = { email, name };
axios
.post('/', credential)
.then(response => {
if(response.status === 201) {
resetInputs()
setMessage(response.data.message)
}
})
.catch(error => {
if (error.response.status === 409) {
setMessage(error.response.data.message)
}
})
.finally(() => { // ***
setTimeout(() => { // ***
setMessage(""); // *** If you want to clear the error message as well
}, 5000); // *** as the normal message
}); // ***
}
or
function handleSubmit(e){
e.preventDefault()
const credential = { email, name };
axios
.post('/', credential)
.then(response => {
if(response.status === 201) {
resetInputs()
setMessage(response.data.message)
setTimeout(() => { // *** If you only want to automatically clear
setMessage(""); // *** this message and not an error message
}, 5000); // ***
}
})
.catch(error => {
if (error.response.status === 409) {
setMessage(error.response.data.message)
}
});
}
You can add setTimout to your axios call, or you can reset it independently like this:
import { useEffect } from "react";
...
useEffect(() => {
let isUnmounted = false;
if (message !== "") {
setTimeout(() => {
if (!isUnmounted ) {
setMessage("");
}
}, 5000);
}
return () => { isUnmounted = true; };
}, [message])
isUnmounted prevents using setMessage() in an unmounted component, it is possible for a user to close the component before time is reached.
Something like this may work (untested):
const useTimedState = (initialState, duration) => {
const [state, setState] = setState(initialState);
useEffect(() => {
if (typeof state === 'undefined') {
return;
}
const timer = setTimeout(() => {
setState();
}, duration);
return () => clearTimeout(timer);
}, [state]);
return [state, setState];
}
export default function Users() {
const [email, setEmail] = useState("");
const [name, setName] = useState("");
const [message, setMessage] = useTimedState(undefined, 5000);
function handleSubmit(e){
e.preventDefault()
const credential = { email, name };
axios
.post('/', credential)
.then(response => {
if(response.status === 201) {
resetInputs()
setMessage(response.data.message)
}
})
.catch(error => {
if (error.response.status === 409) {
setMessage(error.response.data.message)
}
})
}
}

change a class component into a functional component to fix '_id' is not defined

I'm trying to change a class component into a functional component so that I can delete questions that are rendered onto the screen. My main focus is mainly deletedQuestion().
import React, { useEffect, useState } from "react"
import Axios from "axios"
import { deleteQuestion } from './InputQuestionsFunctions'
export default function () {
const [questions, setQuestions] = useState([])
useEffect(() => {
getQuestions()
}, [])
async function getQuestions() {
try {
const res = await Axios.get("http://localhost:5000/QuestionsRoute/me", {
headers: { 'Authorization': `Bearer ${localStorage.getItem("usertoken")}` }
})
// if(res.status)
setQuestions(res.data)
}
catch (e) {
console.log("error while getting questions", e)
}
}
const deletedQuestion = (e) => {
e.preventDefault()
const removedQuestion = {
id:_id
}
deleteQuestion(removedQuestion)
.then(res => {this.props.history.push('/profile')
})
}
const renderQuestions = () => {
return questions.map(eachQuestion => {
return (
<div>
<button onClick={(e) => deletedQuestion(e)}
className="mdi mdi-delete mdi-24px lifeline-icon" />
<p> {eachQuestion.question}</p>
<p style={{ color: "blue" }}> {eachQuestion.answer}</p>
</div >
)
})
}
return (
<div>
{renderQuestions()}
</div>
)
}
right now the _id is undefined but ideally onClick={(e) => deletedQuestion(e)} would use the id to delete the specific question. Below is the imported () for reference!
export const deleteQuestion = removedQuestion => {
const body = {
id:removedQuestion._id
}
const options = {
headers: { 'Authorization': `Bearer ${localStorage.getItem("usertoken")}` }
};
return axios
.delete(
process.env.REACT_APP_SERVER + '/QuestionsRoute/me/_id',
body,
options
).then(res => {
console.log('Deleted')
})
}
I'm new I would greatly appreciate the edited code but any help is appreciated!
You have access to the question in the .map so pass it through when you call deletedQuestion:
const deletedQuestion = (e, question) => {
e.preventDefault()
const removedQuestion = {
id: question._id
}
deleteQuestion(removedQuestion)
.then(res => {this.props.history.push('/profile')
})
}
...
<button onClick={(e) => { deletedQuestion(e, eachQuestion); }}
Inside deletedQuestion block, there is no _id variable, that's why it is undefined.
- You need to pass eachQuestion._id to deletedQuestion
import React, { useEffect, useState } from "react"
import Axios from "axios"
import { deleteQuestion } from './InputQuestionsFunctions'
export default function () {
const [questions, setQuestions] = useState([])
useEffect(() => {
getQuestions()
}, [])
async function getQuestions() {
try {
const res = await Axios.get("http://localhost:5000/QuestionsRoute/me", {
headers: { 'Authorization': `Bearer ${localStorage.getItem("usertoken")}` }
})
// if(res.status)
setQuestions(res.data)
}
catch (e) {
console.log("error while getting questions", e)
}
}
// Now you have _id
const deletedQuestion = (e, _id) => {
e.preventDefault()
const removedQuestion = {
id:_id
}
deleteQuestion(removedQuestion)
.then(res => {this.props.history.push('/profile')
})
}
const renderQuestions = () => {
return questions.map(eachQuestion => {
return (
<div>
{/* pass eachQuestion._id into deletedQuestion*/}
<button onClick={(e) => deletedQuestion(e, eachQuestion._id)}
className="mdi mdi-delete mdi-24px lifeline-icon" />
<p> {eachQuestion.question}</p>
<p style={{ color: "blue" }}> {eachQuestion.answer}</p>
</div >
)
})
}
return (
<div>
{renderQuestions()}
</div>
)
}

Categories