DEV Community

Cover image for React with Typescript
Mohammad Abdul Alim
Mohammad Abdul Alim

Posted on

React with Typescript

At present react js has been a very popular library to build beautiful and scalable user interfaces. Today I would like to build a demo react project with typescript.

Project setup

We would like to use the create-react-app environment built by facebook developer team to create our project. Our project will have a form to add favorite tv series and show the list. It will be a single page website. At first we will run the following command

npx create-react-app --template typescript fav-tv-series
cd fav-tv-series
npm run start
Enter fullscreen mode Exit fullscreen mode

The first command will create a react app named fav-tv-series having typescript template. Then after going to that directory we will run the third command which will create a process running on port number 3000 and look like this
Alt Text

Creating Interface

In typescript our main concern will be defining strict type to every data that will be used. interface is a pretty good way to define a data and use that as a type in ts. In the src folder of the root folder we will create a directory named interfaces and inside it we will create a file named SeriesProps.tsx. Here we will create an interface named SeriesProps like following

export interface SeriesProps {
    seriesList: {
        name: string;
        imdb: number;
        cover: string;
        seasons: number;
        genre: string;
      }[]
}
Enter fullscreen mode Exit fullscreen mode

Updating App

At first we will update the existing App.tsx file by removing the existing code. Our single page web application will contain two components. One is a form where a user will give necessary inputs about his favourite series and another is a list containing those serieses. The data will be stored in a state named seriesList and updated with the help of setSeriesList method.

import React, { useState } from 'react';
import { SeriesProps } from './interfaces/SeriesProps';
import './App.css';
import List from './components/List';
import Form from './components/Form';

function App() {
  const [seriesList, setSeriesList] = useState<SeriesProps["seriesList"]>([]);

  return (
    <div className="App">
      <h1>My Favourite TV Series</h1>
      <Form seriesList={seriesList} setSeriesList={setSeriesList} />
      <List seriesList={seriesList} />
    </div>
  );
}

export default App;

Enter fullscreen mode Exit fullscreen mode

Creating List

In src directory of root folder we will create a directory named components and there we will create the List.tsx file. Our component will look like below

import React, { FC } from "react";
import { SeriesProps } from "../interfaces/SeriesProps";

const List:FC<SeriesProps> = ({seriesList}) => (
    <div className="series-list">
        {seriesList.map((series) => (
            <div className="series-item">
                <img src={series.cover} alt="Series-cover" />
                <p><b>{series.name}</b></p>
                <p>{series.genre}</p> 
                <p>{series.seasons} seasons</p>
                <p>★★★★★ {series.imdb}</p>
            </div>
        ))}
    </div>
);

export default List;
Enter fullscreen mode Exit fullscreen mode

Here we can have a look at FC which means Functional Component and it guides us with types. Here we have passed SeriesProps as props and finally we have used map function to render tv series list.

Creating Form

Now we are left with creating the form element where we will give necessary inputs. Here we will use controlled components to build input elements. For simplicity we will create a state object where the necessary input values will be kept. We will use useState for this.

const [input, setInput] = useState({
        name: "",
        genre: "",
        cover: "",
        imdb: 0,
        seasons: 0
    });
Enter fullscreen mode Exit fullscreen mode

Now we will render the components. Here we will have five input fields having three text and two number type inputs.

return (
        <div className="form-container">
            <div className="form-div">
                <label htmlFor="name">Name</label>
                <input type="text" name="name" id="name" value={input.name} onChange={handleChange} />
            </div>
            <div className="form-div">
                <label htmlFor="genre">Genre</label>
                <input type="text" name="genre" id="genre" value={input.genre} onChange={handleChange} />
            </div>
            <div className="form-div">
                <label htmlFor="cover">Cover Link</label>
                <input type="text" name="cover" id="cover" value={input.cover} onChange={handleChange} />
            </div>
            <div className="form-div">
                <label htmlFor="imdb">IMDB Rating</label>
                <input type="number" name="imdb" id="imdb" value={input.imdb} onChange={handleChange} />
            </div>
            <div className="form-div">
                <label htmlFor="seasons">Total Seasons</label>
                <input type="number" name="seasons" id="seasons" value={input.seasons} onChange={handleChange} />
            </div>
            <button type="button" onClick={handleClick}>Add Series</button>
        </div>
    );
