I have this component:
import React, { Component } from 'react';
import useFetch from "../useFetch";
export class Patient extends Component {
static displayName = Patient.name;
constructor(props) {
super(props);
}
componentDidMount() {
alert("fgggg");
const { isLoading, serverError, apiData } = useFetch(
"/Patient/GetPatients"
);
}
render() {
return (
<div>
</div>
);
}
}
I want to call the useFetch, here is my useFetch:
import React, { useEffect, useState } from "react";
function useFetch(url){
const [isLoading, setIsLoading] = useState(false);
const [apiData, setApiData] = useState(null);
const [serverError, setServerError] = useState(null);
alert("dddd");
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
try {
fetch(url)
.then(response => response.json())
.then(data => setApiData(data));
//const resp = await axios.get(url);
//const data = await resp?.data;
setIsLoading(false);
} catch (error) {
alert(error);
setServerError(error);
setIsLoading(false);
}
};
fetchData();
}, [url]);
return { isLoading, apiData, serverError };
};
export default useFetch;
Erro:
Attempted import error: 'useFetch' is not exported from '../useFetch'.
Can anybody advise?
UPDATE
Thanks for the resource in the answer, but i found this https://blog.bitsrc.io/fetching-data-in-react-using-hooks-c6fdd71cb24a
and now i have changed my code to this:
import React, { useEffect, useState } from "react";
export default function useFetch(url, opts){
const [response, setResponse] = useState(null)
const [loading, setLoading] = useState(false)
const [hasError, setHasError] = useState(false)
useEffect(() => {
setLoading(true)
fetch(url, opts)
.then((res) => {
setResponse(res.data)
setLoading(false)
})
.catch(() => {
setHasError(true)
setLoading(false)
})
}, [url])
return [response, loading, hasError]
}
and
import React, { Component } from 'react';
import useFetch from "../useFetch";
export class Patient extends Component {
static displayName = Patient.name;
constructor(props) {
super(props);
}
componentDidMount() {
alert("fgggg");
const [ response, loading, hasError ] = useFetch("", "");
}
render() {
return (
<div>
</div>
);
}
}
I still get this error
×
Error: Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
Instead of exporting at the end of the file you could export when defining the function/hook.
export default function useFetch(url) {
const [isLoading, setIsLoading] = useState(false);
const [apiData, setApiData] = useState(null);
const [serverError, setServerError] = useState(null);
alert("dddd");
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
try {
fetch(url)
.then((response) => response.json())
.then((data) => setApiData(data));
//const resp = await axios.get(url);
//const data = await resp?.data;
setIsLoading(false);
} catch (error) {
alert(error);
setServerError(error);
setIsLoading(false);
}
};
fetchData();
}, [url]);
return { isLoading, apiData, serverError };
}
As well double check your import path is correct.
Good reference for when creating custom hooks and using them: https://www.freecodecamp.org/news/how-to-create-react-hooks/
UPDATED: As Hozeis commented. You cannot use hook inside class components. Just noticed you were using a class component
Related
I have a cutom hook created as below. It is using axios and useEffect().
import { useState, useEffect } from 'react';
import axios from 'axios';
axios.defaults.baseURL = 'https://jsonplaceholder.typicode.com';
const baseURL="http://127.0.0.1:5000/v1/test_server/"
const useAxios = ({ url, method='get', body = null, headers = JSON.stringify({ accept: '*/*' }),
}) => {
const [response, setResponse] = useState(null);
const [error, setError] = useState('');
const [loading, setloading] = useState(true);
const fetchData = () => {
axios[method](baseURL+url, JSON.parse(headers), JSON.parse(body))
.then((res) => {
setResponse(res.data);
})
.catch((err) => {
setError(err);
})
.finally(() => {
setloading(false);
});
};
useEffect(() => {
fetchData();
}, [method, url, body, headers]);
return { response, error, loading };
};
export default useAxios;
I'm using this hook in another component as
const [playerData, setPlayerData] = useState();
const { playerResponse, playerError, playerLoading} = useAxios({ url: 'player/get_player'})
if (playerResponse !== null ) {
setPlayerData(playerResponse.result);
console.log("Response", playerData);
}
Everything seems correct but the code is showing Cannot read properties of undefined (reading 'result'). Why this is happening?
I have a custom hook named "useFetch" which makes an AJAX request and stores the result in the state. I simply want to format the data received from the ajax using a function in my component but not sure how to do this since the function needs to be called only after the data is received.
An example is below:
import React, { Component, useState } from "react";
import useFetch from "../../../Hooks/useFetch";
const Main = () => {
const { data, isPending, error } = useFetch(
"http://127.0.0.1:8000/api/historic/1"
);
function formatData(data){
//Do some processing of the data after it's been received
}
//This doesn't work of course because it runs before the data has been received
const formatted_data=formatData(data);
return (
//Some display using the formatted data
);
};
export default Main;
This is the custom hook, useFetch, which is used in the above component. I'd prefer to not have to do the formatting in here because the formatting is specifically related to the above component and this custom hook is designed to have more universal utility.
import { useState, useEffect } from "react";
const useFetch = (url) => {
const [data, setData] = useState(null);
const [isPending, setisPending] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch(url, { signal: abortCont.signal })
.then((res) => {
if (res.ok) {
return res.json();
} else {
throw Error("could not fetch data for that resource");
}
})
.then((data) => {
setData(data);
setisPending(false);
setError(null);
})
.catch((er) => {
if (er.name === "AbortError") {
console.log("fetch aborted");
} else {
setError(er.message);
setisPending(false);
}
});
return () => abortCont.abort();
}, [url]);
return { data, isPending, error };
};
export default useFetch;
You should wrap it with useEffect hook with data as it's deps.
const [formattedData, setFormattedData] = useState();
useEffect(() => {
if (!data) return;
const _formattedData = formatData(data);
setFormattedData(_formattedData);
}, [data]);
I am new to react hooks I write a react custom hook
Hook:
import { useState, useEffect } from 'react';
export const useFetch = (url, options) => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
} catch (error) {
setError(error);
}
};
fetchData();
}, []);
return { response, error };
};
And I also write a functional component and i want component render when data comes
here is my component
Component
import React, { useState, useEffect } from 'react';
import './index.scss';
import { List } from '../components';
import { useFetch } from '../../hooks';
export const Subscription = () => {
const res = useFetch('http://localhost:8080/test', {});
const [isLoading, setLoading] = useState(true);
useEffect(() => {
if (res.response.length > 0) {
console.log('this is the test');
setLoading(false);
}
});
const list = res.response;
return (
<div>
{isLoading && <div>Loading...</div>}
{!isLoading && (
<div className="list">
<List subscriptions={list} />
</div>
)}
</div>
);
};
but i am unable to render List component I didn't understand once data comes from backend why list note having data still it having null value and lists is not renderd
I got proper values from backend
useFetch return return { response, error }; ==> const response = useFetch('http://localhost:8080/test', {}); the response is an object containing { response, error }
Do this instead const {response} = useFetch('http://localhost:8080/test', {});
And you should handle loading in useFetch
UseFetch
import { useState, useEffect } from 'react';
export const useFetch = (url, options) => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url, options);
const json = await res.json();
setResponse(json);
setLoading(false)
} catch (error) {
setError(error);
setLoading(false)
}
};
fetchData();
}, []);
return { response, error,loading };
};
Subscription
import React, { useState, useEffect } from 'react';
import './index.scss';
import { List } from '../components';
mport { useFetch } from '../../hooks';
export const Subscription = () => {
const {response: subscriptions, loading} = useFetch('http://localhost:8080/test', {});
return (
<div>
{isLoading && <div>Loading...</div>}
{!isLoading && (
<div className="list">
<List subscriptions={subscriptions} />
</div>
)}
</div>
);
};
I have custom hook(useFetch) that takes URL as input and fetch data from that URL and returns data. I want to implement spinner (already made Spinner component ) on my other components and I tried by making state for the isLoading and setIsLoading of spinner.
my custom hook code:
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [dataArray, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
try {
const fetchData = async () => {
setIsLoading(true);
const res = await fetch(url);
const dataArray = await res.json();
setData(dataArray.data);
};
fetchData();
} catch (err) {
console.error(err);
}
setIsLoading(false);
}, [url]);
return dataArray;
};
export default useFetch;
This is the component that I want to implement spinner.
import React, { useState } from 'react';
import CONSTANTS from '../../constants/constants';
import CompanyLists from '../../components/company-lists/CompanyLists';
import Pagination from '../../components/pagination/Pagination';
import useFetch from '../../components/effects/use-fetch.effect';
import Spinner from '../../components/spinner/Spinner';
const CompanyListing = () => {
const [counter, setCounter] = useState(1);
const companies = useFetch(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
return (
<>
<Container>
<div style={userStyle}>
{companies ? companies.map((company) => <CompanyLists key={company.id} {...company} />) : 'No companies'}
</div>
<Pagination props={companies} counter={counter} name="companies" setCounter={setCounter} />
<Spinner />
</Container>
</>
);
};
const userStyle = {
display: 'grid',
gridTemplateColumns: 'repeat(1, 1fr)',
gridGap: '1rem',
};
export default CompanyListing;
Problem here is: How can I send those loading state from hook to CompanyListing component. Any help will be appreciated.
EDIT:
I have other component that also calls same hook and I want them not to be broken. As I didn't mention on original question .
My another case:
const jobsUrl = `${CONSTANTS.BASE_URL}/jobs?page=${counter}`;
const jobs = useFetch(jobsUrl);
AND
const { city, company_name, company_id, department, description, job_type, position, posted_at, url } = useFetch(
`${CONSTANTS.BASE_URL}/jobs/${id}`
);
How can I destructure in these two cases ?
You do not need anything special here. Just return the isLoading state with the dataArray from the useFetch hook.
As mentioned from the edit you need the useFetch to be more reusable and return data in different formats depending on the API response, hence the state should be initialized as null.
import { useState, useEffect } from 'react';
const useFetch = (url) => {
// it is best to initialize the state as null because response.data
// may be an object or an array depending on the API response
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
try {
const fetchData = async () => {
setIsLoading(true);
const res = await fetch(url);
const value = await res.json();
setData(value.data);
};
fetchData();
} catch (err) {
console.error(err);
}
setIsLoading(false);
}, [url]);
return {data, isLoading};
};
export default useFetch;
In the components you want to use the custom hook, you can destructure
the value for data and isLoading but to futher destructure values from the returned data we have to check if data is null
// destructure the values
const {data, isLoading} = useFetch(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
// in this case data will be an array based on your API response
// please make sure to check data.length before trying to loop over
// and render the content, for example
return (
<div>
{
data.length && data.map(company => (
<CompanyLists key={company.id} {...company} />
));
}
</div>
)
For the second case where you will be fetching data using useFetch(`${CONSTANTS.BASE_URL}/jobs/${id}`); you have to check if the returned data is not null before destructuring further. Example
const { data, isLoading } = useFetch(`${CONSTANTS.BASE_URL}/jobs/${id});
if (isLoading) {
return <div>Loading...</div>
}
if (data) {
const {
city,
company_name,
company_id,
department,
description,
job_type, position, posted_at, url } = data;
return (
// your jsx code
// for example
<h3>{company_name}</h3>
<p>{department}</p>
)
}
You need to return isLoading as well from the hook.
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [dataArray, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
try {
const fetchData = async () => {
setIsLoading(true);
const res = await fetch(url);
const dataArray = await res.json();
setData(dataArray.data);
};
fetchData();
} catch (err) {
console.error(err);
}
setIsLoading(false);
}, [url]);
return {dataArray, isLoading};
};
export default useFetch;
And use this in your component like this
import React, { useState } from 'react';
import CONSTANTS from '../../constants/constants';
import CompanyLists from '../../components/company-lists/CompanyLists';
import Pagination from '../../components/pagination/Pagination';
import useFetch from '../../components/effects/use-fetch.effect';
import Spinner from '../../components/spinner/Spinner';
const CompanyListing = () => {
const [counter, setCounter] = useState(1);
const {dataArray: companies, isLoading} = useFetch(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
return (
<>
<Container>
<div style={userStyle}>
{companies ? companies.map((company) => <CompanyLists key={company.id} {...company} />) : 'No companies'}
</div>
<Pagination props={companies} counter={counter} name="companies" setCounter={setCounter} />
{isLoading && <Spinner />}
</Container>
</>
);
};
const userStyle = {
display: 'grid',
gridTemplateColumns: 'repeat(1, 1fr)',
gridGap: '1rem',
};
export default CompanyListing;
From your custom hook you can return both like
return {companies: dataArray, isLoading };
And destruct both
const {companies, isLoading} = useFetch(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
First, you might need to change your useFetch effect a bit to update isLoading correctly. Then you could return both dataArray and isLoading :
import { useState, useEffect } from 'react';
const useFetch = (url) => {
const [dataArray, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
try {
const fetchData = async () => {
setIsLoading(true);
const res = await fetch(url);
const dataArray = await res.json();
setData(dataArray.data);
};
await fetchData();
} catch (err) {
console.error(err);
} finally {
setIsLoading(false);
}
}, [url]);
return [dataArray, isLoading];
};
export default useFetch;
And use it like the following :
const [companies, isLoading] = useFetch(`${CONSTANTS.BASE_URL}/companies?page=${counter}`);
I would like to get global information from Github user and his repos(and get pinned repos will be awesome). I try to make it with async await but It's is correct? I've got 4 times reRender (4 times console log). It is possible to wait all component to reRender when all data is fetched?
function App() {
const [data, setData] = useState(null);
const [repos, setRepos] = useState(null);
useEffect(() => {
const fetchData = async () => {
const respGlobal = await axios(`https://api.github.com/users/${username}`);
const respRepos = await axios(`https://api.github.com/users/${username}/repos`);
setData(respGlobal.data);
setRepos(respRepos.data);
};
fetchData()
}, []);
if (data) {
console.log(data, repos);
}
return (<h1>Hello</h1>)
}
Multiple state updates are batched but but only if it occurs from within event handlers synchronously and not setTimeouts or async-await wrapped methods.
This behavior is similar to classes and since in your case its performing two state update cycles due to two state update calls happening
So Initially you have an initial render and then you have two state updates which is why component renders three times.
Since the two states in your case are related, you can create an object and update them together like this:
function App() {
const [resp, setGitData] = useState({ data: null, repos: null });
useEffect(() => {
const fetchData = async () => {
const respGlobal = await axios(
`https://api.github.com/users/${username}`
);
const respRepos = await axios(
`https://api.github.com/users/${username}/repos`
);
setGitData({ data: respGlobal.data, repos: respGlobal.data });
};
fetchData();
}, []);
console.log('render');
if (resp.data) {
console.log("d", resp.data, resp.repos);
}
return <h1>Hello</h1>;
}
Working demo
Figured I'd take a stab at it because the above answer is nice, however, I like cleanliness.
import React, { useState, useEffect } from 'react'
import axios from 'axios'
const Test = () => {
const [data, setData] = useState([])
useEffect(() => {
(async () => {
const data1 = await axios.get('https://jsonplaceholder.typicode.com/todos/1')
const data2 = await axios.get('https://jsonplaceholder.typicode.com/todos/2')
setData({data1, data2})
})()
}, [])
return JSON.stringify(data)
}
export default Test
Using a self invoking function takes out the extra step of calling the function in useEffect which can sometimes throw Promise errors in IDEs like WebStorm and PHPStorm.
function App() {
const [resp, setGitData] = useState({ data: null, repos: null });
useEffect(() => {
const fetchData = async () => {
const respGlobal = await axios(
`https://api.github.com/users/${username}`
);
const respRepos = await axios(
`https://api.github.com/users/${username}/repos`
);
setGitData({ data: respGlobal.data, repos: respGlobal.data });
};
fetchData();
}, []);
console.log('render');
if (resp.data) {
console.log("d", resp.data, resp.repos);
}
return <h1>Hello</h1>;
}
he made some mistake here:
setGitData({ data: respGlobal.data, repos: respGlobal.data(respRepos.data //it should be respRepos.data});
For other researchers (Live demo):
import React, { useEffect, useState } from "react";
import { CPromise, CanceledError } from "c-promise2";
import cpAxios from "cp-axios";
function MyComponent(props) {
const [error, setError] = useState("");
const [data, setData] = useState(null);
const [repos, setRepos] = useState(null);
useEffect(() => {
console.log("mount");
const promise = CPromise.from(function* () {
try {
console.log("fetch");
const [respGlobal, respRepos] = [
yield cpAxios(`https://api.github.com/users/${props.username}`),
yield cpAxios(`https://api.github.com/users/${props.username}/repos`)
];
setData(respGlobal.data);
setRepos(respRepos.data);
} catch (err) {
console.warn(err);
CanceledError.rethrow(err); //passthrough
// handle other errors than CanceledError
setError(err + "");
}
}, []);
return () => {
console.log("unmount");
promise.cancel();
};
}, [props.username]);
return (
<div>
{error ? (
<span>{error}</span>
) : (
<ul>
<li>{JSON.stringify(data)}</li>
<li>{JSON.stringify(repos)}</li>
</ul>
)}
</div>
);
}