mihail gaberov

Qui docet, discit.

Build a real-time Order Book application with React and WebSockets

Last updated: Mar 25, 2024

Orderbook view
Orderbook view

What is Order Book?

An Order Book is an application that usually displays some kind of information related to buying and selling stuff.

💡The most common use case is showing data for various assets, such as stocks, bonds, currencies and even cryptocurrencies.

What Are We Building?

In this tutorial, we will see how to build an Order Book web application, that is going to be used to display real-time cryptocurrencies info.

We will use React with Typescript for creating the UI, Redux for managing the application state and styled-components for applying the styling. And last, but not least, WebSockets for fetching the data feeds.

TL;DR

If you want to skip the reading, here 💁 is the GitHub repository with a detailed README 🙌, and here you can see the live demo.

Why Would I Need an Order Book?

In practice Order Books are used by traders to watch the fluctuations of the bidding price and the asking price of certain products - currencies, stocks, etc. This is happening real time, hence the changes could be very rapid. Here is where WebSockets will come in handy, as you will see later.

In the past people did similar thing on paper, but the ‘real-time' part was impossible, of course.

A regular Order Book usually has two sides - buying (or bidding), shown in green on the left side and selling (or asking), red, on the right.

Classic Orderbook
Classic Orderbook

The Plan

Our Order Book app will consist of five parts - orderbook main view, grouping select box, Toggle Feed button, Kill Feed button and Status Message. The app design will be as shown below (note that the Status Message component, that you will see in the my implementation, is missing on these screenshots):

Desktop layout
Desktop layout
Mobile layout
Mobile layout

Application features

I. Orderbook 1. The orderbook has two sides: the buy side and the sell side. 2. Both sides contain information about the amount of orders opened at each price level. 3. Each level displays:

- Price: this is what defines the level. As orders must be placed at a price that is a multiple of the selected markets tick size (0.5) each level will be an increment of 0.5 (as long as there is an order open at that level).

- Size: the total quantity of contracts derived from open orders that have been placed at this level.

- Total: the summed amount of contracts derived from open orders that reside in the book at this level and above. To calculate the total of a given level we take the size of the current level and sum the sizes leading to this price level in the order book. The total is also used to calculate the depth visualizer (colored bars behind the levels), the depth of each level is calculated by taking that level's total as a percentage of the highest total in the book.

II. Grouping Select Box 1. By default the orders are grouped by the select markets ticket size (0.5). 2. Possible toggling of the grouping: between 0.5, 1, 2.5 for XBTUSD market and 0.05, 0.1 and 0.25 for ETHUSD market. 3. To group levels we combine the levels rounded down to the nearest group size e.g. if we change our grouping from 0.5 to 1 then we would combine the data from prices 1000 and 1000.5 and display it under a single level in the orderbook with the price 1000.

III. Toggle Feed Button 1. Toggles the selected market between PI_XBTUSD and PI_ETHUSD - these are the two markets we will support -> Bitcoin/USD and Ethereum/USD. 2. Supports dynamic grouping logic - handles groupings for XBT (0.5, 1, 2.5) and groupings for ETH (0.05, 0.1, 0.25).

IV. Kill Feed Button 1. Clicking this button stops the feed. 2. Clicking this button second time renews the feed.

V. Status Message 1. Shows currently selected market. 2. Shows a message saying the feed is killed.

Tech stack

