diff --git a/.babelrc.js b/.babelrc.js index 1586c37..b5ddf59 100644 --- a/.babelrc.js +++ b/.babelrc.js @@ -1,5 +1,5 @@ -const { NODE_ENV, BABEL_ENV } = process.env -const cjs = NODE_ENV === 'test' || BABEL_ENV === 'commonjs' +const { NODE_ENV, BABEL_ENV } = process.env; +const cjs = NODE_ENV === 'test' || BABEL_ENV === 'commonjs'; module.exports = { presets: [ @@ -28,13 +28,11 @@ module.exports = { '@babel/transform-runtime', { useESModules: !cjs, - version: require('./package.json').dependencies[ - '@babel/runtime' - ] + version: require('./package.json').dependencies['@babel/runtime'], }, ], ].filter(Boolean), assumptions: { enumerableModuleMeta: true, }, -} \ No newline at end of file +}; diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..1b13076 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "printWidth": 100, + "tabWidth": 2, + "singleQuote": true, + "bracketSameLine": true, + "trailingComma": "es5" +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bbe47f..050380e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,5 @@ # Changelog + All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), @@ -7,30 +8,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] ## [0.1.5] - 2022-10-10 + ### Changed + - SDK now uses custom @tobicrain/pocketbase javascript framework ## [0.1.4] - 2022-10-10 + ### Fixed + - SDK now runs on React and React Native ## [0.1.3] - 2022-10-06 + ### Changed -- Redux now toggles between localStorage / AsyncStorage +- Redux now toggles between localStorage / AsyncStorage ## [0.1.2] - 2022-10-05 + ### Changed -- Adjusted Rollup / Babel Config for use in React +- Adjusted Rollup / Babel Config for use in React ## [0.1.1] - 2022-10-04 + ### Changed + - initialCollections now also "subscribe" instead of just "fetch" content once ## [0.1.0] - 2022-10-03 + ### Added + - Initial commit ### Changed -- Readme Instructions and so on \ No newline at end of file + +- Readme Instructions and so on diff --git a/README.md b/README.md index 6cc4768..1efe2cb 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,18 @@ -PocketBase React SDK -====================================================================== +# PocketBase React SDK + [![Npm package version](https://badgen.net/npm/v/pocketbase-react)](https://npmjs.com/package/pocketbase-react) Unofficial React SDK (React, React Native, Expo) for interacting with the [PocketBase JS SDK](https://github.com/pocketbase/js-sdk). - - [Installation](#installation) - [Usage](#usage) - [Caveats](#caveats) - [Development](#development) - ## Installation ### React, React Native or Expo - ```sh # Using npm npm install pocketbase-react --save @@ -23,13 +20,16 @@ npm install pocketbase-react --save #Using yarn yarn add pocketbase-react ``` + ```tsx import { Pocketbase } from 'pocketbase-react'; ``` --- + > 🔧 React Native / Expo doesn't have native `EventSource` implementation, so in order to use the realtime service you'll need to load a `EventSource` polyfill. > I recommend [EventSource/eventsource](https://github.com/EventSource/eventsource) +> > ```sh > # Using npm > npm install eventsource --save @@ -37,7 +37,8 @@ import { Pocketbase } from 'pocketbase-react'; > # Using yarn > yarn add eventsource > ``` -> ```js +> +> ```js > // EventSource.ts > var Source = require('event-source'); > global.EventSource = Source; @@ -68,109 +69,148 @@ const mobileRedirectURL = "expo://..." // for example ``` ## Caveats + ```tsx -import { useAppContent, useAuth } from "pocketbase-react"; +import { useAppContent, useAuth } from 'pocketbase-react'; ``` + ### Records + Reading the records value directly accesses the Redux Store. The value will be changed automatically by the following actions: + - [Initial Fetch](#initialfetch) - [Initial Collections](#usage) - [Subscribe](#subscribe) - [Refetch](#refetch) **Without** Initial Fetch + ```tsx // Corresponds to the stored Redux value, simply reads without making further PocketBase requests -const { records } = useAppContent("COLLECTION_NAME_01") +const { records } = useAppContent('COLLECTION_NAME_01'); ``` + **With** Initial Fetch + ```tsx // When initializing, the desired table is queried once and updated in Redux, records corresponds to the stored Redux value -const { records } = useAppContent("COLLECTION_NAME_01", true) +const { records } = useAppContent('COLLECTION_NAME_01', true); ``` ### Actions + ```tsx -const { actions } = useAppContent("COLLECTION_NAME_01") +const { actions } = useAppContent('COLLECTION_NAME_01'); ``` -> *All following actions are performed on the desired table, in this case -> COLLECTION_NAME_01* +> _All following actions are performed on the desired table, in this case -> COLLECTION_NAME_01_ > > ⚠️ **All actions will not return anything, they will just modify the Redux value according to their intention** Subscribe + ```tsx actions.subscribe(); ``` + Unsubscribe + ```tsx actions.unsubscribe(); ``` + Refetch + ```tsx actions.refetch(); ``` + Create + ```tsx const object = {}; actions.create(object); ``` + Update + ```tsx -const id = "SOME_ID"; +const id = 'SOME_ID'; const object = {}; actions.update(id, object); ``` + DELETE + ```tsx -const id = "SOME_ID"; +const id = 'SOME_ID'; actions.delete(id); ``` ### Auth + ```tsx -const { actions } = useAuth() +const { actions } = useAuth(); ``` Register with Email + ```tsx await actions.registerWithEmail(email: string, password: string); ``` + Sign-In with Email + ```tsx await actions.signInWithEmail(email: string, password: string); ``` + Sign-In with Provider + ```tsx await actions.signInWithProvider(provider: string); ``` -Submit Provider Result + +Submit Provider Result + ```tsx await actions.submitProviderResult(url: string); ``` + Sign-Out + ```tsx actions.signOut(); ``` + Send password reset email + ```tsx await actions.sendPasswordResetEmail(email: string); ``` + Send email verification + ```tsx await actions.sendEmailVerification(email: string); ``` + Update profile + ```tsx await actions.updateProfile(id: string, record: {}); ``` + Update profile + ```tsx await actions.updateEmail(email: string); ``` + Delete user + ```tsx await actions.deleteUser(id: string); -``` \ No newline at end of file +``` diff --git a/package.json b/package.json index 30dc1b8..dfdcb2b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "pocketbase-react", - "version": "0.1.14", + "version": "0.1.15", "description": "Unofficial React SDK (React, React Native, Expo) for interacting with the PocketBase JS SDK", "keywords": [ "pocketbase", diff --git a/rollup.config.js b/rollup.config.js index 0454ac5..99d4506 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,13 +1,13 @@ -import nodeResolve from '@rollup/plugin-node-resolve' -import babel from '@rollup/plugin-babel' -import replace from '@rollup/plugin-replace' -import commonjs from '@rollup/plugin-commonjs' -import { terser } from 'rollup-plugin-terser' -import pkg from './package.json' +import nodeResolve from '@rollup/plugin-node-resolve'; +import babel from '@rollup/plugin-babel'; +import replace from '@rollup/plugin-replace'; +import commonjs from '@rollup/plugin-commonjs'; +import { terser } from 'rollup-plugin-terser'; +import pkg from './package.json'; -const env = process.env.NODE_ENV +const env = process.env.NODE_ENV; -const extensions = ['.js', '.ts', '.tsx', '.json'] +const extensions = ['.js', '.ts', '.tsx', '.json']; const config = { input: 'src/index.ts', @@ -17,7 +17,7 @@ const config = { name: 'PocketbaseReact', globals: { react: 'React', - pocketbase: '@tobicrain/pocketbase' + pocketbase: '@tobicrain/pocketbase', }, }, plugins: [ @@ -36,7 +36,7 @@ const config = { }), commonjs(), ], -} +}; if (env === 'production') { config.plugins.push( @@ -48,7 +48,7 @@ if (env === 'production') { warnings: false, }, }) - ) + ); } -export default config \ No newline at end of file +export default config; diff --git a/src/context/Pocketbase.tsx b/src/context/Pocketbase.tsx index 962f643..b147c3f 100644 --- a/src/context/Pocketbase.tsx +++ b/src/context/Pocketbase.tsx @@ -26,7 +26,10 @@ export const Pocketbase = (props: PocketbaseProviderProps) => { - + {props.children} @@ -34,5 +37,5 @@ export const Pocketbase = (props: PocketbaseProviderProps) => { - ): null; + ) : null; }; diff --git a/src/context/auth.tsx b/src/context/auth.tsx index 3b7bdf9..6ee6f42 100644 --- a/src/context/auth.tsx +++ b/src/context/auth.tsx @@ -25,7 +25,7 @@ export type UpdateEmailType = (email: string) => Promise; export type DeleteUserType = (id: string) => Promise; export interface AuthActions { - registerWithEmail: RegisterWithEmailType + registerWithEmail: RegisterWithEmailType; signInWithEmail: SignInWithEmailType; signInWithProvider: SignInWithProviderType; submitProviderResult: SubmitProviderResultType; @@ -46,11 +46,10 @@ export type AuthProviderProps = { openURL: (url: string) => Promise; }; - export const AuthProvider = (props: AuthProviderProps) => { const client = useClientContext(); const [authProviders, setAuthProviders] = React.useState(); - + const actions: AuthActions = { registerWithEmail: async (email, password) => { await client?.users.create({ @@ -64,7 +63,10 @@ export const AuthProvider = (props: AuthProviderProps) => { }, signInWithProvider: async (provider: string) => { const authProvider = authProviders?.find((p) => p.name === provider); - const url = authProvider?.authUrl + typeof document !== 'undefined' ? props.webRedirectUrl: props.mobileRedirectUrl; + const url = + authProvider?.authUrl + typeof document !== 'undefined' + ? props.webRedirectUrl + : props.mobileRedirectUrl; await props.openURL(url); await StorageService.set('provider', JSON.stringify(authProviders)); }, @@ -77,7 +79,12 @@ export const AuthProvider = (props: AuthProviderProps) => { const providers = JSON.parse(providersString) as AuthProviderInfo[]; const authProvider = providers?.find((p) => p.state === state); if (authProvider && code) { - await client?.users.authViaOAuth2(authProvider.name, code, authProvider.codeVerifier, typeof document !== 'undefined' ? props.webRedirectUrl: props.mobileRedirectUrl); + await client?.users.authViaOAuth2( + authProvider.name, + code, + authProvider.codeVerifier, + typeof document !== 'undefined' ? props.webRedirectUrl : props.mobileRedirectUrl + ); } } }, @@ -97,8 +104,8 @@ export const AuthProvider = (props: AuthProviderProps) => { await client?.users.requestEmailChange(email); }, deleteUser: async (id: string) => { - await client?.users.delete(id) - } + await client?.users.delete(id); + }, }; React.useEffect(() => { @@ -108,7 +115,5 @@ export const AuthProvider = (props: AuthProviderProps) => { })(); }, [props.webRedirectUrl, props.mobileRedirectUrl]); - return ( - {props.children} - ); + return {props.children}; }; diff --git a/src/context/content.tsx b/src/context/content.tsx index 6e03cbc..52e52c5 100644 --- a/src/context/content.tsx +++ b/src/context/content.tsx @@ -4,7 +4,7 @@ import { createContext, useEffect } from 'react'; import { useClientContext } from '../hooks/useClientContext'; import { Record } from '../interfaces/Record'; import { recordsAction } from '../store/actions'; -import { StorageService } from '../service/Storage'; +import { subscriptionsAction } from '../store/actions'; type SubscribeType = (collectionName: string) => Promise; type UnsubscribeType = (collectionName?: string) => Promise; @@ -22,11 +22,7 @@ interface ContentActions { delete: DeleteType; } -interface ContentContext { - actions: ContentActions; -} - -export const ContentContext = createContext({} as ContentContext); +export const ContentContext = createContext({} as ContentActions); export type ContentProviderProps = { children: React.ReactNode; @@ -34,8 +30,8 @@ export type ContentProviderProps = { }; interface MessageData { - action: string; - record: Record; + action: string; + record: Record; } export const ContentProvider = (props: ContentProviderProps) => { @@ -44,56 +40,45 @@ export const ContentProvider = (props: ContentProviderProps) => { const actions: ContentActions = { subscribe: async (collectionName: string) => { - const subscribedCollectionsString = await StorageService.get(StorageService.Constants.SUBSCRIBED) ?? JSON.stringify([]); - var subscribedCollections = JSON.parse(subscribedCollectionsString) as string[]; - - await client?.realtime.subscribe(collectionName, (event: MessageData) => { - switch (event.action) { - case 'create': - dispatch(recordsAction.addRecord(collectionName, event.record)); - break; - case 'update': - dispatch(recordsAction.updateRecord(collectionName, event.record)); - break; - case 'delete': - dispatch(recordsAction.deleteRecord(collectionName, event.record)); - break; - default: - break; - } - }) - .then(() => { - if (!subscribedCollections.includes(collectionName)) { - subscribedCollections.push(collectionName); - } - }) - .catch((_error) => { - subscribedCollections = subscribedCollections.filter((collection) => collection !== collectionName); - }); - - await StorageService.set(StorageService.Constants.SUBSCRIBED, JSON.stringify(subscribedCollections)); - }, - unsubscribe: async (collectionName?: string) => { - const subscribedCollectionsString = await StorageService.get(StorageService.Constants.SUBSCRIBED) ?? JSON.stringify([]); - var subscribedCollections = JSON.parse(subscribedCollectionsString) as string[]; - - if (collectionName) { - await client?.realtime.unsubscribe(collectionName) + await client?.realtime + .subscribe(collectionName, (event: MessageData) => { + switch (event.action) { + case 'create': + dispatch(recordsAction.addRecord(collectionName, event.record)); + break; + case 'update': + dispatch(recordsAction.updateRecord(collectionName, event.record)); + break; + case 'delete': + dispatch(recordsAction.deleteRecord(collectionName, event.record)); + break; + default: + break; + } + }) .then(() => { - subscribedCollections = subscribedCollections.filter((collection) => collection !== collectionName); + dispatch(subscriptionsAction.addSubscription(collectionName)); }) .catch((_error) => { - subscribedCollections = subscribedCollections.filter((collection) => collection !== collectionName); + dispatch(subscriptionsAction.deleteSubscription(collectionName)); }); + }, + unsubscribe: async (collectionName?: string) => { + if (collectionName) { + await client?.realtime + .unsubscribe(collectionName) + .then(() => { + dispatch(subscriptionsAction.deleteSubscription(collectionName)); + }) + .catch((_error) => {}); } else { - await client?.realtime.unsubscribe() - .then(() => { - subscribedCollections = []; - }) - .catch((_error) => {}); + await client?.realtime + .unsubscribe() + .then(() => { + dispatch(subscriptionsAction.setSubscriptions([])); + }) + .catch((_error) => {}); } - - await StorageService.set(StorageService.Constants.SUBSCRIBED, JSON.stringify(subscribedCollections)); }, fetch: async (collectionName: string) => { const records = await client?.records.getFullList(collectionName, 200).catch((_error) => {}); @@ -124,9 +109,5 @@ export const ContentProvider = (props: ContentProviderProps) => { }; }, [props.collections]); - return ( - {props.children} - ); + return {props.children}; }; diff --git a/src/context/index.ts b/src/context/index.ts index c28e648..ee94c6e 100644 --- a/src/context/index.ts +++ b/src/context/index.ts @@ -1,4 +1,4 @@ export * from './content'; export * from './client'; export * from './auth'; -export * from './Pocketbase'; \ No newline at end of file +export * from './Pocketbase'; diff --git a/src/hooks/index.ts b/src/hooks/index.ts index c296700..69c0c6d 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,3 +1,3 @@ export * from './useAppContent'; export * from './useClientContext'; -export * from './useAuth'; \ No newline at end of file +export * from './useAuth'; diff --git a/src/hooks/useAppContent.ts b/src/hooks/useAppContent.ts index 2e2ead9..ad0ef9a 100644 --- a/src/hooks/useAppContent.ts +++ b/src/hooks/useAppContent.ts @@ -4,12 +4,12 @@ import { ContentContext } from '../context'; import { Record } from '../interfaces/Record'; import { StorageService } from '../service/Storage'; -export type SubscribeType = () => Promise +export type SubscribeType = () => Promise; export type UnsubscribeType = () => Promise; -export type FetchType = () => Promise -export type CreateType = (record: {}) => Promise -export type UpdateType = (id: string, record: {}) => Promise -export type DeleteType = (id: string) => Promise +export type FetchType = () => Promise; +export type CreateType = (record: {}) => Promise; +export type UpdateType = (id: string, record: {}) => Promise; +export type DeleteType = (id: string) => Promise; export interface Actions { subscribe: SubscribeType; @@ -23,44 +23,32 @@ export interface Actions { export function useAppContent( collectionName: string, initialFetch: boolean = false -): {records: T[], actions: Actions, isSubscribed: boolean} { - const records = (store.useAppSelector((state) => state.reducer.records[collectionName]) ?? []) as T[]; +): { records: T[]; actions: Actions; isSubscribed: boolean } { + const records = (store.useAppSelector((state) => state.reducer.records[collectionName]) ?? + []) as T[]; + const subscriptions = store.useAppSelector((state) => state.reducer.subscriptions).subscriptions; const context = useContext(ContentContext); useEffect(() => { if (initialFetch) { - context.actions.fetch(collectionName); + context.fetch(collectionName); } }, [collectionName, initialFetch]); const [isSubscribed, setIsSubscribed] = useState(false); - - async function refetchSubscribeState() { - const subscribedCollectionsString = await StorageService.get(StorageService.Constants.SUBSCRIBED) ?? JSON.stringify([]); - var subscribedCollections = JSON.parse(subscribedCollectionsString) as string[]; - setIsSubscribed(subscribedCollections.includes(collectionName)); - } useEffect(() => { - (async () => { - await refetchSubscribeState(); - })(); - }, []) - + setIsSubscribed(subscriptions.includes(collectionName)); + }, [subscriptions]); const actions: Actions = { - subscribe: async () => { - await context.actions.subscribe(collectionName) - await refetchSubscribeState() - }, - unsubscribe: async () => { - context.actions.unsubscribe(collectionName) - await refetchSubscribeState() - }, - fetch: async () => await context.actions.fetch(collectionName), - create: async (record: {}) => await context.actions.create(collectionName, record), - update: async (id: string, record: {}) => await context.actions.update(collectionName, id, record), - delete: async (id: string) => await context.actions.delete(collectionName, id), + subscribe: async () => await context.subscribe(collectionName), + unsubscribe: async () => await context.unsubscribe(collectionName), + fetch: async () => await context.fetch(collectionName), + create: async (record: {}) => await context.create(collectionName, record), + update: async (id: string, record: {}) => await context.update(collectionName, id, record), + delete: async (id: string) => await context.delete(collectionName, id), }; + return { records, actions, isSubscribed }; } diff --git a/src/hooks/useClientContext.ts b/src/hooks/useClientContext.ts index 9fa91f7..a267433 100644 --- a/src/hooks/useClientContext.ts +++ b/src/hooks/useClientContext.ts @@ -7,5 +7,4 @@ const useClientContext = () => { return context; }; - -export { useClientContext }; \ No newline at end of file +export { useClientContext }; diff --git a/src/index.ts b/src/index.ts index 93cbc24..3cc96fa 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,4 +1,4 @@ export * from './hooks'; export * from './interfaces'; export * from './context'; -export * from './store'; \ No newline at end of file +export * from './store'; diff --git a/src/interfaces/index.ts b/src/interfaces/index.ts index d9ccb8e..486ba76 100644 --- a/src/interfaces/index.ts +++ b/src/interfaces/index.ts @@ -1 +1 @@ -export * from './Record'; \ No newline at end of file +export * from './Record'; diff --git a/src/service/Authentication.ts b/src/service/Authentication.ts index 529a8ad..44f1123 100644 --- a/src/service/Authentication.ts +++ b/src/service/Authentication.ts @@ -1,9 +1,9 @@ -import PocketBase from "@tobicrain/pocketbase"; +import PocketBase from '@tobicrain/pocketbase'; export class AuthenticationService { private client: PocketBase; public redirect_url: string; - + constructor(client: PocketBase) { this.client = client; this.redirect_url = ''; @@ -14,37 +14,27 @@ export class AuthenticationService { } async getDataAuth() { - await this.client.users.authViaOAuth2( - 'google', - 'CODE', - 'VERIFIER', - 'REDIRECT_URL' - ); + await this.client.users.authViaOAuth2('google', 'CODE', 'VERIFIER', 'REDIRECT_URL'); } async AuthWithOauth(provider: string, code: string, verifier: string) { - await this.client.users.authViaOAuth2( - provider, - code, - verifier, - this.redirect_url - ); + await this.client.users.authViaOAuth2(provider, code, verifier, this.redirect_url); } - async AuthWithEmail(email: string, password: string){ + async AuthWithEmail(email: string, password: string) { await this.client.users.authViaEmail(email, password); } - async RegisterWithEmail(email: string, password: string){ + async RegisterWithEmail(email: string, password: string) { await this.client.users.create({ email: email, password: password, passwordConfirm: password, - }); + }); } - async getUserData(id:string, token:string) { - const headers = new Headers() - headers.append("Authorization","user "+token) - return await this.client.users.getOne(id) + async getUserData(id: string, token: string) { + const headers = new Headers(); + headers.append('Authorization', 'user ' + token); + return await this.client.users.getOne(id); } -} \ No newline at end of file +} diff --git a/src/service/Storage.ts b/src/service/Storage.ts index 5f57e5e..a4a9e64 100644 --- a/src/service/Storage.ts +++ b/src/service/Storage.ts @@ -1,28 +1,29 @@ -import AsyncStorage from "@react-native-async-storage/async-storage"; +import AsyncStorage from '@react-native-async-storage/async-storage'; export class StorageService { + static Constants = { + SUBSCRIBED: 'subscribed', + }; - static Constants = { - SUBSCRIBED: "subscribed", - } - - static async get(key: string): Promise { - return typeof document !== 'undefined' ? localStorage.getItem(key) : await AsyncStorage.getItem(key); - } + static async get(key: string): Promise { + return typeof document !== 'undefined' + ? localStorage.getItem(key) + : await AsyncStorage.getItem(key); + } - static async set(key: string, value: string): Promise { - if (typeof document !== 'undefined') { - return localStorage.setItem(key, value); - } else { - return await AsyncStorage.setItem(key, value); - } + static async set(key: string, value: string): Promise { + if (typeof document !== 'undefined') { + return localStorage.setItem(key, value); + } else { + return await AsyncStorage.setItem(key, value); } + } - static async remove(key: string): Promise { - if (typeof document !== 'undefined') { - return localStorage.removeItem(key); - } else { - return await AsyncStorage.removeItem(key); - } + static async remove(key: string): Promise { + if (typeof document !== 'undefined') { + return localStorage.removeItem(key); + } else { + return await AsyncStorage.removeItem(key); } -} \ No newline at end of file + } +} diff --git a/src/store/actions/index.tsx b/src/store/actions/index.tsx index e12e462..171718c 100644 --- a/src/store/actions/index.tsx +++ b/src/store/actions/index.tsx @@ -1,3 +1,4 @@ import * as recordsAction from './records'; +import * as subscriptionsAction from './subscriptions'; -export { recordsAction }; \ No newline at end of file +export { recordsAction, subscriptionsAction }; diff --git a/src/store/actions/records.tsx b/src/store/actions/records.tsx index 075c8c7..b7e3bca 100644 --- a/src/store/actions/records.tsx +++ b/src/store/actions/records.tsx @@ -44,11 +44,4 @@ const updateRecord = (key: string, payload: Record) => payload, } as RecordAction); -export { - setRecords, - addRecord, - addRecords, - deleteRecord, - deleteRecords, - updateRecord, -}; +export { setRecords, addRecord, addRecords, deleteRecord, deleteRecords, updateRecord }; diff --git a/src/store/actions/subscriptions.tsx b/src/store/actions/subscriptions.tsx new file mode 100644 index 0000000..e421baa --- /dev/null +++ b/src/store/actions/subscriptions.tsx @@ -0,0 +1,22 @@ +import { SubscriptionAction } from '../reducers/subscriptions'; +import * as ReduxType from '../types'; + +const setSubscriptions = (payload: string[]) => + ({ + type: ReduxType.SET_SUBSCRIPTIONS, + payload, + } as SubscriptionAction); + +const addSubscription = (payload: string) => + ({ + type: ReduxType.ADD_SUBSCRIPTION, + payload, + } as SubscriptionAction); + +const deleteSubscription = (payload: string) => + ({ + type: ReduxType.DELETE_SUBSCRIPTION, + payload, + } as SubscriptionAction); + +export { setSubscriptions, addSubscription, deleteSubscription }; diff --git a/src/store/index.ts b/src/store/index.ts index e31b4f3..caa56e1 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,4 +1,4 @@ export * from './actions'; export * from './reducers'; export * from './types'; -export * from './store'; \ No newline at end of file +export * from './store'; diff --git a/src/store/reducers/index.tsx b/src/store/reducers/index.tsx index 3a38a8e..5b78795 100644 --- a/src/store/reducers/index.tsx +++ b/src/store/reducers/index.tsx @@ -1,12 +1,14 @@ import { combineReducers } from 'redux'; import { records } from './records'; +import { subscriptions } from './subscriptions'; export const appReducer = combineReducers({ records: records, + subscriptions: subscriptions, }); interface AppReducer { records: ReturnType; } -export type State = AppReducer; \ No newline at end of file +export type State = AppReducer; diff --git a/src/store/reducers/records.tsx b/src/store/reducers/records.tsx index ad2372a..1f4cf52 100644 --- a/src/store/reducers/records.tsx +++ b/src/store/reducers/records.tsx @@ -6,7 +6,7 @@ export interface ReduxRecord { } export type RecordAction = { - type: ReduxType.Types; + type: ReduxType.RecordTypes; key: string; payload: null | Record | Record[]; }; @@ -75,4 +75,4 @@ export const records = (state: ReduxRecord = {}, action: RecordAction) => { default: return state; } -}; \ No newline at end of file +}; diff --git a/src/store/reducers/subscriptions.tsx b/src/store/reducers/subscriptions.tsx new file mode 100644 index 0000000..f69b750 --- /dev/null +++ b/src/store/reducers/subscriptions.tsx @@ -0,0 +1,46 @@ +import * as ReduxType from '../types'; + +export interface ReduxSubscriptions { + subscriptions: string[]; +} + +export type SubscriptionAction = { + type: ReduxType.SubscriptionsTypes; + payload: string | string[]; +}; + +function appendSubscription(subscription: string, subscriptions: string[]): string[] { + return [...subscriptions, subscription]; +} + +function deleteSubscription(subscription: string, subscriptions: string[]): string[] { + return subscriptions.filter((sub) => sub !== subscription); +} + +export const subscriptions = ( + state: ReduxSubscriptions = { + subscriptions: [], + }, + action: SubscriptionAction +) => { + const list = state.subscriptions; + + switch (action.type) { + case ReduxType.SET_SUBSCRIPTIONS: + if (Array.isArray(action.payload)) { + return { + subscriptions: action.payload, + }; + } + case ReduxType.ADD_SUBSCRIPTION: + return { + subscriptions: appendSubscription(action.payload as string, list), + }; + case ReduxType.DELETE_SUBSCRIPTION: + return { + subscriptions: deleteSubscription(action.payload as string, list), + }; + default: + return state; + } +}; diff --git a/src/store/store.tsx b/src/store/store.tsx index 12d2ad3..2c743cd 100644 --- a/src/store/store.tsx +++ b/src/store/store.tsx @@ -4,7 +4,6 @@ import { TypedUseSelectorHook, useSelector } from 'react-redux'; import { appReducer } from './reducers'; import thunk from 'redux-thunk'; import { RecordAction } from './reducers/records'; -import AsyncStorage from '@react-native-async-storage/async-storage'; import { StorageService } from '../service/Storage'; interface Storage { @@ -13,22 +12,21 @@ interface Storage { removeItem(key: string, ...args: Array): any; } - const CustomStorage: Storage = { getItem: async (key: string, ..._args: Array) => { return await StorageService.get(key); }, setItem: async (key: string, value: any, ..._args: Array) => { - return StorageService.set(key, value); + return await StorageService.set(key, value); }, removeItem: async (key: string, ..._args: Array) => { - return StorageService.remove(key); + return await StorageService.remove(key); }, }; export const persistConfig = { key: 'root', - storage: CustomStorage + storage: CustomStorage, }; const reducer = combineReducers({ @@ -45,7 +43,6 @@ const useAppDispatch = store.dispatch; type RootState = ReturnType; const useAppSelector: TypedUseSelectorHook = useSelector; - const persistor = persistStore(store); -export { AppDispatch, RootState, useAppDispatch, useAppSelector, store, persistor }; \ No newline at end of file +export { AppDispatch, RootState, useAppDispatch, useAppSelector, store, persistor }; diff --git a/src/store/types/index.ts b/src/store/types/index.ts index 21f9f7c..c57781a 100644 --- a/src/store/types/index.ts +++ b/src/store/types/index.ts @@ -5,10 +5,19 @@ export const UPDATE_RECORD = 'UPDATE_RECORD'; export const DELETE_RECORD = 'DELETE_RECORD'; export const DELETE_RECORDS = 'DELETE_RECORDS'; -export type Types = +export type RecordTypes = | typeof SET_RECORDS | typeof ADD_RECORD | typeof ADD_RECORDS | typeof UPDATE_RECORD | typeof DELETE_RECORD | typeof DELETE_RECORDS; + +export const SET_SUBSCRIPTIONS = 'SET_SUBSCRIPTIONS'; +export const ADD_SUBSCRIPTION = 'ADD_SUBSCRIPTION'; +export const DELETE_SUBSCRIPTION = 'DELETE_SUBSCRIPTION'; + +export type SubscriptionsTypes = + | typeof SET_SUBSCRIPTIONS + | typeof ADD_SUBSCRIPTION + | typeof DELETE_SUBSCRIPTION; diff --git a/tsconfig.json b/tsconfig.json index 799e646..ba76c07 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,11 +15,8 @@ "experimentalDecorators": true, "rootDirs": ["./src", "./types"], "rootDir": "./src", - "typeRoots": [ - "./node_modules/@types", - "./types" - ] + "typeRoots": ["./node_modules/@types", "./types"] }, "include": ["src/**/*", "types"], "exclude": ["node_modules", "dist", "test/**/*"] -} \ No newline at end of file +}