<

!

DOCTYPE

html

>

<

html

lang

=

“en”

>

 

<

head

>

    

<

meta

charset

=

“UTF-8”

/

>

    

<

meta

http

equiv

=

“X-UA-Compatible”

content

=

“IE=edge”

/

>

    

<

meta

name

=

“viewport”

content

=

“width=device-width, initial-scale=1.0”

/

>

    

<

title

>

ReactJS

<

/

title

>

    

<style>

        

table

{

            

border-collapse

:

collapse

;

        

}

 

        

th,

        td

{

            

padding

:

0

0.5rem

;

            

border

:

1px

solid

black

;

        

}

    

</style>

<

/

head

>

 

<

body

>

    

<

div

id

=

“root”

>

<

/

div

>

    

<script

src

=

“https://cdnjs.cloudflare.com/ajax/libs/redux/4.1.0/redux.js”

>

</script>

    

<script

src

=

“https://cdnjs.cloudflare.com/ajax/libs/react-redux/7.2.4/react-redux.js”

>

</script>

 

    

<

!

===

===

===

==

src

/

utils

.

js

===

===

===

==

>

    

<script>

        

class

Uuid

{

            

s4

(

)

{

                

return

Math

.

floor

(

(

1

+

Math

.

random

(

)

)

*

0x10000

)

                    

.

toString

(

16

)

                    

.

substring

(

1

)

;

            

}

            

s16

(

)

{

                

return

this

.

s4

(

)

+

this

.

s4

(

)

+

this

.

s4

(

)

+

this

.

s4

(

)

;

            

}

        

}

        

let

uuid

=

new

Uuid

(

)

;

    

</script>

 

    

<

!

===

===

===

==

slices

/

listStudent_slice

.

js

===

===

===

==

>

    

<script>

        

//Dùng RTK.createSlice khai báo tất cả State, Reducers và Actions luôn

        

let

listStudent_local

=

JSON

.

parse

(

localStorage

.

getItem

(

‘listStudent’

)

)

||

{

data

:

[

]

,

loading

:

false

}

;

        

const

listStudent_slice

=

RTK

.

createSlice

(

{

            

name

:

‘listStudent’

,

            

initialState

:

listStudent_local

,

            

reducers

:

{

                

action_saveStudent

(

state

,

action

)

{

                    

if

(

!

action

.

payload

.

id

)

{

                        

let

new_student

=

{

                            

id

:

uuid

.

s16

(

)

,

                            

username

:

action

.

payload

.

username

,

                            

email

:

action

.

payload

.

email

,

                        

}

;

                        

state

.

data

.

push

(

new_student

)

;

 

                    

}

else

{

                        

let

find_student

=

state

.

data

.

find

(

(

{

id

}

)

=

>

id

==

action

.

payload

.

id

)

;

                        

find_student

.

username

=

action

.

payload

.

username

;

                        

find_student

.

email

=

action

.

payload

.

email

;

                    

}

                    

localStorage

.

setItem

(

‘listStudent’

,

JSON

.

stringify

(

state

)

)

;

                    

return

state

;

                

}

,

                

action_deleteStudent

(

state

,

action

)

{

                    

state

.

data

=

state

.

data

.

filter

(

(

{

id

}

)

=

>

id

!=

action

.

payload

)

;

                    

localStorage

.

setItem

(

‘listStudent’

,

JSON

.

stringify

(

state

)

)

;

                    

return

state

;

                

}

,

                

action_setListStudent

(

state

,

action

)

{

                    

state

.

data

=

action

.

payload

.

map

(

item

=

>

(

{

                        

username

:

item

.

username

,

                        

email

:

item

.

email

,

                        

id

:

item

.

id

,

                    

}

)

)

                    

state

.

loading

=

false

;

                    

localStorage

.

setItem

(

‘listStudent’

,

JSON

.

stringify

(

state

)

)

;

                    

return

state

;

                

}

,

                

//action này nhận nhiệm vụ, nhưng không làm gì,

                

//nghĩa là khi đi qua nó, middleware nhận ra nó thì sẽ làm việc gì đó khác

                

action_saga_getStudents

(

state

,

action

)

{

                    

state

.

loading

=

true

;

                    

return

state

;

                

}

,

            

}

        

}

)

