thunk
This commit is contained in:
parent
67fa902c75
commit
4b869ffe7a
51
package-lock.json
generated
51
package-lock.json
generated
@ -13,6 +13,7 @@
|
|||||||
"@chakra-ui/react": "^2.10.3",
|
"@chakra-ui/react": "^2.10.3",
|
||||||
"@eslint/js": "^9.11.0",
|
"@eslint/js": "^9.11.0",
|
||||||
"@redux-devtools/extension": "^3.3.0",
|
"@redux-devtools/extension": "^3.3.0",
|
||||||
|
"@reduxjs/toolkit": "^2.5.0",
|
||||||
"@stylistic/eslint-plugin": "^2.8.0",
|
"@stylistic/eslint-plugin": "^2.8.0",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
@ -28,6 +29,7 @@
|
|||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^6.23.1",
|
"react-router-dom": "^6.23.1",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
"typescript-eslint": "^8.6.0"
|
"typescript-eslint": "^8.6.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -2451,6 +2453,40 @@
|
|||||||
"redux": "^3.1.0 || ^4.0.0 || ^5.0.0"
|
"redux": "^3.1.0 || ^4.0.0 || ^5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit": {
|
||||||
|
"version": "2.5.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.0.tgz",
|
||||||
|
"integrity": "sha512-awNe2oTodsZ6LmRqmkFhtb/KH03hUhxOamEQy411m3Njj3BbFvoBovxo4Q1cBWnV1ErprVj9MlF0UPXkng0eyg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"immer": "^10.0.3",
|
||||||
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
|
"reselect": "^5.1.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
|
||||||
|
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@reduxjs/toolkit/node_modules/immer": {
|
||||||
|
"version": "10.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
|
||||||
|
"integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/immer"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@remix-run/router": {
|
"node_modules/@remix-run/router": {
|
||||||
"version": "1.16.1",
|
"version": "1.16.1",
|
||||||
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
|
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
|
||||||
@ -8621,6 +8657,15 @@
|
|||||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/redux-thunk": {
|
||||||
|
"version": "3.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
||||||
|
"integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"redux": "^5.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/reflect.getprototypeof": {
|
"node_modules/reflect.getprototypeof": {
|
||||||
"version": "1.0.6",
|
"version": "1.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
|
||||||
@ -8737,6 +8782,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/reselect": {
|
||||||
|
"version": "5.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||||
|
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.8",
|
"version": "1.22.8",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
"@chakra-ui/react": "^2.10.3",
|
"@chakra-ui/react": "^2.10.3",
|
||||||
"@eslint/js": "^9.11.0",
|
"@eslint/js": "^9.11.0",
|
||||||
"@redux-devtools/extension": "^3.3.0",
|
"@redux-devtools/extension": "^3.3.0",
|
||||||
|
"@reduxjs/toolkit": "^2.5.0",
|
||||||
"@stylistic/eslint-plugin": "^2.8.0",
|
"@stylistic/eslint-plugin": "^2.8.0",
|
||||||
"@types/react": "^18.3.12",
|
"@types/react": "^18.3.12",
|
||||||
"@types/react-dom": "^18.3.1",
|
"@types/react-dom": "^18.3.1",
|
||||||
@ -35,6 +36,7 @@
|
|||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^6.23.1",
|
"react-router-dom": "^6.23.1",
|
||||||
"redux": "^5.0.1",
|
"redux": "^5.0.1",
|
||||||
|
"redux-thunk": "^3.1.0",
|
||||||
"typescript-eslint": "^8.6.0"
|
"typescript-eslint": "^8.6.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as types from '../const'
|
import { createAction } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
const createAction = <Payload>(type: string) => (payload?: Payload) => ({ type, payload })
|
import * as types from '../const'
|
||||||
|
|
||||||
export const setStars = createAction<{ id: string, value: number }>(types.SET_STARS)
|
export const setStars = createAction<{ id: string, value: number }>(types.SET_STARS)
|
||||||
export const incrementStars = createAction<{ id: string }>(types.INCREMENT_STARS)
|
export const incrementStars = createAction<{ id: string }>(types.INCREMENT_STARS)
|
||||||
|
32
src/__data__/actions/users.ts
Normal file
32
src/__data__/actions/users.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import * as types from '../const'
|
||||||
|
|
||||||
|
import { setStars } from './stars'
|
||||||
|
|
||||||
|
const createAction = <Payload>(type: string) => (payload?: Payload) => ({ type, payload })
|
||||||
|
|
||||||
|
const fetchUsers = createAction(types.USER_FETCH)
|
||||||
|
const success = createAction(types.USER_FETCH_SUCCESS)
|
||||||
|
const error = createAction<string>(types.USER_FETCH_ERROR)
|
||||||
|
export const reset = createAction(types.USER_FETCH_RESET)
|
||||||
|
|
||||||
|
export const getUsers = () => async (dispatch, getState) => {
|
||||||
|
dispatch(fetchUsers())
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/users/')
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
const data = (await response.json()).body
|
||||||
|
dispatch(success(data))
|
||||||
|
|
||||||
|
Object.keys(data).forEach(userId => {
|
||||||
|
dispatch(setStars({ id: userId, value: data[userId].rated }))
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
throw 'Что-то пошло не так'
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
dispatch(error('Что-то пошло не так'))
|
||||||
|
}
|
||||||
|
}
|
@ -1,3 +1,8 @@
|
|||||||
export const SET_STARS = 'SET_STARS'
|
export const SET_STARS = 'SET_STARS'
|
||||||
export const INCREMENT_STARS = 'INCREMENT_STARS'
|
export const INCREMENT_STARS = 'INCREMENT_STARS'
|
||||||
export const DECREMENT_STARS = 'DECREMENT_STARS'
|
export const DECREMENT_STARS = 'DECREMENT_STARS'
|
||||||
|
|
||||||
|
export const USER_FETCH = 'USER_FETCH'
|
||||||
|
export const USER_FETCH_SUCCESS = 'USER_FETCH_SUCCESS'
|
||||||
|
export const USER_FETCH_ERROR = 'USER_FETCH_ERROR'
|
||||||
|
export const USER_FETCH_RESET = 'USER_FETCH_RESET'
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { createSlice } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
import * as types from '../const'
|
import * as types from '../const'
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@ -21,7 +23,7 @@ const changeStars = (value) => (state, action) => ({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
const createReducer = (initialState, reducers) => (state = initialState, action) => {
|
const createReducer = <State>(initialState: State, reducers) => (state = initialState, action): State => {
|
||||||
const reducer = reducers[action.type]
|
const reducer = reducers[action.type]
|
||||||
if (!reducer) return state
|
if (!reducer) return state
|
||||||
return reducer(state, action)
|
return reducer(state, action)
|
||||||
@ -32,3 +34,15 @@ export const starsReducer = createReducer(initialState, {
|
|||||||
[types.INCREMENT_STARS]: changeStars(1),
|
[types.INCREMENT_STARS]: changeStars(1),
|
||||||
[types.DECREMENT_STARS]: changeStars(-1),
|
[types.DECREMENT_STARS]: changeStars(-1),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const slice = createSlice({
|
||||||
|
name: 'stars',
|
||||||
|
initialState: {},
|
||||||
|
reducers: {
|
||||||
|
setStars(state, action) {
|
||||||
|
state[action.payload.id] = action.payload.value
|
||||||
|
},
|
||||||
|
changeStars,
|
||||||
|
},
|
||||||
|
|
||||||
|
})
|
||||||
|
50
src/__data__/reducers/users.ts
Normal file
50
src/__data__/reducers/users.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import * as types from '../const'
|
||||||
|
|
||||||
|
const createReducer = <State>(initialState: State, reducers) => (state = initialState, action): State => {
|
||||||
|
const reducer = reducers[action.type]
|
||||||
|
if (!reducer) return state
|
||||||
|
return reducer(state, action)
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum Statuses {
|
||||||
|
IDLE = 'IDLE',
|
||||||
|
FETCHING = 'FETCHING',
|
||||||
|
SUCCESS = 'SUCCESS',
|
||||||
|
ERROR = 'ERROR',
|
||||||
|
}
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
data: null,
|
||||||
|
status: Statuses.IDLE,
|
||||||
|
error: null as null | string,
|
||||||
|
}
|
||||||
|
|
||||||
|
const startFetching = (state) => ({
|
||||||
|
...state,
|
||||||
|
error: null,
|
||||||
|
status: Statuses.FETCHING,
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchSuccess = (state, action) => ({
|
||||||
|
...state,
|
||||||
|
error: null,
|
||||||
|
data: action.payload,
|
||||||
|
status: Statuses.SUCCESS,
|
||||||
|
})
|
||||||
|
|
||||||
|
const fetchError = (state, action) => ({
|
||||||
|
...state,
|
||||||
|
error: action.payload,
|
||||||
|
status: Statuses.ERROR,
|
||||||
|
})
|
||||||
|
|
||||||
|
const reset = () => ({
|
||||||
|
...initialState
|
||||||
|
})
|
||||||
|
|
||||||
|
export const reducer = createReducer(initialState, {
|
||||||
|
[types.USER_FETCH]: startFetching,
|
||||||
|
[types.USER_FETCH_SUCCESS]: fetchSuccess,
|
||||||
|
[types.USER_FETCH_ERROR]: fetchError,
|
||||||
|
[types.USER_FETCH_RESET]: reset,
|
||||||
|
})
|
1
src/__data__/selectors/index.ts
Normal file
1
src/__data__/selectors/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * as userSelectors from './users'
|
8
src/__data__/selectors/rootSelector.ts
Normal file
8
src/__data__/selectors/rootSelector.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
import { StoreType } from '../store'
|
||||||
|
|
||||||
|
export const rootSelector = createSelector(
|
||||||
|
(state: StoreType) => state,
|
||||||
|
(state: StoreType) => state
|
||||||
|
)
|
12
src/__data__/selectors/users.ts
Normal file
12
src/__data__/selectors/users.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit'
|
||||||
|
|
||||||
|
import { StoreType } from '../store'
|
||||||
|
import { Statuses } from '../reducers/users'
|
||||||
|
|
||||||
|
import { rootSelector } from './rootSelector'
|
||||||
|
|
||||||
|
const usersRootSelector = createSelector(rootSelector, (state: StoreType) => state.user)
|
||||||
|
|
||||||
|
export const isLoading = createSelector(usersRootSelector, (state) => state.status === Statuses.FETCHING)
|
||||||
|
export const data = createSelector(usersRootSelector, (state) => state.data)
|
||||||
|
export const error = createSelector(usersRootSelector, (state) => state.error)
|
@ -1,15 +1,20 @@
|
|||||||
import { combineReducers, legacy_createStore } from 'redux'
|
import { configureStore } from '@reduxjs/toolkit'
|
||||||
import { devToolsEnhancer } from '@redux-devtools/extension'
|
import { useSelector } from 'react-redux'
|
||||||
|
|
||||||
import { starsReducer } from './reducers/stars'
|
import { starsReducer } from './reducers/stars'
|
||||||
|
import { reducer as usersFetchReducer } from './reducers/users'
|
||||||
|
|
||||||
export const store = legacy_createStore(
|
export const store = configureStore({
|
||||||
combineReducers({
|
reducer: {
|
||||||
stars: starsReducer,
|
stars: starsReducer,
|
||||||
}),
|
user: usersFetchReducer,
|
||||||
devToolsEnhancer({
|
},
|
||||||
name: 'nav2',
|
devTools: {
|
||||||
}),
|
name: 'stars',
|
||||||
)
|
},
|
||||||
|
middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
|
||||||
|
})
|
||||||
|
|
||||||
;(window as unknown as { redux: string }).redux = store as unknown as string
|
export type StoreType = ReturnType<typeof store.getState>
|
||||||
|
|
||||||
|
export const useAppSelector = useSelector<StoreType>
|
||||||
|
@ -14,10 +14,10 @@ export default () => <App/>
|
|||||||
|
|
||||||
let rootElement: ReactDOM.Root
|
let rootElement: ReactDOM.Root
|
||||||
|
|
||||||
export const mount = async (Сomponent, element = document.getElementById('app')) => {
|
export const mount = async (Сomponent, element = document.getElementById('app'), extraData) => {
|
||||||
await config
|
await config
|
||||||
rootElement = ReactDOM.createRoot(element)
|
rootElement = ReactDOM.createRoot(element)
|
||||||
rootElement.render(<Сomponent/>)
|
rootElement.render(<Сomponent />)
|
||||||
|
|
||||||
if(module.hot) {
|
if(module.hot) {
|
||||||
module.hot.accept('./app', ()=> {
|
module.hot.accept('./app', ()=> {
|
||||||
|
@ -6,40 +6,25 @@ import { Profile } from '../components'
|
|||||||
import { users } from '../__data__/users'
|
import { users } from '../__data__/users'
|
||||||
import { setStars } from '../__data__/actions/stars'
|
import { setStars } from '../__data__/actions/stars'
|
||||||
import { ProfileEditModal } from '../components/profileEdit'
|
import { ProfileEditModal } from '../components/profileEdit'
|
||||||
|
import { getUsers } from '../__data__/actions/users'
|
||||||
|
import { UnknownAction } from 'redux'
|
||||||
|
import { useAppSelector } from '../__data__/store'
|
||||||
|
import { Statuses } from '../__data__/reducers/users'
|
||||||
|
import { userSelectors } from '../__data__/selectors'
|
||||||
|
|
||||||
export const Friends = memo(() => {
|
export const Friends = memo(() => {
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
// const [isLoading, setIsLoading] = useState(false)
|
||||||
const [data, setData] = useState(null)
|
// const [data, setData] = useState(null)
|
||||||
const [error, setError] = useState(null)
|
// const [error, setError] = useState(null)
|
||||||
const dispatch = useDispatch()
|
const dispatch = useDispatch()
|
||||||
const [showModal, setShowModal] = useState(false)
|
const [showModal, setShowModal] = useState(false)
|
||||||
|
|
||||||
|
const isLoading = useAppSelector(userSelectors.isLoading)
|
||||||
|
const data = useAppSelector(userSelectors.data)
|
||||||
|
const error = useAppSelector(userSelectors.error)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const getUser = async () => {
|
dispatch(getUsers() as unknown as UnknownAction)
|
||||||
setIsLoading(true)
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/users/')
|
|
||||||
|
|
||||||
if (response.ok) {
|
|
||||||
const data = (await response.json()).body
|
|
||||||
setData(data)
|
|
||||||
|
|
||||||
Object.keys(data).forEach(userId => {
|
|
||||||
dispatch(setStars({ id: userId, value: data[userId].rated }))
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
throw 'Что-то пошло не так'
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (e) {
|
|
||||||
setError(e.message)
|
|
||||||
} finally {
|
|
||||||
setIsLoading(false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
getUser()
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
if(!data || isLoading) return <h1>loading...</h1>
|
if(!data || isLoading) return <h1>loading...</h1>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user