Skip to content

Commit

Permalink
Merge pull request #100 from Christiantyemele/feature/useStorage-Hook
Browse files Browse the repository at this point in the history
Feature/use storage hook
  • Loading branch information
Christiantyemele authored Apr 9, 2024
2 parents 5df3aed + 4a429db commit 4555549
Show file tree
Hide file tree
Showing 3 changed files with 387 additions and 44 deletions.
134 changes: 90 additions & 44 deletions docs/useStorage.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,54 +9,100 @@ One of the main benefits of Context API is its ability to simplify state managem

* Context API has two core concepts:

- Providers
- Consumers
#

- Providers
- Consumers
#


**Providers** is to define and keep track of specific pieces of state. This state can then be accessed by all the child components nested inside the Provider. These child components, known as **Consumers**, are responsible for accessing or modifying the state provided by the Context Provider.


## React Context with useStorage

To make the most use of the useStorage hook we are going to be using the Context library here this is because of its simplicity and also its ability to pass data from parent to children components without the use of props.<br>
Here is a basic form of how the react Context and usestorage hook function in other for us to have persistent and reusable data across our components.<br>
the react context works by creating a context object that holds the data we want to share between the components, once this context object is being created it is used in our components by first wrapping the component in a context provider then any component that imports(inherits) from this component can have access to the data, then now comes the useStorage hook which to persist that data to the localstorage or any other storage of our web browser to make the data persist accross page refresh or any other storage. <br>
The usestorage hook performs its task by taking the key and associated value converting it to a json string then using the ```setItem()``` method to store it in the storage with its associated key. And also has a ```getItem()``` for retrieving the value stored based on the corresponding key and a ```removeItem()``` method which will be used to remove key value pair from the storage. <br>
To conclude the react Context and useStorage helps our application to have reusable and persist data accross components.<br>
The flow is as shown

