Extract a `react-blessed` Component and Add the `useInterval` Hook
July 8, 2020
This is the 4th post in a series where we will be creating a developer dashboard in the terminal using react-blessed
and react-blessed-contrib
. For more information about the series and to take a sneak peak at what we're building, go to the 1st post in the series for more context.
- 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.
Dashboard.js
File
Large The following is the current state of the dashboard.js
file. Most of the functionality is all in one big file. The problem is that as we continue to build our dashboard this file will become hard to managed.
So, in this post we'll focus on moving out each section of our dashboard (which is just one at the moment) into their own React component.
import React from 'react';
import blessed from 'blessed';
import { render } from 'react-blessed';
import figlet from 'figlet';
const FONTS = [
/* ... bunch of fonts ... */
];
const App = () => {
const [fontIndex, setFontIndex] = React.useState(0);
React.useEffect(() => {
const timer = setTimeout(() => setFontIndex(fontIndex + 1), 1000);
return () => clearTimeout(timer);
}, [fontIndex]);
const now = new Date();
const date = now.toLocaleString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
});
const time = figlet.textSync(
now.toLocaleString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true,
}),
{
font: FONTS[fontIndex % FONTS.length],
},
);
return (
<box
top="center"
left="center"
width="65%"
height="65%"
border={{ type: 'line' }}
style={{
border: { fg: 'blue' },
}}
>
{`${date}
${time}`}
</box>
);
};
const screen = blessed.screen({
autoPadding: true,
smartCSR: true,
title: 'Developer Dashboard',
});
screen.key(['escape', 'q', 'C-c'], () => process.exit(0));
render(<App />, screen);
Dashboard.js
File
Simplify To simplify our dashboard.js
file we'll copy most of the code and move it into a new file called Today.js
. The remaining pieces are concerned with react-blessed
and rendering the top level components. Currently, we only need to render the <Today />
component, but in subsequent posts we will add others.
import React from 'react';
import blessed from 'blessed';
import { render } from 'react-blessed';
import Today from './components/Today';
const App = () => {
return <Today updateInterval={1000} />;
};
const screen = blessed.screen({
autoPadding: true,
smartCSR: true,
title: 'Developer Dashboard',
});
screen.key(['escape', 'q', 'C-c'], () => process.exit(0));
render(<App />, screen);
Today.js
Component
New The new Today.js
component is code taken from the previous version of dashboard.js
. The main difference is that we export the component to make it available for the dashboard to import.
import React from 'react';
import figlet from 'figlet';
const FONTS = [
/* ... bunch of fonts ... */
];
export default function Today({ updateInterval = 1000 }) {
const [fontIndex, setFontIndex] = React.useState(0);
React.useEffect(() => {
const timer = setTimeout(() => setFontIndex(fontIndex + 1), 1000);
return () => clearTimeout(timer);
}, [fontIndex]);
const now = new Date();
const date = now.toLocaleString('en-US', {
month: 'long',
day: 'numeric',
year: 'numeric',
});
const time = figlet.textSync(
now.toLocaleString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true,
}),
{
font: FONTS[fontIndex % FONTS.length],
},
);
return (
<box
top="center"
left="center"
width="65%"
height="65%"
border={{ type: 'line' }}
style={{
border: { fg: 'blue' },
}}
>
{`${date}
${time}`}
</box>
);
}
Replace useEffect/setTimeout with useInterval
In our next post we will start fetching data to populate or refresh our widgets, so we'll do a little preparation for that. Instead of using React.useEffect
and window.setTimeout
, let's move to an interval approach instead. We could try combining React.useEffect
and window.setInterval
, but there are some oddities that can arise when merging those ideas.
Dan Abramov goes into great detail explaining concerns of using React.useEffect
and window.setInterval
in a blog post titled Making setInterval Declarative with React Hooks. The gist is that if the code is rendered to frequently the window.setInterval
could get cleared before it has a chance to trigger. So, we'll install a custom React hook called useInterval
, written by Donavon West, that takes into consideration the issues brought up by Dan.
npm install @use-it/interval
import React from 'react';
import figlet from 'figlet';
import useInterval from '@use-it/interval';
const FONTS = [
/* ... bunch of fonts ... */
];
export default function Today({ updateInterval = 1000 }) {
const [fontIndex, setFontIndex] = React.useState(0);
useInterval(() => {
setFontIndex(fontIndex + 1);
}, updateInterval);
/* ... rest of code ... */
return ( /* ... JSX ... */ );
}
Conclusion
Since we pulled out code from dashboard.js
into Today.js
, the code is a now bit more streamlined and setup for future enhancements.
Tweet about this post and have it show up here!