๐React + TS
Initialize CRA with TypeScript.
To start a new Create React App project with TypeScript, you can run:
In case you want to add typescript to an existing React project, you can add the following dependencies:
Next, rename any file to be a TypeScript file (e.g. src/index.js
to src/index.tsx
) and restart your development server!
Type errors will show up in the same console as the build one. You'll have to fix these type errors before you continue development or build your project. For advanced configuration, see here.
Components
Working with components in React using TypeScript.
Props
Props
There is a special way of defining components in a .tsx
file, to use the props
in the component. We could also define the prop type definition in the functional component parameters, but that would be cumbersome for various components that our app might have. So there is an alternative provided by '@types/react
', which is available via the 'react
' library, and we use the 'FC
' component from that library.
This code snippet above shows how we import and use the FC
component function. In the end, it is a type definition for a function, which stands for 'Functional Component'
, and internally defines type definitions for props
and children
for a functional component.
Type Annotations
Here, React.FC
or FC
is a generic type, which is defined by React on a functional component, and we can now have type definitions for our props in this component. We are not creating a new generic type, but are plugging in a concrete value for that internally used generic type before that type T defined by that React.FC
type. We are doing this so that typescript does not infer the generic type here because here we are not calling some generic function with some parameters where the values could then be used to for the inference, but rather we're defining a function and letting typescript know how it should treat this function internally that it should get some props defined by us and merge those with some base props like the children prop, which all functional components have.
We use this, for better auto complete and more descriptive code in our editor, but it also has some other advantages, the main one being that the place where we use this <Todos>
component. If we do not pass the "items"
prop to the <Todos>
component, Typescript will show a warning that we are missing props that we have defined in our functional component.
Data Models
Since the props we pass to our functional component will not always be simple, they will carry more info with them , typically in an object, it is better and easier to read the code that is modular, and uses type
, or interface
or class
models (type definitions) to describe the props for our React.FC
type annotation that we add to the existing props. We can define these using interface
as well, but here we will look at class
models.
This is the type of object that we will pass to our <Todos>
component, where the prop 'items'
will have an array of type 'Todo
', as defined in our model file todo.ts
. In our App
component, where we use our Todos
component, we need to pass the 'items' prop with the data so that it can be rendered dynamically in our <Todos>
component. The way we create a new data in the items
prop is a little different, i.e, by calling the constructor
of the class model we defined in todo.ts
file.
This makes our code well structured and difficult to misuse our component in any way. This ensures data cannot be manipulated, and that we get most of the errors during development instead of runtime. This is the benefit of using Typescript with React!
Practice
Let's outsource the <li>
element from the above example into it's own component <TodoItem>
.
Forms
onSubmit
event
onSubmit
eventWhile submitting forms in React, we use the event.preventDefault()
to prevent the browser default action when we submit a form. While handling the form submission, we need to provide the "event"
parameter to the submit handler, in order to prevent the browser default action. Typescript does not know the type of the "event"
that we pass to this submit handler function, and thus we use a special type, a type annotation provided by React, called "FormEvent"
, which will tell typescript that this is an event object type which comes from the browser automatically when listening to a onSubmit
event on a form. There are other events as well, like "MouseEvent"
, which we could get on a onClick
event.
useRef
useRef
To get the input values of what the user has entered, we can either listen to every keystroke, like the onChange
event, but here we will prefer to use the useRef
, to point to an input and get the input value at once. We need to be explicitly about the type of data we will be storing in this ref
because will show an error since we do not provide any type to the ref we create using the useRef()
hook. To provide a type to the newly created ref, we can use generics.
useRef()
is a generic type out of the box in our project. We can add the angled brackets and add a concrete type of ref
we want to create in this instance. Now, the concrete value or the HTML element that we want to connect the ref
with, is defined within these angled brackets.
Here, we want to use the ref
with an <input>
HTML element, so the concrete type for this element is a built-in type, called HTMLInputElement
. All the DOM elements have a built-in type, which we can use to refer to them. For example, a <button>
will have HTMLButtonElement
, a <p>
will have HTMLParagraphElement
, et al.
So now we are making clear that the ref
we are actually creating here, will actually be connected to an HTMLInputElement
. But we also need to provide a starting value to it, since this ref
could be assigned to another HTML element. Since we have no initial connection, we can make this as null
.
Fetching the ref
value
ref
valueTo get the value of the reference, we can use it in the same way we used to get the value in JSX files, but the autocomplete suggestion from our IDE adds a '?' after 'current', like so:
inputRef.current?.value
. This question mark added here signals to typescript that it tries to access 'value'
and if it succeeds, the value will be stored in the assigned variable, and if that fails, i.e, the connection between the HTML element
and the ref
is not established, it will store null in the assigned variable.
In case we are sure that the connection has been established between the ref
and the HTML element, we can then get the value of the ref by using the same syntax mentioned above, but just replacing the '?' with a '!' (exclamation). This tells typescript, that we are sure the ref
will have a value that is not null / undefined. With that, if we hover over the variable where we store this value, the inferred type will be a string
, and nothing else.
The question mark (?) and exclamation (!) operators are not specific to React, but they are used in typescript for checking if a value is null / undefined, or has a definite value other than null / undefined.
Function Props
Moving on with the same example as above, we want to update the Todos
list with the new Todo
added by the user via the NewTodoItem
component. But first, we need a function, that will send this enteredVal
to the place we are using the NewTodoItem
component, i.e, the App
component. We do this in the same way we used to before, by sending a function as a prop to NewTodoItem
component, which adds the enteredVal
to the list of "todos" managed in the App
component. We will have to define the prop type in the NewTodoItem
component, as we did before.
State
We want to tell typescript that the type of data managed by the useState()
hook in our case would be an array of Todo
types, that we defined earlier in the models/
folder, inside of the todo.ts
file. We can do this, because the useState()
is a generic type again, and we can give it a concrete type value. We would still need to give the default value to useState()
hook when we define it, and store the returned values in a de-structured array, just like before.
Removing a Todo Item
We can remove a Todo
item from the todo list by using the following code snippets, which in the end do not need to use the MouseEvent
from the React library. The problem we will face here, is that we will create a prop chain, by sending the delete function prop from <TodoItem>
to <Todos>
and then finally <App>,
where we manage the state of all Todo
items.
Context API
We are creating prop chains when passing functions as props for adding and remove items from the todo list. So, the basic solution to avoid prop chains is to use the React Context API.
We will start off by creating a new file called 'todos-context.tsx
' inside a new folder, called store/
, which will contain our context object, as well as the context provider for our <App>
Some important concepts from the above code:
We use the type alias
TodosContextTypeObj
, to define the structure of the context object, which we will be using across our app. We do this for better linting, as our IDE will point out to the places where we misuse the context object types, or declare them incorrectly, or in cases where we add functions to the context, but forget ot give them the appropriate parameters.The same type alias is used to define the structure of the context value, which we provide to the context provider.
In case there is any error, in defining or using the context object values, the IDE will point out the specific place where we have misused the context object property.
With the help of context API, we remove prop chaining from the app, and thus we can then remove the prop type definitions from the components, and use the context object to then use all the props from within this context.
The final project can be found here. The same project is also available with Redux-Toolkit
here.
Last updated