# Lesson 13 - Redux-toolkit

## Giới thiệu

Redux Toolkit là một bộ công cụ giúp quản lý trạng thái ứng dụng Redux một cách dễ dàng và hiệu quả hơn. Nó giảm bớt boilerplate code và cung cấp một số tính năng hữu ích để làm cho việc sử dụng Redux dễ dàng hơn.

**Tính năng chính của Redux Toolkit**:

* Cung cấp cấu trúc **chuẩn** cho việc viết Redux logic.
* Tích hợp sẵn Thunk Middleware để xử lý các tác vụ bất đồng bộ.
* Tạo action và reducer dễ dàng

<figure><img src="https://content.gitbook.com/content/bApz6rKK01BWUpp5sM0n/blobs/1torw4YRzPCsSaaFHcym/image.png" alt=""><figcaption><p><a href="https://redux.js.org/assets/images/ReduxDataFlowDiagram-49fa8c3968371d9ef6f2a1486bd40a26.gif">Redux basic follow</a></p></figcaption></figure>

Cài đặt:&#x20;

```bash
npm install @reduxjs/toolkit react-redux
```

## Thành phần

Trước khi dùng Redux-toolkit, ta cần phải biết, hiểu được các thành phần cũng như các hàm và cách hoạt động của Redux-toolkit.

1. **createSlice**: là một hàm giúp bạn định nghĩa một slice Redux, bao gồm reducers và actions. Nó tự động tạo reducers và actions dựa trên một đối tượng mô tả trạng thái ban đầu, các reducers, và tên của slice.

```javascript
const userInfoSlice = createSlice({
  name: 'userInfo',
  initialState: {
        data: null,
        success: false,
        loading: false,
        message: '',
   },
  reducers: {
    login: (state) => {
      // logic set data
      // example
      state.data = {
        username: "Mindx School",
        accessToken: 'abcxyz',
        refreshToken: 'klmnopq',
        email: 'mindx@edu.vn'
      };
      state.success = true;
      state.message = "Đăng nhập thành công!"
    },
    logout: (state) => {
     // logic clear data
      // example
      state.data = null;
      state.success = true;
      state.message = "Đăng xuất thành công!"
    },
  },
});
```

2. **Reducers**: Reducers là các hàm pure function nhận vào trạng thái hiện tại và hành động (action) và trả về trạng thái mới. Redux Toolkit giúp tạo reducers một cách dễ dàng bằng cách sử dụng `createSlice`. Cách lấy reducer từ createSlice:

```javascript
const authReducer = userInfoSlice.reducer;
```

3. **Actions**: Actions là các đối tượng chứa thông tin về việc xảy ra hành động (ví dụ: "đăng nhập"). Redux Toolkit tạo ra actions tự động dựa trên reducers khi bạn sử dụng `createSlice`.

```javascript
const { login, logout } = userInfoSlice.actions;
```

Ví dụ hoàn thiện khởi tạo một slice reducer

```javascript
const userInfoSlice = createSlice({
  name: 'userInfo',
  initialState: {
        data: null,
        success: false,
        loading: false,
        message: '',
   },
  reducers: {
    login: (state) => {
      // logic set data
      // example
      state.data = {
        username: "Mindx School",
        accessToken: 'abcxyz',
        refreshToken: 'klmnopq',
        email: 'mindx@edu.vn'
      };
      state.success = true;
      state.message = "Đăng nhập thành công!"
    },
    logout: (state) => {
     // logic clear data
      // example
      state.data = null;
      state.success = true;
      state.message = "Đăng xuất thành công!"
    },
  },
});
export const userInfoReducer = userInfoSlice.reducer;
export const { login, logout } = userInfoSlice.actions;
```

{% hint style="info" %}
Redux không hỗ trợ xử lý bất đồng bộ, Redux toolkit sẽ hỗ trợ sử dụng một dạng middleware giúp xử lý bất đồng bộ với trường extraReducers trong hàm createSlice
{% endhint %}

### extraReducers

Cho phép bạn thêm reducers và xử lý actions từ các slice khác mà không cần phải thay đổi mã nguồn gốc của slice đó.

Ví dụ:

```javascript
// userSlice.js
import { createSlice } from '@reduxjs/toolkit';
import { fetchUser } from './userActions';

const userSlice = createSlice({
  name: 'user',
  initialState: { 
  // init state
  },
  reducers: {
    // reducers của slice gốc
  },
  extraReducers: (builder) => {
    builder
      .addCase(fetchUser.pending, (state) => {
        state.loading = true;
        state.error = null;
      })
      .addCase(fetchUser.fulfilled, (state, action) => {
        state.loading = false;
        state.data = action.payload;
      })
      .addCase(fetchUser.rejected, (state, action) => {
        state.loading = false;
        state.error = action.error.message;
      });
  },
});

export default userSlice.reducer;
```

Để sử dụng được actions bất đồng bộ như phía trên, ta cần tìm hiểu createAsyncThunk

### createAsyncThunk

Là một hàm giúp quản lý các tác vụ bất đồng bộ một cách dễ dàng. Nó tự động tạo các actions để xử lý các tác vụ bất đồng bộ và cung cấp cơ chế lấy dữ liệu và quản lý lỗi.

```javascript
const fetchUser = createAsyncThunk('users/fetchUser', async (userId) => {
  const response = await (call api response);
  return response.data;
});
```

{% hint style="info" %}
Có thể sử dụng axios trong việc call api.
{% endhint %}

4. **configureStore**: configureStore là một hàm giúp bạn tạo Redux store dễ dàng. Bạn chỉ cần cung cấp danh sách các reducers và Redux Toolkit sẽ tự động tạo Redux store.

