Working with React and Typescript to create a Newsletter Subscription component
I recently made a contribution to a Mozilla application called voice-web. It’s an interface designed to collection speech donations for the Common Voice project; an initiative to make voice recognition technology non-proprietary and open source.
Motivation for Contributing
I decided to contribute to this site’s front end because I’ve been meaning to get more familiar with both React and Typescript. I also noticed that the site design was overseen by a professional designer and I was excited to get more experience working with/from with comps vs. something like bootstrap.
What I made
By following the specs and requirements shown in the initial issue, I eventually ended up with the following:
This component was fairly simple, but to implement it I needed to adopt the methods used by the project as a whole. Because of this there were a quite a few learning curves for me to get over. The purpose of this post will be to go over some of what I learned in overcoming them, linking to resources that readers might find useful along the way. If you plan on working on Typescript + React some of what’s covered here might help you solve certain issues in the future.
Function vs. Class Components
I never worked with Typescript + React before this project, so when I saw the following I got confused and encountered my first hurdle:
Combining React with Typescript means that fields will become statically typed. So when you’re passing state or properties into a functional component the data type of parameters will be stated before you reach the function implementation. A functional component without typescript might be easier to implement at first, but by not having type checking you can increase the number of bugs in your application as a whole. Type checking also has the advantage of making the code more readable, as you’ll immediately understand the type of parameters/return values in a function, it removes some guesswork.
Class components are a bit more complicated. With Typescript your React components state and property fields will be defined in interfaces, which can in turn extend other interfaces. Ogundipe Samuel from LogRocket explained them well in this post, stating:
One of TypeScript’s core principles is that type-checking focuses on the shape that values have. This is sometimes called “duck typing” or “structural subtyping”.
In TypeScript, interfaces fill the role of naming these types and are a powerful way of defining contracts within your code and contracts with code outside of your project.
In React it’s best to keep as many components as possible stateless. However I knew that for my Email Subscription component I’d want to keep track of its state; at a minimum I’d need to keep track of whether the component’s email input was valid or not so I could display certain styles or errors. So, needless to say I ended up creating a class component.
But a lot more needed to be added at this point. Since my component was essentially a form it needed to become a controlled component.
In React, controlled components are recommended for implementing forms. Controlled components handle form data through the component’s state, while uncontrolled components handle form data in the DOM itself.
For example, for my html email input, I had a property called
onChange that would trigger a
handleChange()function. This function would then set the components state using
Adhering to this practice ensures that the component’s state is the ‘single source of truth’. Also, with an Uncontrolled component you may run into a issue with
refs. As Manjunath in this article states:
React recommends using controlled components over refs to implement forms. Refs offer a backdoor to the DOM, which might tempt you to use it to do things the jQuery way. Controlled components, on the other hand, are more straightforward — the form data is handled by a React component. However, if you want to integrate React with a non-React project, or create a quick and easy form for some reason, you can use a
Error Assigning to State
When I first built my component and was attempting to assign to state, I would get an annoying error that didn’t make any real sense to me at the time:
Cannot assign to ‘state’ because it is a constant or a read-only property
Looking at my code, nothing about it screamed ‘read-only’ to me, so I ended up relying on google to help me solve this one. Initially my search didn’t help me much, and at first lead me to believe that I ran into a possible regression error.
In an effort to improve type safety and prevent error prone code through accessing an undefined state, DefinatelyTyped made it so that a compile time error was created if a state variable was accessed but never initialized.
In addition to that assigning a value to
this.state would throw an error if done in a constructor. This is because it’s bad practice to access the state in a constructor, since it could cause potential problems with mutability. So now whenever the state is accessed in such a way, the error:
Cannot assign to ‘state’ because it is a constant or a read-only property
Is shown during compile time.
There are different approaches for fixing this. For me, I was able to solve it by creating an interface for my state…
Using that as my state type in the component, then defining state fields in a single object set on state, within the constructor.
I got a fairly thorough review from one of the main contributors from voice-web after I made a PR for my changes. I decided to add a section here based off a couple things that were mentioned.
This touches on an issue in another project I was working on, Github-Dashboard. Developers were having css conflicts between pages because every css file imported into the project was having its selectors applied globally. There are many solutions to this problem, as outlined here. However here it was recommend that I namespace the selectors in
Notice what was mentioned about constructors at the end, and how he mentions that they could be avoided. There’s a chance this is being recommended because the constructor adds redundancy by setting props, and can lead to mutations in state if not handled properly.
If you see on line 35 here I set a component in my email subscription component’s state. But why is this a bad thing to do?
As mentioned in the React docs:
State should contain data that a component’s event handlers may change to trigger a UI update…adding redundant or computed values to state means that you need to explicitly keep them in sync rather than rely on React computing them for you.
State should have the simplest possible representation, meaning that boolean flags should be used whenever possible instead of more complex values. Render() should compute data based off the state.
I think when I try to learn new technologies this will be the approach I take from now on, for the most part. Before my standard process would be to create small, personal, standalone projects that would practice what I’d have read in the documentation or some guide. But by contributing a feature to something already established by seasoned developers I have been able to adopt their good habits fairly early in my own development. It was a great experience and I learned a lot from this.