Bài 3: Quản lý State của DApp
Bài viết này sẽ cung cấp cho bạn đầy đủ thông tin để quản lý các dữ liệu cho DAp của bạn. Đọc thêm để tìm hiểu ngay!

Sau khi đã thiết kế giao diện và kết nối ví điện tử trên DApp của mình (tham khảo bài 2: Thiết kế giao diện DApp), bạn sẽ cần phải quản lý các dữ liệu liên quan tới DApp đó như: Wallet, Account, Balance, vân vân. Công cụ giúp bạn làm được việc này chính là Redux.
Redux là gì?
Redux là một thư viện Javascript để quản lý state của ứng dụng, thường được sử dụng chung với ReactJS. Redux Toolkit là một thư viện giúp lập trình viên viết Redux dễ dàng và đơn giản hơn.
Ở bài viết này, chúng ta sẽ cùng tìm hiểu nhanh về redux-toolkit và ứng dụng nhanh vào project của mình với một ví dụ đơn giản là tăng hoặc giảm một biến đếm counter.
Link hình: vib-2021-3eca7a19-82be-4c9f-8bfc-cbeac838106b.png
Cài đặt Redux Toolkit và React-Redux
Bước 1. Mở Terminal và nhập lệnh
# NPM
npm install @reduxjs/toolkit react-redux
Bước 2. Khởi tạo store
- Store là 1 object chứa tất cả state toàn cục của ứng dụng.
- Redux-toolkit: sử dụng configureStore để tạo store.
- Tạo một tệp có tên src/store/index.ts. Import API configStore từ Redux-toolkit. Chúng ta sẽ bắt đầu bằng cách tạo Redux store trống và export nó, sau đó định nghĩa App State và Dispatch Types để tham chiếu khi cần sử dụng.
File: src/store/index.ts
import { configureStore } from '@reduxjs/toolkit'
export const store = configureStore({
reducer: {},
})
// Infer the `AppState` and `AppDispatch` types from the store itself
export type AppState = ReturnType<typeof store.getState>
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch
Bước 3. Cung cấp Redux Store vào React App
Sau khi store được tạo, ở src/index.tsx, ta wrap <App/> bên trong một component hỗ trợ của react-redux là Provider. Nhờ đó tất cả component trong <App/> có thể truy cập được store.
File: src/index.tsx
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
import { store } from "store";
import { Provider } from "react-redux";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById("root")
Bước 4. Tạo Redux State Slice
- Tạo một file mới đặt tên src/store/counter.reducer.ts
- File này import createSlice API từ Redux-toolkit. Redux-toolkit được sử dụng nhằm kết hợp giữa actions và reducers lại với nhau. Đó chính là nhiệm vụ của slice, được thực thi bằng hàm createSlice.
File: src/store/counter.reducer.ts
import { createSlice } from "@reduxjs/toolkit";
// Define a type for the slice state
interface CounterState {
value: number;
}
// Define the initial state using that type
const initialState: CounterState = {
value: 0
};
export const counterSlice = createSlice({
name: "counter",
// `createSlice` will infer the state type from the `initialState` argument
initialState,
reducers: {
increment: (state) => {
state.value += 1;
},
decrement: (state) => {
state.value -= 1;
},
}
});
export const { increment, decrement } = counterSlice.actions;
export default counterSlice.reducer;
Bước 5. Thêm Slice Reducers vào Store
- Chúng ta cần import counterReducer từ "./counter.reducer" đã được tạo ở trên và thêm nó vào store.
- Bằng việc định nghĩa field bên trong biến reducer, chúng ta đã nói với store là hãy dùng các các slice reducer function để xử lý các cập nhật của state đó.
File: src/store/index.ts
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./counter.reducer";
export const store = configureStore({
reducer: {
counter: counterReducer
}
});
// Infer the `AppState` and `AppDispatch` types from the store itself
export type AppState = ReturnType<typeof store.getState>;
// Inferred type: {counter: counterState}
export type AppDispatch = typeof store.dispatch;
Bước 6. Sử dụng redux tại component
Ở src/components/counter/index.tsx ta sẽ:
- Sử dụng useSelector của react-redux để lấy state counter từ store.
- Sử dụng useDispatch để trả về function dispatch, truyền increment và decrement vào dispatch để gọi 2 action này.
File: src/components/counter/index.tsx
import { useDispatch, useSelector } from "react-redux";
import { AppState, AppDispatch } from "store";
import { increment, decrement } from "store/counter.reducer";
const Counter = () => {
const count = useSelector((state: AppState) => state.counter.value);
const dispatch = useDispatch<AppDispatch>();
return (
<div>
<div>Counter</div>
<button onClick={() => dispatch(increment())}>Increase Counter</button>
<button onClick={() => dispatch(decrement())}>Decrease Counter</button>
<p>Counter value: {count}</p>
</div>
);
};
export default Counter;
Ứng dụng vào project: Tạo Wallet Reducer
Bước 1. Cài đặt React-Redux và Redux Toolkit
Bước 2. Khởi tạo store
File: src/store/index.ts
import { configureStore } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {}
});
// Infer the `AppState` and `AppDispatch` types from the store itself
export type AppState = ReturnType<typeof store.getState>;
// Inferred type: {wallet: WalletState}
export type AppDispatch = typeof store.dispatch;
Bước 3. Cung cấp Redux Store vào React App
File: src/index.tsx
import { Provider } from "react-redux";
import "./index.css";
import App from "./App";
import { WalletKitProvider } from "@gokiprotocol/walletkit";
import { store } from "store";
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<WalletKitProvider
defaultNetwork="devnet"
app={{
name: "My App"
}}
>
<App />
</WalletKitProvider>
</Provider>
</React.StrictMode>,
document.getElementById("root")
);
Bước 4. Tạo Redux State Slice
File: src/store/wallet.reducer.ts
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
export interface WalletState {
walletAddress: string;
balance: number;
}
// Define the initial state using that type
const initialState: WalletState = {
walletAddress: "",
balance: 0
};
export const walletSlice = createSlice({
name: "wallet",
initialState,
reducers: {
// Use the PayloadAction type to declare the contents of `action.payload`
setWalletInfo: (state, action: PayloadAction<WalletState>) => {
state.walletAddress = action.payload.walletAddress;
state.balance = action.payload.balance;
}
}
});
export const { setWalletInfo } = walletSlice.actions;
export default walletSlice.reducer;
Bước 5. Thêm Slice Reducers vào Store
import { configureStore } from "@reduxjs/toolkit";
import walletReducer from "store/wallet.reducer";
export const store = configureStore({
reducer: {
wallet: walletReducer
}
});
// Infer the `AppState` and `AppDispatch` types from the store itself
export type AppState = ReturnType<typeof store.getState>;
// Inferred type: {wallet: WalletState}
export type AppDispatch = typeof store.dispatch;
Bước 6. Sử dụng redux tại component
File: src/App.tsx
import { useCallback, useEffect } from "react";
import { useDispatch } from "react-redux";
import {
useWalletKit,
useSolana,
useConnectedWallet
} from "@gokiprotocol/walletkit";
import { Button, Col, Row, Space } from "antd";
import WalletInfo from "components/walletInfo";
import Transfer from "components/transfer";
import { AppDispatch } from "store";
import { setWalletInfo, WalletState } from "store/wallet.reducer";
import "./App.css";
function App() {
// Goki hooks
const wallet = useConnectedWallet();
const { connect } = useWalletKit();
const { disconnect, providerMut } = useSolana();
const dispatch = useDispatch<AppDispatch>();
const fetchBalance = useCallback(async () => {
// TODO: fetch balance
let walletInfo: WalletState = {
walletAddress: wallet?.publicKey.toBase58() || "",
balance: 0
};
if (wallet && providerMut) {
walletInfo.balance = await providerMut.connection.getBalance(
wallet.publicKey
);
}
dispatch(setWalletInfo(walletInfo));
}, [providerMut, wallet]);
useEffect(() => {
fetchBalance();
}, [fetchBalance]);
return (
<Row justify="center" gutter={[24, 24]}>
<Col span={12}>
<Row gutter={[24, 24]}>
<Col span={24} style={{ paddingTop: "50px" }}>
<WalletInfo />
</Col>
{/* Button connect wallet */}
<Col span={24} style={{ textAlign: "center" }}>
{wallet ? (
<Space>
<Button type="primary" onClick={disconnect}>
Disconnect
</Button>
<Button type="primary" onClick={fetchBalance}>
Update Wallet
</Button>
</Space>
) : (
// Call connectWallet function when click Button
<Button type="primary" onClick={connect}>
Connect Wallet
</Button>
)}
</Col>
<Col span={24}>
<Transfer />
</Col>
</Row>
</Col>
</Row>
);
}
export default App;
File: src/components/walletInfo.tsx
import { Card, Col, Row, Typography } from "antd";
import { useSelector } from "react-redux";
import { AppState } from "store";
const WalletInfo = () => {
const {
wallet: { walletAddress, balance }
} = useSelector((state: AppState) => state);
return (
<Card>
<Row gutter={[24, 24]}>
{/* Wallet address */}
<Col span={24}>
<Typography.Text>Account Address: {walletAddress}</Typography.Text>
</Col>
{/* Wallet balance */}
<Col span={24}>
<Typography.Text>Balance: {balance}</Typography.Text>
</Col>
</Row>
</Card>
);
};
export default WalletInfo;
File: src/components/transfer.tsx
import { useSelector } from "react-redux";
import { Card, Col, Row, Typography } from "antd";
import { AppState } from "store";
const Transfer = () => {
const {
wallet: { walletAddress, balance }
} = useSelector((state: AppState) => state);
return (
<Card title="Transfer">
<Row gutter={[24, 24]}>
{/* Sender address */}
<Col span={24}>
<Typography.Text>Sender Address: {walletAddress}</Typography.Text>
</Col>
{/* Sender balance */}
<Col span={24}>
<Typography.Text>Sender Balance: {balance}</Typography.Text>
</Col>
</Row>
</Card>
);
};
export default Transfer;
Đây sẽ là giao diện khi chưa kết nối wallet:
Đây là giao diện khi kết nối wallet:
Và đây là giao diện khi đã kết nối thành công wallet:
Dữ liệu wallet sẽ được lưu ở global store. Bất cứ component nào (ví dụ như Transfer) cần sử dụng đến dữ liệu này sẽ có thể truy cập để sử dụng, cũng như được cập nhật mới data khi store thay đổi.
Về Sentre Protocol
Sentre Protocol là nền tảng mở All-in-One hoạt động trên mạng lưới Solana, cung cấp DApp Store và giao thức mở giúp tích lũy thanh khoản. Với Sentre:
- Người dùng có thể cài đặt các DApp yêu thích của mình và trải nghiệm thế giới DeFi trên một nền tảng duy nhất;
- Lập trình viên và đối tác có thể phát hành DApp thông qua Sen Store, tận dụng tài nguyên có sẵn và tự do đóng góp cho nền tảng.