integrate useImmutable

This commit is contained in:
Michael Fatemi 2021-07-15 09:08:59 -04:00
parent 0735d3ed3a
commit a48ad180e0
10 changed files with 104 additions and 124 deletions

7
.env
View File

@ -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/

View File

@ -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 (
<NotificationsProvider>
<GroupsProvider>
<div style={{ padding: '1rem', maxWidth: '100vw' }}>
<div style={style}>
{JSON.stringify(imm)}
{/* Reset button */}
<button onClick={() => imm.z.a++}>Increment</button>
<button onClick={() => imm.z.c.push(imm.z.c.length)}>Push</button>
<BrowserRouter>
<Switch>
<Route

View File

@ -1,11 +1,4 @@
import * as immutable from 'immutable';
import {
createContext,
useCallback,
useEffect,
useMemo,
useState,
} from 'react';
import { createContext, useCallback, useEffect, useMemo } from 'react';
import {
cancelCarpoolInvite,
getCarpool,
@ -13,33 +6,23 @@ import {
sendCarpoolInvite,
} from '../api';
import { useMe } from '../hooks';
import { ICarpool } from '../types';
import UISecondaryBox from '../UI/UISecondaryBox';
import useImmutable from '../useImmutable';
import CarpoolDetails from './CarpoolDetails';
import InvitationsAndRequests from './InvitationsAndRequests';
import MemberList from './MemberList';
class CarpoolState extends immutable.Record({
id: 0,
name: '',
eventId: -1,
event: {
id: -1,
name: '',
formattedAddress: '',
latitude: 0,
longitude: 0,
placeId: '',
},
members: immutable.List<{ id: number; name: string }>(),
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<number, ICarpool['invitations'][0]>;
};
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<CarpoolState>({
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<number, ICarpool['invitations'][0]> = {};
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) {

View File

@ -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]

View File

@ -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() {
}}
>
<h3 style={{ marginBlockEnd: '0' }}>Members</h3>
{members.size > 0 ? (
{members.length > 0 ? (
<div style={{ display: 'flex', flexDirection: 'column' }}>
{membersToShow.map((member) => (
<MemberRow member={member} key={member.id} />

View File

@ -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<number>()
);
const addTentativeInvite = useCallback((userId: number) => {
setTentativeInvites((t) => t.add(userId));
}, []);
const removeTentativeInvite = useCallback((userId: number) => {
setTentativeInvites((t) => t.delete(userId));
}, []);
const [tentativeInvites] = useImmutable<Record<number, boolean>>({});
const refresh = useCallback(() => {
getEvent(id).then(setEvent);
@ -142,8 +132,6 @@ export default function Event({
event,
refresh,
default: false,
addTentativeInvite,
removeTentativeInvite,
tentativeInvites,
signups,
hasCarpool,

View File

@ -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;

View File

@ -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<string, IEventSignup>,
addTentativeInvite: (id: number) => {
console.error('not implemented: addTentativeInvite');
},
removeTentativeInvite: (id: number) => {
console.error('not implemented: removeTentativeInvite');
},
tentativeInvites: immutable.Set<number>(),
tentativeInvites: {} as Record<number, boolean>,
hasCarpool: false,
setHasCarpool: (has: boolean) => {
console.error('not implemented: setHasCarpool');

View File

@ -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 ? (
<CancelIcon
onClick={() => removeTentativeInvite(user.id)}
onClick={() => delete tentativeInvites[user.id]}
style={{ cursor: 'pointer' }}
/>
) : (
<PersonAddIcon
onClick={() => addTentativeInvite(user.id)}
onClick={() => (tentativeInvites[user.id] = true)}
style={{ cursor: 'pointer' }}
/>
))}

View File

@ -14,24 +14,44 @@ function createEdgeForObject<T extends PlainJSObject>(
value: T,
setValue: Dispatch<SetStateAction<T>>
): T {
// @ts-expect-error
const edge: T = {};
for (let [key, keyValue] of Object.entries(value)) {
const set = (next: SetStateAction<typeof keyValue>) => {
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<typeof keyValue>) => {
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<T extends PlainJS>(
value: PlainJSArray<T>,
setValue: Dispatch<SetStateAction<PlainJSArray<T>>>
@ -58,7 +78,8 @@ function createEdgeForArray<T extends PlainJS>(
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<T extends PlainJS>(
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];
}
},
});