Enter fullscreen mode Exit fullscreen mode

Here we can see that the value of each input field will be stored to the state object. We can see that all input fields have a function named handleChange which will be invoked as an onChange listener and the button has an onClick listener named handleClick. We will implement these two methods now. The handleChange method is quite straight forward. Here we destructure the input state and update the particular state element needed to be updated. One important thing to notice is that the type of event we are passing to that function. Here the type is ChangeEvent<HTMLInputElement> which means our handleChange method will only accept html input element change event. One thing to notice is that we have kept the name and value of each input same for which we can use [name]: value statement.

const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
        const { value, name } = e.target;
        setInput({
            ...input,
            [name]: value
        });
    };
Enter fullscreen mode Exit fullscreen mode

Before implementing the handleClick method we need to define a props which will be used to update and store the series list. As we have already defined a state in our App.tsx using useState, we need to pass those in this Form component and use in our handleClick method. Lets have a look at the following interface.

interface Props {
    seriesList: SeriesProps["seriesList"],
    setSeriesList: Dispatch<SetStateAction<SeriesProps["seriesList"]>>
}
Enter fullscreen mode Exit fullscreen mode

Now we will implement our handleClick method.

const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
        const { name, genre, cover, imdb, seasons } = input;
        if(!name && !genre && !cover && !imdb && !seasons) return;
        const series = { name, genre, cover, imdb, seasons };
        setSeriesList([...seriesList, series]);
        setInput({
            name: "",
            genre: "",
            cover: "",
            imdb: 0,
            seasons: 0
        });
    };
Enter fullscreen mode Exit fullscreen mode

Our method only accepts a mouse event coming from an html button element. At first we have destructured our input state. Then we have checked whether any input field is empty. If so then we won't move further. Otherwise we have created a series object and appended it to the series list. After that we have made all fields empty. So our complete Form.tsx looks like this

import React, { FC, useState, ChangeEvent, MouseEvent, Dispatch, SetStateAction } from "react";
import { SeriesProps } from "../interfaces/SeriesProps";

interface Props {
    seriesList: SeriesProps["seriesList"],
    setSeriesList: Dispatch<SetStateAction<SeriesProps["seriesList"]>>
}

const Form: FC<Props> = ({ seriesList, setSeriesList }) => {

    const [input, setInput] = useState({
        name: "",
        genre: "",
        cover: "",
        imdb: 0,
        seasons: 0
    });

    const handleChange = (e: ChangeEvent<HTMLInputElement>) => {
        const { value, name } = e.target;
        setInput({
            ...input,
            [name]: value
        });
    };

    const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
        const { name, genre, cover, imdb, seasons } = input;
        const series = { name, genre, cover, imdb, seasons };
        if(!name && !genre && !cover && !imdb && !seasons) return;
        setSeriesList([...seriesList, series]);
        setInput({
            name: "",
            genre: "",
            cover: "",
            imdb: 0,
            seasons: 0
        });
    };

    return (
        <div className="form-container">
            <div className="form-div">
                <label htmlFor="name">Name</label>
                <input type="text" name="name" id="name" value={input.name} onChange={handleChange} />
            </div>
            <div className="form-div">
                <label htmlFor="genre">Genre</label>
                <input type="text" name="genre" id="genre" value={input.genre} onChange={handleChange} />
            </div>
            <div className="form-div">
                <label htmlFor="cover">Cover Link</label>
                <input type="text" name="cover" id="cover" value={input.cover} onChange={handleChange} />
            </div>
            <div className="form-div">
                <label htmlFor="imdb">IMDB Rating</label>
                <input type="number" name="imdb" id="imdb" value={input.imdb} onChange={handleChange} />
            </div>
            <div className="form-div">
                <label htmlFor="seasons">Total Seasons</label>
                <input type="number" name="seasons" id="seasons" value={input.seasons} onChange={handleChange} />
            </div>
            <button type="button" onClick={handleClick}>Add Series</button>
        </div>
    );
};