```mermaid
graph TD;
subgraph "ContextObject"
contextobject[key = value]
end
subgraph "Components"
A[component1]
B[component2]
end
subgraph "useStorage"
usestoragehook[useStoragehook]
end
subgraph "BrowserStorage"
localstorage[LocalStorage]
end
subgraph "ContextProvider"
provider[Provider]
end
ContextObject --> ContextProvider
ContextProvider --> Components
A -->|setItem,removeItem| useStorage
B --> |setItem,removeItem| useStorage
useStorage--> |getItem| A
useStorage --> |getItem|B
useStorage --> |settingItem/ removingItem|BrowserStorage
BrowserStorage --> |gettingItem|useStorage
TO implement the React context with the useStorage, we first start by creating and interface from which we define our data
and methods to read and write data across components by implementing the interface.<br>
Once this interface created we then create a context provider by importing context from react the we create a context provider from
it were all the child components to that component will consume its value.<br>
Once this done we have a reusable and a none tree shaking code, Now then comes the useStorage hook, the usestorage hook then
has then accepts the data provided by the consumer components and uses method such and getitem('key') and returns a value , setitem('key', value)
to store and item in the browser's storage using a key and value depending from which value and key may come from that provider by the
context provider for example.
So This is how we can obtain reusable , non-tree shaking and persistent data across our components.<br>

## Transactions using the browser's IndexDB storage
indexDB is a low level API for client-side storage of significant anounts of structured
including files and blobs

### KeyConcepts
+ Asynchronous, therefore it won't block any main thread operations.
+ lets you access data offline.
+ can store a large amount of data ( more than the local storage or session storage) of complex value types.
+ A noSQL database which makes it very flexible and dangerous.

### keywords
+ **Object stores** : the mechanism by which data is stored in the database.
+ **Database** : A repository of information , typically comprising of one or more object stores
+ **Index** : and index is a specialized object store for looking up records in another object store called the reference object store.
+ **request** : The operation by which reading and writing on a database is done. Every request represents one or more read or write operations

IndexDB just as the localStorage and sessionStorage stores data on client side and provides methods for easy retrieval , adding , and removing data from this storage
depending on whether the store object uses a ```keypath``` or a ``` key generator``` and you can also use and index for retrieving the data based on the key in the index data store which
maps the value in the object store.

### Basic Pattern
1. open the database.
2. create an object in the database.
3. start a transaction and make a request to do some database operations.
4. wait for the operation to be completed by listening to the right kind od DOM events.
5. do something with the result.

#### Possible Errors
One of the common possible errors when opening a databaseis ```VER_ERR``` it indicates that the version of the database stored on the
disk is greater than the version that you are trying to open. this an error case that must be handled by the
error handler.

Source, [IndexedDB](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
)
```mermaid
classDiagram
class Storage {
<<interface>>
+ getItem<T>(key: string): Promise<T>
+ setItem<T>(key: string, value: T): Promise<void>
+ removeItem(key: string): Promise<void>
+ clear(): Promise<void>
}
class LocalstorageImpl {
}
class IndexDBImpl{
}
class MemoryStorageImp{
- dic: Map<string, any>
}
class IndexStorageImpl{
- dic: Map<any, any>
}
class Hook{
+ item: value
+ setItem(item: value): void
}
class ContextData {
+ data Map<string, any>
+ setData(data: Map<string, any>)
}
class Context {
+ storage: storage
+ data: connection
}
Context --> ContextData: use
Hook --> Context: use
MemoryStorageImp --> Storage: implements
LocalstorageImpl --> Storage: implements
IndexStorageImpl --> Storage: implements
IndexDBImpl --> Storage: implements
Context --> Storage: use
```

235 changes: 235 additions & 0 deletions power-pay-frontend/src/Hooks/StorageContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import {createContext, PropsWithChildren, useEffect, useState} from 'react';


// Define the StorageContextData interface.
export interface StorageContextData<T> {
item: Record<string, T>;
setItem: (key: string, value: T) => Promise<boolean>;
removeItem: (key: string) => Promise<boolean>;
clear: () => Promise<boolean>;
}

// Define the StorageService interface.
interface StorageService {
clear: () => Promise<boolean>;
getItem: <T>(key: string) => Promise<T | undefined>;
removeItem: (key: string) => Promise<boolean | void>



}
interface SetStorage {
setItem: <T>(key: string, value: T) => Promise<boolean | void>;
}
interface SetStorageIDB {
setItem: <T>(value: object, key: IDBValidKey) => Promise<boolean | void>;
}

// Define the LocalStorageService class implementing the StorageService interface.
export class LocalStorageService implements StorageService, SetStorage {
async getItem<T>(key: string): Promise<T | undefined> {
const found = localStorage.getItem(key);
if (!found) return undefined;
return JSON.parse(found) as T;
}

async setItem<T>(key: string , value: T): Promise<boolean | void> {
localStorage.setItem(key, JSON.stringify(value));
return true;
}
async removeItem(key: string): Promise<boolean> {
localStorage.removeItem(key);
return true;
}

async clear(): Promise<boolean> {
localStorage.clear();
return true;
}
}

export class IndexedDBStorageService implements StorageService, SetStorageIDB {
private dbName: string;
private version: number;
private db: IDBDatabase

constructor(dbName: string = 'dbname', version?: number) {
this.dbName = dbName;

}

async openDatabase(dbName?: string, Version?: number): Promise<IDBDatabase> {
return new Promise((resolve, reject) => {

const request = window.indexedDB.open(dbName ?? this.dbName);

request.onerror = (event: Event) => {
reject((event.target as IDBOpenDBRequest).error);
};

request.onsuccess = (event: Event) => {
resolve((event.target as IDBOpenDBRequest).result);
};

request.onupgradeneeded = (event: Event) => {

this.db = (event.target as IDBOpenDBRequest).result;
const objectstore = this.db.createObjectStore('objectstore', {keyPath: 'id'})
};
});
}

async clear(): Promise<boolean> {

if (!this.db)
await this.openDatabase()

return new Promise((resolve, reject) => {
const transaction =

this.db.transaction('objectstore', 'readwrite')
.objectStore('objectstore')
.clear();
transaction.onsuccess = (event: Event) => {
resolve((event.target as IDBRequest).result)
}
transaction.onerror = (event: Event) => {
reject((event.target as IDBRequest).onerror)
}
})

}

async getItem<T>(keypath: any): Promise<T | undefined> {
if (!this.db)
await this.openDatabase()
return new Promise((resolve, reject) => {
const transaction = this.db.transaction('objectstore', 'readonly')

.objectStore('objectstore')

.get(keypath)

transaction.onsuccess = (event) => {

resolve((event.target as IDBRequest).result)
}
transaction.onerror = (event: Event) => {
reject((event.target as IDBRequest).onerror)
}
})
}

async removeItem(key: string): Promise<boolean | void> {
if (!this.db)
await this.openDatabase()

return new Promise((resolve, reject) => {
const transaction = this.db.transaction('objectstore', 'readwrite')

.objectStore('objectstore')

.delete(key);

transaction.onerror = (event: Event) => {
reject((event.target as IDBRequest).error);
};

transaction.onsuccess = (event: Event) => {
resolve((event.target as IDBRequest).result);
};
});
}

async setItem<T>(value: object, key?: IDBValidKey): Promise<boolean | void> {
if (!this.db)
await this.openDatabase()

return new Promise((resolve, reject) => {
const transaction = this.db.transaction('objectstore', 'readwrite')
.objectStore('objectstore')

.add(value, key);


transaction.onerror = (event: Event) => {

reject((event.target as IDBRequest).error);
};

transaction.onsuccess = (event: Event) => {

resolve((event.target as IDBRequest).result);

};
});

}
}

// Define a global constant for the key.
const
STORAGE_KEY = "new_key";

// Create the StorageContext with default functions for getItem and setItem.
const
StorageContext = createContext<StorageContextData<unknown> | undefined>(undefined);

// Define the StorageProvider component
export function

StorageProvider<T>({children, storageService}: PropsWithChildren<{ storageService: StorageService }>) {
const [storedValue, setStoredValue] = useState<Record<string, T>>({});

// Initialize the state with the value from the local storage if it exists.
useEffect(() => {
storageService
.getItem<Record<string, T>>(STORAGE_KEY)
.then((item) => {
if (item) {
setStoredValue(item);
}
});
}, [storageService]);

// Updates the local storage whenever the state changes.
useEffect(() => {
storageService.setItem(STORAGE_KEY, storedValue);
}, [storageService, storedValue]);

// Remove the item from local storage and set the stored value to undefined
const clearItem = async (key: string): Promise<boolean> => {
localStorage.removeItem(key);
setStoredValue((prevState) => {
const newState = {...prevState};
delete newState[key];
return newState;
});
return true;
};

// Define the context value.
const contextValue: StorageContextData<T> = {
item: storedValue,
setItem: async (key, value) => {
setStoredValue((prevState) => ({
...prevState,
[key]: value,
}));
return true;
},
removeItem: async (key) => clearItem(key),
clear: async () => {
setStoredValue({});
return true;
},
};

return (
<StorageContext.Provider value={contextValue}>
{children}
</StorageContext.Provider>
);
}

export default StorageContext;
Loading

0 comments on commit 4555549

Please sign in to comment.