I'm obviously not cleaning up correctly and cancelling the axios GET request the way I should be. On my local, I get a warning that says
Can't perform a React state update on an unmounted component. This is
a no-op, but it indicates a memory leak in your application. To fix,
cancel all subscriptions and asynchronous tasks in a useEffect cleanup
function.
On stackblitz, my code works, but for some reason I can't click the button to show the error. It just always shows the returned data.
https://codesandbox.io/s/8x5lzjmwl8
Please review my code and find my flaw.
useAxiosFetch.js
import {useState, useEffect} from 'react'
import axios from 'axios'
const useAxiosFetch = url => {
const [data, setData] = useState(null)
const [error, setError] = useState(null)
const [loading, setLoading] = useState(true)
let source = axios.CancelToken.source()
useEffect(() => {
try {
setLoading(true)
const promise = axios
.get(url, {
cancelToken: source.token,
})
.catch(function (thrown) {
if (axios.isCancel(thrown)) {
console.log(`request cancelled:${thrown.message}`)
} else {
console.log('another error happened')
}
})
.then(a => {
setData(a)
setLoading(false)
})
} catch (e) {
setData(null)
setError(e)
}
if (source) {
console.log('source defined')
} else {
console.log('source NOT defined')
}
return function () {
console.log('cleanup of useAxiosFetch called')
if (source) {
console.log('source in cleanup exists')
} else {
source.log('source in cleanup DOES NOT exist')
}
source.cancel('Cancelling in cleanup')
}
}, [])
return {data, loading, error}
}
export default useAxiosFetch
index.js
import React from 'react';
import useAxiosFetch from './useAxiosFetch1';
const index = () => {
const url = "http://www.fakeresponse.com/api/?sleep=5&data={%22Hello%22:%22World%22}";
const {data,loading} = useAxiosFetch(url);
if (loading) {
return (
<div>Loading...<br/>
<button onClick={() => {
window.location = "/awayfrom here";
}} >switch away</button>
</div>
);
} else {
return <div>{JSON.stringify(data)}xx</div>
}
};
export default index;
Here is the final code with everything working in case someone else comes back.
import {useState, useEffect} from "react";
import axios, {AxiosResponse} from "axios";
const useAxiosFetch = (url: string, timeout?: number) => {
const [data, setData] = useState<AxiosResponse | null>(null);
const [error, setError] = useState(false);
const [errorMessage, setErrorMessage] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let unmounted = false;
let source = axios.CancelToken.source();
axios.get(url, {
cancelToken: source.token,
timeout: timeout
})
.then(a => {
if (!unmounted) {
// #ts-ignore
setData(a.data);
setLoading(false);
}
}).catch(function (e) {
if (!unmounted) {
setError(true);
setErrorMessage(e.message);
setLoading(false);
if (axios.isCancel(e)) {
console.log(`request cancelled:${e.message}`);
} else {
console.log("another error happened:" + e.message);
}
}
});
return function () {
unmounted = true;
source.cancel("Cancelling in cleanup");
};
}, [url, timeout]);
return {data, loading, error, errorMessage};
};
export default useAxiosFetch;
Based on Axios documentation cancelToken is deprecated and starting from v0.22.0 Axios supports AbortController to cancel requests in fetch API way:
//...
React.useEffect(() => {
const controller = new AbortController();
axios.get('/foo/bar', {
signal: controller.signal
}).then(function(response) {
//...
});
return () => {
controller.abort();
};
}, []);
//...
The issue in your case is that on a fast network the requests results in a response quickly and it doesn't allow you to click the button. On a throttled network which you can achieve via ChromeDevTools, you can visualise this behaviour correctly
Secondly, when you try to navigate away using window.location.href = 'away link' react doesn't have a change to trigger/execute the component cleanup and hence the cleanup function of useEffect won't be triggered.
Making use of Router works
import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom'
import useAxiosFetch from './useAxiosFetch'
function App(props) {
const url = 'https://www.siliconvalley-codecamp.com/rest/session/arrayonly'
const {data, loading} = useAxiosFetch(url)
// setTimeout(() => {
// window.location.href = 'https://www.google.com/';
// }, 1000)
if (loading) {
return (
<div>
Loading...
<br />
<button
onClick={() => {
props.history.push('/home')
}}
>
switch away
</button>
</div>
)
} else {
return <div>{JSON.stringify(data)}</div>
}
}
ReactDOM.render(
<Router>
<Switch>
<Route path="/home" render={() => <div>Hello</div>} />
<Route path="/" component={App} />
</Switch>
</Router>,
document.getElementById('root'),
)
You can check the demo working correctly on a slow network
Fully cancellable routines example, where you don't need any CancelToken at all (Play with it here):
import React, { useState } from "react";
import { useAsyncEffect, E_REASON_UNMOUNTED } from "use-async-effect2";
import { CanceledError } from "c-promise2";
import cpAxios from "cp-axios"; // cancellable axios wrapper
export default function TestComponent(props) {
const [text, setText] = useState("");
const cancel = useAsyncEffect(
function* () {
console.log("mount");
this.timeout(props.timeout);
try {
setText("fetching...");
const response = yield cpAxios(props.url);
setText(`Success: ${JSON.stringify(response.data)}`);
} catch (err) {
CanceledError.rethrow(err, E_REASON_UNMOUNTED); //passthrough
setText(`Failed: ${err}`);
}
return () => {
console.log("unmount");
};
},
[props.url]
);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>{text}</div>
<button onClick={cancel}>Abort</button>
</div>
);
}
This is how I do it, I think it is much simpler than the other answers here:
import React, { Component } from "react";
import axios from "axios";
export class Example extends Component {
_isMounted = false;
componentDidMount() {
this._isMounted = true;
axios.get("/data").then((res) => {
if (this._isMounted && res.status === 200) {
// Do what you need to do
}
});
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
return <div></div>;
}
}
export default Example;
Related
In following codes, eslint will give a warning.
Line 24:6: React Hook useEffect has a missing dependency: 'fetchPosts'. Either include it or remove the dependency array react-hooks/exhaustive-deps
import { useState, useEffect } from 'react';
import { useLocation } from "react-router-dom";
import { Layout } from './Layout';
import { TwitterPost, reloadTwitterEmbedTemplate } from '../TwitterPost';
import '../../styles/pages/TimelinePage.css'
import axios from 'axios';
export const TimelinePage = () => {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const location = useLocation();
const fetchPosts = async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
};
useEffect(() => {
if (location.pathname !== '/') return;
fetchPosts();
}, [location]);
const postTemplates = posts.map((post: any) => {
if (post.media_name === 'twitter') return <TwitterPost mediaUserScreenName={post.media_user_screen_name} mediaPostId={post.media_post_id} />;
return null;
});
return(
<Layout body={
<div id="timeline">
<div>{postTemplates}</div>
<div className="show-more-box">
<button type="button" className="show-more-button" onClick={fetchPosts}>show more</button>
</div>
</div>
} />
);
};
I fixed the warning by adding fetchPosts. Then I followed eslint instructions using useCallback and adding variables used in fetchPosts to deps. This change causes a loop. How should I fix the loop and eslint warning?
import { useState, useEffect, useCallback } from 'react';
import { useLocation } from "react-router-dom";
import { Layout } from './Layout';
import { TwitterPost, reloadTwitterEmbedTemplate } from '../TwitterPost';
import '../../styles/pages/TimelinePage.css'
import axios from 'axios';
export const TimelinePage = () => {
const [posts, setPosts] = useState([]);
const [page, setPage] = useState(1);
const location = useLocation();
const fetchPosts = useCallback(async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
}, [page, posts]);
useEffect(() => {
if (location.pathname !== '/') return;
fetchPosts();
}, [location, fetchPosts]);
const postTemplates = posts.map((post: any) => {
if (post.media_name === 'twitter') return <TwitterPost mediaUserScreenName={post.media_user_screen_name} mediaPostId={post.media_post_id} />;
return null;
});
return(
<Layout body={
<div id="timeline">
<div>{postTemplates}</div>
<div className="show-more-box">
<button type="button" className="show-more-button" onClick={fetchPosts}>show more</button>
</div>
</div>
} />
);
};
I highly recommend this article to really understand what's going on when you use the useEffect hook. It talks, among other things, about your exact problem and ways to solve it. That said, you should move the function inside the useEffect callback, something like:
export const TimelinePage = () => {
/* ... */
useEffect(() => {
if (location.pathname !== '/') return;
const fetchPosts = async () => {
const res = await axios.get('/api/posts', { params: { page: page } });
setPosts(posts.concat(res.data));
reloadTwitterEmbedTemplate();
setPage(page + 1);
}
fetchPosts();
}, [location]);
/* ... */
};
I have an issue where when my user is a new user both my create profile and create characters to FireBase Realtime database are not loading before my user profile page renders. I understand that useEffects run after the render. But after user profile and characters are created in the database I don't have the issue. I can log off and refresh my app, sign in and everything loads in time. Here is m code. I've tried writing my functions inside the useEffect several different ways and I get the same results every time. I saw one post where someone using a .then() but that doesn't appear to work in my situation. I rather not use any additional add-ins like AXIOs or other packages. I feel like there has to be a way to do this with the native built in tools of React and Firebase. Any advice is much appreciated. Edit: Here is my layout.
App.js
<AuthProvider>
<DBProvider>
<Switch>
<PrivateRoute path="/profile" component={ProfileBar} />
<PrivateRoute path="/update-profile" component={UpdateProfile} />
<Route path="/login" component={Login} />
<Route path="/signup" component={Signup} />
<Route path="/forgot-password" component={ForgotPassword} />
</Switch>
</DBProvider>
</AuthProvider>
</Router>
AuthContext.js
import React, { useContext, useState, useEffect } from 'react'
import { auth} from '../firebase'
const AuthContext = React.createContext()
export function useAuth() {
return useContext(AuthContext)
}
export function AuthProvider({ children }) {
const [currentUser, setCurrentUser] = useState()
const [loading, setLoading] = useState(true)
function signup(email, password, displayName) {
let promise = new Promise ((resolve, reject) => {
auth.createUserWithEmailAndPassword(email, password)
.then((ref) => {
ref.user.updateProfile({
displayName: displayName
});
resolve(ref);
})
.catch((error) => reject(error));
})
return promise
}
useEffect(() => {
const unsubscribe = auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
})
return unsubscribe
}, [])
}
DBContext.js
import { db } from '../firebase'
import { useAuth } from './AuthContext'
import React, { useState, useEffect, useContext } from 'react'
const DBContext = React.createContext() // React Database FireStore .DB
export function useDB() {
useContext(DBContext);
}
export function DBProvider({ children }) {
const [profileData, setProfileData] = useState()
const [loading, setLoading] = useState(true)
const { currentUser } = useAuth()
function checkCurrentUser(){
if(currentUser){
checkProfile()
}
if(!currentUser){
setLoading(false)
console.log("No current user logged in.")
}
}
function checkProfile(){
db.ref(`users/` + currentUser.uid + `/profile`)
.on('value', (snapshot) => {
const data = snapshot.val()
if(data == null){
console.log(data, "New user... Generating profile!")
createUserProfile()
}
if(data){
getProfile()
}
});
}
function createUserProfile(){
let profile = {}
profile.gameMaster = false
profile.editor = false
profile.email = currentUser.email
profile.displayName = currentUser.displayName
db.ref('users/' + currentUser.uid).set({
profile
}).then(() =>{
getProfile()
})
}
function getProfile(){
db.ref(`users/` + currentUser.uid + `/profile`)
.on('value', (snapshot) => {
const profile = snapshot.val()
setLoading(false)
setProfileData(profile)
console.log("Profile set to State from Database.")
})
}
useEffect(() => {
checkCurrentUser()
},[])
}
Profile.js
<Switch>
<CharacterProvider>
<Route path={`${match.path}/characters`} component={CharacterSheets} />
<Route path={`${match.path}/journal`} component={Journal} />
<Route path={`${match.path}/game_charts`} component={GameCharts} />
<Route path={`${match.path}/game_rules`} component={GameRules} />
</CharacterProvider>
</Switch>
CharacterContext.js
useEffect(() => {
const ref = db.ref(`users/` + currentUser.uid + `/characters`)
ref.on('value', snapshot => {
const data = snapshot.val()
if(data){
console.log("Setting Characters to State from Database.")
setCharacters(JSON.parse(data))
setLoading(false)
}
if(data == null){
console.log("Setting Characters to State from template.")
setCharacters(characterTemplate)
setLoading(false)
}
})
return () => ref.off();
}, [])
useEffect(() => {
if(characters){
db.ref(`users/` + currentUser.uid).child("/characters").set(JSON.stringify(characters))
}
console.log("Data saved to firebase.")
}, [characters])
CharacterCards.js
import { useCharacter } from '../../../contexts/CharacterContext'
import CharacterCard from './CharacterCard'
import CharacterCardEdit from '../../ProfileContainer/CharacterEdit/CharacterCardEdit'
import SuccessRoller from '../CharacterComponents/SuccessRoller/SuccessRoller'
export default function CharacterCards() {
const { handleCharacterAdd, characters, selectedCharacter, selectedCharacterSuccessRoller } = useCharacter()
return (
<div>
<div className="add_button-container">
<button onClick={handleCharacterAdd} className="add_button-main" >Add Character</button>
</div>
<div className="parent-container">
<div>
{characters?.map(character => {
return (
<CharacterCard key={character.id} {...character} />
)
})
}
</div>
<div>
{selectedCharacter && <CharacterCardEdit character={selectedCharacter} />}
{selectedCharacterSuccessRoller && <SuccessRoller character={selectedCharacterSuccessRoller} />}
</div>
</div>
</div>
)
}
Because your code is sharded out into many functions for readability, there are a lot of listeners that are created but don't get cleaned up. In particular great care needs to be taken with .on listeners as they may be re-fired (you could use .once() to help with this). An example of this bug is in checkProfile() which listens to the user's profile, then calls getProfile() which also listens to the profile. Each time the profile is added, another call to getProfile() is made, adding yet another listener. Plus, each of the listeners in checkProfile() and getProfile() aren't ever cleaned up.
I've made a number of assumptions about your code structure and untangled it so you can read and understand it top-to-bottom. This is especially important when working with React hooks as their order matters.
// firebase.js
import firebase from "firebase/app";
import "firebase/auth";
import "firebase/database";
firebase.initializeApp({ /* ... */ });
const auth = firebase.auth();
const db = firebase.database();
export {
firebase,
auth,
db
}
// AuthContext.js
import { auth } from "./firebase";
import React, { useContext, useEffect, useState } from "react";
const AuthContext = React.createContext();
export default AuthContext;
export function useAuth() { // <- this is an assumption
return useContext(AuthContext);
}
async function signup(email, password, avatarName) {
const userCredential = await auth.createUserWithEmailAndPassword(email, password);
await userCredential.user.updateProfile({
displayName: avatarName
});
return userCredential;
}
export function AuthProvider(props) {
const [currentUser, setCurrentUser] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => auth.onAuthStateChanged(user => {
setCurrentUser(user)
setLoading(false)
}), []);
return (
<AuthContext.Provider
value={{
currentUser,
loading,
signup
}}
>
{props.children}
</AuthContext.Provider>
);
}
// DBContext.js
import { db } from "./firebase";
import { useAuth } from "./AuthContext";
import React, { useEffect, useState } from "react";
const DBContext = React.createContext();
export default DBContext;
export function DBProvider(props) {
const [profileData, setProfileData] = useState();
const [loading, setLoading] = useState(true);
const { currentUser, loading: loadingUser } = useAuth();
useEffect(() => {
if (loadingUser) {
return; // still initializing, do nothing.
}
if (currentUser === null) {
// no user signed in!
setProfileData(null);
return;
}
// user is logged in
const profileRef = db.ref(`users/` + currentUser.uid + `/profile`);
const listener = profileRef.on('value', snapshot => {
if (!snapshot.exists()) {
// didn't find a profile for this user
snapshot.ref
.set({ // <- this will refire this listener (if successful) with the below data
gameMaster: false,
editor: false,
email: currentUser.email,
displayName: currentUser.displayName
})
.catch((error) => console.error("Failed to initialize default profile", error));
return;
}
setProfileData(snapshot.val());
setLoading(false);
});
return () => profileRef.off('value', listener); // <- cleans up listener
}, [currentUser, loadingUser]);
return (
<DBContext.Provider
value={{
profileData,
loading
}}
>
{props.children}
</DBContext.Provider>
);
}
// CharacterContext.js
import { db } from "./firebase";
import { useAuth } from "./AuthContext";
import React, { useEffect, useState } from "react";
const CharacterContext = React.createContext();
export default CharacterContext;
export function CharacterProvider(props) {
const { currentUser, loading: loadingUser } = useAuth();
const [characters, setCharacters] = useState();
const [loading, setLoading] = useState(true);
useEffect(() => {
if (loadingUser) {
return; // still initializing, do nothing.
}
if (!currentUser) {
// no user signed in!
setCharacters(null);
return;
}
const charactersRef = db.ref(`users/${currentUser.uid}/characters`);
const listener = charactersRef.on('value', snapshot => {
if (!snapshot.exists()) {
// no character data found, create from template
snapshot.ref
.set(DEFAULT_CHARACTERS); // <- this will refire this listener (if successful)
.catch((error) => console.error("Failed to initialize default characters", error));
return;
}
setCharacters(JSON.parse(snapshot.val()));
setLoading(false);
});
return () => charactersRef.off('value', listener);
}, [currentUser, loadingUser]);
return (
<CharacterContext.Provider
value={{
characters,
loading
}}
>
{props.children}
</CharacterContext.Provider>
);
}
I thought had a better grasp of hooks but I've clearly got something wrong here. Not all of the character objects will have what I'm trying to get but it wont work with those that do.
I cna't even build in a check for character.comics.available. Same errors appear. I'm presuming I'm getting them before the state is set? But {character.name} always works.
CharacterDetail.js
import React from "react";
import { useParams } from "react-router-dom";
import useCharacter from "../hooks/useCharacter";
const CharacterDetail = () => {
// from the Route path="/character/:id"
const { id } = useParams();
// custom hook. useCharacter.js
const [character] = useCharacter(id);
// this only works sometimes but errors if i refresh the page
// console.log(character.comics.available);
return (
<div>
<h2 className="ui header">Character Details</h2>
<p>Works every time: {character.name}</p>
<div className="ui segment"></div>
<pre></pre>
</div>
);
};
export default CharacterDetail;
Custom hook useCharacter.js
import { useState, useEffect } from "react";
import marvel from "../apis/marvel";
const useCharacter = (id) => {
const [character, setCharacter] = useState({});
useEffect(() => {
loadItem();
return () => {};
}, [id]);
const loadItem = async (term) => {
const response = await marvel.get(`/characters/${id}`);
console.log(response.data.data.results[0]);
setCharacter(response.data.data.results[0]);
};
return [character];
};
export default useCharacter;
error when console is uncommented
Uncaught TypeError: Cannot read property 'available' of undefined
at CharacterDetail (CharacterDetail.js:11)
...
Here is the character object.
thanks to #Nikita for the pointers. Settled on this...
CharacterDetail.js
import React from "react";
import { useParams } from "react-router-dom";
import useCharacter from "../hooks/useCharacter";
const CharacterDetail = () => {
const { id } = useParams();
// custom hook. useCharacter.js
const { isLoading, character } = useCharacter(id);
const isArray = character instanceof Array;
if (!isLoading && isArray === false) {
console.log("isLoading", isArray);
const thumb =
character.thumbnail.path +
"/portrait_uncanny." +
character.thumbnail.extension;
return (
<div>
<h2 className="ui header">{character.name}</h2>
<img src={thumb} />
<div className="ui segment">{character.comics.available}</div>
<div className="ui segment">{character.series.available}</div>
<div className="ui segment">{character.stories.available}</div>
</div>
);
}
return <div>Loading...</div>;
};
export default CharacterDetail;
useCharacter.js
import { useState, useEffect } from "react";
import marvel from "../apis/marvel";
function useCharacter(id) {
const [character, setCharacter] = useState([]);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
setIsLoading(true);
const fetchData = async () => {
setIsLoading(true);
await marvel
.get(`/characters/${id}`)
.then((response) => {
/* DO STUFF WHEN THE CALLS SUCCEEDS */
setIsLoading(false);
setCharacter(response.data.data.results[0]);
})
.catch((e) => {
/* HANDLE THE ERROR (e) */
});
};
fetchData();
}, [id]);
return {
isLoading,
character,
};
}
export default useCharacter;
I have been trying to use a cleanup function to cancel the API call I a user presses the back button before the request is resolved.
However I still receive the same error "Warning: Can't perform a React state update on an unmounted component.".
I am using fetch function, I added the abortController but still I receive the same warning.
import React, { useState, useEffect, useReducer, useContext } from "react";
import { ActivityIndicator } from "react-native";
import AllThumbnails from "../components/AllThumbnails";
import reducer from "../functions/reducer";
import { lightColors, darkColors } from "../constants/Colors";
import { ThemeContext } from "../context/context";
import ScreenContainer from "../components/UI/ScreenContainer";
export default function AllCatScreen(props) {
const { navigation, route } = props;
const [categories, setCategories] = useState([]);
const [state, dispatch] = useReducer(reducer, { catPage: 1 });
const [theme] = useContext(ThemeContext);
const { taxonomy } = route.params;
useEffect(() => {
const abortCtrl = new AbortController();
const opts = { signal: abortCtrl.signal };
let isActive = true;
fetch(`${siteURL}/wp-json/wp/v2/${taxonomy.endPoint}`, opts)
.then((response) => response.json())
.then((res) => {
if (isActive) {
setCategories([...categories, ...res]);
}
})
.catch((err) => console.log(err));
return function cleanup() {
isActive = false;
console.log(isActive);
abortCtrl.abort();
};
}, []);
if (categories.length == 0) {
return (
<ScreenContainer notYet={true}>
<ActivityIndicator size="large" color={theme.colors.text} />
</ScreenContainer>
);
} else {
return (
<ScreenContainer notYet={false}>
<AllThumbnails
data={categories}
navigation={navigation}
catThumb={true}
action={[state, dispatch]}
fetchData={fetchData}
/>
</ScreenContainer>
);
}
}
I have read that react native should support the AbortController. I am using Expo SDK 38 but even in the clean up function logging the console doesn't work. Does anyone know what's wrong?
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>
);
};