export default Form;
Enter fullscreen mode Exit fullscreen mode

Now we are only left with adding css styles. For simplicity we have made change only in the App.css files which looks like

.form-container {
  width: 400px;
  margin: auto;
}

h1 {
  text-align: center;
}

.form-div {
  margin-bottom: 10px;
}

input[type='text'],
input[type='number'] {
  float: right;
  width: 70%;
  padding: 3px;
}

input[type='checkbox'] {
  margin-left: 110px;
}

button {
  margin: 10px 0;
  padding: 10px 0;
  width: 100%;
  cursor: pointer;
  font-weight: bold;
  text-transform: uppercase;
  font-size: 16px;
}

p {
  line-height: 5px;
}

.series-list {
  display: flex;
  flex-flow: wrap;
  margin: 50px auto;
  width: 90%;
}

.series-item {
  padding: 0 20px 20px 0;
  width: 300px;
}
Enter fullscreen mode Exit fullscreen mode

After we are finished with all coding, we can have a look at our browser's http://localhost:3000/ link. After adding some data it should look like following
Image description
The whole project is in github. You can very well have a look in it here.

Happy Coding 😀😀😀😀😀

Discussion (23)

Collapse
lukeshiru profile image
LUKESHIRU

A few things about your React and TS usages, first React:

  • Ideally you should avoid having objects for state, and use one state per property of that object which makes updating way easier and also reduces the amount of re-rendering:
const [name, setName] = useState("");
const [genre, setGenre] = useState("");
const [cover, setCover] = useState("");
const [imdb, setIMDB] = useState(0);
const [seasons, setSeasons] = useState(0);
Enter fullscreen mode Exit fullscreen mode
  • You should avoid sending the state setter directly to the parent as you do with setSeriesList and instead of that, you should just have an event like onAdd or similar, and handle that from the parent (one-way data flow). And about TS:
  • You could use ChangeEventHandler instead of ChangeEvent, and MouseEventHandler instead of MouseEvent for handleChange and handleClick:
const handleChange: ChangeEventHandler<HTMLInputElement> = event => {
    //
};

const handleClick: MouseEventHandler<HTMLButtonElement> = event => {
    // ...
};
Enter fullscreen mode Exit fullscreen mode

And this is ideal for both React and TS:

  • The type of the properties ideally should be readonly and optional, making it easier to use and avoiding unwanted mutations.

Let me know if you want some examples of this suggestions implemented, and I can share a CodeSandbox with some comments.

Cheers!

Collapse
alim1496 profile image
Mohammad Abdul Alim Author

Thanks a lot for your valuable comment. I clearly understood the points of avoiding having object for state. Can you please explain why it is better to use ChangeEventHandler instead of ChangeEvent. Also I wanted to understand your comment regarding one way flow of data in this perspective.

Collapse
lukeshiru profile image
LUKESHIRU

That is just the type that's used by the event handlers, and that particularly doesn't have much to do with one way data flow. You can confirm this, just hover the onChange property in one of the inputs and then do cmd+click in mac or ctrl+click in Linux or Windows, that will take you to the type definition and you'll see that the type is not (event: ChangeEvent<T>) => void, but actually ChangeEventHandler<T> | undefined. The T there is a generic for all the elements that have the onChange property, but you can read it as ChangeEventHandler<HTMLInputElement>, and the | undefined is because all properties of native elements are optional (that's why I suggest you do the same with your properties so they provide a better DX).

