I am trying to pass an argument into fetcher as a body parameter with useSWR method. I have the following and there is an undefined error.
const fetcher = (url: string, someBodyArgument: string) => fetch(url, {
body: JSON.stringify({someBodyArgument})
}).then(res => res.json())
const { data, error, isLoading } = useSWR(["/api/test", "bleh"], fetcher)
if (error) return <div>Failed to load API</div>
if (isLoading) return <div>Loading</div>
if (!data) return null
Everything works fine without the second argument in the array, but I get "Failed to load API" every time when I add the second argument for some reason - despite following documentation.
Related
I have the following custom hook, useFetch, in my React (v18.1.0) project to fetch data from a Node.js server.
export default function useFetch(url, requestType, headers, body) {
const [error, setError] = useState(false);
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
console.log('Inside useFetch hook');
useEffect(() => {
console.log('Inside useEffect in the useFetch hook');
const controller = new AbortController();
async function retrieveData(reqUrl) {
try {
console.log('Inside the useFetch try block');
const res = await fetchData(
reqUrl,
requestType,
headers,
body,
controller.signal
);
console.log('Done with fetch; this is the server response ', res);
setData(res);
setLoading(false);
console.log('Done with useFetch try block.');
} catch (err) {
if (err.name === 'AbortError') {
console.log('Fetch aborted');
} else {
setError(true);
setData(err);
setLoading(false);
}
}
}
retrieveData(url);
return () => {
controller.abort();
};
}, []);
return { loading, error, data };
}
My useFetch hook uses a function called fetchData to send a fetch request to the server.
async function fetchData(url, requestType, headers, payload, abortSignal) {
console.log('Inside the fetch function');
let res = await fetch(url, {
method: requestType,
headers: headers,
body: payload,
signal: abortSignal ? abortSignal : null,
});
if (res.ok) {
const resJson = await res.json();
console.log('Returning value from fetch function');
return { status: res.status, response: resJson };
} else {
await fetchErrorHandler(res);
}
}
The useFetch hook is invoked once in my VideoPlayer component.
function VideoPlayer() {
const { videoId } = useParams();
const url = `http://localhost:5000/videos/${videoId}`;
const { loading, error, data } = useFetch(url, 'GET', {}, null);
return (
<div>
{loading && <div />}
{!loading && error && <h2>{data.message}</h2>}
{!loading && !error && (
<video width={600} height={450} controls src={data.response.videoUrl}>
Cannot display video player.
</video>
)}
</div>
);
}
The problem I'm facing is that when the VideoPlayer component is mounted to the DOM and useFetch is called, the execution flow looks like this:
Execution flow of useFetch hook. As is seen in the image, everything seems fine until the line Inside the fetch function is printed in the console. After this, the useEffect hook within the useFetch is called again, for reasons I'm unable to understand (my dependency array is empty and, moreover, there's no state change at this point). Then, it tries to execute the fetch another time, aborts it, and then eventually returns a response, presumably to the original fetch request. At the end of this process, useFetch is called yet again. If anyone can help me shed some light on why the hook is behaving this way, instead of simply executing the fetch request once and returning the response, I will greatly appreciate it.
I assume you use React in StrictMode - this is the default for apps created with create-react-app.
In strict mode, effects can fire twice while in development mode. You can verify that this causes your problem by running a production build. If the problem goes away, it is likely caused by StrictMode.
My suggestion is to not change anything - actually, your code seems to work fine: the first effect execution triggers a fetch, then the second effect execution aborts the initial fetch and fetches for a second time. This is exactly, what the code is expected to do.
You can use axios to deal with API
So this is more of a javascript question than a reactjs question. I am creating a protected route in reactjs. In this, I am fetching the '/checkauth' get request to check the authentication and it correctly returns the response to me. However the problem is that since it is an async function, it takes time to return that value and thus my return statement is executed earlier.
This is the code I am having problem with.
const [auth, setAuth] = useState();
const checkAuthentication = async ()=>{
const res = await fetch('/checkauth', {
method : "GET",
credentials: "include",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
}
});
const data = await res.json();
return res.status===200 ? (data.isAuthenticated) : (false) ;
}
useEffect(async ()=>{
const data = await checkAuthentication();
setAuth(data)
}, []);
return auth ? <Outlet /> : <Navigate to='/signin' />;
Here the auth is always undefined and thus it always navigates to signing.
Use three states.
const [auth, setAuth] = useState("loading");
...
setAuth(data ? "auth" : "not-auth");
...
if (auth === "loading")
return <Loading />
else if (auth === "not-auth")
return <Navigate to='/signin' />
else
return <Outlet />
You can return a loading spinner while fetching the data, and when the request completes the state will be updated and the <Outlet /> component will be rendered.
By the way: When you are passing an async function to the useEffect hook, it returns a promise and useEffect doesn't expect the callback function to return Promise, rather it expects that the callback returns nothing (undefined) or a function (typically a cleanup function).
Try this:
useEffect(() => {
// declare the async data fetching function
const fetchData = async () => {
// get the data from the api
const data = await fetch('https://yourapi.com');
// convert the data to json
const json = await response.json();
// set state with the result
setData(json);
}
// call the function
fetchData()
// make sure to catch any error
.catch(console.error);;
}, [])
More info:
React Hook Warnings for async function in useEffect: useEffect function must return a cleanup function or nothing
I have created a redux that is going to request an API and if the result is 200, I want to redirect the user to another page using history.
The problem is: I don't know how to trigger this change if the action is a success.
I could redirect the user in my useCase function but I can't use history.push pathName/state argument because it only works in a React component.
So this is what I have done in my React component:
const acceptProposalHandler = () => {
store.dispatch(acceptProposal(id)).then(() => {
setTimeout(() => {
if (isAccepted) { //isAccepted is false by default but is changed to true if the
//request is 200
history.push({
pathname: urls.proposal,
state: {
starterTab: formatMessage({id: 'proposalList.tabs.negotiation'}),
},
});
}
}, 3000);
});
};
Sometimes it works but other times it wont. For some reason, .then is called even if the request fails.
I'm using setTimeOut because if I don't, it will just skip the if statement because the redux hasn't updated the state with isAccepted yet.
This is my useCase function from redux:
export const acceptProposal = (id: string) => async (
dispatch: Dispatch<any>,
getState: () => RootState,
) => {
const {auth} = getState();
const data = {
proposalId: id,
};
dispatch(actions.acceptProposal());
try {
await API.put(`/propostas/change-proposal-status/`, data, {
headers: {
version: 'v1',
'Content-Type': 'application/json',
},
});
dispatch(actions.acceptProposalSuccess());
} catch (error) {
dispatch(actions.acceptProposalFailed(error));
}
};
What I'm doing wrong? I'm using Redux with thunk but I'm not familiar with it.
".then is called even if the request fails." <- this is because acceptProposal is catching the API error and not re-throwing it. If an async function does not throw an error, it will resolve (i.e. call the .then). It can re-throw the error so callers will see an error:
export const acceptProposal = (id: string) => async (
// ... other code hidden
} catch (error) {
dispatch(actions.acceptProposalFailed(error));
// ADD: re-throw the error so the caller can use `.catch` or `try/catch`
throw error;
}
};
I have a subcomponent Viewer that uses a refetch function passed down to its parent Homescreen.
The lazyQuery in homescreen is structured as follows:
const [getById, {loading, error, data, refetch}] = useLazyQuery(GET_BY_ID);
This will get an object from my mongoDB by its id, and when I need to call it again and reload data into my custom activeObject variable, I use the follow function:
const refetchObjects= async () => {
const {loading, error, data } = await refetch();
if (error) { console.log(error);}
if (data) {
activeObject = data.getRegionById;
}
}
However, sometimes the return object of await refetch(); is undefined and I'm not sure why.
I'm using React 16.13.0. I have the following function to deal with submit events:
handleFormSubmit(e) {
e.preventDefault();
const NC = this.state.newCoop;
delete NC.address.country;
fetch('/coops/',{
method: "POST",
body: JSON.stringify(this.state.newCoop),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
}).then(response => {
if (response.ok) {
return response.json();
}
console.log(response.json());
console.log(response.body);
throw new Error(response.statusText);
}).catch(errors => {
console.log(errors);
this.setState({ errors });
});
}
However, I'm having an issue getting the errors properly from the response. When an error occurs, my endpoint returns a 400 request with the error text. This is what happens in curl:
curl --header "Content-type: application/json" --data "$req" --request POST "http://localhost:9090/coops/"
{"phone":["The phone number entered is not valid."]}
But the response.statusText contains "400 (Bad Request)". What's the right way to catch the error text and preserve it for future parsing? If my endpoint needs to format the data differently, what should it do (using Django/Python 3.7)?
Edit:
This is the Input component in which I'm trying to display errors:
<Input inputType={'text'}
title = {'Phone'}
name = {'phone'}
value = {this.state.newCoop.phone}
placeholder = {'Enter phone number'}
handleChange = {this.handleInput}
errors = {this.state.errors}
/>
And the code of the input component, src/Input.jsx
import React from 'react';
import {FormControl, FormLabel} from 'react-bootstrap';
const Input = (props) => {
return (
<div className="form-group">
<FormLabel>{props.title}</FormLabel>
<FormControl
type={props.type}
id={props.name}
name={props.name}
value={props.value}
placeholder={props.placeholder}
onChange={props.handleChange}
/>
{props.errors && props.errors[props.name] && (
<FormControl.Feedback>
<div className="fieldError">
{props.errors[props.name]}
</div>
</FormControl.Feedback>
)}
</div>
)
}
export default Input;
When I run console.log(errors) they appear as such:
{phone: Array(1), web_site: Array(1)}
The Response.ok property API states that:
Response.ok Read only
A boolean indicating whether the response was successful (status in
the range 200–299) or not.
That means that even response.ok is false, the response.json() will return the data.
Body.json()
Takes a Response stream and reads it to completion. It returns a
promise that resolves with the result of parsing the body text as
JSON.
So, in your code, you should define your first fetch resolve to asynchronous and if the response it's not ok, then throw with the resolved response.json() using await:
handleFormSubmit(e) {
e.preventDefault();
const NC = this.state.newCoop;
delete NC.address.country;
fetch('/coops/',{
method: "POST",
body: JSON.stringify(this.state.newCoop),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
}).then(async response => { // Define the first resolve to an asynchronous function
if (response.ok) {
// If it's OK, resolve JSON and return the actual data
return await response.json();
// or better set react state
// const data = await response.json();
// this.setState({ data });
} else {
// It's not OK, throw an error with the JSON data
// so you'll be able to catch
throw await response.json();
}
}).catch(errors => {
// Here you should get the actual errors JSON response
console.log(errors);
this.setState({ errors });
});
}
You can check the test example working using fetch-mock in this Stackblitz workspace.
If my endpoint needs to format the data differently, what should it do
(using Django/Python 3.7)?
You'll have to let us know more about how your endpoint handles the requests by providing some code and explanation.
UPDATE
Regarding your component and displaying the errors, the result JSON returns an array of errors for each field. If you'll only one error, then change the endpoint to return a string instead of an array or display just the first error. If you'll have multiple errors, then you can map and render through all errors array for each field:
const Input = (props) => {
return (
<div className="form-group">
<FormLabel>{props.title}</FormLabel>
<FormControl
type={props.type}
id={props.name}
name={props.name}
value={props.value}
placeholder={props.placeholder}
onChange={props.handleChange}
/>
// If you just want to display the first error
// then render the first element of the errors array
{props.errors && props.errors[props.name] && (
<FormControl.Feedback>
<div className="fieldError">
{props.errors[props.name][0]}
</div>
</FormControl.Feedback>
)}
// Or if you may have multiple errors regarding each field
// then map and render through all errors
{/*
{props.errors && props.errors[props.name] && (
<FormControl.Feedback>
{props.errors[props.name].map((error, index) => (
<div key={`field-error-${props.name}-${index}`} className="fieldError">
{error}
</div>
))}
</FormControl.Feedback>
)}
*/}
</div>
)
}
Actually, the fetch API is a little different from others. you should pass another .then() to the get the data and then analyze it, and using several callbacks make codes hardly to read, I use async/await to handle the error:
async handleFormSubmit(e) {
e.preventDefault();
const NC = this.state.newCoop;
delete NC.address.country;
try {
const response = await fetch('/coops/',{
method: "POST",
body: JSON.stringify(this.state.newCoop),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
});
if (response.ok) {
const result = await response.json();
console.log('_result_: ', result);
return result;
}
throw await response.json();
} catch (errors) {
console.log('_error_: ', errors);
this.setState({ errors });
}
}
Update for your new question:
Definitely, it is another question, that why the error doesn't appear, in fact, the phone error is and JavaScript Array, and you should show it by its way like below code, I use restructuring assignment for props:
import React from 'react';
import { FormControl, FormLabel } from 'react-bootstrap';
const Input = ({
title,
type,
name,
value,
placeholder,
handleChange,
errors,
}) => (
<div className="form-group">
<FormLabel>{title}</FormLabel>
<FormControl
type={type}
id={name}
name={name}
value={value}
placeholder={placeholder}
onChange={handleChange}
/>
{errors && errors[name] && (
<FormControl.Feedback>
{errors[name].map((err, i) => (
<div key={err+i} className="fieldError">{err}</div>
))}
</FormControl.Feedback>
)}
</div>
);
export default Input;
You are not really explaining what you want to do with the response. But based on your use for throw new Error I assume you want the following .catch call to handle it. In the below solution, errors would be assigned the object from the JSON response.
response.json() returns a promise. If you want to "throw" that value as an error you have to do something like this (instead of throw new Error(...)):
return response.json().then(x => Promise.reject(x))
Returning a rejected promise from a .then callback causes the promises returned by said .then call to be rejected as well.
In context:
fetch('/coops/',{
method: "POST",
body: JSON.stringify(this.state.newCoop),
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
}).then(response => {
if (response.ok) {
return response.json();
}
return response.json().then(x => Promise.reject(x));
}).catch(errors => {
console.log(errors);
this.setState({ errors });
});
Note: Since you are not doing anything with the return value of a successful response, the return response.json() inside the if statement is not necessary. You could rewrite that call to:
.then(response => {
if (!response.ok) {
return response.json().then(x => Promise.reject(x));
}
})
If my endpoint needs to format the data differently, what should it do (using Django/Python 3.7)?
Since we don't know what structure your component expects there is not much we can suggest. –
let the catch part catch failure errors and then catch the errors you need in the then part
}).then(response => {
//use a switch to got through the responses or an if statement
var response = JSON.Parse(response)
//here am assuming after you console log response you gt the correct array placement
if(response[0]==200){
// this indicates success
return JSON.stringify({"success":"true","message":"success message";})
}else if(response[0]==400){
// this indicates error
return JSON.stringify({"success":"false","message":"Error message";})
}
}).catch(errors => {
console.log(errors);
this.setState({ errors });
});
on the other end you could check the first entry in the array to determine whether the query was success or failure , then handle message appropriately
With the fetch API Response objects represent the Response meta - connection information, headers, that kind of thing.
At this stage the body has not been read.
response.statusText is the from the HTTP Response Status line, eg 200 Ok or 400 (Bad Request) in your case.
response.ok is a helper for checking if response.status >= 200 && response.status < 300
So far nothing we have looked at here is the contents of the response.
For that you need to use response.json(), response.text(), or response.blob(). As long as there is a body any of these functions can be used to read it - regardless of the HTTP Status Code.
Each of these functions read the body of sent to you by the server and does some kind of additional processing on it (check MDN or your preferred source of documentation for more information).
So in your case - to read and do something with the error returned to your by the server I would look at something like
fetch(...)
.then(response => {
if (response.ok) {
return response.json();
}
if (response.status === 400) {
return Promise.reject(response.json());
}
return Promise.reject(new Error(`Unexpected HTTP Status: ${response.status} - ${response.statusText}`));
})
.then(...)
.catch(...);