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
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
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;
}[]
}
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;
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;
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
});
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>
);
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
});
};
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"]>>
}
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
});
};
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;
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;
}
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
The whole project is in github. You can very well have a look in it here.
Happy Coding 😀😀😀😀😀
Top comments (17)
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 ofChangeEvent
. Also I wanted to understand your comment regarding one way flow of data in this perspective.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
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
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 auseCallback
withinput
as a dependency, but with the function approach it is not necessary.I’m not sure that several
useState
s is better, actually. How about auseReducer
instead?Thank you for sharing this! there were some things I didn't know of that you cleared up.
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.
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:
same amount of code, safer/cleaner code
"Because you avoid shooting yourself on the foot and wrongly mutating the state object" - how is your example prevents that? u not dispatching anything, u basically using useReducer as a useState. Anyways, it's good if it works for you, I don't know how come it is safer/cleaner than this
const [state,setState] = useState({ a: 1, b: 2});
setState(prev =>({...prev, a:3 });
Notice how it does the same thing without writing a reducer?
BTW you using the reducer wrong. To use useReducer right, to "avoid shooting yourself on the foot" refer to docs reactjs.org/docs/hooks-reference.h... .
I know you have seen them, but maybe take a second look so you can see what I'm talking about.
it is safer because you dont need to remember to spread the old object everytime you need to update the state, it is cleaner because you are only spreading an object on the reducer, and you don’t have to rely on other devs doing it. “You not dispatching anything” you clearly don’t know the difference between reducer and flux paradigm, they are connected but are not the same, the documentation states: “ useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values” in verbatim, tell me where in there it shows that im wrong
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
'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 =)
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?