The issue with one-way data flow is obvious when you have a property with the type Dispatch<SetStateAction<...>> because you basically receiving a state setter from the parent and updating it from the child, which is basically going against the correct flow which is from parent to child.

In an scenario in which you have a child with data that's needed from the parent, you should just use events, adding something custom like onAdd and letting the parent decide what to do with that.

Hope that explains it better :)

Thread Thread
alim1496 profile image
Mohammad Abdul Alim Author

nicely explained
thanks a lot

Collapse
kylesureline profile image
Kyle Scheuerlein

Yeah, an object is probably not ideal for useState, but you can optimize it by passing a function. Instead of doing this...

setInput({...input, [name]: value})

...you can do this:

setInput((prevState) => ({...prevState, [name]: value}))

The key distinction is that with the first approach, handleChange really ought to be a useCallback with input as a dependency, but with the function approach it is not necessary.

I’m not sure that several useStates is better, actually. How about a useReducer instead?

Collapse
rammina profile image
Rammina

Thank you for sharing this! there were some things I didn't know of that you cleared up.

Collapse
theluk profile image
Lukas Klinzing

I would disagree partly. Using set state for each field will cause rendering 5 times when you want to reset all fields at once. Also if you set one field, it will rerender the app the same amount of times as it would with setFields. Your other points sound a lot like just opinions because there is no specific reason in using a eventhandler type instead of a event type. If there are reasons for your suggestions, please let us know. Would like to hear your reasons behind this

Collapse
lukeshiru profile image
LUKESHIRU • Edited on

You're wrong, if you set all fields at once, that re-renders once, the state changes don't run in place, but actually are scheduled. I explained the reasoning for the types in other comment, but is because is the actual type used by event handlers in React. If that changes, you'll get type errors that you can actually resolve :D

Thread Thread
theluk profile image
Lukas Klinzing

You are right, but as I said, partly.

"Currently (React 16 and earlier), only updates inside React event handlers are batched by default” , according to Dan Abramov.

And

"React may batch multiple setState() calls into a single update for performance”, according to React’s documentation

So it is possible, that it is not batched.

Collapse
konstagap profile image
konstagap

hi, Luke, could you explain why is it bad to have object as state and how having many usestate has less rerenderings than having all in one object?

Collapse
lukeshiru profile image
LUKESHIRU

Every time any of those props change, we have to change the entire o object, and that will trigger a re render in that and every child element that uses the state. If you have different states for every value, then the only things that re-render are the ones that actually use that property. Updating the state also becomes easier, instead of:

