From a48ad180e0792ebc6ab80c114ea93537d1be6b01 Mon Sep 17 00:00:00 2001 From: Michael Fatemi Date: Thu, 15 Jul 2021 09:08:59 -0400 Subject: [PATCH] integrate useImmutable --- .env | 7 +- src/components/App.tsx | 10 +++ src/components/Carpool/Carpool.tsx | 93 +++++++++-------------- src/components/Carpool/InvitationList.tsx | 3 +- src/components/Carpool/MemberList.tsx | 4 +- src/components/Event/Event.tsx | 16 +--- src/components/Event/EventCarpools.tsx | 7 +- src/components/Event/EventContext.tsx | 9 +-- src/components/Event/EventSignups.tsx | 16 +--- src/components/useImmutable.ts | 63 ++++++++++----- 10 files changed, 104 insertions(+), 124 deletions(-) diff --git a/.env b/.env index c2a4a2c..9643b21 100644 --- a/.env +++ b/.env @@ -1,5 +1,2 @@ -REACT_APP_API_DOMAIN_LOCAL=http://localhost:5000/ -REACT_APP_API_DOMAIN_DOCN=https://wheelshare-altbackend-2efyw.ondigitalocean.app/ -REACT_APP_API_DOMAIN_WWW=https://api.wheelshare.app/ - -REACT_APP_API_DOMAIN=https://api.wheelshare.app/ +REACT_APP_API_LOCAL=http://localhost:5000/ +REACT_APP_API_PROD=https://api.wheelshare.app/ diff --git a/src/components/App.tsx b/src/components/App.tsx index a12346c..4cb6008 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -3,6 +3,7 @@ import { BrowserRouter, Route, Switch } from 'react-router-dom'; import GroupsProvider from '../state/GroupsProvider'; import NotificationsProvider from '../state/Notifications/NotificationsProvider'; import { useMe } from './hooks'; +import useImmutable from './useImmutable'; import WheelShare from './WheelShare'; import WheelShareLoggedOut from './WheelShareLoggedOut'; @@ -23,11 +24,20 @@ const style: CSSProperties = { export default function App() { const user = useMe(); + const [imm] = useImmutable({ + x: 0, + y: 0, + z: { a: 1, b: 2, c: [0, 1, 2] }, + }); return (
+ {JSON.stringify(imm)} + {/* Reset button */} + + (), - invitations: - immutable.Map< - number, - { isRequest: boolean; user: { id: number; name: string } } - >(), -}) {} +type CarpoolState = { + id: number; + name: string; + event: ICarpool['event']; + members: { id: number; name: string }[]; + invitations: Record; +}; export const CarpoolContext = createContext({ - carpool: new CarpoolState(), + carpool: {} as CarpoolState, sendInvite: (user: { id: number; name: string }) => { console.error('not implemented: sendInvite'); }, @@ -52,36 +35,36 @@ export const CarpoolContext = createContext({ }); export default function Carpool({ id }: { id: number }) { - const [carpool, setCarpool] = useState(new CarpoolState()); + const [carpool, setCarpool] = useImmutable({ + id, + name: '', + event: {} as ICarpool['event'], + members: [], + invitations: {}, + }); useEffect(() => { getCarpool(id).then((carpool) => { - setCarpool( - new CarpoolState({ - id: carpool.id, - name: carpool.name, - eventId: carpool.eventId || carpool.event.id, - event: carpool.event, - members: immutable.List(carpool.members), - invitations: immutable.Map( - carpool.invitations.map((invite) => [invite.user.id, invite]) - ), - }) - ); + const invitationsMap: Record = {}; + carpool.invitations.forEach((invite) => { + invitationsMap[invite.user.id] = invite; + }); + setCarpool({ + id: carpool.id, + name: carpool.name, + event: carpool.event, + members: carpool.members, + invitations: invitationsMap, + }); }); - }, [id]); + }, [id, setCarpool]); const sendInvite = useCallback( (user: { id: number; name: string }) => { if (carpool) { sendCarpoolInvite(id, user.id) .then(() => { - setCarpool((carpool) => - carpool.set( - 'invitations', - carpool.invitations.set(user.id, { isRequest: false, user }) - ) - ); + carpool.invitations[user.id] = { isRequest: false, user }; }) .catch(console.error); } else { @@ -97,22 +80,14 @@ export default function Carpool({ id }: { id: number }) { (user: { id: number; name: string }) => { cancelCarpoolInvite(id, user.id) .then(() => { - setCarpool( - (carpool) => - carpool && { - ...carpool, - invitations: carpool.invitations.filter( - (invite) => invite.user.id !== user.id - ), - } - ); + delete carpool.invitations[user.id]; }) .catch(console.error); }, - [id] + [carpool.invitations, id] ); - const eventId = carpool.eventId; + const eventId = carpool.event.id; const leave = useCallback(() => { if (eventId) { diff --git a/src/components/Carpool/InvitationList.tsx b/src/components/Carpool/InvitationList.tsx index dc8aa2f..95d9fa2 100644 --- a/src/components/Carpool/InvitationList.tsx +++ b/src/components/Carpool/InvitationList.tsx @@ -59,9 +59,8 @@ export default function InvitationList() { const invitedUserIDs = useMemo( () => new Set( - carpool.invitations + Object.values(carpool.invitations) .filter((invitation) => !invitation.isRequest) - .valueSeq() .map((invitation) => invitation.user.id) ), [carpool.invitations] diff --git a/src/components/Carpool/MemberList.tsx b/src/components/Carpool/MemberList.tsx index 6e6a5e0..adf5859 100644 --- a/src/components/Carpool/MemberList.tsx +++ b/src/components/Carpool/MemberList.tsx @@ -24,7 +24,7 @@ export default function MemberList() { const { leave, carpool } = useContext(CarpoolContext); const members = carpool.members; const membersToShow = members.slice(0, shownMembersCount); - const hiddenMemberCount = members.size - membersToShow.size; + const hiddenMemberCount = members.length - membersToShow.length; const { sendCarpoolRequest, cancelCarpoolRequest } = useContext(NotificationsContext); @@ -54,7 +54,7 @@ export default function MemberList() { }} >

Members

- {members.size > 0 ? ( + {members.length > 0 ? (
{membersToShow.map((member) => ( diff --git a/src/components/Event/Event.tsx b/src/components/Event/Event.tsx index 4eb44cb..871b917 100644 --- a/src/components/Event/Event.tsx +++ b/src/components/Event/Event.tsx @@ -1,4 +1,3 @@ -import * as immutable from 'immutable'; import { useCallback, useEffect, useRef, useState } from 'react'; import { green, lightgrey } from '../../lib/colors'; import { @@ -14,6 +13,7 @@ import UILink from '../UI/UILink'; import UIPlacesAutocomplete from '../UI/UIPlacesAutocomplete'; import UISecondaryBox from '../UI/UISecondaryBox'; import UISecondaryHeader from '../UI/UISecondaryHeader'; +import useImmutable from '../useImmutable'; import useThrottle from '../useThrottle'; import EventCarpools from './EventCarpools'; import EventContext from './EventContext'; @@ -49,17 +49,7 @@ export default function Event({ }); const me = useMe(); - const [tentativeInvites, setTentativeInvites] = useState( - immutable.Set() - ); - - const addTentativeInvite = useCallback((userId: number) => { - setTentativeInvites((t) => t.add(userId)); - }, []); - - const removeTentativeInvite = useCallback((userId: number) => { - setTentativeInvites((t) => t.delete(userId)); - }, []); + const [tentativeInvites] = useImmutable>({}); const refresh = useCallback(() => { getEvent(id).then(setEvent); @@ -142,8 +132,6 @@ export default function Event({ event, refresh, default: false, - addTentativeInvite, - removeTentativeInvite, tentativeInvites, signups, hasCarpool, diff --git a/src/components/Event/EventCarpools.tsx b/src/components/Event/EventCarpools.tsx index 788c5be..90c46b5 100644 --- a/src/components/Event/EventCarpools.tsx +++ b/src/components/Event/EventCarpools.tsx @@ -167,7 +167,7 @@ export default function Carpools() { createCarpool({ name: me.name + "'s Carpool", eventId: event.id, - invitedUserIds: tentativeInvites.toArray(), + invitedUserIds: Object.keys(tentativeInvites).map(Number), }) .then(({ id }) => { setCreatedCarpoolId(id); @@ -180,12 +180,11 @@ export default function Carpools() { const tentativeInviteNames = useMemo(() => { if (!signups) return []; - const names = tentativeInvites.map((id) => { + const names = Object.keys(tentativeInvites).map((id) => { const signup = signups[id]; return signup?.user.name; }); - const nonNull = names.filter((n) => n != null); - return nonNull.toArray() as string[]; + return names.filter((n) => n != null); }, [tentativeInvites, signups]); let createCarpoolSection; diff --git a/src/components/Event/EventContext.tsx b/src/components/Event/EventContext.tsx index c946df3..363956c 100644 --- a/src/components/Event/EventContext.tsx +++ b/src/components/Event/EventContext.tsx @@ -1,6 +1,5 @@ import { createContext } from 'react'; import { IEvent, IEventSignup } from '../types'; -import * as immutable from 'immutable'; const EventContext = createContext({ refresh: () => { @@ -9,13 +8,7 @@ const EventContext = createContext({ event: null! as IEvent, default: true, signups: {} as Record, - addTentativeInvite: (id: number) => { - console.error('not implemented: addTentativeInvite'); - }, - removeTentativeInvite: (id: number) => { - console.error('not implemented: removeTentativeInvite'); - }, - tentativeInvites: immutable.Set(), + tentativeInvites: {} as Record, hasCarpool: false, setHasCarpool: (has: boolean) => { console.error('not implemented: setHasCarpool'); diff --git a/src/components/Event/EventSignups.tsx b/src/components/Event/EventSignups.tsx index bb1211f..16c212f 100644 --- a/src/components/Event/EventSignups.tsx +++ b/src/components/Event/EventSignups.tsx @@ -21,12 +21,7 @@ function EventSignup({ }) { const { user, latitude, longitude } = signup; const me = useMe(); - const { - addTentativeInvite, - removeTentativeInvite, - tentativeInvites, - hasCarpool, - } = useContext(EventContext); + const { tentativeInvites, hasCarpool } = useContext(EventContext); const extraDistance = useMemo(() => { if (myPlaceDetails != null && !(latitude === null || longitude === null)) { @@ -66,10 +61,7 @@ function EventSignup({ myPlaceDetails, ]); - const isTentativelyInvited = useMemo( - () => tentativeInvites.has(signup.user.id), - [signup.user.id, tentativeInvites] - ); + const isTentativelyInvited = signup.user.id in tentativeInvites; if (user.id === me?.id) { return null; @@ -98,12 +90,12 @@ function EventSignup({ {!hasCarpool && (isTentativelyInvited ? ( removeTentativeInvite(user.id)} + onClick={() => delete tentativeInvites[user.id]} style={{ cursor: 'pointer' }} /> ) : ( addTentativeInvite(user.id)} + onClick={() => (tentativeInvites[user.id] = true)} style={{ cursor: 'pointer' }} /> ))} diff --git a/src/components/useImmutable.ts b/src/components/useImmutable.ts index 3a2564c..d13500c 100644 --- a/src/components/useImmutable.ts +++ b/src/components/useImmutable.ts @@ -14,24 +14,44 @@ function createEdgeForObject( value: T, setValue: Dispatch> ): T { - // @ts-expect-error - const edge: T = {}; - for (let [key, keyValue] of Object.entries(value)) { - const set = (next: SetStateAction) => { - const v = typeof next === 'function' ? next(keyValue) : next; - setValue((value) => ({ ...value, [key]: v })); - }; + return new Proxy(value, { + set: (target, property, value) => { + setValue((v) => ({ ...v, [property]: value })); - Object.defineProperty(edge, key, { - enumerable: true, - configurable: false, - get: () => createEdge(keyValue, set), - set: (v) => void setValue((value) => ({ ...value, [key]: v })), - }); - } - return edge; + return true; + }, + // @ts-expect-error + get: (target, property: keyof T) => { + const keyValue = target[property]; + const set = (next: SetStateAction) => { + const v = typeof next === 'function' ? next(keyValue) : next; + setValue((value) => ({ ...value, [property]: v })); + }; + + return createEdge(keyValue, set); + }, + deleteProperty: (target, property) => { + setValue((v) => { + const newValue = { ...v }; + // @ts-ignore + delete newValue[property]; + return newValue; + }); + + return true; + }, + }); } +const inPlaceArrayOperations = [ + 'fill', + 'reverse', + 'push', + 'pop', + 'shift', + 'unshift', +]; + function createEdgeForArray( value: PlainJSArray, setValue: Dispatch>> @@ -58,7 +78,8 @@ function createEdgeForArray( if (typeof property === 'number') { return target[property]; } else { - if (typeof target[property] === 'function') { + // @ts-ignore + if (inPlaceArrayOperations.includes(property)) { return function () { const newValue = [...value]; const method = newValue[property]; @@ -67,9 +88,15 @@ function createEdgeForArray( setValue(newValue); return result; }; - } else { - return target[property]; + } else if (typeof target[property] === 'function') { + return function () { + console.log(target[property]); + // @ts-ignore + return target[property].apply(target, arguments); + }; } + + return target[property]; } }, });