As we know, hooks are functions which you can use with functional components only.
Hooks allows us to reuse the functionality. Consider hooks as a reusable functions or functionalities.
A custom hook needs to be prefixed with use, similar to every other Hook.
Today we will create a custom hooks called as useFetch. This hook will take care of all API calls and will return a response.
You can create a custom hook in a separate file and export it either as a default or a named export.
useFetch
//custom hook must have use prefix
export const useFetch = (url, actionObject, dispatch) => {
useEffect(() => {
console.log("useEffect of useFetch.. making HTTTP call..");
fetch(url)
.then((response) => response.json())
.then((data) => {
if (data) {
actionObject.payload = data;
dispatch(actionObject);
}
});
}, [url]);
};
Above hook will call the specified url, capture the response and call the dispatch method with action object after setting the payload.
Notice how the useEffect has dependency on the url. So if same url is requested again, useEffect won't make an HTTP request.
We will use above hook to make HTTP calls in our Fetch call. We will extend our earlier example of useReducer as well.
We need to import this hook into our file in case its in another file. We have removed our useEffect where we were making http call earlier. We have replaced it with the useFetch like below:
useFetch(state.url, { type: "UPDATE_BOOKS", payload: [] }, dispatch);
Rest of the code remains same, there is no change apart from this.
import { useFetch } from "./useFetch";
const FetchAPIDemo = () => {
// Component State Object
const defaultState = {
url: "http://localhost:80/books.json",
books: [],
};
const [clickCount, setClickCount] = useState(0);
// reducer function
const reducer = (prevState, action) => {
//define actions here using switch-case
switch (action.type) {
case "UPDATE_BOOKS": {
return {
...prevState,
books: action.payload,
};
}
case "CHANGE_URL": {
return {
...prevState,
url: action.payload.newURL,
};
}
default:
//its good practice to throw error as a default case
throw new Error(
`Could not find the dispatched action ==> ${action.type} <==`
);
}
};
// reducer function with default state value associated with it
const [state, dispatch] = useReducer(reducer, defaultState);
useFetch(state.url, { type: "UPDATE_BOOKS", payload: [] }, dispatch);
const changeAPIUrl = () => {
// setUrl(newURL) is replaced by dispatch
dispatch({
type: "CHANGE_URL",
payload: { newURL: "http://YOUR_IP_ADDRESS:80/books.json" },
});
};
return (
<React.Fragment>
<div className="App">
<header className="App-header">
<p>Current counter value is {clickCount}</p>
{state.books.length > 0 ? (
<div className="add-column-margin">
<div
id="myCarousel"
className="carousel slide"
data-ride="carousel"
>
<ol className="carousel-indicators">
{state.books.map((book, index) => {
return (
<li
key={index}
data-target="#myCarousel"
data-slide-to={index}
className={index === 0 ? "active" : ""}
></li>
);
})}
</ol>
<div className="carousel-inner">
{state.books.map((book, index) => {
return (
<div
key={index}
className={
index === 0 ? "carousel-item active" : "carousel-item"
}
>
<img
className="d-block w-100"
src={book.coverImage}
alt="First slide"
></img>
<div className="carousel-caption d-none d-md-block">
<h5
style={{
color: "white",
backgroundColor: "rgba(0, 0, 0, 0.6)",
}}
>
{book.title}
</h5>
</div>
</div>
);
})}
</div>
<a
className="carousel-control-prev"
href="#myCarousel"
role="button"
data-slide="prev"
>
<span
className="carousel-control-prev-icon"
aria-hidden="true"
></span>
<span className="sr-only">Previous</span>
</a>
<a
className="carousel-control-next"
href="#myCarousel"
role="button"
data-slide="next"
>
<span
className="carousel-control-next-icon"
aria-hidden="true"
></span>
<span className="sr-only">Next</span>
</a>
</div>
<button
className="btn btn-primary mx-auto float-left"
onClick={() => {
setClickCount((prevClickCount) => prevClickCount + 1);
}}
>
Increment Count
</button>
<button
className="btn btn-primary mx-auto float-right"
onClick={changeAPIUrl}
>
Change URL
</button>
</div>
) : (
<p>Loading...</p>
)}
</header>
</div>
</React.Fragment>
);
};
export default FetchAPIDemo;
Output
When component is rendered and click count changes, we only see useFetch called once as per the console message:
Post click of change url button, useFetch is called again (notice the badge count) below:
Comments
Post a Comment