DEV Community

Afikode Olusola Emma
Afikode Olusola Emma

Posted on

LEARN CONTEXT API BY BUILDING A MINI FINANCIAL APP

There are different ways to manage data in an application. One of the ways data is managed in a ReactJs application is using of concept of prop drilling. Prop drilling requires that data is passed through every parent component down to the child. This method can pose some form of cumbersome nature and may not be completely scalable.

In this article, we will be exploring the concept of Context API that makes it easier to manage application state. First we need to understand what context is, how it can be implemented in a React application and then we will build a prototype banking app for proper understanding. To have a proper understanding of this article, some knowledge of React and JavaScript will be required. Let’s first define Context. You should have some basic knowledge of ReactJs before going through this.

Context

Context makes available a way for data to be passed through the component tree without having to pass props through every level downwards. This means that data is managed in a global way. With Context, data can be set globally; while it is accessible directly by any of the children component without the need to pass it through every parent element. To put it simply, Context helps us to avoid nesting through different components level to access data.
Context has some APIs, namely: React.createContext, Context.Cosumer, Context.Provider etc. In this article, we will concentrate on React.createContext and Context.Provider.

Let’s create our application in the following steps. We will create a react application using create-react-app

We will use material UI component to style the various interfaces.
Our application is to show how state is managed across the deposit, balance and withdrawal screens of our financial application.
After creating the fakebank app with create-react-app, under the src folder we will create a folder called components, where all our folders including the context component will be stored. In the component folder, create a file context.js. The context folder is where the entire application state is being managed.

In the context component, some component will be imported from react; namely useState and useContext

import React from {useState, useContext}
Enter fullscreen mode Exit fullscreen mode

Next we call in the createContext method, which provides a way for data to be passed through the component tree without having to manually pass props on every level.

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

const AppContext = React.createContext();
Enter fullscreen mode Exit fullscreen mode

The next thing is to create the component via which the children props will be passed; we will also set the initial states of the balance, withdrawal and deposit screen respectively.

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

const AppContext = React.createContext();

