16
React Hooks and Fetch
This blog will be detailing how to use React Hooks with Fetch to get data.
"Note that all React packages need to be 16.8.0 or higher to enable Hooks. Hooks won't work if you forget to update".
Let's first look at the database I used for this demonstration. I used a rails API, but we won't be getting any deeper than this; all you need to know are the attributes that are set up in the table to understand the examples.
create_table "projects", force: :cascade do |t|
t.string "title"
t.string "description"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
This code snippet shows a table called "Projects" that has 2 attributes "Title" and "Description".
Displayed is the "project display functional component". The goal. heere. is to have this component fetch all the projects from my backend so that it can be displayed.
Let's first take a look at the "imports"
import { useEffect } from 'react';
import { fetchProjects } from './projectAction';
import { useSelector, useDispatch } from 'react-redux'
useEfect
is similar tocomponentDidMount()
which runs additional code before React has updated the DOM. This is important because we want to get our data before the page loads. No data means no projects can be displayed on the DOM. How do we get our data? Yes, Fetch!
This is where the fetch request is happening.fetchProjects
is a functional component I created just to handle get request.useDispatch
is similar toconnect
.useDispatch
is React Hooks' way to trigger a state change.useSelector
is similar to how we would call "this.props.projects" to get data from the redux store; now we haveuseSelector
.
export default function ProjectDisplay() {
const projects = useSelector(state => state.projects)
const dispatch = useDispatch()
useEffect(() => {
dispatch(fetchProjects())
}, [])
}
-
const
declaration of "projects" is the return value ofuseSelector
. How you set up your reducer(s) and store reflects how you call your state. Typically if you only passed in 1 reducer component in my redux store, you would just useconst projects =useSelector(state => state)
, but I passed in acombineReducer
, which like it says, combines all the different reducer components, so you have to specify which one you want. How theStore
looks like ...
import { applyMiddleware, createStore, combineReducers } from "redux";
import thunk from "redux-thunk"
import userReducer from "./reducers/userReducer";
import projecReducer from "./reducers/projectReducer"
import taskReducer from "./reducers/taskReducer"
let rootReducer = combineReducers({user: userReducer, projects: projecReducer, tasks:taskReducer })
export default createStore(rootReducer, applyMiddleware(thunk))
2.const
declaration of "dispatch" gives us access to the usDispatch function by just calling Dispatch
-
useEffect
takes in 2 arguments. The first argument takes in a function. In this example, we created a call-back function withdispatch
to trigger a change instate
. Then in our function dispatch we call on ourfetch
function (fetchProject). The second argument inuseEffect
takes in an array of dependencies. In this example, we have none, so we left the array empty.
You should already be familiar with how to write a fetch
function, so I will only be getting into how it changes our state
.
export function fetchProjects(){
return dispatch => {
fetch('http://localhost:3000/projects')
.then(r => r.json())
.then( projects => {
dispatch({type:"SET_PROJECTS", payload: projects})})
}
}
What is fetch doing? Fetch is going to the url provided, in this case the route of my backend and 'GET'/getting all the projects.
The new addition to this fetch request is
dispatch({type:SET_PROJECTS, payload: projects}
. In the secondpromise
of our function. We calldispatch
to change the state, which we still need to do within the fetch request.dispatch
takes in anaction
, an object that describes what happened ({type:SET_PROJECTS, payload: projects}
).type
is a string used to match ourcase
in theswitch
statement (located in the reducer which changes our store'sstate
).payload
in this example is the data retrieved.
Remember that we are passing in reducers to our redux store, so to change our state
inside of our store, we must modify the information in our reducers. After the fetch request, the second dispatch accesses the reducer.
This is the Project Reducer:
export default function projectReducer(state=[], action) {
switch(action.type){
case "SET_PROJECTS":
return action.payload;
}
}
- The project reducer takes in 2 arguments. 1. the state, which is being defaulted to an empty array, and 2. action, which is what refers to the object that was sent through dispatch (
dispatch({type:SET_PROJECTS, payload: projects}
).
2.With switch
the value of the expression (action.type) is compared with the values of each case. in this example, we used action.type, which renders out to be "SET_PROJECTS" because it is what was passed through in our dispatch
. In the case statement, we are returning the state. By calling action.payload the state is snow equal to the data we got from our fetch, which is an array of Project objects .
const projects = useSelector(state => state.projects)
Now that our state
is an array of project objects We can render them in our DOM. remember our useSelector
function declared earlier in the project component? We can use the const project like this ...
return(
<div>
<h1>Projects</h1>
<ul>
{projects.map(p =>{
return(
<li key={p.id}>
<p>{p.description}</p>
<p>{p. completion_rate}</p>
<button onClick={()=>{handleClick({p})}}>Delete</button>
</li>
)})}
</ul>
</div>
)
As you can see we are mapping through projects and displaying each project in an unordered list on the DOM.
16