I'm working on a react project where I have created an API and that I want to use for my react project, I'm dealing with react hooks and not able to call API correctly. I have tried couple of things, but I keep getting one or other kind of error.
Latest method I have tried is giving me this error
Line 8:20: React Hook "useFetch" is called in function "filterList" which is neither a React function component or a custom React Hook function
Code for filterList.js look like this
/* to filter the products based on the categories */
import useFetch from './apiService'
export default function filterList(arr, method, courses) {
const URL = "http://localhost:8000/api/getCourses";
const result = useFetch(URL, {});
courses = result;
console.log(courses, 'Inside filterList')
if(method == null) return courses;
else {
return courses.filter(course => {
const categoryArray = course.category.split(" ");
if(arr.length > 0) {
if(categoryArray.some(r => arr.indexOf(r) >= 0)) {
return course;
}
}
else {
return courses;
}
return courses;
})
}
}
and code for apiService.js where I have created useFetch function look like this
import { useEffect, useState } from 'react';
// to fetch the data from api
const useFetch = (url, defaultData) => {
const [data, updateData] = useState(defaultData);
useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const json = await response.json();
updateData(json);
}
fetchData();
}, [url]);
return data;
};
export default useFetch;
Please let me know what is the thing that I'm doing wrong and what should be correct method to do this.
Related
I used axios in useEffect of my wrapper component and I sent the data as props to the other component "singleQuestionnaire", in singleQuestionnaire component, I destructured the data, in the first try, it works fine, but after reloading the page it doesn't work with an error : can not read property "map" of undefined
import React, { useEffect, useState } from "react";
import SingleQuestionnaire from "./SingleQuestionnaire";
import { fetchQuestions } from "../../../api/index";
const Questionnaires = ({ match }) => {
const [questions, setQuestions] = useState([]);
const pid = match.params.id;
const getQuestionnaire = async (pid) => {
try {
const { data } = await fetchQuestions(pid);
console.log(data.data, "action in component");
setQuestions(data.data);
} catch (error) {
console.log(error);
}
};
useEffect(() => {
getQuestionnaire(pid);
}, []);
console.log("all questions", questions);
return (
<div>
<SingleQuestionnaire questions={questions} setQuestions={setQuestions} />
</div>
);
};
export default Questionnaires;
and this is my singleQuestionnaire component:
import React, { useEffect, useState } from "react";
const SingleQuestionnaire = ({ questions, setQuestions }) => {
const [questionnaire, setQuestionnaire] = useState([]);
console.log(questions);
const { data } = questions;
console.log("data", data.farmInformationQuestionnaireData);
return <div>simple component</div>;
};
export default SingleQuestionnaire;
For the first time, in console I can see the data "data.data.farmInformationQuestionnaireData". It's an array but for the second time it's undefind.
because questions in SingleQuestionnaire is an empty array before we fetch
which causes an error here
const { data } = questions;
you can add a loading text because initially questions will be an empty array then it will be your res.data (assuming it's an object)
const SingleQuestionnaire = ({ questions, setQuestions }) => {
const [questionnaire, setQuestionnaire] = useState([]);
console.log(questions);
if(questions.length === 0 ) return <h1> Loading</h1>
const { data } = questions;
console.log("data", data.farmInformationQuestionnaireData);
return <div>simple component</div>;
};
it is happening because of the async API call. When you make an async call, the thread does not wait, it moves on and it starts executing other things.
Now your async call might be complete but your callback will not be executed until the stack is empty, that's just how javaScript works. I recommend you use some kind of loader gif or text
{questions ? <SingleQuestionnaire questions={questions} setQuestions={setQuestions} /> : <p>Loading...</p>}
I have a lot of functions looking like this
doSomething = async (...) => {
try {
this.setState({loading: true});
...
var result = await Backend.post(...);
...
this.setState({loading: false});
} catch(err) {
this.setState({error: err});
}
}
Basically I have 2 variables loading & error that I have to manage for a lot of functions and the code is basically the same for all of them. Since there are no decorators in javascript and I do not wish to install any experimental lib for that how could I wrap this function to remove the duplicated setStates from above ?
Here is my current way, I pass the function as parameter.
We have many API, fetch data form backend, we have to handle error and do something with data.
Only data of service are different, the handling error is the same.
private processServiceResponse(resp: any, doSthWithData: (data: any) => void) {
let { errors } = resp;
if (this.hasError(errors)) {
this.handleServiceErr(errors);
return;
}
let data = resp;
if (resp && resp.data) {
data = resp.data;
}
doSthWithData(data);
}
And here is how i pass function as parameter.
let rest1 = service1.getData();
processServiceResponse(rest1,(data)=>{
//only need to focus with processing data.
})
PS: It's typescript coding.
if you are using function conponents, you can define a custom hook to avoid repeat code
//useFetch.js
import { useState, useEffect } from 'react';
import axios from 'axios';
function useFetch(url) {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
setLoading('loading...')
setData(null);
setError(null);
const source = axios.CancelToken.source();
axios.get(url, { cancelToken: source.token })
.then(res => {
setLoading(false);
//checking for multiple responses for more flexibility
//with the url we send in.
res.data.content && setData(res.data.content);
res.content && setData(res.content);
})
.catch(err => {
setLoading(false)
setError('An error occurred. Awkward..')
})
return () => {
source.cancel();
}
}, [url])
return { data, loading, error }
export default useFetch;
usage:
import useFetch from './useFetch';
import './App.css';
function App() {
const { data: quote, loading, error } =
useFetch('https://api.quotable.io/random')
return (
<div className="App">
{ loading && <p>{loading}</p> }
{ quote && <p>"{quote}"</p> }
{ error && <p>{error}</p> }
</div>
);
}
export default App;
You can use a Higher Order Function (a function that takes as argument another function) to make the common loading and error functionality reusable. It is very similar to a decorator pattern. For example:
const doSomething = withLoadingAndErrorHandling(Backend.post, this.setState);
function withLoadingAndErrorHandling(fn, setState) {
return async function(...args) {
try {
setState({loading: true});
var result = await fn(args);
setState({loading: false});
return result;
} catch(err) {
setState({error: err});
}
}
}
I am new with react hooks, i'm trying to get info from an API but when i do the request i get 2 responses first an empty array and then the data of the API, why am i getting that empty array! , this is my first question, i'm sorry.
Thanks for helping me !
import {useState, useEffect} from 'react';
const getSlides = (API) => {
const[data,setData] = useState([]);
const getData = () =>
fetch(`${API}`)
.then((res) => res.json())
useEffect(() => {
getData().then((data) => setData(data))
},[])
return data
}
export default getSlides;
The useEffect() hook runs after the first render. Since you've initialized the data state with an empty array, the first render returns an empty array.
If you're component depends on data to render, you can always conditionally return null until your data is loaded.
Also, I recommend using an async function for api requests, it allows you to use the await keyword which makes your code easier to read. The only caveat, is that you cannot pass an async function to useEffect, instead define an async function inside your hook, and then call it.
import React, { useState, useEffect } from "react";
const API = "https://example.com/data";
const GetSlides = (props) => {
const [data, setData] = useState();
useEffect(() => {
async function getData() {
const request = fetch(API);
const response = await request;
const parsed = await response.json();
setData(parsed);
}
getData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (data === undefined) {
return null;
}
return <>data</>;
};
export default GetSlides;
Of course, you can still use Promise chaining if you desire.
useEffect(() => {
async function getData() {
await fetch(API)
.then((res) => res.json())
.then((data) => setData(data));
}
getData();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
<GetSlides api="https://yay.com" />
react components need to be title case
import React, { useState, useEffect } from 'react'
const GetSlides = ({ api }) => {
const [data, setData] = useState(null)
const getData = async () =>
await fetch(`${api}`)
.then((res) => res.json())
.then((data) => setData(data))
useEffect(() => {
getData()
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])
console.log(data)
return <div>slides</div>
}
export default GetSlides
The effect callback function is called after the render of your component. (Just like componentDidMount) So during the first render phase, the data state has not been set yet.
You initialize your data with and empty array here:
const[data,setData] = useState([] <- empty array);
useEffect runs after your component is mounted, and then calls the API, that it might take a few seconds or minutes to retrieve the data, but you return the data right away before knowing if the API finished its call.
If you want to return the data after it has been retrieved from the API, you should declare and async method
const getSlides = async (API) => {
try {
const res = await fetch(API);
const data = await res.json();
return data;
} catch (e) {
throw new Error(e);
}
}
Note that it is not necessary hooks for this function
I'm attempting to call a Graph QL Query after receiving data from my useEffect hook. I need the data from the response to use in the Query. Hooks however cannot be called conditionally. If I take away the condition however, loadedAnime will be undefined. How do I get around this restraint?
import React, { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import AnimeBanner from "../components/AnimeBanner";
import { useHttpClient } from "../Hooks/http-hook";
import { GetAnimeData } from "../GraphQLFunctions";
import { useQuery } from "#apollo/react-hooks";
import gql from "graphql-tag";
const GET_ANIME_INFO = gql`
query GetAnimeInfo($name: String!) {
Media(search: $name) {
title {
romaji
english
native
userPreferred
}
episodes
id
bannerImage
}
}
`;
const Anime = (props) => {
//Logic for getting anime data from mongoDB (episodes, name, cover image)
const { isLoading, error, sendRequest } = useHttpClient();
const [loadedAnime, setloadedAnime] = useState();
const URLTitle = useParams().URLTitle;
useEffect(() => {
const fetchAnime = async () => {
try {
const responseData = await sendRequest(
"http://localhost:5000/api/anime/" + URLTitle
);
setloadedAnime(responseData.animeData[0]);
} catch (err) {
console.log(err);
}
};
fetchAnime();
}, [sendRequest, URLTitle]);
if (isLoading || error) {
return null;
}
//Logic for getting anime data from anilist (Descriptions, tags, banner, trailer, etc.)
const { apiData, apiLoading, apiError } = useQuery(GET_ANIME_INFO, {
variables: {
name: loadedAnime.anime_name,
},
});
if (apiLoading || apiError) {
return null;
}
return <AnimeBanner src={apiData.Media.bannerImage} />;
};
export default Anime;
Short Answer: You can checkout useLazyQuery instead of useQuery.
Documentation link: https://www.apollographql.com/docs/react/data/queries/#executing-queries-manually
When React mounts and renders a component that calls the useQuery hook, Apollo Client automatically executes the specified query. But what if you want to execute a query in response to a different event, such as a user clicking a button?
The useLazyQuery hook is perfect for executing queries in response to events other than component rendering. This hook acts just like useQuery, with one key exception: when useLazyQuery is called, it does not immediately execute its associated query. Instead, it returns a function in its result tuple that you can call whenever you're ready to execute the query
import React, { useState } from 'react';
import { useLazyQuery } from '#apollo/client';
function DelayedQuery() {
const [dog, setDog] = useState(null);
const [getDog, { loading, data }] = useLazyQuery(GET_DOG_PHOTO);
if (loading) return <p>Loading ...</p>;
if (data && data.dog) {
setDog(data.dog);
}
return (
<div>
{dog && <img src={dog.displayImage} />}
<button onClick={() => getDog({ variables: { breed: 'bulldog' } })}>
Click me!
</button>
</div>
);
}
You can either call the query after the await finishes or you can call your query in another useEffect once you update state after your api call. In general, something like this,
const [state, setState] = useState({})
useEffect(async () => {
const result = await get('/api/blah-blah-blah')
// run your query here now that the await has resolved
}, [someDependency])
or
const [state, setState] = useState({})
useEffect(async () => {
const result = await get('/api/blah-blah-blah')
setState(result)
}, [someDependency])
useEffect(() => {
if(state.id) {
// run the query
}
}, [state.someProp])
Beginner here.
Trying to fetch some data from a server and display it in my react component once its fetched.
However, I am having trouble integrating the async function into my react component.
import React, { useState } from "react";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const data = fetchData().catch((error) => console.error(error));
return (
<div>
{data.item.title}
</div>
);
};
export default TestingGraphQL;
I'd like to simply show a spinner or something while waiting, but I tried this & it seems because a promise is returned I cannot do this.
Here you would need to use the useEffect hook to call the API.
The data returned from the API, I am storing here in a state, as well as a loading state to indicate when the call is being made.
Follow along the comments added in between the code below -
CODE
import React, { useState, useEffect } from "react"; // importing useEffect here
import Layout from "#layouts/default";
import ContentContainer from "#components/ContentContainer";
import { request } from "graphql-request";
async function fetchData() {
const endpoint = "https://localhost:3090/graphql"
const query = `
query getItems($id: ID) {
item(id: $id) {
title
}
}
`;
const variables = {
id: "123123123"
};
const data = await request(endpoint, query, variables);
// console.log(JSON.stringify(data, undefined, 2));
return data;
}
const TestingGraphQL = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
// useEffect with an empty dependency array works the same way as componentDidMount
useEffect(async () => {
try {
// set loading to true before calling API
setLoading(true);
const data = await fetchData();
setData(data);
// switch loading to false after fetch is complete
setLoading(false);
} catch (error) {
// add error handling here
setLoading(false);
console.log(error);
}
}, []);
// return a Spinner when loading is true
if(loading) return (
<span>Loading</span>
);
// data will be null when fetch call fails
if (!data) return (
<span>Data not available</span>
);
// when data is available, title is shown
return (
<Layout>
{data.item.title}
</Layout>
);
};
since fetchData() returns a promise you need to handle it in TestingGraphQL. I recommend onComponentMount do your data call. Setting the data retrieved into the state var, for react to keep track of and re-rendering when your data call is finished.
I added a loading state var. If loading is true, then it shows 'loading' otherwise it shows the data. You can go about changing those to components later to suit your needs.
See the example below, switched from hooks to a class, but you should be able to make it work! :)
class TestingGraphQL extends Component {
constructor() {
super();
this.state = { data: {}, loading: true};
}
//when the component is added to the screen. fetch data
componentDidMount() {
fetchData()
.then(json => { this.setState({ data: json, loading: false }) })
.catch(error => console.error(error));
}
render() {
return (
{this.state.loading ? <div>Loading Spinner here</div> : <div>{this.state.data.item.title}</div>}
);
}
};