Forever Functional: Structuring a React project functionally
Tags: javascript, webdev, functional, react
Published: 2022-05-21 21:06:38


by [Federico Kereki](https://blog.openreplay.com/authors/federico-kereki)

In this series of articles, we've been discussing Functional Programming (FP), and how to apply it for several tasks. But, how do we apply its concepts to, say, a React-based web application? In FP, we may talk about avoiding side effects, for example, but websites are obviously going to interact with the user and back-end APIs. Applying TDD ([Test Driven Development](https://en.wikipedia.org/wiki/Test-driven_development)) we will also find that, unless we plan things well, testing will be difficult, nearly impossible. Easy testing is a hallmark of well-designed functions, so we need a good functional overall design.

I'm usually working on front- and back-end projects all the time, so let's consider how I configured a [React 18](https://reactjs.org/blog/2022/03/29/react-v18.html) project (yes, we're working with the latest React!) so we could develop the website but allow for easy testing. (By the way, that style also made it easy to work with [Storybook](https://storybook.js.org/), a tool I highly recommend.) In this article, we will discuss the structure of our project and how we managed to curb side effects, all by applying FP techniques we've already seen in previous articles; let's get into it!

## Of side effects

We have often written about side effects. In a previous article, [Injecting for Purity](https://blog.openreplay.com/forever-functional-injecting-for-purity), we discussed what we meant by side effects:

*What are side effects? One category is when the program interacts with outside entities: the user, a database, another computer, a remote service. The second category is when code changes the global state: modifying some global variable or mutating an object or array received as an argument, for example. Note that side effects aren't some sort of "collateral damage": with our definition, something as trivial as logging to the console is considered a side effect — even if you totally planned to do so!*

As we mentioned then, a website devoid of any side effects would genuinely be of no interest at all! The site wouldn't be able to get input for the user, wouldn't do API calls, or even allow navigating from page to page, since all of these actions change the state of the site in one way or another! In the mentioned article, we described how we could inject functionality to solve or minimize some problems; let's expand that here to fully consider:

- how to work with API calls
- how to handle global state
- how to do navigation

We will start with a basic React 18 project, consider some standard ways of organizing components, pages, and the like, and then discuss the functional changes that we'll be using. 


## Structuring a basic React project

You can create React projects by hand, but [the most uncomplicated way](https://reactjs.org/docs/create-a-new-react-app.html) by far is using the `create-react-app` tool. To get the basic structure, do:

```
npx create-react-app sample-project
```

This creates a simple directory, and all your code should go in the `/src` subdirectory. There we have:
- `index.js` and `index.css`, that set some things up and render the `App` component
- `App.js` and `App.css`, the base component for the website
- a few other files that do not concern us here.

According to [common architecture patterns](https://blog.openreplay.com/react-architecture-patterns-for-your-projects), we should add several other directories to better structure our project. We'll be working with `Context`; some differences would be expected with state management tools such as [Redux](https://redux.js.org/) or [MobX](https://mobx.js.org/), for example.

```
└── /src
    ├── /assets
    ├── /components
    ├── /context
    ├── /hooks
    ├── /pages
    ├── /services
    └── /utils
```

The described directories are:

- `assets` includes icons, images, fonts, etc.
- `components` includes all components that are used in several places
- `context` (which I prefer to name `store`, but let's keep the suggested name) will have the `Context` definitions we'll be using. We'll talk more about this below.
- `hooks` is reserved for any hooks that I may define.
- `pages` has the main structure for my site; these components are usually aligned with the routing structure for the app. Each page will have a subdirectory of its own, with possibly some components used exclusively by it.
- `services` (or possibly `api`) has code to interact with external APIs
- `utils` (or `functions`) has global utility functions, used in several places.

I usually add a few directories of my own:

- `constants`, for all constants (duh!) used in the project
- `i18n`, for [internationalization](https://en.wikipedia.org/wiki/Internationalization_and_localization) (i18n) concerns. I usually work with `react-i18next` for this; read more [here](https://react.i18next.com/).
- `layouts`, to define layouts for the application, sharing, for instance, common headers or sidebars.
- `routing`, to define all routes and navigation; we'll see more about this below.
- `storybook`, created by Storybook when you set it up for React, as described [here](https://storybook.js.org/tutorials/intro-to-storybook/react/en/get-started/). Note, however, that we won't include [stories](https://storybook.js.org/docs/react/get-started/whats-a-story) here; they will reside next to the components they apply to.
- `styles`, for color definitions, global styling, font sizes, and all such constants that apply to the app's styles.

What we don't have, is a `test` directory; tests (usually [written with Jest](https://jestjs.io/docs/getting-started)) go in the same directory as the component or function to which they apply. The complete structure would be as follows.

```
└── /src
    ├── /assets
    ├── /components
    ├── /constants
    ├── /context
    ├── /hooks
    ├── /i18n
    ├── /layouts
    ├── /pages
    ├── /routing
    ├── /services
    ├── /storybook
    └── /utils
```

So far, everything seems quite normal -- so what's new? The answer lies in how we work with the store, APIs, and routing; let's get into that.


## Open Source Session Replay

_[OpenReplay](https://github.com/openreplay/openreplay) is an open-source alternative to FullStory and LogRocket. It gives you full observability by replaying everything your users do on your app and showing how your stack behaves for every issue. OpenReplay is self-hosted for full control over your data._

![replayer.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1647097041609/Qi6sMOaaB.png)

_Happy debugging for modern frontend teams - [start monitoring your web app for free](https://github.com/openreplay/openreplay)._



## Addressing functional concerns

Three things complicate development and testing, because they go against the grain of what FP would require:

- Working with the store, and directly modifying state, is a no-no. We don't want components to directly access and modify the state of the app, for the very same reasons that using and modifying global values is frowned upon. Components will need access to certain parts of the state and may have to update it, but we want to do this in a controlled fashion.

- A widespread pattern has components directly using APIs. (For instance, a dropdown to select a country could call a service to get the list of countries to show.) Testing components that do this kind of external access is complex, though Jest allows it through [manual mocks](https://jestjs.io/docs/manual-mocks). 

- The third problem for testing occurs if an action in some page causes navigation and uses routes; it can be solved using [the testing library](https://testing-library.com/docs/example-react-router/), but we'd rather go for something simpler, requiring less boilerplate.

All these behaviors are clear side effects, and as it happens, there's a common solution for all of them: the use of injection. Let's see how!

## Working with state

How to work with global state by using `Context` is the simplest solution. We'll define the state using the `useState(...)` hook, including both values and their corresponding update functions in the `Context`. (See more about this hook [here](https://reactjs.org/docs/hooks-state.html).) Any component that needs a value from the global state will be able to access it, and if it needs to update it, that will be possible through the update function. Let's see a possible state, for an imaginary app that deals with singers, studios, and songs.

```javascript
// store/index.jsx

import { createContext, useState } from "react";

const DataContext = createContext({});

const DataProvider = ({ children }) => {
  const [globalSingers, setGlobalSingers] = useState([]);
  const [globalSongs, setGlobalSongs] = useState([]);
  const [globalStudios, setGlobalStudios] = useState([]);

  const dataState = {
    globalSingers,
    globalSongs,
    globalStudios,
    setGlobalSingers,
    setGlobalSongs,
    setGlobalStudios,
  };

  return (
    <DataContext.Provider value={dataState}>{children}</DataContext.Provider>
  );
};

export { DataContext, DataProvider };
```

We export `DataContext` (the context that will be imported by any component that requires something from the global state) and `DataProvider`, which we'll need in the main app. Oh, and if you wonder why everything's called `globalSomething`, it's just something I like to do, to easily see that something is global, not local; you need not do this! 

My `index.js` would be as follows, though we'll add more to it later. As we put our definitions in `/store/index.jsx`, we may import from `/store`; we'll use this pattern for all our providers, as you'll see. 

```javascript
// /index.tsx

import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { DataProvider } from "./store";

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <DataProvider>
      ...
    </DataProvider>
  </StrictMode>
);

```

If a component needs something from the global store, it will include code as follows.

```javascript
import { useContext } from "react";
// ...

import { DataContext } from "../store";
// ...

export const someComponent = () => {
    const { globalSingers, setGlobalSingers } = useContext(DataContext);
    // ...
}
```

We have solved the first problem: components can get and set global values in a controlled way. How to test components that use context [is a well-known pattern](https://testing-library.com/docs/example-react-context/), so no problem there. We won't get into details, but the important fact is that we've simplified testing and isolated side effects for a more functional style. Let's move on to the second problem in our list, dealing with service calls.

## Working with APIs

The second usual problem that we mentioned is that components sometimes need to call APIs, and that's another side effect that we want to control. Fortunately, the same kind of solution that we discussed in the previous section still works. We will set up a global context that will include all functions that do API calls (maybe using [fetch](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API), [Axios](https://axios-http.com/), [SuperAgent](https://visionmedia.github.io/superagent/), or the like; implementation details aren't relevant) taken from our `/services` directory. For instance, our imagined music app could require services to get, add, delete, or update an artist; get the songs that an artist sings; get the list of studios, etc.

```javascript
// services/index.jsx

import { createContext } from "react";

import { getArtist } from ...
import { addArtist } from ...
import { deleteArtist } from ...
// ...

const ApiContext = createContext({});

const ApiProvider = () => {
  const apiState = {
    getArtist,
    addArtist,
    deleteArtist,
    updateArtist,
    getSongsOfArtist,
    getAllStudios,
    // ...
  };

  return <ApiContext.Provider value={apiState}>{children}</ApiContext.Provider>;
};

export { ApiContext, ApiProvider };

```

We'll modify our initial `index.js` file to refer to this new provider. 

```javascript
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { DataProvider } from "./store";
import { ApiProvider } from "./services";

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <ApiProvider>
      <DataProvider>
        ... 
      </DataProvider>
    </ApiProvider>
  </StrictMode>
);
```

We won't have to repeat details here. Again, as we defined the API context and provider in `/services/index.jsx`, we import from `/services`. An important detail: nobody should import anything else from that directory; you should go through the provider to use a service. As with global state, if a component needs to do an API call, it will just get the corresponding function from `ApiContext`; tests will also be straightforward, providing mocks in a context. Again, we've provided a functional solution and managed to isolate the needed side effects. On to the last problem, working with navigation!

## Working with Navigation

We've only got one problem left: dealing with navigation in the app... and it should be no surprise that, for a third time, we'll use the solution of injecting functions through context! In our `/routing` directory, we'd define all routes plus the corresponding navigation functions. For example, our imagined app could have a page for all singers (and one for a specific singer), another for songs, etc. Using [`react-router`](https://reactrouter.com/) and the [`useNavigate(...)`](https://reactrouter.com/docs/en/v6/api) hook, code would look as follows.

```javascript
// routing/index.jsx

import { createContext } from "react";
import { BrowserRouter, Routes, Route, useNavigate } from "react-router-dom";

import {
  AllSingers,
  OneSinger,
  Songs,
  Page404
  // ...
} from "../pages";

const ALL_SINGERS_ROUTE = "/allsingers";
const SINGER_ROUTE = "/onesinger/:id";
const SONGS_ROUTE = "/songs";
// ...

const RoutingContext = createContext({});

const RoutingProvider = ({ children }) => {
  const navigate = useNavigate();

  const routingState = {
    navigateToAllSingers: () => navigate(ALL_SINGERS_ROUTE),
    navigateToOneSinger: (id) => navigate(SINGER_ROUTE.replace(":id", id)),
    navigateToSongs: () => navigate(SONGS_ROUTE),
    // ...
  };

  return (
    <RoutingContext.Provider value={routingState}>
      {children}
    </RoutingContext.Provider>
  );
};

const RoutesProvider = () => {
  return (
    <BrowserRouter>
      <RoutingProvider>
        <Routes>
          <Route path={ALL_SINGERS_ROUTE} element={<AllSingers />} />
          <Route path={SINGER_ROUTE} element={<OneSinger />} />
          <Route path={SONGS_ROUTE} element={<Songs />} />
          ...
          <Route path="*" element={<Page404 />} />
        </Routes>
      </RoutingProvider>
    </BrowserRouter>
  );
};

export { RoutesProvider, RoutingContext };
```

There are several points of interest here:

- the `RoutesProvider` component defines and provides all routes for the application
- For the third time, the usage of `/routing/index.jsx` allows us to directly import from `/routing`
- all pages are imported by this component, to be used in routing
- in a real app, we'd also have layout components, many more pages, protected routes, and obvious basic details like logging in and out - but all of this isn't relevant for our discussion
- navigation is done through the `navigateToXXX(...)` functions only; these may include parameters: check out `navigateToOneSinger(...)` that creates a dynamic route based on a parameter.
- a component that wants to navigate to a different page will get the corresponding function from the context and use it; the rest of the app won't even "know" about routes, and all details about routes and navigation are encapsulated in our context and provider

The final version of our `index.js` file would be as follows.

```javascript
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { DataProvider } from "./store";
import { ApiProvider } from "./services";
import { RoutesProvider } from "./routing";

const root = createRoot(document.getElementById("root"));
root.render(
  <StrictMode>
    <ApiProvider>
      <DataProvider>
        <RoutesProvider />
      </DataProvider>
    </ApiProvider>
  </StrictMode>
);
```

Now our application is complete! Finally, testing will be along the same lines as in the two previous sections: just a matter of providing a mock function in a context and verifying that the component calls it correctly.

## Conclusion

We've seen how to use the injection pattern to solve three problems in a functional way: accessing and modifying global state, calling external APIs, and doing navigation in the app. In all cases, solutions were based on using `Context`. This allowed for easy testing, requiring only simple mocks. FP may be considered "more difficult", but in this case I'd argue that the functional solution is quite general and simpler than all alternatives; a good win!

[![newsletter](https://blog.openreplay.com/static/e6b18eda7a327511296a2f27692f9c70/e9957/newsletter.png)](https://newsletter.openreplay.com/)