```javascript
import { configureStore } from '@reduxjs/toolkit';

const store = configureStore({
  reducer: {
    userInfo: userInfoReducer,
    // Các reducers khác
  },
});
```

Dùng cho toàn bộ dự án:

File App.jsx

```javascript
import { Provider } from 'react-redux';
import store from 'store directory';

const App = ()=>{

    return (
        <Provider store={store}>
            // component, router ,...
        </Provider>
    )
};
export default App;
```

### useSelector, useDispatch

useSelector là một hàm giúp lấy các reducer từ store ra và sử dụng chúng giống như một state, chỉ khác là muốn cập nhật hay thay đổi, cần sử dụng qua dispatch được gắn với useDispatch để bắn action đã được định nghĩa từ createSlice.

Ví dụ:

```javascript
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { fetchUser, logoutUser } from './authActions';

function App() {
  const isAuthenticated = useSelector((state) => state.auth.isAuthenticated);
  const user = useSelector((state) => state.userInfo);
  const dispatch = useDispatch();

  const handleLogin = () => {
    const user = { username: 'example_user' };
    dispatch(fetchUser(user));
  };

  const handleLogout = () => {
    dispatch(fetchUser());
  };

  return (
    <div>
      <h1>Redux Toolkit Login Demo</h1>
      {isAuthenticated ? (
        <div>
          <p>Welcome, {user.username}!</p>
          <button onClick={handleLogout}>Logout</button>
        </div>
      ) : (
        <div>
          <p>You are not logged in.</p>
          <button onClick={handleLogin}>Login</button>
        </div>
      )}
    </div>
  );
}

export default App;
```

## Demo

Thực hiện cài đặt Redux toolkit với chức năng Đăng nhập, Đăng ký

## Protected Route (Tìm hiểu thêm)

Trong lúc sử dụng ứng dụng phía Client, ta cần phải xác định được khi nào thì người dùng được phép vào các route. Đơn giản là khi người dùng đã đăng nhập, trong trường hợp này, ta sẽ chỉ cần kiểm tra được access token có hợp lệ hay không, nếu không sẽ cần phải đẩy người dùng về trang login.

Lưu ý: kể cả khi người dùng reload lại page, cũng cần phải xác thực xem có được sử dụng hay không.

Thực hiện tạo một component ProtectedRoute và cài đặt logic như sau:

1. ProtectedRoute cần được bọc toàn bộ App (trừ các giao diện về Đăng nhập, Đăng ký,... liên quan về auth).
2. Thực hiện kiểm tra xem có access token trong localStorage chưa:
   1. Chưa: đẩy về trang Đăng nhập.
   2. Có: thực hiện xác thực token với server. nếu không hợp lệ, xoá token và đẩy về trang Đăng nhập, nếu hợp lệ thì trả về giao diện (children).
3. Trong Layout của Auth (Đăng nhập, Đăng ký,..) thực hiện kiểm tra nếu có access token thì đẩy về Trang chủ, nơi được bọc bởi ProtectedRoute để xác thực token.

Ví dụ code demo cài đặt ProtectedRoute:

`ProtectedRoute.jsx`

```javascript
import {useEffect} from 'react';
import {fetchUserInfo} from 'action directory';
import {useSelector, useDispatch} from 'react-redux';
import {useNavigate} from 'react-router-dom';

const ProtectedRoute = (props) => {
    const userInfo = useSelector(state => state.userInfoReducer);
    const accessToken = localStorage.getItem('accessToken');
    const dispatch = useDispatch();
    const navigate = useNavigate();
    
    useEffect(() => {
        if(accessToken) {
            // logic authentication token here...
            // example fetch user info with token
            dispatch(fetchUserInfo(accessToken));
        } else {
            navigate('/auth/login');
        }
    }, [navigate, dispatch]);
    
    useEffect(() => {
        if(userInfo.data) {
            if(!userInfo.data.success) {
                localStorage.removeItem('accessToken');
                navigate('/auth/login);
            }
        }
    }, [userInfo, navigate]);
    
    if(!userInfo.data || !userInfo.data.success) return <div>Loading...</div>;
    return props.children;
}
export default ProtectedRoute;
```

`AuthLayout.jsx`

```javascript
import React, {useState} from 'react';
import { Navigate,Outlet } from 'react-router-dom';

const AuthLayout = () => {
  const accessToken = localStorage.getItem('accessToken');
  const [loading, setLoading] = useState(true);
  const navigate = useNavigate();
  
  useEffect(() => {
    if(accessToken) {
      navigate('/home');
    } else {
      setLoading(false);
    }
  }, [setLoading]);
  
  if (loading) return <div>Loading...</div>;
  
  return (
    <div className="auth-container">
       <Outlet/>
    </div>
  )
}

export default AuthLayout;
```

`App.jsx`

```javascript
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import RootLayout from './layouts/Root/RootLayout';
import AuthLayout from './layouts/Auth/AuthLayout';
import './App.scss';

function App() {

  return (
    <BrowserRouter>
      <Routes>
        <Route
           path='/'
           element={
           <ProtectedRoute>
              <RootLayout />
           </ProtectedRoute>
        }
        >
           // route .... 
        </Route>
        <Route path='/auth' element={<AuthLayout />}>
          // route .... 
        </Route>
      </Routes>
    </BrowserRouter>
  )
}

export default App;
```

Dựa vào ví dụ trên, tiếp tục hoàn thiện chức năng cho ứng dụng về phần auth như kiểm soát token hết hạn, logic refresh token,  refresh token hết hạn.
