React – a JavaScript library for building user interfaces --> reactjs
Typescript – is an open-source language that extends JavaScript by adding types. --> typescript
Redux is a predictable state container for JavaScript apps. It helps you write applications that behave consistently, run in different environments (client, server and native). --> redux
Redux Toolkit – as per official Redux documentation, it is recommended approach for writing Redux logic. It wraps around the Redux core, and contains all needed packages and functions for start building a Redux app. --> redux-toolkit
Why do we want to write single page apps? - The main reason is that they allow us to offer a more-native-app-like experience to the user.
Developing modern web applications involves not only UI building, but also require state management. One of the most comprehensive library for this is Redux. In this tutorial you will learn how to setup Redux using the latest libraries and simplified techniques available in 2020.
Table of Contents
- Why to choose Redux Toolkit
- How to Setup Create-React-App with Typescript and Redux
- How to Structure your Redux
- Connecting Redux with React using useDispatch and useSelector hooks
- Summary
Why to choose Redux Toolkit
Simple – Includes utilities to simplify common use cases like store setup, creating reducers, immutable update logic, and more.
Opinionated – Provides good defaults for store setup out of the box, and includes the most commonly used Redux addons build-in.
Powerful – Takes inspiration from libraries like Immer and Autodux to let you write ‘mutative’ immutable update logic, and even create entire ‘slices’ of state automatically.
Effective – Lets you focus on the core logic your app needs, so you can do more work with less code.
How to Setup Create-React-App with Typescript and Redux
For this Redux toolkit tutorial lets start with setup a new react application using CRA:
npx create-react-app redux-toolkit –template typescript
or
yarn create-react-app redux-toolkit –template typescript
npx on the first line is no a typo – it’s a package runner tool that comes with npm 5.2+
Note:
If you’ve previously installed creat-react-app via npm globally, please uninstall the package using "npm uninstall name_of_the_package" to ensure that npx always uses the latest version. Global installs of create-react-app are no longer supported.
cd redux-toolkit
npm start
or yarn start
(in case you are using ‘yarn’)
Please double check if following packages are installed. In case they are not present, to add typescript to a create-react-app project, first install it:
npm install –save typescript @types/node @types/react @types/react-dom @types/jest
or
yarn add typescript @types/node @types/react @types/react-dom @types/jest
Next we will add redux-toolkit, redux-logger and uuid with following:
npm install –save react-redux redux-logger @reduxjs/toolkit uuid
or
yarn add react-redux redux-logger @reduxjs/toolkit uuid
How to Structure your Redux
src
App
App.tsx
App.css
type.d.ts
index.tsx
index.css
store
todos
index.ts
todos.initialState.ts
todos.reducer.ts
todos.slice.ts
root-reducer.ts
store.ts
Now we are ready to start configuring our store:
Step 1: Create a folder "/src/store"
All files related to our Redux will be placed here.
Step 2: Create a file "store.ts"
in "src/store"
This file will contain "configureStore" function that is abstraction over the standart Redux "createStore" function and it is responsible for the store setup. If we want to use "redux-logger" and apply custom middleware, we need to import "getDefaultMiddleware()" function and spread all available props together with "logger", then pass it as a prop to "configureStore".
import { configureStore, getDefaultMiddleware } from '@reduxjs/toolkit';
import logger from 'redux-logger';
import { reducer } from './root-reducer';
const middleware = [...getDefaultMiddleware(), logger];
export default configureStore({
reducer,
middleware,
});
Step 3: Create a file "root-reducer.ts" in "src/store"
Because we structure the app in the long run to scale, we want to have a separate "root-reducer.ts" file, so that we can import additional reducers.
import { todosSlice } from './todos/todos.slice';
export const reducer = {
todos: todosSlice.reducer
};
Now we do not have a todoSlice in our reducer, lets create it:
Step 4: Create a folder "src/store/todos"
This folder will contain all the logic related to "todos slice".
Step 5: Create following files: "index.ts", "todos.initialState.ts", "todos.reducers.ts", "todos.slice.ts"
All todo's logic of the store state(interfaces, reducer, actions, todo slice)
Step 6: Lets start with "todos.slice.ts" in "src/store/todos"
import { createSlice } from '@reduxjs/toolkit';
import { todosInitialState, todosReducer } from '.';
*createSlice - a function that accepts an initial state, an object full of reducer
functions, and a slice name that automatically generates action creators and action
types that correspond to the reducers and state. Internally it uses createAction and createReducer*
export const todosSlice = createSlice({
name: 'todos', // name used in action types
initialState: todosInitialState, // initial state for the reducer
reducers: todosReducer,
});
export const {
actions: { // action creators exported from todosSlice.actions available out of the box
create: createTodoActionCreator
},
} = todosSlice;
Step 7: Then proceed with our ‘"todos.initialState.ts"’ in "src/todos":
import { v1 as uuid } from 'uuid';
import { ITodo } from '../../type';
export const todosInitialState: ITodo[] = [
{
id: uuid(),
desc: 'Learn Redux-ToolKit',
isComplete: false,
},
];
CreateTodo.tsx:
Step 8: Lets add missing information in "src/store/todos/todos.reducer.ts"
Note:
In order to be more consistent it is advisable, for all of the models below to have respective interface. For the purpose of this tutorial some of these parts are simplified.
import { PayloadAction } from '@reduxjs/toolkit';
import { v1 as uuid } from 'uuid';
import { ITodo } from '../../type';
export const todosReducer = {
create: {
reducer: (
state: ITodo[],
action: PayloadAction<{ id: string; desc: string; isComplete: boolean }>
) => {
state.push(action.payload);
},
prepare: ({ desc }: { desc: string }) => ({
payload: {
id: uuid(),
desc,
isComplete: false,
},
}),
}
};
Step 9: Then exports these files in "src/store/todos/index.ts"
export * from './todos.reducer';
export * from './todos.initialState';
export * from './todos.slice';
Connecting Redux with React Components with useDispatch and useSelector hooks
Step 10: Create a folder "src/components"
All components will be placed there for now.
Step 11: Create a folder "src/app"
Step 12: Move files "App.tsx" and "App.css" into "src/app"
Step 13: Populate "App.tsx" with following peace of code:
import React from 'react';
import { useSelector } from 'react-redux';
import { State } from '../type';
import { CreateTodo, TodoList } from '../components';
import './App.css';
const App = function () {
const todos = useSelector((state: State) => state.todos);
return (
<div className="App">
<div className="App__header">
<h1>Redux Toolkit</h1>
<CreateTodo />
</div>
<div className="App__body">
<TodoList todos={todos} />
</div>
</div>
);
};
export default App;
Step 14: Create following files into "src/components":
CreateTodo.tsx:
import React, { FC, FormEvent, useState, ChangeEvent } from 'react'
import { useDispatch } from 'react-redux';
import { createTodoActionCreator } from '../store/todos';
interface ICreateTodoProps { }
export const CreateTodo: FC<ICreateTodoProps> = () => {
const dispatch = useDispatch();
const [newTodoInput, setNewTodoInput] = useState<string>('');
const handleCreateNewTodo = (e: FormEvent<HTMLFormElement>): void => {
e.preventDefault();
if (!newTodoInput.length) return;
dispatch(createTodoActionCreator({ desc: newTodoInput }));
setNewTodoInput('');
};
const handleNewInputChange = (e: ChangeEvent<HTMLInputElement>): void => {
setNewTodoInput(e.target.value);
};
return (
<form onSubmit={handleCreateNewTodo}>
<label htmlFor="new-todo">Add new:</label>
<input
onChange={handleNewInputChange}
id="new-todo"
value={newTodoInput}
/>
<button type="submit">Create</button>
</form>
)
}
Todo.tsx:
import React, { FC } from 'react'
import { ITodo } from '../type'
interface ITodoProps {
key: string;
todo: ITodo
}
export const Todo: FC<ITodoProps> = ({ key, todo }) => <li>{todo.desc}</li>
TodoList.tsx:
import React, { FC } from 'react'
import { ITodo } from '../type'
import { Todo } from '.'
interface ITodoList {
todos: ITodo[]
}
export const TodoList: FC<ITodoList> = ({ todos }) => {
return (
<ul className="App__list">
<h2>My Todos:</h2>
{todos &&
todos.map((todo, i: number) => (
<Todo key={todo.id} todo={todo} />
))}
</ul>
)
}
Step 15: Create "type.d.ts" file into root folder:
export interface ITodo {
id: string;
desc: string;
isComplete: boolean;
}
export interface State {
todos: Todo[];
}
Summary
It’s amazing how simple is to setup Redux in 2020. We used tools Typescript, React Hooks, Redux Toolkit
Thank you for reading the article! I hope you find it useful 😊.
Top comments (0)