This commit is contained in:
Primakov Alexandr Alexandrovich 2025-01-23 18:30:45 +03:00
parent 67fa902c75
commit 4b869ffe7a
13 changed files with 208 additions and 43 deletions

51
package-lock.json generated
View File

@ -13,6 +13,7 @@
"@chakra-ui/react": "^2.10.3",
"@eslint/js": "^9.11.0",
"@redux-devtools/extension": "^3.3.0",
"@reduxjs/toolkit": "^2.5.0",
"@stylistic/eslint-plugin": "^2.8.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
@ -28,6 +29,7 @@
"react-redux": "^9.2.0",
"react-router-dom": "^6.23.1",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"typescript-eslint": "^8.6.0"
}
},
@ -2451,6 +2453,40 @@
"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": {
"version": "1.16.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.16.1.tgz",
@ -8621,6 +8657,15 @@
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
"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": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
@ -8737,6 +8782,12 @@
"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": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",

View File

@ -20,6 +20,7 @@
"@chakra-ui/react": "^2.10.3",
"@eslint/js": "^9.11.0",
"@redux-devtools/extension": "^3.3.0",
"@reduxjs/toolkit": "^2.5.0",
"@stylistic/eslint-plugin": "^2.8.0",
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
@ -35,6 +36,7 @@
"react-redux": "^9.2.0",
"react-router-dom": "^6.23.1",
"redux": "^5.0.1",
"redux-thunk": "^3.1.0",
"typescript-eslint": "^8.6.0"
}
}

View File

@ -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 incrementStars = createAction<{ id: string }>(types.INCREMENT_STARS)

View 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('Что-то пошло не так'))
}
}

View File

@ -1,3 +1,8 @@
export const SET_STARS = 'SET_STARS'
export const INCREMENT_STARS = 'INCREMENT_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'

View File

@ -1,3 +1,5 @@
import { createSlice } from '@reduxjs/toolkit'
import * as types from '../const'
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]
if (!reducer) return state
return reducer(state, action)
@ -32,3 +34,15 @@ export const starsReducer = createReducer(initialState, {
[types.INCREMENT_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,
},
})

View 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,
})

View File

@ -0,0 +1 @@
export * as userSelectors from './users'

View File

@ -0,0 +1,8 @@
import { createSelector } from '@reduxjs/toolkit'
import { StoreType } from '../store'
export const rootSelector = createSelector(
(state: StoreType) => state,
(state: StoreType) => state
)

View 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)

View File

@ -1,15 +1,20 @@
import { combineReducers, legacy_createStore } from 'redux'
import { devToolsEnhancer } from '@redux-devtools/extension'
import { configureStore } from '@reduxjs/toolkit'
import { useSelector } from 'react-redux'
import { starsReducer } from './reducers/stars'
import { reducer as usersFetchReducer } from './reducers/users'
export const store = legacy_createStore(
combineReducers({
export const store = configureStore({
reducer: {
stars: starsReducer,
}),
devToolsEnhancer({
name: 'nav2',
}),
)
user: usersFetchReducer,
},
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>

View File

@ -14,10 +14,10 @@ export default () => <App/>
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
rootElement = ReactDOM.createRoot(element)
rootElement.render(<Сomponent/>)
rootElement.render(<Сomponent />)
if(module.hot) {
module.hot.accept('./app', ()=> {

View File

@ -6,40 +6,25 @@ import { Profile } from '../components'
import { users } from '../__data__/users'
import { setStars } from '../__data__/actions/stars'
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(() => {
const [isLoading, setIsLoading] = useState(false)
const [data, setData] = useState(null)
const [error, setError] = useState(null)
// const [isLoading, setIsLoading] = useState(false)
// const [data, setData] = useState(null)
// const [error, setError] = useState(null)
const dispatch = useDispatch()
const [showModal, setShowModal] = useState(false)
const isLoading = useAppSelector(userSelectors.isLoading)
const data = useAppSelector(userSelectors.data)
const error = useAppSelector(userSelectors.error)
useEffect(() => {
const getUser = async () => {
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()
dispatch(getUsers() as unknown as UnknownAction)
}, [])
if(!data || isLoading) return <h1>loading...</h1>