const AppProvider = ({ children }) => {
  const initialValues = { balance: 0, deposit: 0, withdrawal: 0 };
  const [inputValue, setInputValue] = useState(initialValues);

  let walletBalance = inputValue.balance;
  let amountDeposit = inputValue.deposit;
  let amountWithdrawn = inputValue.withdrawal;
Enter fullscreen mode Exit fullscreen mode

The function above would return the children props wrapped with the AppContext.Provider. The Provider allows consuming components to subscribe to changes in context. Next, we will export our AppContext and AppProvider components. But first, we will set up a custom hook to make our code cleaner. (NOTE: You may decide not to use a custom hook)

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

const AppContext = React.createContext();

const AppProvider = ({ children }) => {
  const initialValues = { balance: 0, deposit: 0, withdrawal: 0 };
  const [inputValue, setInputValue] = useState(initialValues);

  let walletBalance = inputValue.balance;
  let amountDeposit = inputValue.deposit;
  let amountWithdrawn = inputValue.withdrawal;


 return (
    <AppContext.Provider>
      {children}
    </AppContext.Provider>
  );

}

//custom hook

const useGlobalContext = () => {
  return useContext(AppContext);
};

export { useGlobalContext, AppProvider };
Enter fullscreen mode Exit fullscreen mode

We will wrap the App component in the index.js file with the exported AppProvider.

<React.StrictMode>
    <AppProvider>
      <App />
    </AppProvider>
  </React.StrictMode>
Enter fullscreen mode Exit fullscreen mode

Next is to pass the values of the initial state of our application to the AppProvider component.

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

const AppContext = React.createContext();

const AppProvider = ({ children }) => {
  const initialValues = { balance: 0, deposit: 0, withdrawal: 0 };
  const [inputValue, setInputValue] = useState(initialValues);

  let walletBalance = inputValue.balance;
  let amountDeposit = inputValue.deposit;
  let amountWithdrawn = inputValue.withdrawal;


 return (
    <AppContext.Provider value={{
        walletBalance,
        amountDeposit,
        amountWithdrawn,
      }}>
      {children}
    </AppContext.Provider>
  );

}

//custom hook

const useGlobalContext = () => {
  return useContext(AppContext);
};

export { useGlobalContext, AppProvider };
Enter fullscreen mode Exit fullscreen mode

Next we need to create the interfaces of the deposit, withdrawal and balance screens. As said earlier, we will be using the material UI framework to build our application interfaces.

import React from "react";
import {
  Box,
  Card,
  CardContent,
  Typography,
  CardActions,
  Button,
  Divider,
} from "@mui/material";


const Deposit = () => {
  return (
    <Box
      sx={{
        marginTop: "10rem",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <Card sx={{ minWidth: 700, background: "#A52A2A", color: "white" }}>
        <CardContent>
          <Typography
            sx={{ fontSize: 20, textAlign: "center", fontWeight: "bold" }}
            gutterBottom
          >
            Deposit
          </Typography>
          <Divider color="white" />

          <Box
            sx={{ display: "flex", justifyContent: "space-around", mt: "1rem" }}
          >
            <Typography sx={{ mb: 1.5, color: "white" }} color="text.secondary">
              Balance
            </Typography>
            <Typography sx={{ mb: 1.5, color: "white" }} color="text.secondary">
              $100
            </Typography>
          </Box>

          <Typography sx={{ fontWeight: "bold" }}>Deposit Amount</Typography>
          <form>
            <input
              type="text"
              id="deposit"
              name="deposit"
              value="deposit"
            />
          </form>
        </CardContent>
        <CardActions>
          <Button
            variant="contained"
            sx={{ background: "white", color: "black" }}

          >
            Deposit
          </Button>
        </CardActions>
      </Card>
    </Box>
  );
};

export default Deposit;

Enter fullscreen mode Exit fullscreen mode

The interface above is for the deposit screen; the same process will be repeated for the other screens. The balance above is hardcoded with a sum of $100, this will be dynamically changes later on.

After creating the interfaces, in the context component, we need to create event handlers to handle the changes that handle input forms where the amount deposited and withdrawn will be typed; this will be handled in the context component.

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

const AppContext = React.createContext();

const AppProvider = ({ children }) => {
  const initialValues = { balance: 0, deposit: 0, withdrawal: 0 };
  const [inputValue, setInputValue] = useState(initialValues);

  let walletBalance = inputValue.balance;
  let amountDeposit = inputValue.deposit;
  let amountWithdrawn = inputValue.withdrawal;


  //handle input change
  const handleChange = (e) => {
    const { name, value } = e.target;
    setInputValue({ ...inputValue, [name]: value });
  };

  return (
    <AppContext.Provider
      value={{
        walletBalance,
        amountDeposit,
        amountWithdrawn,
        handleChange, 
      }}
    >
      {children}
    </AppContext.Provider>
  );
};

//custom hook

const useGlobalContext = () => {
  return useContext(AppContext);
};

export { useGlobalContext, AppProvider };

Enter fullscreen mode Exit fullscreen mode

The custom hook will be destructured in each of the screens to pass the values in the context component.

const { walletBalance, amountDeposit, handleChange } =
    useGlobalContext();
Enter fullscreen mode Exit fullscreen mode

Below shows the values passed into the deposit component.

In this article, we have examined how the context API hook can be used to maintain state in a react application, the process of putting the app state in a single components therefore making it easy to pass data across different views.

import React from "react";
import {
  Box,
  Card,
  CardContent,
  Typography,
  CardActions,
  Button,
  Divider,
} from "@mui/material";
import { useGlobalContext } from "./context";

const Deposit = () => {
  const { walletBalance, amountDeposit, handleChange } =
    useGlobalContext();
  return (
    <Box
      sx={{
        marginTop: "10rem",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <Card sx={{ minWidth: 700, background: "#A52A2A", color: "white" }}>
        <CardContent>
          <Typography
            sx={{ fontSize: 20, textAlign: "center", fontWeight: "bold" }}
            gutterBottom
          >
            Deposit
          </Typography>
          <Divider color="white" />

          <Box
            sx={{ display: "flex", justifyContent: "space-around", mt: "1rem" }}
          >
            <Typography sx={{ mb: 1.5, color: "white" }} color="text.secondary">
              Balance
            </Typography>
            <Typography sx={{ mb: 1.5, color: "white" }} color="text.secondary">
              {walletBalance}
            </Typography>
          </Box>

          <Typography sx={{ fontWeight: "bold" }}>Deposit Amount</Typography>
          <form>
            <input
              type="text"
              id="deposit"
              name="deposit"
              value={amountDeposit}
              onChange={handleChange}
            />
          </form>
        </CardContent>
        <CardActions>
          <Button
            variant="contained"
            sx={{ background: "white", color: "black" }}
          >
            Deposit
          </Button>
        </CardActions>
      </Card>
    </Box>
  );
};

export default Deposit;

Enter fullscreen mode Exit fullscreen mode

This will be replicated on the other two screens, namely: withdrawal and balance.

The next thing is to handle the logic that keeps track of the deposit, amount and balance. Remember every logic is handled in the context component.

 //handle incrementing the deposit made to update balance
  const handleAmount = (e) => {
    e.preventDefault();

    if (regex.test(amountDeposit)) {
      walletBalance += parseInt(amountDeposit);
      inputValue.balance = walletBalance;
      setInputValue({ ...inputValue, walletBalance });
      amountDeposit = "";
      inputValue.deposit = amountDeposit;
      setInputValue({ ...inputValue, amountDeposit });
    } else {
      alert("You have entered an invalid character!!!");
    }
  };
Enter fullscreen mode Exit fullscreen mode

The functions updates the balance once deposit is made.

 //handle withdrawals
  const withdrawalHandler = (e) => {
    e.preventDefault();
    if (regex.test(amountWithdrawn)) {
      walletBalance -= parseInt(amountWithdrawn);
      inputValue.balance = walletBalance;
      setInputValue({ ...inputValue, walletBalance });
      amountWithdrawn = "";
      inputValue.withdrawal = amountWithdrawn;
      setInputValue({ ...inputValue, amountWithdrawn });
    } else {
      alert("You have entered an invalid character!!!");
    }
  };
Enter fullscreen mode Exit fullscreen mode

Pass the handleAmount and withdrawalHandler amount into the provider and get it destructured in their respective components.

<AppContext.Provider
      value={{
        walletBalance,
        amountDeposit,
        amountWithdrawn,
        handleChange,
        handleAmount,
        withdrawalHandler,
      }}
    >
      {children}
    </AppContext.Provider>
Enter fullscreen mode Exit fullscreen mode

The useGlobalContext on the withdrawal and deposit should look like the below:

const { walletBalance, amountDeposit, handleChange, handleAmount } =
    useGlobalContext();
Enter fullscreen mode Exit fullscreen mode

and

const { walletBalance, amountWithdrawn, handleChange, withdrawalHandler } =
    useGlobalContext();
Enter fullscreen mode Exit fullscreen mode

The balance components is thus:

import React from "react";
import {
  Box,
  Card,
  CardContent,
  Typography,
  Divider,
} from "@mui/material";
import { useGlobalContext } from "./context";

const Balance = () => {
  const { walletBalance } = useGlobalContext();
  return (
    <Box
      sx={{
        marginTop: "10rem",
        display: "flex",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <Card sx={{ minWidth: 700, background: "#A52A2A", color: "white" }}>
        <CardContent>
          <Typography
            sx={{ fontSize: 20, textAlign: "center", fontWeight: "bold" }}
            gutterBottom
          >
            Balance
          </Typography>
          <Divider color="white" />

          <Box
            sx={{ display: "flex", justifyContent: "space-around", mt: "1rem" }}
          >
            <Typography sx={{ mb: 1.5, color: "white" }} color="text.secondary">
              Balance
            </Typography>
            <Typography sx={{ mb: 1.5, color: "white" }} color="text.secondary">
              {walletBalance}
            </Typography>
          </Box>
        </CardContent>
      </Card>
    </Box>
  );
};

export default Balance;

Enter fullscreen mode Exit fullscreen mode

In this article we have been able to learn the React Context API in a very practical manner by building a financial app that helps keeps track record of deposit, amount and withdrawal. You can dig deeper into more detail by reading the official react documentation

Oldest comments (1)

Collapse
 
temiogundeji profile image
Temiogundeji

This came at the right time welldone. What if I want to to make an API call within the context. Can you give an explanation on that?