DEV Community

Cover image for ## HOOKS + CONTEXTAPI === AWESOME
Avinash S
Avinash S

Posted on

## HOOKS + CONTEXTAPI === AWESOME

Disclaimer: Only for ReactJS/JS Devs - TLDR

Hey NERDS. We are gonna discuss about some AWESOME concepts in React like

  • Hooks (useState, useEffect, useContext & useReducer) &
  • Context API

FIRST THINGS FIRST,

This blog assumes that if not decent, at-least some level of understanding in when & how to use class components, state, props & handling events.

With that clarification out of our way, lets get started ->
For those who use Class Components for a while, as per docs HOOKS are a new addition in React 16.8 & by this FEB 2021, it will be 2 yrs old (not so new, may be)

Having said that, Hooks are better when it comes to performance, abstraction & readability in comparison to class based components. Yes true, if you don't believe my word, take it from React Core Team demo
NOTE: Video is about inspiration for HOOKS

But ... What is HOOK?

Provides some direct API that makes us to use state in functional components . In simple terms, they let you use state & other react features without having to write a class.

Here is an implementation of hook called useState hook for example,

import React, { Fragment, useState } from "react";

export const Example = () => {
  const [name, setName] = useState("");
  const [login, setLogin] = useState(false);
  return (
    <Fragment>
      {!login ? (
        <input
          type="name"
          value={name}
          placeholder="Enter your Name"
          onChange={(e) => setName(e.target.value)}
        />
      ) : (
        <div className="display__text">Hello {name}</div>
      )}

      <button type="submit" onClick={() => setLogin(!login)}>
        {!login ? "Login" : "Go back"}
      </button>
    </Fragment>
  );
};

Enter fullscreen mode Exit fullscreen mode

Basic useState implementation

From above, you can see it is just simple login simulation app, i have used useState hook to use the state within the functional Component, one state for handling user input name field and other for toggling into welcome page.

Yes, you can useState as many times you want, there is no restriction but there is one caveat to using hooks in general, that is they should be used only inside the function definition.

Just for Comparison, check the equivalent class component implementation here,

import React, { Component, Fragment } from "react";

export class Example extends Component {
  constructor(props) {
    super(props);
    this.state = {
      name: "",
      login: false
    };
  }

  render() {
    return (
      <Fragment>
        {!this.state.login ? (
          <input
            type="name"
            value={this.state.name}
            placeholder="Enter your Name"
            onChange={(e) => this.setState({ name: e.target.value })}
          />
        ) : (
          <div className="display__text">Hello {this.state.name}</div>
        )}

        <button
          type="submit"
          onClick={() => this.setState({ login: !this.state.login })}
        >
          {!this.state.login ? "Login" : "Go back"}
        </button>
      </Fragment>
    );
  }
}

Enter fullscreen mode Exit fullscreen mode

Class Based Implementation

I bet you can already see the difference, not only 10 unnecessary lines are removed, overall code logic looks neat. We can further drill down in our hooks implementation

import React, { Fragment, useState } from "react";

export const Example = () => {
  //using objects to keep multiple states
  const [state, setState] = useState({ name: "", login: false });
  const { name, login } = state;

  return (
    <Fragment>
      {!login ? (
        <input
          type="name"
          value={name}
          placeholder="Enter your Name"
          onChange={(e) => setState({ ...state, name: e.target.value })}
        />
      ) : (
        <div className="display__text">Hello {name}</div>
      )}

      <button
        type="submit"
        onClick={() => setState({ ...state, login: !login })}
      >
        {!login ? "Login" : "Go back"}
      </button>
    </Fragment>
  );
};
Enter fullscreen mode Exit fullscreen mode

Modified useState implementation

Well, this look more better than before. Instead of using many useState(s) , irrespective of number of states in your application, we can use single useState call with an object to account for all the states. Also, check i have also changed the setLogin, setName into single setState call with (...) spread operator to update the respective states on particular event.


All is fine. But how to use life cycle methods like componentDidMount, componentDidUpdate, componentWillUnmountfor API calls or side effects in HOOKS?

To answer that, we have an another hook called useEffect which takes function as a argument. Cool thing about this hook is, it replaces all the life cycle methods mentioned above.

import React, { Fragment, useState, useEffect } from "react";
import Axios from "axios";

export const Example = () => {
  const [state, setState] = useState({ posts: [] });
  const postsUrl = "https://jsonplaceholder.typicode.com/posts";

  useEffect(() => {
    //Fetching Posts

    const fetchPosts = async () => {
      const posts = await Axios.get(postsUrl);
      setState({ ...state, posts: posts.data });
    };
    try {
      fetchPosts();
    } catch (err) {
      console.log("Network error", err);
    }
    //dependency array is none, it acts as componentDidMount
  }, []);

  return (
    <>
      <h1>My posts</h1>
      <ul>
        {state.posts.length > 0 &&
          state.posts.map((posts, index) => {
            return <li key={index}>{posts.title}</li>;
          })}
      </ul>
    </>
  );
};

Enter fullscreen mode Exit fullscreen mode

Basic useEffect implementation (componentDidMount)

import React, { useState, useEffect } from "react";

export const Example = () => {
  const [title, setTitle] = useState("");

  useEffect(() => {
    document.title = title;
    //runs everytime when title state is changed
    //Acts as componentDidUpdate
  }, [title]);

  return (
    <input
      type="text"
      name="title"
      value={title}
      placeholder="Enter your title"
      onChange={(e) => setTitle(e.target.value)}
    />
  );
};


Enter fullscreen mode Exit fullscreen mode

Basic useEffect implementation (componentDidUpdate)

