Build a Terminal Dashboard in React with `react-blessed`
June 30, 2020
This is the first post in a series where we will be creating a developer dashboard in the terminal using react-blessed
and react-blessed-contrib
. We will discuss various ways to layout the dashboard, show how to change fonts, style with colors, and position text within each widget. Then we will shift focus to create widgets to display developer content such as an interactive time log, a pomodoro timer, displaying recent commits, showing currently running docker containers, and more. Once you know the basics you can create a widget of your own to add to the dashboard.
- Bootstrap
react-blessed
Application - Add ESLint Rules to
react-blessed
App - Change text font with
figlet
- Extract Component and Add
useInterval
hook - Fetch and display current weather with
weather-js
- Extract custom hook to simplify data fetching
- Change text color with
chalk
andgradient-string
- Position and Align Text inside a
<box>
Element - Make a Percentage Based Layout
- Layout Dashboard with
react-blessed-contrib
Grid
NOTE: You can find the code for this project in GitHub and you watch the whole Build a Terminal Dashboard with React video series on egghead.io.
The Tweet that Started It All
Creating the Project
From the terminal create a new folder called devdash
, immediately change directories, and then create a new package.json
file with npm init -y
to auto-answer yes to all the prompts.
mkdir devdash && cd $_
npm init -y
Installing the Dependencies
First we'll install react
, blessed
(a low-level terminal interface for node.js), and react-blessed
(a custom React renderer for blessed
).
Since we'll be using React and JSX, we will install some babel
dependencies and we'll use Bash Brace Expansion to shorten the syntax.
npm install react blessed react-blessed
npm install @babel/{core,register,preset-env,preset-react}
Create the JavaScript Files
Now, that our dependencies are installed, we'll quickly create an index.js
file and dashboard.js
file
touch {index,dashboard}.js
Create a Start NPM Script
First, let's open the package.json file and create a start
script of node index
. That'll let us later be able to start our app with npm start
from the command-line
{
"name": "devdash",
"main": "index.js",
"scripts": {
"start": "node index"
}
}
Babel Register the Application
Inside the index.js
, we'll need to register our application with babel and add two presets of babel/preset-env
(which is a smart preset letting us use the latest JavaScript) and babel/preset-react
(which includes plugins to support JSX).
After getting babel setup, we can require the actual dashboard.js
file that will contain our React code.
require('@babel/register')({
presets: [['@babel/preset-env'], ['@babel/preset-react']],
});
require('./dashboard');
React Blessed Hello World
In our dashboard.js
file we'll import react
, blessed
, and react-blessed
.
The underlying blessed
module defines a set of widgets that can all be rendered through react-blessed
using lowercased tag titles. So, we'll use the <box>
element and return "Hello World!" inside our App
React component. You can think of a box
kind of like a <div>
when using ReactDOM.
Then, create the blessed
screen instance, and give it some settings. There are a lot of additional settings you can look through in the blessed
docs.
In order to exit, we'll listen to a set of key events (escape
, q
, and control-c
) and if one of those combinations is pressed, we'll exit the process with a zero.
And then, we'll render the App
component to the screen. This step is similar to what you'd do with ReactDOM.render
import React from 'react';
import blessed from 'blessed';
import { render } from 'react-blessed';
const App = () => {
return <box>Hello World!</box>;
};
const screen = blessed.screen({
autoPadding: true,
smartCSR: true,
title: 'Developer Dashboard',
});
screen.key(['escape', 'q', 'C-c'], () => process.exit(0));
const component = render(<App />, screen);
Now, we can run our app from the terminal by typing npm start
and we should see "Hello World!". It's not very exciting, but it's a start.
Adding Style to Box
To add a bit of style to our app, we'll go back to our dashboard.js
file and add some props to the <box>
element. Adjust the top
to the "center" of the screen, make the left
also be "center", give it a width
of "50%"", and provide height
of "50%"" as well.
Then we'll give it a border
and change the type
to "line", and then much like with ReactDOM, you can add style using the style
prop for finer control. We'll change the border's foreground color to blue.
const App = () => {
return (
<box
top="center"
left="center"
width="50%"
height="50%"
border={{ type: 'line' }}
style={{
border: { fg: 'blue' },
}}
>
Hello World!
</box>
);
};
Now, if we run our app again with npm start
the app will be a centered box with a blue border.
Partially Dynamic Component
In order to make things a bit more dynamic, we'll create a dateTime
variable and set it to the current date using Date.prototype.toLocaleSting
and provide some basic formatting for the en-US
locale.
Now, instead of "Hello World!" we'll replace that with a string template of Today is ${dateTime}
.
const App = () => {
let dateTime = new Date().toLocaleString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true,
});
return (
<box
top="center"
left="center"
width="50%"
height="50%"
border={{ type: 'line' }}
style={{
border: { fg: 'blue' },
}}
>
{`Today: ${dateTime}`}
</box>
);
};
If we run our app again, we'll still see a centered box, but now with today's formatted date.
Using React Hooks to Live Update Terminal
Before we wrap up, let's leverage React hooks inside our component to auto-update its contents.
To do this, we'll add a counter. Down in our string template, we'll add Counter is ${count}
and then define count
and setCount
from React.useState
.
Then we'll create a new timer
variable and assign it to React.useRef
. We'll use this to keep track of the current timer.
Now, we'll use React.useEffect
and assign the current timer
to setTimeout
where we'll increment count
by one after one second has occured. Then, to clean up after ourselves we'll return a function that clears the timeout if there happens to be a timer queued up. Make sure to add count
to the dependency array so the useEffect
will update when the value of count
changes.
const App = () => {
const [count, setCount] = useState(0);
const timer = useRef(null);
useEffect(() => {
timer.current = setTimeout(() => setCount(count + 1), 1000);
return () => clearTimeout(timer.current);
}, [count]);
let dateTime = new Date().toLocaleString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
hour12: true,
});
return (
<box
top="center"
left="center"
width="50%"
height="50%"
border={{ type: 'line' }}
style={{
border: { fg: 'blue' },
}}
>
{`Today: ${dateTime}
Count: ${count}`}
</box>
);
};
Now, if we go back to our terminal and start our app we'll see the current date and time along with a counter that is updating after every second.
Conclusion
This post was to help you get started with react-blessed
to create a terminal app using React. This is the first post in a pipeline of many for this series, so stay tuned to this blog, my twitter, and/or the egghead.io playlist for this series.
Tweet about this post and have it show up here!