From fd0eb66563f173debb5aaad31ae3d2c5336b2b0a Mon Sep 17 00:00:00 2001 From: Primakov Alexandr Alexandrovich Date: Tue, 12 Nov 2024 20:06:45 +0300 Subject: [PATCH] useSyncExternalStore --- lib/store.ts | 16 ++++++++ src/__data__/context.ts | 6 +++ src/__data__/users.ts | 25 +++++++++++ src/app.tsx | 30 +++++++++++--- src/components/profile/from.tsx | 3 +- src/components/profile/profile.tsx | 66 +++++++++++++++++++++++------- src/components/stars.tsx | 19 +++++---- src/hooks.ts | 31 ++++++++++++++ src/pages/friends.tsx | 29 +++---------- 9 files changed, 172 insertions(+), 53 deletions(-) create mode 100644 lib/store.ts create mode 100644 src/__data__/context.ts create mode 100644 src/__data__/users.ts create mode 100644 src/hooks.ts diff --git a/lib/store.ts b/lib/store.ts new file mode 100644 index 0000000..5dfcc38 --- /dev/null +++ b/lib/store.ts @@ -0,0 +1,16 @@ +export const createStore = (initialState) => { + let state = initialState + const listeners = new Set<() => void>() + + return { + getState: () => state, + subscribe: (listener) => { + listeners.add(listener) + return () => {listeners.delete(listener)} + }, + setState: (newState) => { + state = newState + listeners.forEach((listener) => listener()) + }, + } +} diff --git a/src/__data__/context.ts b/src/__data__/context.ts new file mode 100644 index 0000000..bdfbc57 --- /dev/null +++ b/src/__data__/context.ts @@ -0,0 +1,6 @@ +import { createContext } from 'react' + +export const stars = createContext<{ + stars: Record + setStar: (userId: string, rate: number) => void +}>(null) diff --git a/src/__data__/users.ts b/src/__data__/users.ts new file mode 100644 index 0000000..08e7a38 --- /dev/null +++ b/src/__data__/users.ts @@ -0,0 +1,25 @@ +import { createStore } from '../../lib/store' + +export const users = { + 'some-user-id': { + id: 'some-user-id', + name: 'alexandr', + surname: null, + email: null, + rated: 3, + avatar: + 'https://www.gravatar.com/avatar/6529e885535ef67a3fad810ad71167c2c03f79480936e9b3a714731753cbb47e?d=robohash', + friends: [{ id: '2' }], + }, + '2': { + id: '2', + name: 'not alexandr', + surname: null, + email: null, + rated: 2, + avatar: 'https://www.gravatar.com/avatar/6e?d=robohash', + friends: [{ id: 'some-user-id' }], + }, +} + +export const usersStore = createStore(users) diff --git a/src/app.tsx b/src/app.tsx index 221e6c2..83362cf 100644 --- a/src/app.tsx +++ b/src/app.tsx @@ -1,16 +1,34 @@ -import React from 'react' +import React, { useCallback, useState } from 'react' import { BrowserRouter } from 'react-router-dom' import { ChakraProvider } from '@chakra-ui/react' import { Dashboard } from './dashboard' +import { stars as starsContext } from './__data__/context' +import { users } from './__data__/users' const App = () => { + const [stars, setStar] = useState( + Object.entries(users).reduce( + (acc, [id, user]) => ({ ...acc, [id]: user.rated }), + {}, + ), + ) + + const updateStar = useCallback((userId: string, rate: number) => + setStar((state) => ({ ...state, [userId]: rate })), [setStar]) return ( - - - - - + + + + + + + ) } diff --git a/src/components/profile/from.tsx b/src/components/profile/from.tsx index 98f7ebd..9c522e2 100644 --- a/src/components/profile/from.tsx +++ b/src/components/profile/from.tsx @@ -27,9 +27,8 @@ export const FormTest = () => { }, []) const onSibmit = ({ name, age }) => { - console.log(1111111, name, age) + // console.log(1111111, name, age) } - console.log(1111111, 22222, watch().name) return ( & { id: string @@ -29,8 +31,6 @@ type User = Record & { const features = getFeatures('nav2') - - export const Profile = ({ user, isLink, @@ -40,7 +40,7 @@ export const Profile = ({ isLink?: boolean title?: string }) => { - const [rated, setRated] = useState(user.rated || 0) + // const [rated, setRated] = useState(user.rated || 0) return ( @@ -60,14 +60,23 @@ export const Profile = ({ {features['stars'] && ( )} - {!isLink && features['buttons'] && } + {!isLink && + features['buttons'] && + user.friends?.map((friend) => ( + + ))} ) } @@ -116,17 +125,44 @@ const Form = memo<{ initialState: string; onSubmit(value: string): void }>( Form.displayName = 'FormMemo' -const Counter = ({ value, setValue, horiaontal = false }) => { - const Wrapper = horiaontal ? HStack : VStack +const withStars = + (Component) => + ({ userId }) => { + const { stars, setStar } = useContext(starsContext) + + const addStar = useCallback( + () => setStar(userId, stars[userId] + 1), + [userId, setStar], + ) + const subStar = useCallback( + () => setStar(userId, stars[userId] - 1), + [userId, setStar], + ) + + return ( + + ) + } + +const Counter = ({ stars, addStar, subStar, userId }) => { + const { rate, setUserRate } = useUsers((state) => state[userId].rated) + console.log(userId) return (
- - {value} + + {rate} - - - + + +
) } + +const CounterWithStars = withStars(Counter) diff --git a/src/components/stars.tsx b/src/components/stars.tsx index 45f5b66..feeee6e 100644 --- a/src/components/stars.tsx +++ b/src/components/stars.tsx @@ -1,6 +1,8 @@ import { HStack, Icon } from '@chakra-ui/react' import { FaRegStar, FaStar } from 'react-icons/fa6' import React, { useMemo, useState } from 'react' +import { stars } from '../__data__/context' +import { useUsers } from '../hooks' const useStars = (currentRated, starsCount) => { const [rated, setRated] = useState(currentRated) @@ -18,27 +20,30 @@ const useStars = (currentRated, starsCount) => { export const Stars = ({ count, - rated, - setRated, + // rated, + // setRated, + userId, }: { count: number - rated: number - setRated: (rate: number) => void + userId: string + // rated: number + // setRated: (rate: number) => void }) => { // const { // stars, // setRated: starsSetRated // } = useStars(rated, count) + const { rate, setUserRate } = useUsers(state => state[userId].rated) return ( {Array.from({ length: count }).map((_, index) => - index + 1 > rated ? ( + index + 1 > rate ? ( setRated(index + 1)} + onClick={() => setUserRate(userId, index + 1)} > @@ -47,7 +52,7 @@ export const Stars = ({ key={index} color="orange.400" cursor="pointer" - onClick={() => starsSetRated(index + 1)} + onClick={() => setUserRate(userId, index + 1)} > diff --git a/src/hooks.ts b/src/hooks.ts new file mode 100644 index 0000000..9886aec --- /dev/null +++ b/src/hooks.ts @@ -0,0 +1,31 @@ +import { useEffect, useState, useSyncExternalStore } from 'react' + +import { usersStore } from './__data__/users' + +export const useUsers = (selector) => { + // const [rate, setRate] = useState(selector(usersStore.getState())) + + // useEffect(() => { + // const unsubscribe = usersStore.subscribe(() => { + // setRate(selector(usersStore.getState())) + // }) + + // return unsubscribe + // }, []) + + const rate = useSyncExternalStore(usersStore.subscribe, () => + selector(usersStore.getState()), + ) + + return { + rate, + setUserRate: (userId: string, rate: number) => + usersStore.setState({ + ...usersStore.getState(), + [userId]: { + ...usersStore.getState()[userId], + rated: rate, + }, + }), + } +} diff --git a/src/pages/friends.tsx b/src/pages/friends.tsx index 8098c43..d2f9b4c 100644 --- a/src/pages/friends.tsx +++ b/src/pages/friends.tsx @@ -1,33 +1,16 @@ import { HStack } from '@chakra-ui/react' -import React from 'react' +import React, { memo } from 'react' import { Profile } from '../components' +import { users } from '../__data__/users' -const users = { - 'some-user-id': { - id: 'some-user-id', - name: 'alexandr', - surname: null, - email: null, - rated: 3, - avatar: - 'https://www.gravatar.com/avatar/6529e885535ef67a3fad810ad71167c2c03f79480936e9b3a714731753cbb47e?d=robohash', - }, - '2': { - id: '2', - name: 'not alexandr', - surname: null, - email: null, - rated: 2, - avatar: 'https://www.gravatar.com/avatar/6e?d=robohash', - }, -} - -export const Friends = () => { +export const Friends = memo(() => { return ( ) -} +}) + +Friends.displayName = 'memo(Friends)'