From above you can see componentDidMount & componentDidUpdate implementation in hooks with useEffect. First one fetches posts from an fake rest API endpoint and empty dependency array is passed as an second argument to simulate componentDidMount and second one updates the title tab of browser whenever the input field value is updated by the user. Note it has an dependency array value and it simulates componentDidUpdate.

In addition to useState & useEffect hook there are built_in_hooks like useReducer, useRef, useLayoutEffect & some more which you can check in React Docs


Alright! What is Context in nutshell?

In simple terms, if you use context, we can avoid prop drilling meaning there is no need to pass props to unnecessary intermediate component in between when you work with deep nested components.


import React, { createContext } from "react";

//creating context
export const AppContext = createContext();

//Not necessary in react new versions
export const AppConsumer = AppContext.Consumer;

//Exporting provider
export const AppProvider = ({ children }) => {
  return <AppContext.Provider>{children}</AppContext.Provider>;
};


Enter fullscreen mode Exit fullscreen mode

useReducer WITH contextAPI (AppContext.js)

You can see, I have to createcontext first and passing initial values to it is optional, then export respective context and provider in AppContext.js.

Note: You have to wrap the children prop inside the Provider
pass in the values you wanna pass in value prop in Provider so that you can use the values latter in wherever you wanna use in component tree (Here we have passed dispatch function and state)

import React from "react";
import "./styles.css";
import { Example } from "./Components/Example";
import { AppProvider } from "./Contexts/AppContext";

export default function App() {
  return (
    <AppProvider>
      <Example />
    </AppProvider>
  );
}


Enter fullscreen mode Exit fullscreen mode

useReducer WITH contextAPI(App.js)

Make sure to wrap the provider with components you wanna use context values in App.js as well.

So, all set before talking about how to use context values inside the respective components, there is another hook we need to discuss about which simulates how redux works called useReducer

Alt Text

For those who doesn't know how redux works, this is the simple flow diagram that explains well

Redux is quite complex to understand but in simple terms, we as users dispatch an action (trigger an event) in an application, there is pure function called reducerwhich takes in respective action and the current state, updates with new state based on the action.


import React, { createContext, useReducer } from "react";
//importing Reducer
import { reducer } from "../reducer";

//creating context
export const AppContext = createContext();

//Not necessary in react new versions
export const AppConsumer = AppContext.Consumer;

//Exporting provider
export const AppProvider = ({ children }) => {
  //usng useReducer hook and passing reducer and initial state to it
  const [state, dispatch] = useReducer(reducer, []);

  //pass the app state and dispatch function to the value prop in provider
  return (
    <AppContext.Provider value={{ state, dispatch }}>
      {children}
    </AppContext.Provider>
  );
};


Enter fullscreen mode Exit fullscreen mode

useReducer WITH contextAPI (AppContext.js)

useReducer hook takes in two things - reducer function and the state and return two things again the state and dispatch function

//reducer function which takes in dispatched action and update
//the respective state

export const reducer = (todos, action) => {
  switch (action.type) {
    case "ADD_TODO":
      return [
        ...todos,
        {
          id: Date.now(),
          todoName: action.payload.todoName,
          completed: false
        }
      ];
    default:
      return todos;
  }
};


Enter fullscreen mode Exit fullscreen mode

useReducer WITH contextAPI (reducer.js)

Here you can see the implementation of reducer, which is nothing but a pure function takes in state and action. Based on the action type, we update the state.
Note: payload (optional) is the data we pass while dispatching an action.

So, todos is our initial State and with help of reducer we are updating the state, here it is adding to the todo array with condition of action type and payload.

All good. Now there is only one piece remaining to fix this puzzle which is attaching dispatching function to form submission. To do that, if you remember we have passed state and dispatch in Provider . In order to access that, we have an another hook called useContextthat takes in a parameter of respective provider. Just by using this one line, we will get access to context values.


import React, { Fragment, useState, useContext } from "react";
import { AppContext } from "../Contexts/AppContext";
import { Todo } from "./Todo";

export const Example = () => {
  const [todoName, setTodoName] = useState("");
  // here used useContext hook to access state & dispatch from top level
  const { state, dispatch } = useContext(AppContext);

  const handleSubmit = (e) => {
    e.preventDefault();
    //dispatching action here along with payload
    dispatch({ type: "ADD_TODO", payload: { todoName: todoName } });
    setTodoName("");
  };
  return (
    <Fragment>
      <form onSubmit={handleSubmit}>
        <input
          type="text"
          name="todoName"
          value={todoName}
          onChange={(e) => setTodoName(e.target.value)}
          placeholder="Enter the todo Name"
        />
        <button type="submit">Submit</button>
        <Todo todos={state} />
      </form>
    </Fragment>
  );
};


Enter fullscreen mode Exit fullscreen mode

useReducer WITH contextAPI (Example.js)

Now that we have our dispatch we want to attach it with form submission and pass the action type and payload along with it

Finally pass the state you get from useContext to Todo Component to list down below the field.

This might lead to one question, why so much code just to update state i can do this easily with useState. Yes you can. But what if you want to update the todo, delete the todo, mark as completed then there will be whole lot of complexity if you use useStatethat is where useReducer comes handy.

useReducer VS useState
useReducer is meant for handling complex state update updates, every action for the application will be mentioned only in reducer function that makes state changes predictable. Though both does the same things, using one over another completely depends on the situation you are in.

Final Thoughts:

This is not ALL, there are other hooks which has good use cases but it is not covered here. Checkout if you want to learn more about HOOKS in this docs. Once you feel confident in using hooks, you can learn to develop your own CUSTOM HOOKS *Yes you can do that.

Please let me know if there is anything i've missed,

Happy Coding*


Top comments (0)