;

        

// export ra các actions, reducer tương ứng 😀

        

const

{

action_saveStudent

,

action_deleteStudent

,

action_setListStudent

,

action_saga

_

getStudents

}

=

listStudent_slice

.

actions

;

        

const

listStudent_reducer

=

listStudent_slice

.

reducer

;

    

</script>

 

    

<

!

===

===

===

==

src

/

api

/

studentAPI

===

===

===

==

>

    

<script

type

=

“text/babel”

>

        

//API cần để riêng 1 file

        

// Hàm này được button dispatch lên, trong quá trình chạy, nó lại dispatch 1 action khác

        

const

getStudentsOnline

=

(

url

)

=

>

async

(

dispatch

)

=

>

{

            

console

.

log

(

“url: “

,

url

)

;

            

let

response

=

await

fetch

(

url

)

;

            

let

responseJSON

=

await

response

.

json

(

)

;

            

dispatch

(

action_setListStudent

(

responseJSON

)

)

;

        

}

    

</script>

 

    

<script>

        

// Các Effect Creator của saga hay gặp

        

// takeEvery: luôn chạy

        

// takeLatest: chạy action cuối, các action bị ghi đè sẽ cancel

        

// takeLeading: ngược lại, chạy action đầu, các action sau định ghi đè thì bị cancel

        

// debounce, throttle, fork, retry, put   …

        

const

productSaga

=

function

*

(

)

{

            

//Các saga thông thường chỉ chạy 1 lần khi khởi động được rootSaga chạy

            

//Sử dụng takeEvery để lắng nghe liên tục và bắt các sự kiện khi có action gửi lên, chứ saga này không chạy lại nữa

            

// => (‘*’) => lắng nghe mọi action

 

            

// console.log(“Product saga”);

            

// yield ReduxSaga.effects.takeEvery(‘*’, handle_getProductsOnline)

        

}

 

        

const

handle_getStudentsOnline

=

function

*

(

action

)

{

            

console

.

log

(

‘Watting 1s’

,

action

)

;

            

yield

ReduxSaga

.

effects

.

delay

(

1000

)

            

console

.

log

(

‘Watiing done’

)

;

            

let

url

=

action

.

payload

;

            

let

response

=

yield

fetch

(

url

)

;

            

let

responseJSON

=

yield

response

.

json

(

)

;

            

yield

ReduxSaga

.

effects

.

put

(

action_setListStudent

(

responseJSON

)

)

        

}

 

        

const

studentSaga

=

function

*

(

)

{

            

//console.log(“student saga”);

            

// Liên tục lắng nghe 1 action và phản ứng với nó bằng takeEvery

            

// action.type được khai báo bằng 2 cách: dùng chuỗi: “reducer_name/action_function_name” hoặc “action_function().type”

            

// => Khi gặp action.type ==> chạy hàm phản ứng “logStudent”

            

// yield ReduxSaga.effects.takeEvery(‘listStudent/action_deleteStudent’, logStudent)

            

yield

ReduxSaga

.

effects

.

takeEvery

(

action_saga_getStudents

(

)

.

type

,

handle_getStudentsOnline

)

        

}

        

const

rootSaga

=

function

*

(

)

{

            

// console.log(“Root saga”);

            

// RootSaga sẽ khởi chạy tất cả các saga khác lần đầu tiên khi khởi động

            

yield

ReduxSaga

.

effects

.

all

(

[

productSaga

(

)

,

studentSaga

(

)

]

)

        

}

    

</script>

 

    

<

!

===

===

===

==

src

/

store

.

js

===

===

===

==

>

    

<script>

        