Here is a list of the main technologies we will be using:

  • React with TypeScript (`yarn create react-app my-app --template typescript`) — a UI library we will use for building our application’s user interfaces.
  • Redux — a state management library we will use for managing our application’s state.
  • WebSockets — The WebSocket object provides the API for creating and managing a WebSocket connection to a server, as well as for sending and receiving data on the connection. We will use it to implement the logic for consuming the live feeds as well as to be able to stop and renew.
  • styled-components — a CSS-in-JS library that lets you define the CSS styles of your components using ES6 template literals. We will use it to add styles to our app and make the look and feel beautiful. It utilizes tagged template literals to style your components and removes the mapping between components and styles. This means that when you’re defining your styles, you’re actually creating a normal React component that has your styles attached to it.
  • react-testing-library — The React Testing Library is a very light-weight solution for testing React components. We will use it for testing the UI components of our app.
  • Jest - a JavaScript Testing Framework that became the de facto standard when we talk about testing React applications. We will use it to write some unit tests that will cover the reducer functions we have in our app.
  • The Doing

    From this point onward I will try to guide you through the process I followed when building this.

    💡I must say, that what I am showing you here, is just a way of creating such an app, but not the way in any case. Probably folks with more experience in the crypto, would do it much better.

    Project Structure

    The project structure is pretty straight forward. We are using React and styled-components, which makes this way of structuring very convenient. Let's see first what it looks like and then I will explain the what and the why.

    Project structure
    Project structure

    As you can see on the image above, I have organized most of the components in folders. Each folder contains an index.tsx file, a styles.tsx and a .test.tsx files.

    index.tsx - contains the code responsible for the component logic.

    styles.tsx - contains the code responsible for styling the component, here is where styled-components shines.

    .text.tsx - these contain the component unit tests.

    Let me give you a short summary of what is the idea behind each of the components in the components folder. Starting top to bottom:

    Button - renders a button with given background color and title. It's used for the two buttons in the footer - Toggle Feed and Kill Feed / Renew Feed.

    DepthVisualizer - this is the component responsible for drawing the red and the green backgrounds you are seeing behind the numbers. It does this by rendering a row (an html div element) with given width, position being left (Bids) or right (Asks).

    Footer - not much to say here, contains the two buttons used in the app.

    GroupingSelectBox - renders the select box we use to change the grouping value, using setGrouping reducer to amend the application state when grouping is being changed.

    Header - the header part, renders the title of the application as well as GroupingSelectBox component.

    Loader - renders loading animation implemented by leveraging SVG.

    OrderBook - contains the core logic of the app. Separated components are located in sub folders, also the Redux state management logic is here.

    Spread - renders the spread value, displayed in the middle of the header (in desktop view). The component itself contains short methods for calculating the amount itself and the percentage value.

    StatusMessage - a small component used to display status messages. It basically shows which market is currently being displayed and whether the feed is killed.

    Rendering Performance

    Here is a good moment to talk about rendering performance and inline styling a bit.

    Rendering is the process of React asking your components to describe what they want their section of the UI to look like based on the current combination of props and state. This process is triggered by a change of the state in your component. This change could be caused by some of the props being changed or by some internal logic of the component.

    The point here is that when re-rendering happens unnecessary, it reduces the performance of our app. This is exactly what happened to me when I introduced the initial implementation of DepthVisualizer component. It was using styled-components, i.e. JavaScript, for the drawing part. In order to solve this, I have changed the component to use inline styles, i.e. pure CSS, instead of  CSS in JS approach. In other words, my bottleneck was using JavaScript animations, which is famous reason for reduced performance.

    Here is how it looks like:

    const DepthVisualizer: FunctionComponent<DepthVisualizerProps> = ({windowWidth, depth, orderType }) => {
      return <div style={{
        backgroundColor: `${orderType === OrderType.BIDS ? DepthVisualizerColors.BIDS : DepthVisualizerColors.ASKS}`,
        height: "1.250em",
        width: `${depth}%`,
        position: "relative",
        top: 21,
        left: `${orderType === OrderType.BIDS && windowWidth > MOBILE_WIDTH ? `${100 - depth}%` : 0}`,
        marginTop: -24,
        zIndex: 1,
      }} />;
    };
    
    export default DepthVisualizer;

    Inline styling is when you write your CSS along with your markup, as value for the style attribute. This is something that is NOT considered as a good practice, but as you can see here, there are cases when it's necessary to use it.

    💡Usually you would extract your CSS code in a separate file.

    Footer - a simple dummy component used to render the two buttons in the footer  of the app.

    Dummy components, also known as stateless or representational, are components that don't hold state and usually are used just to visualize data in some way. This data is being passed via the props. For example the isFeedKilled flag in the component above.

    If such component needs to execute some kind of interaction, it usually does this by accepting (again via the props, e.g. toggleFeedCallback) callback functions that can be executed when that interaction happens. For example clicking a button.

    On the opposite side we could have smart or state-full components. They are the ones that are connected to the app state and can manipulate it directly. Usually they are the ones that read the data from the state and pass it to the stateless components via their props.

    GroupingSelectBox - this one contains the Select element you can use to switch between the groupings.

    Header - the header part of the app, it takes care of setting properly the layout consisted of the title 'Order Book' on the left and the select box on the right.

    Loader - this is used as an indicator for when the data has not yet been loaded, it leverages a SVG animation I have found online.

    OrderBook - this is where the real thing is happening. This guy consists of a few smaller components:

  • TableContainer - used for styling the views for both, Odds and Bets, sides.
  • TitleRow - this is the component responsible for displaying the titles of the columns - prize, size and total respectively.
  • React - UI - styled components

    When we talk about component-based structure, such as the one React provides us, styled-components library could be one of the first choices one might make when styling is needed. Like Josh Comeau says in his detailed article:

    💡It's a wonderful tool. In many ways, it's changed how I think about CSS architecture, and has helped me keep my codebase clean and modular, just like React!

    As the name of the lib hints, we could easily style our components by using CSS-in-JS pattern. Here is an example of how I used it to write the styles for my Button component.

    import styled from "styled-components";
    
    interface ContainerProps {
      backgroundColor: string;
    }
    
    export const Container = styled.button<ContainerProps>`
      padding: .3em .7em;
      margin: 1em;
      border-radius: 4px;
      border: none;
      color: white;
      background: ${props => props.backgroundColor};
      font-family: "Calibri", sans-serif;
      font-size: 1.2em;
      
      &:hover {
        cursor: pointer;
        opacity: .8;
      }
    `

    Notice how I am using an interface in my styles file, also the background property being passed as an argument via props. This is part of the CSS-in-JS story. The possibility to use CSS code in JavaScript or (as someone might say) vice versa comes very handy. For example when we need a component to look differently depending something, we can pass through its props a parameter to define this.

    As every style is actually a component, this way of writing styles feels a lot like writing React components. I mean, in the end, everything is components, right?

    Responsiveness and Page Visibility Detection

    While working on this app I read in several places, that for applications which support rapid updates, is a good practice to implement some kind of mechanism for pausing the whole thing when it is not being used by the user. For example when the user minimizes the browser window or just opens another tab.

    Since our Orderbook is consuming a lot of new chunks of data every second via WSS, I decided to implement such mechanism as well.

    What this does is:

  • show a loader when the data is not there yet
  • change the meta title to signify the app is in paused mode
  • unpause the work once the app window is on focus
  • Active mode
    Active mode
    Paused mode
    Paused mode

    You may see the whole implementation here.

    The essential part is in the useEffect hook, which is triggered only once when the application renders for first time. In there we take advantage of the Page Visibility API by attaching the necessary listeners. And then, in the handlers, we simply execute the logic we want.

    Window Size Detection

    Almost in every app that has some level of responsiveness there is a need of logic for detecting the changes in the window size and taking some actions accordingly. In other words, you need to know when your app is being viewed in certain screen size, so you could arrange your components and adjust your styles so that everything looks nice and in place.

    This is especially valid for mobile friendly applications, where the responsiveness is essential.

    Our implementation of the window size change detection is based on the innerWidtgh property of the browser window object and onresize event that is being triggered when it gets resized. I am attaching a listener for this event in a useEffect hook in App.tsx file. And then, every time when the window size changes, I am setting the new width to a state variable via setWindowWidth hook.

    const [windowWidth, setWindowWidth] = useState(0);
    ...
    ...
    
    // Window width detection
    useEffect(() => {
      window.onresize = () => {
        setWindowWidth(window.innerWidth);
      }
      setWindowWidth(() => window.innerWidth);
    }, []);

    Then propagate this variable down through all interested components and use it accordingly. For example here is how I use in OrderBook/index.tsx in order to know when and where to render the TitleRow component.

    {windowWidth > MOBILE_WIDTH && <TitleRow windowWidth={windowWidth} reversedFieldsOrder={false} />}

    TitleRow component - desktop view
    TitleRow component - desktop view
    TitleRow component - mobile view
    TitleRow component - mobile view

    Note that it appears on different position depending on that whether you are seeing the app on desktop or mobile.

    You may take a look at the component itself and see similar approach of using the window width there.

    State Management with Redux

    As you probably guessed already, I used Redux for managing the state of the app.

    The main logic behind that is concentrated in the orderbookSlice reducer. In the following few lines I will walk you through it and see how and why I built it that way.

    First we define the interface and the initial state of our orderbook data. The initial state contains the default values we need to have in place when starting the app.

    export interface OrderbookState {
      market: string;
      rawBids: number[][];
      bids: number[][];
      maxTotalBids: number;
      rawAsks: number[][];
      asks: number[][];
      maxTotalAsks: number;
      groupingSize: number;
    }
    
    const initialState: OrderbookState = {
      market: 'PI_XBTUSD', // PI_ETHUSD
      rawBids: [],
      bids: [],
      maxTotalBids: 0,
      rawAsks: [],
      asks: [],
      maxTotalAsks: 0,
      groupingSize: 0.5
    };

    Then there are few short, self-explanatory methods helping to manipulate the levels data:

    const removePriceLevel = (price: number, levels: number[][]): number[][] => levels.filter(level => level[0] !== price);
    
    const updatePriceLevel = (updatedLevel: number[], levels: number[][]): number[][] => {
      return levels.map(level => {
        if (level[0] === updatedLevel[0]) {
          level = updatedLevel;
        }
        return level;
      });
    };
    
    const levelExists = (deltaLevelPrice: number, currentLevels: number[][]): boolean => currentLevels.some(level => level[0] === deltaLevelPrice);
    
    const addPriceLevel = (deltaLevel: number[], levels: number[][]): number[][] => {
      return [ ...levels, deltaLevel ];
    };

    Then the real magic is happening. If the size returned by a delta is 0 then that price level should be removed from the orderbook, otherwise you can safely overwrite the state of that price level with new data returned by that delta.

    /** The orders returned by the feed are in the format
     of [price, size][].
     * @param currentLevels Existing price levels - `bids` or `asks`
     * @param orders Update of a price level
     */
    const applyDeltas = (currentLevels: number[][], orders: number[][]): number[][] => {
      let updatedLevels: number[][] = currentLevels;
    
      orders.forEach((deltaLevel) => {
        const deltaLevelPrice = deltaLevel[0];
        const deltaLevelSize = deltaLevel[1];
    
        // If new size is zero - delete the price level
        if (deltaLevelSize === 0 && updatedLevels.length > ORDERBOOK_LEVELS) {
          updatedLevels = removePriceLevel(deltaLevelPrice, updatedLevels);
        } else {
          // If the price level exists and the size is not zero, update it
          if (levelExists(deltaLevelPrice, currentLevels)) {
            updatedLevels = updatePriceLevel(deltaLevel, updatedLevels);
          } else {
            // If the price level doesn't exist in the orderbook and there are less than 25 levels, add it
            if (updatedLevels.length < ORDERBOOK_LEVELS) {
              updatedLevels = addPriceLevel(deltaLevel, updatedLevels);
            }
          }
        }
      });
    
      return updatedLevels;
    }

    What follows after these, are few helper methods. Let me say few words for each of them below.

  • addTotalSums - with the help of this method we iterate through the orders data, bids or asks, and calculate for each of them the total sum. Total sum value is then used for making the background visualizations.
  • addDepths - we use this method to calculate the so called depth for each order. These values would be used later by the depth meter component to display the red and green rows in the background.
  • getMaxTotalSum - this one returns the max value of all total sums.
  • Everything below is what we use for creating the application state. As per the Redux Toolkit documentation, it’s using createSliceAPI to create the slice.

    Redux state
    Redux state
    Creating a slice requires a string name to identify the slice, an initial state value, and one or more reducer functions to define how the state can be updated. Once a slice is created, we can export the generated Redux action creators and the reducer function for the whole slice.

    The last few lines consist of the exports in question - action creators, state slices selectors and the main reducer.

    export const { addBids, addAsks, addExistingState, setGrouping, clearOrdersState } = orderbookSlice.actions;
    export const selectBids = (state: RootState): number[][] => state.orderbook.bids;
    export const selectAsks = (state: RootState): number[][] => state.orderbook.asks;
    export const selectGrouping = (state: RootState): number => state.orderbook.groupingSize;
    export const selectMarket = (state: RootState): string => state.orderbook.market;
    export default orderbookSlice.reducer;

    With all that, our state manipulation logic is complete 🎉

    Now it’s time to take a look at the protocol we used in our app to take advantage of all these rapid changes in the data we consume.

    WSS

    OK, I am sure you got it till now - we use Web Socket communication protocol for fetching data into our application. We also use its features, as you will see in a moment, to accomplish other things. Such as toggling the feeds and subscribe/unsubscribe from the data channel.

    Here is how I used it.

    Instead of trying to rely on manual implementation, I used react-use-websocket package. It gives you all you need when you want to leverage WSS in a React app. If you want to go into details about this, you may take a look at their documentation.

    A Few Words About My Implementation

    What we need fist is the endpoint URL where the data feeds are coming from. I am sure, there are multiple options out there, when we talk about crypto currencies. In our app I used the one provided by www.cryptofacilities.com/.

    const WSS_FEED_URL: string = 'wss://www.cryptofacilities.com/ws/v1';

    Then the only thing we need to do, to start consuming the data, is to put the useWebSocket hook to work. As you may guessed already, this hook is provided by the package mentioned above.

    import useWebSocket from 'react-use-websocket';
    
    ...
    ...
    ...
    
    const { sendJsonMessage, getWebSocket } = useWebSocket(WSS_FEED_URL, {
        onOpen: () => console.log('WebSocket connection opened.'),
        onClose: () => console.log('WebSocket connection closed.'),
        shouldReconnect: (closeEvent) => true,
        onMessage: (event: WebSocketEventMap['message']) =>  processMessages(event)
      });

    We pass the the endpoint as first argument and few callback functions after that. These help us to perform certain actions when one of the following happens:

  • onOpen - what to do when WebSocket connection is established.
  • onClose - what to do when WebSocket connection is terminated.
  • shouldReconnect - this is just a flag, saying if we want automatic reconnect when the connection drops for some reason.
  • onMessage - this is the main event that brings us the chunks with the data (I call processMessage method every time when that happens, which means every time when a new chunk of data is received, we process it and display it respectively.
  • In order to decide whether we are adding data to the current state or we should initialize it, we check for a property called numLevels . This is something that comes from the API, in the very first time we establish the WebSocket connection.

    Initial payload
    Initial payload

    The rest of the code you see in this file is mostly for preparing and rendering the results on the screen.

    The most interesting part would be the method buildPriceLevels that is used for both halves - bids and asks. It sorts the data, makes the necessary calculations and pass it to the relevant components for visualizing it. That being DepthVisualizer and PriceLevelRow I mentioned earlier in this article.

    Grouping

    The grouping is important part of how the order book works as it defines by what ticket size are grouped the orders.

    In our application I have implemented a toggling functionality as per market, that allow grouping as it follows:

  • Between 0.5, 1, 2.5 for XBTUSD market.
  • XBTUSD market grouping
    XBTUSD market grouping

  • Between 0.05, 0.1 and 0.25 for ETHUSD market.
  • ETHUSD market grouping
    ETHUSD market grouping

    There is a short gist I created when trying to figure out how to implement the grouping logic. You may find it here.

    Also, aside that gits, while developing this, I have performed more than a few experiments, out of the project itself. And just because these just local files on my computer, I will publish them here, for those of you who are even more curious.

    It’s a small side npm project that has only one dependency. Here is the package.json file.

    {
      "name": "grouping",
      "version": "1.0.0",
      "main": "index.js",
      "license": "MIT",
      "dependencies": {
        "lodash.groupby": "^4.6.0"
      }
    }

    And here is the code itself.

    const bids = [
        [
            50163,
            110
        ],
        [
            50162,
            13140
        ],
        [
            50158,
            3763
        ],
        [
            50156,
            1570
        ],
        [
            50155,
            21997
        ],
        [
            50152.5,
            450
        ],
        [
            50151,
            4669
        ],
        [
            50150.5,
            10329
        ],
        [
            50150,
            2500
        ],
        [
            50149.5,
            450
        ],
        [
            50149,
            4022
        ],
        [
            50148,
            20000
        ],
        [
            50147,
            5166
        ],
        [
            50146.5,
            5274
        ],
        [
            50145,
            174609
        ],
        [
            50143,
            20000
        ],
        [
            50141,
            28000
        ],
        [
            50140.5,
            5000
        ],
        [
            50138,
            6000
        ],
        [
            50132.5,
            4529
        ],
        [
            50132,
            4755
        ],
        [
            50131,
            12483
        ],
        [
            50128.5,
            61115
        ],
        [
            50128,
            23064
        ],
        [
            50125.5,
            181363
        ]
    ]
    
    /* function roundDownNearest(num, acc) {
        if (acc < 0) {
            return Math.floor(num * acc) / acc;
        } else {
            return Math.floor(num / acc) * acc;
        }
    } */
    
    /* function groupByTicketSize(ticketSize, levels) {
        const result = levels.map((element, idx) => {
            const nextLevel = levels[idx + 1];
    
            if (nextLevel) {
                const currentPrice = element[0];
                const currentSize = element[1];
                const nextPrice = nextLevel[0];
                const nextSize = nextLevel[1];
                console.log("current level: ", element)
                console.log("next level: ", nextLevel)
    
                element[0] = roundDownNearest(currentPrice, ticketSize);
    
                if (currentPrice - nextPrice < ticketSize) {
                    element[1] = currentSize + nextSize;
                }
                console.log("==================================> Result: ", element)
    
                return element;
            }
    
        }).filter(Boolean); 
       
    
        console.log("============================================================");
        console.log(result)
    } */
    
    const test = [
        [1004.5, 1],
        [1001.5, 1],
        [1001,   1],
        [1000.5, 1],
        [1000,   1],
        [999.5,  1],
        [999,    1],
        [990,    1],
        [988,    1]
    ]
    
    function groupByTicketSize(ticketSize, levels) {
        const result = [];
    
        for (let i = 0; i < levels.length; i++) {
            console.log(levels[i])
            const prevLevel = levels[i-1]
            const level1 = levels[i]
            const level2 = levels[i+1]
    
            if (prevLevel && level1 && level1[0] - ticketSize === prevLevel) return
    
            if (level2 && level1[0] - level2[0] < ticketSize) {
                const newLevel = [level2[0], level1[1] + level2[1]];
                console.log("newLevel", newLevel)
                result.push(newLevel);
            } else {
                result.push(level1)
            }
        }
    
        console.log("============================================================");
        console.log(result)
    }
    
    // groupByTicketSize(1, bids);
    groupByTicketSize(1, test);

    Unit Tests

    For performing unit testing I used react-testing-library.

    The main idea behind it that the developer should write tests only for what the user will see and interact with. There is no much point of testing implementation details.

    💡Imagine, just to give you an example, you have implemented a list component that is just displaying lines of text data. Say something like a todo list. Then imagine that this data is coming from an API call in the shape of array. A data structure that you could easily iterate through via various methods - some sort of a loop cycle, such as for() or while(). Or use, another more functional approach, say .map() method. Now ask your self - for the end user, the one that will just see the listed text data, does your implementation matter? As long as everything works as expected and in a good, performant way, the answer is ‘no, it does not’.

    This is what your tests should reflect on.

    In the context of our Order Book application, each test file is located in the same directory as the implementation file. Most of the tests are short and self explanatory, due to the fact that these are testing mostly rendering logic and only the happy path.

    For example let’s take a look at the button component tests below.

    import React from 'react';
    import { render, screen } from '@testing-library/react';
    import Button from './index';
    
    test('renders button with title', () => {
      render(<Button backgroundColor={'red'} callback={jest.fn} title={'Toggle'} />);
      const btnElement = screen.getByText(/Toggle/i);
      expect(btnElement).toBeInTheDocument();
    });

    It just verifies that the component is rendered properly and it displays what we expect the user to see. Which is the title Toggle in this case.

    For testing the reducers I have used Jest, as this is the only not visual part that we cover. These tests are also pretty simple and self-explanatory. I use them for testing whether the initial application state is in place and that adding price levels to that state works correctly.

    Deploying to Vercel

    Finally 🎉

    After finishing the development and testing our application, let’s put it live.

    I used Vercel platform for this purpose. They offer pretty rich and easy to use interface as well as integrations for all famous source control platforms out there. Including, of course, Github. Where our application repo lives.

    Assuming you have a GitHub account, what you need to do if you want to deploy it on your own is to login with it here.

    Vercel login screen
    Vercel login screen

    Click on +New Project button on the top right corner. Then import your Git repository using the provided options in the screen that opens. Here is how mine looks like.

    Vercel Import Git Repository screen
    Vercel Import Git Repository screen

    After importing the project, you will be able to do the actual deploy. When finished, Vercel will generate URLs for you to access your newly deployed app.

    Vercel production deployment screen
    Vercel production deployment screen

    And I think you will receive an email letting you know if your deployment was successful. That email also contains these URLs.

    Vercel successful deployment email
    Vercel successful deployment email

    Congratulations 👏🏻

    You now have your own Order Book application up and running online.

    Build Badge

    This is not order book related, but anyway. I decided to share it with you here. It’s about those small details that make the big picture somehow more completed and attractive.

    Maybe some of you have wondered how can you get one of these so called badges?

    Here is the answer - https://shields.io/.

    You go to the Other section and find GitHub Deployments option.

    Then click on it and follow the instructions.

    There is one more thing you need to do in order to have this fully functioning. You go to your GitHub repository → Actions tab and create new workflow file. You may just go ahead and copy the content of mine from here. Name it main.yml.

    What this will do is running the jobs defined in that file. In our case this is just the build job which is basically spinning a new build and running the tests.

    After completing this, you just need add the following lines to your README file.

    <!-- prettier-ignore-start -->
    [![Tests](https://github.com/mihailgaberov/orderbook/actions/workflows/main.yml/badge.svg)](https://github.com/mihailgaberov/orderbook/actions/workflows/main.yml)
    [![Build Status][build-badge]][build]
    
    [build-badge]: https://img.shields.io/github/deployments/mihailgaberov/orderbook/production?label=vercel&logoColor=vercel
    [build]: https://github.com/mihailgaberov/orderbook/deployments
    <!-- prettier-ignore-end -->

    💡Don’t forget to put your own details in the URLs, i.e. your GitHub username and the name of your repository.

    After pushing these changes you should see the badges displayed on your README 🥳.

    GitHub badges
    GitHub badges

    Wrapping Up

    If you are reading this from the beginning, I will name you a champion 🍾

    It has been a long trip, but hopefully interesting and fun to walk to!

    Now it’s time to summarize what we have done here and try to extract some useful insights which will help us in our future development challenges.

    I will layout below my opinion of what was the most challenging in building this application. And I will be even more eager to find out what is yours.

    Rendering Performance

    This is really bit me in the beginning, when I was building the UI and was trying to implement the drawing of the price level rows. I mentioned earlier how I have managed to solve it and I think this is going to be something I will remember for sure.

    Grouping Functionality

    Implementing this was also kind of challenging due to the fact that there were several factors I had to take into account. Being the market we are in and the range I had to do the calculations in. It took me a while to polish it (remember the side mini project and the gist I shared in the previous sections) and I still think it could be improved even more. Try switching between the markets and the grouping values multiple times and observe the result.

    Space For Improvement

    One thing already mentioned is for sure the grouping. Which should also improve the visualizing of the red and green parts - they (almost) always should form a not ideal triangle.

    If we try to look at the bigger picture, this Order Book application can be a part of a dashboard screen filled with a other widgets as well, and they all can interact between them. For example, changing the grouping of the Order Book to reflect on changing the views in the other widgets as well. Say showing a market chart like this one below.

    I am not even mentioning adding new markets as an improvement, as it’s kinda obvious. But this should be taken into account when building the functionality for the current markets, as to do it in a way that will be easily extendable. So that adding a new market to the order book be a trivial and quick task to do.

    I think that would be all from me.

    Thanks for reading! 🙏

    References

    Here are few links you might find useful to read:

    The styled-components Happy Path
    A (Mostly) Complete Guide to React Rendering Behaviour
    You earned a free joke. Rate this article to get it.
    ← Go home