I’m always willing to learn, no matter how much I know. As a software engineer, my thirst for knowledge has increased a lot. I know that I have a lot of things to learn daily.
But before I could learn more, I wanted to master the fundamentals. To make myself a better developer, I wanted to understand more about how to create great product experiences.
This post is my attempt to illustrate a Proof of Concept (PoC) I built to try out some ideas.
I had some topics in mind for this project. It needed to:
When I say high-quality software, this can mean so many different things. But I wanted to focus on three parts:
User experience was the main focus of this PoC. The software and techniques would be the foundation that enabled a good experience for users.
To make the state consistent, I wanted a type system. So I chose TypeScript. This was my first time using Typescript with React. This project also allowed me to build custom hooks and test it properly.
I came across this library called tsdx that sets up all the Typescript configuration for you. It’s mainly used to build packages. Since this was a simple side project, I didn’t mind giving it a try.
After installing it, I chose the React template and I was ready to code. But before the fun part, I wanted to set up the test configuration too. I used the React Testing Library as the main library together with jest-dom to provide some awesome custom methods (I really like the toBeInTheDocument
matcher).
With all that installed, I overwrote the jest config by adding a new jest.config.js
:
module.exports = {
verbose: true,
setupFilesAfterEnv: ["./setupTests.ts"],
};
And a setupTests.ts
to import everything I needed.
import "@testing-library/jest-dom";
In this case, I just had the jest-dom
library to import. That way, I didn’t need to import this package in my test files. Now it worked out of the box.
To test this installation and configuration, I built a simple component:
export const Thing = () => <h1>I'm TK</h1>;
In my test, I wanted to render it and see if it was in the DOM.
import React from 'react';
import { render } from '@testing-library/react';
import { Thing } from '../index';
describe('Thing', () => {
it('renders the correct text in the document', () => {
const { getByText } = render(<Thing />);
expect(getByText("I'm TK")).toBeInTheDocument();
});
});
Now we are ready for the next step.
Here I wanted to have only two routes for now. The home page and the search page - even though I’ll do nothing about the home page.
For this project, I’m using the react-router-dom
library to handle all things router-related. It’s simple, easy, and fun to work with.
After installing it, I added the router components in the app.typescript
.
import { BrowserRouter as Router, Switch, Route } from 'react-router-dom';
export const App = () => (
<Router>
<Switch>
<Route path="/search">
<h1>It's the search!</h1>
</Route>
<Route path="/">
<h1>It's Home</h1>
</Route>
</Switch>
</Router>
);
Now if we enter the localhost:1234
, we see the title It's Home
. Go to the localhost:1234/search
, and we’ll see the text It's the search!
.
Before we continue to start implementing our search page, I wanted to build a simple menu to switch between home and search pages without manipulating the URL. For this project, I’m using Material UI to build the UI foundation.
For now, we are just installing the @material-ui/core
.
To build the menu, we have the button to open the menu options. In this case they’re the “home” and “search” options.
But to build a better component abstraction, I prefer to hide the content (link and label) for the menu items and make the Menu
component receive this data as a prop. This way, the menu doesn’t know about the items, it will just iterate through the items list and render them.
It looks like this:
import React, { Fragment, useState, MouseEvent } from 'react';
import { Link } from 'react-router-dom';
import Button from '@material-ui/core/Button';
import MuiMenu from '@material-ui/core/Menu';
import MuiMenuItem from '@material-ui/core/MenuItem';
import { MenuItem } from '../../types/MenuItem';
type MenuPropsType = { menuItems: MenuItem[] };
export const Menu = ({ menuItems }: MenuPropsType) => {
const [anchorEl, setAnchorEl] = useState<null | HTMLElement>(null);
const handleClick = (event: MouseEvent<HTMLButtonElement>): void => {
setAnchorEl(event.currentTarget);
};
const handleClose = (): void => {
setAnchorEl(null);
};
return (
<Fragment>
<Button aria-controls="menu" aria-haspopup="true" onClick={handleClick}>
Open Menu
</Button>
<MuiMenu
id="simple-menu"
anchorEl={anchorEl}
keepMounted
open={Boolean(anchorEl)}
onClose={handleClose}
>
{menuItems.map((item: MenuItem) => (
<Link to={item.linkTo} onClick={handleClose} key={item.key}>
<MuiMenuItem>{item.label}</MuiMenuItem>
</Link>
))}
</MuiMenu>
</Fragment>
);
};
export default Menu;
Don’t panic! I know it is a huge block of code, but it is pretty simple. the Fragment
wrap the Button
and MuiMenu
(Mui
stands for Material UI. I needed to rename the component because the component I’m building is also called menu).
It receives the menuItems
as a prop and maps through it to build the menu item wrapped by the Link
component. Link is a component from react-router to link to a given URL.
The menu behavior is also simple: we bind the handleClick
function to the button’s onClick
. That way, we can change anchorEl
when the button is triggered (or clicked if you prefer). The anchorEl
is just a component state that represents the Mui menu element to open the menu switch. So it will open the menu items to let the user chooses one of those.
Now, how do we use this component?
import { Menu } from './components/Menu';
import { MenuItem } from './types/MenuItem';
const menuItems: MenuItem[] = [
{
linkTo: '/',
label: 'Home',
key: 'link-to-home',
},
{
linkTo: '/search',
label: 'Search',
key: 'link-to-search',
},
];
<Menu menuItems={menuItems} />
The menuItems
is a list of objects. The object has the correct contract expected by the Menu
component. The type MenuItem
ensures that the contract is correct. It is just a Typescript type
:
export type MenuItem = {
linkTo: string;
label: string;
key: string;
};
#react #typescript #programming