I am not able to find where is the issue with this custom hook?
import { useState, useEffect } from "react";
const SAMPLE_DATA_URL = "../feed/sample.json";
const useFetch = () => {
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(false);
useEffect(() => {
const doFetch = async () => {
setLoading(true);
await fetch(SAMPLE_DATA_URL)
.then(res => res.json())
.then(jsonData => {
setResponse(jsonData);
})
.catch(err => setError(err))
.finally(() => setLoading(false));
};
doFetch();
},[]);
return { response, error, loading };
};
export default useFetch;
on network tab I can see 200 OK but the preview is saying "You need to enable JavaScript to run this app." and also the response is html of my index screen. I checked javascript in browser is allowed and the json file is a valid json.
on return object I am getting error: true
Where its been used
import React from "react";
import styles from "./Series.module.css";
import { TitleBar } from "../../atoms";
import {useFetch} from '../../utils';
const Series = () => {
const { response, loading, error } = useFetch();
return (
<div >
<TitleBar>Popular Series</TitleBar>
<div className={styles.content}>
{loading && <p>Loading...</p>}
{error && <p>Oops, Something went wrong...</p>}
{response && <p>response</p>}
</div>
</div>
);
};
export default Series;
If you are using CRA, you can put your sample.json inside your public folder and so you can fetch the URL directly:
fetch("sample.json")
.then(...)
.then(...)
Although, you don't need to do all that as you can just import the data like any other js modules
import data from "./sample.json"; // Path
const App = () => {
return (
<div className="App">
{data.map(item => {
// return JSX with item...
})}
</div>
);
};
codesandbox examples.
Related
I have made a custom hook that takes url and fetches the data in json format. But when I am trying to assign the data into const users using use state, I getting the error :
'Uncaught Error: Too many re-renders. React limits the number of renders to prevent an infinite loop'
Here is the component from where I am trying to assign:
import React, { useState } from "react";
import useFetch from "./fetchData";
import Users from "./Users";
const ASSIGN5 = () => {
const [users, setUsers] = useState();
const { data, isLoading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
setUsers(data);
return (
<div className="container">
<h1 className="">Users Management App</h1>
{isLoading && <p>Loading users...</p>}
{error && <p>{error}</p>}
<Search onHandleSearch={handleSearch} />
{users && <Users users={users} />}
</div>
);
};
export default ASSIGN5;
And here is the useFetch hook:
import React, { useEffect, useState } from "react";
const useFetch = (url) => {
const [data, setData] = useState([]);
const [isloading, setIsloading] = useState(true);
const [error, setError] = useState();
useEffect(() => {
fetch(url)
.then((res) => {
if (!res.ok) {
throw Error("Fetching unsucessful");
} else {
return res.json();
}
})
.then((data) => {
setData(data);
setIsloading(false);
setError(null);
})
.catch((error) => {
setError(error.message);
setIsloading(false);
});
}, [url]);
return { data, isloading, error };
};
export default useFetch;
But it runs fine when I use data directly without assigning but I need to because have to filter the data using functions
I am expecting that the data will assigned to the const users
Don't call state setters unconditionally in the component body or that'll trigger infinite renders.
It appears you don't need the users state at all because it's just an alias of the data array returned by your useFetch hook.
const ASSIGN5 = () => {
const { data, isLoading, error } = useFetch(
"https://jsonplaceholder.typicode.com/users"
);
return (
<div className="container">
<h1 className="">Users Management App</h1>
{isLoading && <p>Loading users...</p>}
{error && <p>{error}</p>}
<Search onHandleSearch={handleSearch} />
{data?.length && <Users users={data} />}
</div>
);
};
If you want to rename it you can use
const { data: users, isLoading, error } = useFetch(...);
// now you can replace `data` with `users`
Search and handleSearch weren't defined but I assume those are in your actual code somewhere.
Components are typically PascalCase, so ASSIGN5 should be Assign5.
I'm using an API to fetch data. When I console.log my data, it shows as an array. But when I try to map over it to get the data to display, it tells me that .map is not a function. I created a custom useFetch hook and then I'm importing it into a separate component. Here's my code and a screenshot of the console.log:
useFetch.js
import { useEffect, useState } from 'react'
function useFetch(url) {
const [data, setData] = useState(null)
const [isLoading, setIsLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
fetch(url)
.then(response => {
if (!response.ok) {
throw Error("Sorry, couldn't fetch data for this resource!")
}
return response.json()
})
.then(responseData => {
setData(responseData)
setIsLoading(false)
setError(null)
})
.catch(error => {
setIsLoading(false)
setError(error.message)
})
}, [url])
return { data, isLoading, error }
}
export default useFetch
List.js
import React from 'react'
import useFetch from './useFetch'
function PrizeList2017() {
const { data } = useFetch('http://api.nobelprize.org/v1/prize.json?year=2017&yearTo=2017')
return (
<div className="prize-list-2017-container">
<h1>2017</h1>
{data.map(prize => (
<div key={prize.id}>
<h2>{prize.category}</h2>
</div>
))}
{console.log(data)}
</div>
)
}
export default PrizeList2017
console.log
console.log info image
Your help is greatly appreciated!
This data is not present yep when you try to do the map so do:
{data && data.prizes && data.prizes.map(prize => (
I am using useEffect to hit an api and display some data from the response.It works well in console but when i try to display the data in a component it throws an error.I am checking for the loading state though.I am showing the data after a i get a response then where does this null coming from
App.js file:
import { useState, useEffect } from 'react';
import Details from './components/Details/Details';
import Header from './components/Header/Header';
import GlobalStyle from './globalStyles';
const API_KEY = 'Private';
// const URL = `https://geo.ipify.org/api/v1?apiKey=${API_KEY}&ipAddress=${ip}`;
function App() {
const [ip, setIp] = useState('8.8.8.8');
const [response, setResponse] = useState(null);
const [error, setError] = useState(null);
const [isLoading, setIsLoading] = useState(false);
useEffect(() => {
const fetchData = async () => {
setIsLoading(true);
try {
const res = await fetch(
`https://geo.ipify.org/api/v1?apiKey=${API_KEY}&ipAddress=${ip}`
);
const json = await res.json();
setResponse(json);
setIsLoading(false);
} catch (error) {
setError(error);
}
};
fetchData();
// return { response, error, isLoading };
}, [ip]);
return (
<>
<GlobalStyle />
<Header getIp={(q) => setIp(q)} />
<Details isLoading={isLoading} res={response} error={error} />
</>
);
}
export default App;
Header.js file:
import { useState } from 'react';
import { FaArrowRight } from 'react-icons/fa';
import React from 'react';
import { Form, FormInput, Head, HeadLine, Button } from './Header.elements';
// import { useFetch } from '../../useFetch';
const Header = ({ getIp }) => {
const [input, setInput] = useState('');
const onChange = (q) => {
setInput(q);
getIp(q);
};
return (
<>
{/* styled components */}
<Head>
<HeadLine>IP Address Tracker</HeadLine>
<Form
onSubmit={(e) => {
e.preventDefault();
onChange(input);
setInput('');
}}
>
<FormInput
value={input}
onChange={(e) => {
setInput(e.target.value);
}}
placeholder='Search for any IP address or Domain'
/>
<Button type='submit'>
<FaArrowRight />
</Button>
</Form>
</Head>
</>
);
};
export default Header;
Details.js file:
import React from 'react';
import { Box, Location } from './Details.elements';
const Details = ({ res, error, isLoading }) => {
console.log(res);
return isLoading ? (
<div>loading...</div>
) : (
<>
<Box>
<Location>{res.location.city}</Location>
</Box>
</>
);
};
export default Details;
the error it shows:
That happens because on the first render, Details component will receive isLoading=false and res=null, so it will try to render the box so it's throwing the error.
You can initialize isLoading as true.
const [isLoading, setIsLoading] = useState(true);
Or render the Location if res has some value.
<Box>
{res && <Location>{res.location.city}</Location>}
</Box>
According to React documentation :
https://reactjs.org/docs/hooks-reference.html
By default, effects run after every completed render, but you can
choose to fire them only when certain values have changed.
So your component is rendering at least once with isLoading as false before even the API call starts.
You have two choices here:
Set isLoading initial value to true
Add optional chaining res?.location.city
https://codesandbox.io/s/stackoverflow-67755606-uuhqk
Im having troubles rendering components based on api calls in React. I fetch my data in useEffect hook update a state with the data. The state is null for a while before the api get all the data but by that time, the components are rendering with null values. This is what I have:
import React, { useEffect, useState } from 'react'
import axios from 'axios';
import { Redirect } from 'react-router-dom';
const Poll = (props) => {
const [poll, setPoll] = useState(null);
//if found is 0 not loaded, 1 is found, 2 is not found err
const [found, setFound] = useState(0);
useEffect(() => {
axios.get(`api/poll/${props.match.params.id}`)
.then(res => {
console.log(res.data);
setPoll(res.data);
setFound(1);
})
.catch(err => {
console.log(err.message);
setFound(2);
});
}, [])
if(found===2) {
return(
<Redirect to="/" push />
)
}else{
console.log(poll)
return (
<div>
</div>
)
}
}
export default Poll
That is my workaround but it doesnt feel like thats the way it should be done. How can I set it so that I wait for my api data to get back then render components accordingly?
You don't need to track the state of the API call like const [found, setFound] = useState(1). Just check if poll exists and also you can create a new state variable for tracking the error.
For example if (!poll) { return <div>Loading...</div>} this will render a div with 'loading...' when there is no data. See the code below, for complete solution,
import React, { useEffect, useState } from 'react'
import axios from 'axios';
import { Redirect } from 'react-router-dom';
const Poll = (props) => {
const [poll, setPoll] = useState(null);
const [hasError, setHasError] = useState(false);
useEffect(() => {
axios.get(`api/poll/${props.match.params.id}`)
.then(res => {
console.log(res.data);
setPoll(res.data);
})
.catch(err => {
console.log(err.message);
setHasError(true)
});
}, [])
if(!poll) {
console.log('data is still loading')
return(
<div>Loading....</div>
)
}
if (hasError) {
console.log('error when fetching data');
return (
<Redirect to="/" push />
)
}
return (
<div>
{
poll && <div>/* The JSX you want to display for the poll*/</div>
}
</div>
);
}
export default Poll
In your than, try to use a filter:
setPoll(poll.filter(poll => poll.id !== id));
Make sure to replace id by your identificator
The standard way is to have other variables for the loading and error states like this
const Poll = (props) => {
const [poll, setPoll] = useState(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState(false);
useEffect(() => {
setLoading(true);
axios.get(`api/poll/${props.match.params.id}`)
.then(res => {
console.log(res.data);
setPoll(res.data);
})
.catch(err => {
console.log(err.message);
setError(true);
})
.finally(()=> {
setLoading(false);
};
}, [])
if(error) return <span>error<span/>
if(loading) return <span>loading<span/>
return (
<div>
// your poll data
</div>
)
}
I have created a custom Hook which fetches data from the server, sends dispatch to the store and returns data. It is usable if I want to list all comments in my app, however, I wanted to reuse it in the component where I need to fetch all comment replies, and that should happen only when certain button is clicked.
This is the hook down below.
import { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
const useFetch = (url, options, actionType, dataType) => {
const [response, setResponse] = useState([]);
const dispatch = useDispatch();
useEffect(() => {
(async () => {
const res = await fetch(url);
const json = await res.json();
setResponse(json);
})();
}, []);
useEffect(() => {
dispatch({ payload: response, type: actionType });
}, [response]);
const data = useSelector(state => state[dataType]);
return data;
};
export default useFetch;
Inside of my component I need to fetch replies when a button is clicked
const ParentComment = ({ comment }) => {
const handleShowMoreReplies = (e) => {
e.preventDefault();
}
let replies = useFetch(
`/api/comment_replies?comment_id=${comment.id}`,
null,
"REPLIES_RECEIVED",
"replies"
);
return (
<div>
<Comment comment={comment} />
<div className="replies">
{replies.map(reply => (
<Comment key={reply.id} comment={reply} />
))}
<a href="#" className="show_more" onClick={handleShowMoreReplies}>
Show More Replies ({comment.replies_count - 1})
</a>
</div>
</div>
);
};
If I put useFetch call inside of the handler I hget an error that Hooks can't be called there, but I need to call it only when the button is clicked so I don't know if there is a way to implement that.
I think you have subtle problems in your useFetch hook
1.your useEffect is having dep of ${url} and ${actionType } which you need to define.
2.In order to call this hook by clicking the button, you need to expose the setUrl as follows
const useFetch = ( initialUrl, options, actionType, dataType) => {
const [url, setUrl ] = useState(initialUrl);
const dispatch = useDispatch();
useEffect(() => {
const fetchData = async () => {
try {
const res = await fetch(url);
const data = await res.json();
dispatch({ payload: data, type: actionType });
} catch (error) {
console.log(error);
}
};
fetchData();
}, [url, actionType]);
const data = useSelector(state => state[dataType]);
return [ data, setUrl ];
};
export default useFetch;
Then when you are trying to use this hook, you can
const [data, fetchUrl] = useFetch(
`/api/comment_replies?comment_id=${comment.id}`,
null,
"REPLIES_RECEIVED",
"replies"
);
Then every time you have a button you can simply call
fetchUrl(${yourUrl}).
your hook will receive the new URL, which is the dep of your hook and rerender it.
Here is an related article
https://www.robinwieruch.de/react-hooks-fetch-data