Introduction to React Hooks
During my time at Flatiron School, I was introduced to the world of Redux. I must say — it was not my favorite resource. I found it to be difficult to conceptualize, as well as very buggy. Parsing through responses, sifting through multiple files, and using React’s connect functionality proved to be very difficult, and the overall functionality of my first application using React/Redux and Ruby on Rails suffered for it.
I am currently taking the React Front to Back course by Brad Traversy, which i have found to be incredibly insightful and useful. One of the topics that we breached in building the Github Finder application was utilizing React Hooks, specifically useState, useContext, and tying it together with Context Provider. Let’s take a brief walk through the world of React Hooks.
Use State
The useState hook in React is used to allow functional components to manage state. In a more basic approach, we would use a constructor method to utilize the props that are passed down from the top-level component, App. This also required us to declare components as classes, which can be a bit more tedious than functional components. Traditionally, we would have set attributes with destructuring, like so:
constructor(props) {
super(props);
this.state = { text: '' };
However, since we can no longer use the keyword ‘this’ in a functional component, we can employ the useState Hook to set a variable state, and plug in the value within the functions and forms defined in the component.
First, we import useState at the top of our functional component.
import React, { useState } from 'react';
If we were using state to capture changes in the text entered into and submitted from a search bar, we could do it like so:
const [text, setText] = useState('');
Here, we are doing a few things. First, we are declaring the piece of state that we want to manipulate — text. The second argument that we pass in is the function that will be called when we update the state, setText; this is the replacement of the this.setState method in a class component. Finally, we pass the initial state into the useState Hook, i.e. text will be set to an empty string upon initialization.
Now, we can call this setText function throughout the component. In the places in which we want to update the state of the component, we would simply pass the value of text into the setText function. React will re-render the component with the updated value.
The final distinction here is how the value of text is referenced throughout the component. When we use a traditional class component, we would have to reference the piece of state for text as:
<p>{this.state.text}</p>
With useState, this becomes much simpler.
<p>{text}</p>
Cleaner, right?
Use Context
As mentioned above, one of the issues with building a React application, without using a library like Redux or an API like Context, is passing props down the tree of components. Context allows us to make data ‘global’ throughout the application, rendering the need to pass props down the tree moot. This is particularly useful in customizing the UI for users, as you can maintain the current user globally, and tailor parts of the application to reflect the aforementioned user’s information.
<h1>Hello, {username}!</h1>
In Github Finder, useContext pulls the state into a separate compartment, and dispatches actions and utilizes reducers to update state.
First, we will create a ‘context’ that will be used to manage state for the application, or the parts that are wrapped in this context.
import { createContext } from 'react';const exampleContext = createContext();export default exampleContext;
Second, we will take a look at our reducer file.
import { SET_LOADING } from '../types';export default (state, action) => {
switch(action.type) { case SET_LOADING:
return {
...state,
loading: false,
} default:
return state;
}
Here, we see the flow for how we update state using a reducer. The reducer takes two arguments: the current state (initial state), and the action that will be dispatched to update the state. Within the switch statement of the reducer, we destructure the state in order to update the piece of state that we want to change; in this case, we are setting the boolean Loading to false. The ‘type’ attribute of action comes from a separate file, which identifies what we are trying to do, i.e. set the loading piece of state.
//types.jsexport const SET_LOADING = 'SET_LOADING';
Finally, we include a default argument in the reducer, which returns an initial value for state, ensuring that we never return null or undefined.
The last piece of this flow is the state file of the context that we are creating. This file will include the useReducer Hook, and must import the corresponding context and reducer files. We also import the types from the types file, which will be included in our dispatch.
import React, { useReducer } from 'react';
import ExampleContext from './ExampleContext';
import ExampleReducer from './ExampleReducer';
import { SET_LOADING } from '../types';const exampleState = (props) => {
const initialState = { loading: false, }
const [state, dispatch] = useReducer(ExampleReducer, initialState);
}const setLoading = () => dispatch({ type: SET_LOADING }); return ( <ExampleContext.Provider
value={{
loading: state.loading,
}}
>
{props.children}
</ExampleContext.Provider>
);
};export default ExampleState;
In this file, we set the initial state of loading to false, as the application will not be loading data until we trigger an event, such as a search or re-route to another component. Similar to how we used the useState Hook, the useReducer Hook takes in the state, the dispatch function, and combines our reducer file with the initial state to update our application’s ‘global’ state.
Below the declaration of the initial state (ExampleState), we declare a function setLoading, which returns a dispatch. The dispatch takes in an argument of an object, which has a type of SET_LOADING, pointing to the functionality of the function that we are declaring.
In the final return statement, we wrap our context using Provider, which does exactly as its name implies: provides state. Within the ExampleContext, our loading value becomes state.loading, which will reflect the current value of the loading boolean, whether it is true or false. In the final line, the props that are defined within the ExampleContext functional component will be made available to all components that are wrapped within it.
This makes our App component much cleaner, as we don’t have to use any functions of methods — they are handled in our context files (reducer, actions, state).
import React from 'react';
import ExampleState from './context/example/ExampleState';
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
import Home from './components/pages/Home';
import NotFound from './components/pages/NotFound';const App = () => {return (<ExampleState> <Router> <div className='App'> <div className='container'> <Switch> <Route exact path='/' component={Home} /> <Route component={NotFound} /> </Switch></Router></ExampleState>);};export default App;
Now, our state is available to all components wrapped in ExampleState. Neat, huh?
I hope that this gives you a cursory understanding of some basic React Hooks, and how they can be used to make components functional, and make state globally accessible for easier transfer of data between components. Happy Coding!