//Khai báo sagaMiddleware (xem thêm trên trang chủ cách khai báo khi sử dụng npm install)

        

const

sagaMiddleware

=

ReduxSaga

.

default

(

)

;

 

        

//Khai báo store với ReduxToolkit như ở dưới đây

        

//ReduxToolkit đã cài đặt sẵn middlewareThunk và Redux devtools extension rồi, ko cần cài đặt gì thêm nữa

        

const

store

=

RTK

.

configureStore

(

{

            

reducer

:

{

                

listStudent

:

listStudent_reducer

,

                

//product: product_reducer,

            

}

,

            

middleware

:

(

getDefaultMiddleware

)

=

>

                

// Có 3 middleware ban đầu của Redux-Saga (mặc định là true) (chưa test đc chức năng serializableCheck)

                

getDefaultMiddleware

(

{

                    

thunk

:

true

,

                    

serializableCheck

:

true

,

                    

immutableCheck

:

true

,

                

}

)

.

concat

(

sagaMiddleware

)

,

  

//sài thêm “sagaMiddleware”

            

devTools

:

true

,

//default là true (có dùng devTools không)

        

}

)

 

        

// Khởi chạy rootSaga sau khi được mount vào Store

        

sagaMiddleware

.

run

(

rootSaga

)

;

    

</script>

 

    

<

!

===

===

===

==

components

/

App

.

js

===

===

===

==

>

    

<script

type

=

“text/babel”

>

        

function

App

(

props

)

