React useReducer Hook - (Part - II)
As we have seen most of the basics of useReducer in my earlier article, lets continue with code.
As discussed earlier, we will use 2 custom actions, each for updating books and URL. We will continue to use useState for clickCount to show that useState and useReducer can work together!
Reducer
// 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} <==`
);
}
};
As you can see above, by using spread operation, we are copying previous state first and then updating the specific state values.
By doing this, we are preventing other state values being overwritten.
Dispatch
UPDATE_BOOKS
useEffect(async () => {
console.log("Use Effect for Books API call is called..");
const response = await fetch(state.url);
const data = await response.json();
// setBooks(data) is replaced by dispatch so reducer can do that for you!
dispatch({ type: "UPDATE_BOOKS", payload: data });
}, [state.url]);
CHANGE URL
const changeAPIUrl = () => {
// setUrl(newURL) is replaced by dispatch
dispatch({
type: "CHANGE_URL",
payload: { newURL: "http://YOUR_IP_ADDRESS:80/books.json" },
});
};
Notice that for books, its a straightforward assignment but for url, we can use payload to send objects as well.
Entire JS File
Apart from above, there are no other code changes required in our previous example of using useEffect and fetch.
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);
useEffect(async () => {
console.log("Use Effect for Books API call is called..");
const response = await fetch(state.url);
const data = await response.json();
// setBooks(data) is replaced by dispatch so reducer can do that for you!
dispatch({ type: "UPDATE_BOOKS", payload: data });
}, [state.url]);
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
There is no visible difference in the output in comparison to the useEffect and fetch.
When component renders for the first time
- Notice that useEffect is called, it has fetched the data using http://localhost:80/books.json
When incrementing the count
- When we increment the count, useEffect is not called.
Changing the URL
Invalid dispatch action
Just to show you the demo, alongside UPDATE_BOOKS, I will also dispatch UPDATE_USER action, so reducer will throw custom error as below:
//invalid action, reducer will throw error
dispatch({ type: "UPDATE_USER", payload: data });
Comments
Post a Comment