setObject({ ...object, prop: "value* });
Enter fullscreen mode Exit fullscreen mode

Is just:

setProp("value");
Enter fullscreen mode Exit fullscreen mode

The idea with the state hook is to have smaller states, compared to the old and ugly this.state/this.setState

Cheers!

Thread Thread
konstagap profile image
konstagap

Thanks you for explanation, I guess it's not gonna do any extra rerenderings in this particular case as we have all inputs in one component which still gonna render either way. thanks 👍

Thread Thread
joaolss profile image
João Lucas Silva

I'm also not a fan of having object as state, but your explanation for more rerenders does not apply, because if your state is an object but you are passing a variable inside this object which is a primitive then value equality applies for react to rerender or not, and if you are passing the entire object, the same would apply for creating the object inline with all the separate states inside it, it would be a new reference every render and would trigger rerender, so it only applies for nested objects, which no one should ever do because it is so much worse than a plain object inside one single state

Thread Thread
konstagap profile image
konstagap

Why do u think nested object is something that no one should ever do? I deal with forms and I have lots of nested objects that has arrays of objects and so on, I use one single object to keep all together in form, to update one field I just shallow copy old object, change field, pass new reference to setState and it works no problem, Is there another way how to do it?

Thread Thread
joaolss profile image
João Lucas Silva

useReducer would be much better to deal with nested objects inside a state

Thread Thread
lukeshiru profile image
LUKESHIRU • Edited on

The explanation is more for nested stuff, but yeah, too much to explain in a single comment, but enough for other folks to google it. The reason of not having to do a copy of the object every time should be compelling enough.

Edit: I answered from the notifications and for some reason the answer ended up here, but it was for the comment about "this doesn't apply to plain objects".

Thread Thread
joaolss profile image
João Lucas Silva

Just that you did the explanation using primitives, it would be good to mention the problem is nested objects bc of reference equality, sorry if im being cranky, but i feel it is hard enough for beginners to grasp the reference equality thing on js, it is always good to emphasize this when it comes up

Thread Thread
lukeshiru profile image
LUKESHIRU

I generally prefer to just say "never do this bad practice" instead of, "don't do it in this 9 of 10 scenarios, and in that 1 scenario that you can, be careful". My point is that we shouldn't default to objects as state, and we should try to have one state per value, which also encourages you to avoid having lots of state in a component.

Thread Thread
joaolss profile image
João Lucas Silva

I'm not a fan of dos and don'ts that dont explain why this should be the case, it encourages people to be the "ctrl-c ctrl-v of stackoverflow" kind of dev, instead of creating a deeper understanding of the language and the framework

Thread Thread
lukeshiru profile image
LUKESHIRU

I mean, if folks can't google by themselves and need every single thing to be explained in a comment, how will they improve as devs over time? I can go over why something is a bad practice in a comment or I can just mention is a bad practice and let them google it themselves, (or even let them ask and generate a deeper discussion as it happened today 😅). If somebody is expecting to have everything served on a silver plate, learning new things will be harder and harder over time 🙁

Thread Thread
konstagap profile image
konstagap

'useReducer would be much better to deal with nested objects inside a state'

-what is the benefit of that? writing more code? setup reducer and dispatch actions when u still keep state as an object and can just copy an object and use same setState function. Same performance too. But much better.

The reason of not having to do a copy of the object every time should be compelling enough.

  • I dont understand what is wrong with making copy of object? u try save memory? u try save what?
    So when calling useState u create a state var and a method setState:
    const [state.setState] =useState('');
    when u put each value in your own useState u create a bunch of independent setState methods takes space, which can be replaced with one setState if initial state was an object. compelling enough?)))
    Lets say u have 30 fields? what a noise in code, what a waste)))

  • If somebody is expecting to have everything served on a silver plate, learning new things will be harder and harder over time 🙁

Brother, learning will be harder overtime no matter what, that is why we are here, to help and explain. dont be like that, just be good and bring to the table =)

Thread Thread
lukeshiru profile image
LUKESHIRU

If you have a state with 30 fields (either as props of an object or as separate states), tbh the problem is elsewhere.

And when I said that not having to do a copy of the object should be enough of a reason to not do it this way, it wasn't because of performance (even if performance would be worse in normal scenarios), but more about avoiding the boilerplate every time we update the state.
I mean, the old this.setState did that copy for us, but the useState hook doesn't because it wasn't designed to be used like that.

Thread Thread
joaolss profile image
João Lucas Silva • Edited on

Because you avoid shooting youself on the foot and wrongly mutating the state object, you can use immer to immutability, and you can deal with more complex trabsformation in the reducer, and in last case you can write I reducer simple enough that it just contains the code that you would have to write inside setState to mutate a variable inside the object, so you dont have to write it in each setState (or useCallback just to do that) instead you can do this logic in the reducer and use the dispatch as the setState, so yes it is definitely better to use the useReducer hook and there is no benefit at all in using useState for objects

Edit: putting a example so you can see what im talking about:

const reducer = (state, action) => ({ ...state, ...action })
...
const [state,setState] = useReducer(reducer, { a: 1, b: 2})
...
setState({ a: 3 })
// new state: { a: 3, b: 2 }
Enter fullscreen mode Exit fullscreen mode

same amount of code, safer/cleaner code