{

            

//Sử dụng useSelector và useDispatch sẽ tiện hơn rất nhiều so với phải dùng HOC

            

const

useAppDispatch

=

ReactRedux

.

useDispatch

(

)

;

            

const

useAppSelector

=

ReactRedux

.

useSelector

;

 

            

const

listStudent

=

useAppSelector

(

state

=

>

state

.

listStudent

)

;

            

const

dispatch_saveStudent

=

(

student

)

=

>

useAppDispatch

(

action_saveStudent

(

student

)

)

;

            

const

dispatch_deleteStudent

=

(

id

)

=

>

useAppDispatch

(

action_deleteStudent

(

id

)

)

;

            

// Đoạn dưới là dispatch 1 function (function này là 1 hàm bất đồng bộ, nó chạy xong rồi lại dispatch 1 hàm khác)

            

const

dispatch_getStudentsOnline

=

(

url

)

=

>

useAppDispatch

(

getStudentsOnline

(

url

)

)

;

            

const

dispatch_saga_getStudents

=

(

url

)

=

>

useAppDispatch

(

action_saga_getStudents

(

url

)

)

;

 

            

const

[

url_fakeStudent

,

setUrl_fakeStudent

]

=

React

.

useState

(

‘https://jsonplaceholder.typicode.com/users’

)

            

const

[

filterName

,

setFilterName

]

=

React

.

useState

(

)

;

            

const

[

student

,

setStudent

]

=

React

.

useState

(

{

username

:

,

email

:

,

id

:

}

)

;

 

            

const

updateState_student

=

(

e

)

=

>

{

                

let

newStudent

=

{

.

.

.

student

}

;

                

newStudent

[

e

.

target

.

name

]

=

e

.

target

.

value

;

                

setStudent

(

newStudent

)

            

}

;

 

            

const

onSaveStudent

=

(

e

)

=

>

{

                

if

(

!

student

.

username

||

!

student

.

email

)

return

;

                

dispatch_saveStudent

(

student

)

;

                

setStudent

(

{

username

:

,

email

:

,

id

:

}

)

;

            

}

;

 

            

return

(

                

<

div

>

                    

<

button

onClick

=

{

(

)

=

>

{

setStudent

(

{

username

:

,

email

:

,

id

:

}

)

}

}

>

New

Student

<

/

button

>

<

br

/

>

                    

<

input

type

=

“text”

placeholder

=

“username”

name

=

“username”

                        

value

=

{

student

.

username

}

                        

onChange

=

{

updateState_student

}

                    

/

>

<

br

/

>

                    

<

input

                        

type

=

“text”

placeholder

=

“email”

name

=

“email”

                        

value

=

{

student

.

email

}

                        

onChange

=

{

updateState_student

}

                    

/

>

<

br

/

>

                    

<

button

onClick

=

{

onSaveStudent

}

>

                        

{

student

.

id

?

‘Update Student’

:

‘Add Student’

}

                    

<

/

button

>

<

br

/

>

<

br

/

>

                    

<

input

type

=

“text”

placeholder

=

“Search …”

name

=

“txtFilterName”

                        

value

=

{

filterName

}

                        

onChange

=

{

(

e

)

=

>

{

setFilterName

(

e

.

target

.

value

)

}

}

                    

/

>

                    

<

table

>

                        

<

thead

>

                            

<

tr

>

                                

<

th

>

#

<

/

th

>

                                

<

th

>

Username

<

/

th

>

                                

<

th

>

Email

<

/

th

>

                                

<

th

>

Action

<

/

th

>

                            

<

/

tr

>

                        

<

/

thead

>

                        

<

tbody

>

                            

{

                                

listStudent

.

data

                                    

.

filter

(

(

item

)

=

>

item

.

username

.

includes

(

filterName

)

)

                                    

.

map

(

(

item

,

index

)

=

>

{

                                        

return

(

                                            

<

tr

key

=

{

item

.

id

}

>

                                                

<

td

>

{

index

}

<

/

td

>

                                                

<

td

>

{

item

.

username

}

<

/

td

>

                                                

<

td

>

{

item

.

email

}

<

/

td

>

                                                

<

td

>

                                                    

<

button

onClick

=

{

(

e

)

=

>

{

setStudent

(

{

.

.

.

item

}

)

}

}

>

S

a

<

/

button

>

                                                    

<

button

onClick

=

{

(

e

)

=

>

{

dispatch_deleteStudent

(

item

.

id

)

}

}

>

X

ó

a

<

/

button

>

                                                

<

/

td

>

                                            

<

/

tr

>

                                        

)

;

                                    

}

)

                            

}

                        

<

/

tbody

>

                    

<

/

table

>

<

br

/

>

                    

<

select

value

=

{

url_fakeStudent

}

onChange

=

{

(

e

)

=

>

{

setUrl_fakeStudent

(

e

.

target

.

value

)

}

}

>

                        

<

option

value

=

“https://jsonplaceholder.typicode.com/users”

>

https

:

//jsonplaceholder.typicode.com/users</option>

                        

<

option

value

=

“https://fakestoreapi.com/users?limit=10”

>

https

:

//fakestoreapi.com/users?limit=10</option>

                    

<

/

select

>

<

br

/

>

                    

<

button

onClick

=

{

e

=

>

{

dispatch_getStudentsOnline

(

url_fakeStudent

)

}

}

>

Get

Student

Online

<

/

button

>

  

<

br

/

>

                    

<

button

onClick

=

{

e

=

>

{

dispatch_saga_getStudents

(

url_fakeStudent

)

}

}

>

Get

Student

Saga

<

/

button

>

                    

{

listStudent

.

loading

&&

<

img

src

=

“https://vcdn-ione.vnecdn.net/2016/07/13/loading-256-0001-4566-1468383063.gif”

width

=

“12px”

/

>

}

                

<

/div>

            );

        }

    </s

cript

>

 

    

<

!

===

===

===

==

src

/

index

.

js

===

===

===

==

>

    

<script

type

=

“text/babel”

>

        

//Connect React và Redux bằng Provider, với store=store đã được khai báo ở trên

        

const

Provider

=

ReactRedux

.

Provider

;

        

ReactDOM

.

render

(

            

<

Provider

store

=

{

store

}

>

                

<

App

/

>

            

<

/Provider>,

            document.getElementById(‘root’)

        );

    </s

cript

>

<

/

body

>

 

<

/

html

>