mirror of
https://github.com/myfatemi04/wheelshare-frontend.git
synced 2025-04-21 11:20:17 -04:00
standardize api
This commit is contained in:
parent
179047d672
commit
7e27f35b0a
|
@ -1,43 +0,0 @@
|
||||||
import axios from 'axios';
|
|
||||||
import { APIError } from './error';
|
|
||||||
import { makeAPIGetCall } from './utils';
|
|
||||||
|
|
||||||
// eslint-disable-next-line
|
|
||||||
const dev = process.env.NODE_ENV === 'development';
|
|
||||||
export const API_ENDPOINT = 'http://localhost:5000/api';
|
|
||||||
export const ION_AUTHORIZATION_ENDPOINT = dev
|
|
||||||
? 'https://ion.tjhsst.edu/oauth/authorize?response_type=code&client_id=rNa6n9YSg8ftINdyVPpUsaMuxNbHLo9dh1OsOktR&scope=read&redirect_uri=http%3A%2F%2Flocalhost%3A3000%2Fauth%2Fion%2Fcallback'
|
|
||||||
: 'https://ion.tjhsst.edu/oauth/authorize?response_type=code&client_id=rNa6n9YSg8ftINdyVPpUsaMuxNbHLo9dh1OsOktR&scope=read&redirect_uri=https%3A%2F%2Fwheelshare.space%2Fauth%2Fion%2Fcallback';
|
|
||||||
|
|
||||||
axios.defaults.baseURL = API_ENDPOINT;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param code The code sent by the OAuth provider
|
|
||||||
* @param provider The provider that send the OAuth code
|
|
||||||
*/
|
|
||||||
export async function createSession(
|
|
||||||
code: string,
|
|
||||||
provider: string
|
|
||||||
): Promise<string> {
|
|
||||||
const response = await axios.post('/auth/create_session', {
|
|
||||||
code,
|
|
||||||
provider,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.status === 200) {
|
|
||||||
return response.data.session_id;
|
|
||||||
} else {
|
|
||||||
throw new APIError('/auth/create_session', response.data.error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getMe(): Promise<Carpool.User> {
|
|
||||||
let result = await makeAPIGetCall('/users/@me');
|
|
||||||
return result.data.data;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function getPublicUser(id: string): Promise<Carpool.PublicUser> {
|
|
||||||
let result = await makeAPIGetCall(`/users/${id}`);
|
|
||||||
return result.data.data;
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
export class APIError extends Error {
|
|
||||||
constructor(route: string, message: string) {
|
|
||||||
super('API Error @' + route + ': ' + message);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
export type PlaceDetails = {
|
|
||||||
formattedAddress: string;
|
|
||||||
latitude: number;
|
|
||||||
longitude: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export async function getPlaceDetails(
|
|
||||||
placeId: string
|
|
||||||
): Promise<PlaceDetails | null> {
|
|
||||||
if (placeId == null) {
|
|
||||||
console.warn('placeId was null');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await fetch('http://localhost:5000/api/place/' + placeId);
|
|
||||||
const json = await result.json();
|
|
||||||
|
|
||||||
return json;
|
|
||||||
}
|
|
100
src/api/utils.ts
100
src/api/utils.ts
|
@ -1,100 +0,0 @@
|
||||||
import axios, { AxiosResponse } from 'axios';
|
|
||||||
import getSessionId from '../lib/getSessionId';
|
|
||||||
import { APIError } from './error';
|
|
||||||
|
|
||||||
export function createUrlWithGetParameters(
|
|
||||||
url: string,
|
|
||||||
params?: Record<string, string | undefined>
|
|
||||||
) {
|
|
||||||
if (params) {
|
|
||||||
// Stringify the parameters
|
|
||||||
let stringifiedParameters = '';
|
|
||||||
for (let [name, value] of Object.entries(params)) {
|
|
||||||
if (value) {
|
|
||||||
stringifiedParameters +=
|
|
||||||
encodeURIComponent(name) + '=' + encodeURIComponent(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stringifiedParameters) {
|
|
||||||
url += '?' + stringifiedParameters;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function graphql(query: string, variables?: any) {
|
|
||||||
return new Promise<AxiosResponse>((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.post(
|
|
||||||
'/graphql',
|
|
||||||
{ query, variables },
|
|
||||||
{ headers: { Authorization: 'Bearer ' + getSessionId() } }
|
|
||||||
)
|
|
||||||
.then((successfulResponse) => {
|
|
||||||
resolve(successfulResponse.data.data);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
if (error.response.status === 401) {
|
|
||||||
localStorage.removeItem('session_token');
|
|
||||||
window.location.pathname = '/';
|
|
||||||
} else {
|
|
||||||
reject(new APIError('/api/users/@me', error.response.data.error));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeAPIPostCall(url: string, data?: any) {
|
|
||||||
return new Promise<AxiosResponse>((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.post(url, data, {
|
|
||||||
headers: { Authorization: 'Bearer ' + getSessionId() },
|
|
||||||
})
|
|
||||||
.then((successfulResponse) => resolve(successfulResponse))
|
|
||||||
.catch((error) => {
|
|
||||||
if (error.response?.status === 401) {
|
|
||||||
localStorage.removeItem('session_token');
|
|
||||||
window.location.pathname = '/';
|
|
||||||
} else {
|
|
||||||
reject(new APIError(url, error.response?.data?.error));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeAPIGetCall(
|
|
||||||
url: string,
|
|
||||||
params?: Record<string, string | undefined>
|
|
||||||
) {
|
|
||||||
url = createUrlWithGetParameters(url, params);
|
|
||||||
return new Promise<AxiosResponse>((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.get(url, {
|
|
||||||
headers: { Authorization: 'Bearer ' + getSessionId() },
|
|
||||||
})
|
|
||||||
.then((successfulResponse) => resolve(successfulResponse))
|
|
||||||
.catch((error) => {
|
|
||||||
if (error.response?.status === 401) {
|
|
||||||
localStorage.removeItem('session_token');
|
|
||||||
window.location.pathname = '/';
|
|
||||||
} else {
|
|
||||||
reject(error);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export function makeAPIDeleteCall(url: string) {
|
|
||||||
return new Promise<AxiosResponse>((resolve, reject) => {
|
|
||||||
axios
|
|
||||||
.delete(url, { headers: { Authorization: 'Bearer ' + getSessionId() } })
|
|
||||||
.then((successfulResponse) => resolve(successfulResponse))
|
|
||||||
.catch((error) => {
|
|
||||||
if (error.response?.status === 401) {
|
|
||||||
localStorage.removeItem('session_token');
|
|
||||||
} else {
|
|
||||||
reject(new APIError(url, error.response.data.error));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useCallback, useEffect, useState } from 'react';
|
import { useCallback, useEffect, useState } from 'react';
|
||||||
import { getMe } from '../../api/api';
|
import { getMe } from '../api';
|
||||||
import AuthenticationContext, { AuthState } from './AuthenticationContext';
|
import AuthenticationContext, { AuthState } from './AuthenticationContext';
|
||||||
|
|
||||||
export default function AuthenticationWrapper({
|
export default function AuthenticationWrapper({
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { useContext, useEffect, useState } from 'react';
|
import { useContext, useEffect, useState } from 'react';
|
||||||
import { Redirect, useLocation, useParams } from 'react-router-dom';
|
import { Redirect, useLocation, useParams } from 'react-router-dom';
|
||||||
import AuthenticationContext from './AuthenticationContext';
|
import AuthenticationContext from './AuthenticationContext';
|
||||||
|
import { createSession } from './createSession';
|
||||||
|
|
||||||
export default function Authenticator() {
|
export default function Authenticator() {
|
||||||
const { provider } = useParams<{ provider: string }>();
|
const { provider } = useParams<{ provider: string }>();
|
||||||
|
@ -11,14 +12,7 @@ export default function Authenticator() {
|
||||||
useState<'pending' | 'errored' | 'authenticated'>('pending');
|
useState<'pending' | 'errored' | 'authenticated'>('pending');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('http://localhost:5000/create_session', {
|
createSession(code!)
|
||||||
method: 'post',
|
|
||||||
body: JSON.stringify({ code }),
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.status === 'success') {
|
if (data.status === 'success') {
|
||||||
localStorage.setItem('session_token', data.token);
|
localStorage.setItem('session_token', data.token);
|
||||||
|
|
16
src/components/Authentication/createSession.ts
Normal file
16
src/components/Authentication/createSession.ts
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
export async function createSession(code: string) {
|
||||||
|
const res = await fetch('http://localhost:5000/create_session', {
|
||||||
|
method: 'post',
|
||||||
|
body: JSON.stringify({ code }),
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const json = await res.json();
|
||||||
|
|
||||||
|
return {
|
||||||
|
status: json.status,
|
||||||
|
token: json.token,
|
||||||
|
};
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
import { useEffect, useRef, useState } from 'react';
|
import { useEffect, useRef, useState } from 'react';
|
||||||
import { post } from './api';
|
import { post, removeEventSignup } from './api';
|
||||||
import { green, lightgrey } from './colors';
|
import { green, lightgrey } from './colors';
|
||||||
import latlongdist, { R_miles } from './latlongdist';
|
import latlongdist, { R_miles } from './latlongdist';
|
||||||
import UIButton from './UIButton';
|
import UIButton from './UIButton';
|
||||||
|
@ -306,9 +306,7 @@ export default function Event({ event }: { event: IEvent }) {
|
||||||
(prev.interested === true && interested === false) ||
|
(prev.interested === true && interested === false) ||
|
||||||
(interested === true && prev.placeId !== null && placeId === null)
|
(interested === true && prev.placeId !== null && placeId === null)
|
||||||
) {
|
) {
|
||||||
fetch(`http://localhost:5000/api/events/${event.id}/signup`, {
|
removeEventSignup(event.id).finally(() => setUpdating(false));
|
||||||
method: 'delete',
|
|
||||||
}).finally(() => setUpdating(false));
|
|
||||||
prev.interested = false;
|
prev.interested = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
|
import { getEvents } from './api';
|
||||||
import { IEvent } from './Event';
|
import { IEvent } from './Event';
|
||||||
import EventStream from './EventStream';
|
import EventStream from './EventStream';
|
||||||
|
|
||||||
|
@ -6,9 +7,7 @@ export default function Events() {
|
||||||
const [events, setEvents] = useState<IEvent[]>([]);
|
const [events, setEvents] = useState<IEvent[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('http://localhost:5000/api/events')
|
getEvents().then(setEvents);
|
||||||
.then((res) => res.json())
|
|
||||||
.then(setEvents);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useParams } from 'react-router';
|
import { useParams } from 'react-router';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
|
import { getGroup, getGroupEvents } from './api';
|
||||||
import { IEvent } from './Event';
|
import { IEvent } from './Event';
|
||||||
import EventCreatorLink from './EventCreatorLink';
|
import EventCreatorLink from './EventCreatorLink';
|
||||||
import EventStream from './EventStream';
|
import EventStream from './EventStream';
|
||||||
|
@ -21,14 +22,11 @@ export default function Group() {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
fetch('http://localhost:5000/api/groups/' + id)
|
getGroup(+id)
|
||||||
.then((response) => response.json())
|
|
||||||
.then(setGroup)
|
.then(setGroup)
|
||||||
.finally(() => setLoading(false));
|
.finally(() => setLoading(false));
|
||||||
|
|
||||||
fetch('http://localhost:5000/api/groups/' + id + '/events')
|
getGroupEvents(+id).then(setEvents);
|
||||||
.then((response) => response.json())
|
|
||||||
.then(setEvents);
|
|
||||||
}, [id]);
|
}, [id]);
|
||||||
|
|
||||||
if (!group && !loading) {
|
if (!group && !loading) {
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useCallback, useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
|
import { deleteGroup } from './api';
|
||||||
import { IGroup } from './Group';
|
import { IGroup } from './Group';
|
||||||
import UILink from './UILink';
|
import UILink from './UILink';
|
||||||
import UIPressable from './UIPressable';
|
import UIPressable from './UIPressable';
|
||||||
|
@ -9,9 +10,8 @@ function GroupSettings({ group }: { group: IGroup }) {
|
||||||
const [deletionSuccessful, setDeletionSuccessful] =
|
const [deletionSuccessful, setDeletionSuccessful] =
|
||||||
useState<boolean | null>(null);
|
useState<boolean | null>(null);
|
||||||
|
|
||||||
const deleteGroup = useCallback(() => {
|
const onClickedDelete = useCallback(() => {
|
||||||
fetch('http://localhost:5000/api/groups/' + group.id, { method: 'delete' })
|
deleteGroup(group.id)
|
||||||
.then((res) => res.json())
|
|
||||||
.then(({ status }) => {
|
.then(({ status }) => {
|
||||||
setDeletionSuccessful(status === 'success');
|
setDeletionSuccessful(status === 'success');
|
||||||
})
|
})
|
||||||
|
@ -24,7 +24,7 @@ function GroupSettings({ group }: { group: IGroup }) {
|
||||||
<UISecondaryBox>
|
<UISecondaryBox>
|
||||||
<h1>Settings</h1>
|
<h1>Settings</h1>
|
||||||
{deletionSuccessful !== true && (
|
{deletionSuccessful !== true && (
|
||||||
<UIPressable onClick={deleteGroup}>Delete Group</UIPressable>
|
<UIPressable onClick={onClickedDelete}>Delete Group</UIPressable>
|
||||||
)}
|
)}
|
||||||
{deletionSuccessful !== null &&
|
{deletionSuccessful !== null &&
|
||||||
(deletionSuccessful ? (
|
(deletionSuccessful ? (
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
|
import { getGroups } from './api';
|
||||||
import { IGroup } from './Group';
|
import { IGroup } from './Group';
|
||||||
import GroupCreatorLink from './GroupCreatorLink';
|
import GroupCreatorLink from './GroupCreatorLink';
|
||||||
import GroupJoinerLink from './GroupJoinerLink';
|
import GroupJoinerLink from './GroupJoinerLink';
|
||||||
|
@ -8,9 +9,7 @@ export default function Groups() {
|
||||||
const [groups, setGroups] = useState<IGroup[]>([]);
|
const [groups, setGroups] = useState<IGroup[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
fetch('http://localhost:5000/api/groups')
|
getGroups().then(setGroups);
|
||||||
.then((res) => res.json())
|
|
||||||
.then(setGroups);
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -1,18 +1,70 @@
|
||||||
export function post(path: string, data: any) {
|
export async function post(path: string, data: any) {
|
||||||
return fetch('http://localhost:5000/api' + path, {
|
const res = await fetch('http://localhost:5000/api' + path, {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
body: JSON.stringify(data),
|
body: JSON.stringify(data),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function delete$(path: string, data: any) {
|
export async function delete$(path: string) {
|
||||||
return fetch('http://localhost:5000/api' + path, {
|
const res = await fetch('http://localhost:5000/api' + path, {
|
||||||
method: 'delete',
|
method: 'delete',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function get(path: string) {
|
||||||
|
const res = await fetch('http://localhost:5000/api' + path);
|
||||||
|
return await res.json();
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PlaceDetails = {
|
||||||
|
formattedAddress: string;
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export async function getPlaceDetails(
|
||||||
|
placeId: string
|
||||||
|
): Promise<PlaceDetails | null> {
|
||||||
|
if (placeId == null) {
|
||||||
|
console.warn('placeId was null');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await get('/place/' + placeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function removeEventSignup(eventId: number) {
|
||||||
|
return await delete$(`/events/${eventId}/signup`);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getEvents() {
|
||||||
|
return await get('/events');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGroup(id: number) {
|
||||||
|
return await get('/groups/' + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGroupEvents(id: number) {
|
||||||
|
return await get('/groups/' + id + '/events');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getGroups() {
|
||||||
|
return await get('/groups');
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deleteGroup(id: number) {
|
||||||
|
return await delete$('/groups/' + id);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getMe() {
|
||||||
|
return await get('/users/@me');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { useState, useEffect, useCallback } from 'react';
|
import { useState, useEffect, useCallback } from 'react';
|
||||||
import { getPlaceDetails, PlaceDetails } from '../api/google';
|
import { getPlaceDetails, PlaceDetails } from './api';
|
||||||
import useThrottle from './useThrottle';
|
import useThrottle from './useThrottle';
|
||||||
|
|
||||||
export default function usePlace(placeId: string | null) {
|
export default function usePlace(placeId: string | null) {
|
||||||
|
|
88
src/store.ts
88
src/store.ts
|
@ -1,88 +0,0 @@
|
||||||
import { useEffect, useState } from 'react';
|
|
||||||
|
|
||||||
export type UpdateListener<T> = (newValue: StoreValue<T> | null) => void;
|
|
||||||
|
|
||||||
export type ValueFetcher<T> = (key: string) => Promise<T | null>;
|
|
||||||
|
|
||||||
export type StoreValue<T> = { value: T } | { error: any };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is a general-purpose, subscribable key-value store for content like posts, groups, and spaces.
|
|
||||||
*/
|
|
||||||
export class Store<T> {
|
|
||||||
/**
|
|
||||||
* Stores the internal data. If the value is `null`, then we attempted to fetch the data, but it did not exist.
|
|
||||||
*/
|
|
||||||
private data = new Map<string, StoreValue<T> | null>();
|
|
||||||
private listeners = new Map<string, Set<UpdateListener<T>>>();
|
|
||||||
|
|
||||||
constructor(private fetcher: ValueFetcher<T>) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @param key The key to get the data for
|
|
||||||
* @param forceRefresh If the data already exists, fetch it again anyway
|
|
||||||
*/
|
|
||||||
get(key: string, forceRefresh = false): StoreValue<T> | null {
|
|
||||||
if (!this.data.has(key) || forceRefresh) {
|
|
||||||
this.fetcher(key)
|
|
||||||
.then((value) => {
|
|
||||||
this.set(key, value ? { value } : null);
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
this.set(key, { error });
|
|
||||||
});
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.data.get(key) ?? null;
|
|
||||||
}
|
|
||||||
|
|
||||||
set(key: string, value: StoreValue<T> | null) {
|
|
||||||
if (this.listeners.has(key)) {
|
|
||||||
this.listeners.get(key)?.forEach((callback) => {
|
|
||||||
callback(value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.data.set(key, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
subscribe(key: string, listener: UpdateListener<T>) {
|
|
||||||
if (!this.listeners.has(key)) {
|
|
||||||
this.listeners.set(key, new Set());
|
|
||||||
}
|
|
||||||
|
|
||||||
this.listeners.get(key)?.add(listener);
|
|
||||||
}
|
|
||||||
|
|
||||||
unsubscribe(key: string, listener: UpdateListener<T>) {
|
|
||||||
if (this.listeners.has(key)) {
|
|
||||||
if (this.listeners.get(key)?.has(listener)) {
|
|
||||||
this.listeners.get(key)?.delete(listener);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.warn(
|
|
||||||
'Unsubscribed from',
|
|
||||||
key,
|
|
||||||
'but listener does not exist: ',
|
|
||||||
listener
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useStoredValue<T>(store: Store<T>, key: string) {
|
|
||||||
const [value, setValue] = useState<StoreValue<T> | null>(store.get(key));
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const callback = (value: StoreValue<T> | null) => setValue(value);
|
|
||||||
store.subscribe(key, callback);
|
|
||||||
|
|
||||||
return () => store.unsubscribe(key, callback);
|
|
||||||
}, [key, store]);
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user