Skip to content

Commit

Permalink
Merge pull request #3 from amirHossein-Ebrahimi/destroy
Browse files Browse the repository at this point in the history
destroy last instance on new effect call
  • Loading branch information
realamirhe authored Jun 18, 2021
2 parents 407b2d3 + ebe8d39 commit 5e2c798
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 34 deletions.
79 changes: 65 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,12 @@ Your used/defined APIs are entirely under your control. Make it possible to defi

<details>
<summary>Simple</summary>
💛
It was very enjoyable and simple as far as I was concerned. 💛
</details>

<details>
<summary>Typescript</summary>
🔥
Feel free to contribute or suggest any changes in [issues page](https://github.com/amirHossein-Ebrahimi/react-aptor/issues)
</details>

## How to use
Expand Down Expand Up @@ -184,15 +184,15 @@ const Main = () => {

Pass **createRef** to the Connector component (made in the third step), and then you can access all of the APIs inside **ref.current**

### Using of optional chaining
### 💡 Using of optional chaining

> function call can be much more readable with [optional chaining](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Optional_chaining) & related [babel plugin](https://babeljs.io/docs/en/babel-plugin-proposal-optional-chaining)
```jsx
const apiKeyHandler = () => ref.current?.api_key();
```
### Better naming
### 💡 Better naming
> In case you need `ref.current` more than one time, it is a good idea to rename it at the first place
Expand All @@ -205,12 +205,37 @@ const apiKeyHandler = () => {
};
```
### Can I remove if check in handlers
### 💡 Can I remove if check in handlers
Cause the default value for ref can be undefined (in **createRef**) and null (in **useRef**) Typescript will complain about possibility for not-existence of apis. [see more](https://fettblog.eu/typescript-react/hooks/#useref).
In normal world react will bind your API to given ref after the Connector mount
### Micro api instructions
If you're using ref in useEffect or somewhere which is guaranteed to have the ref bounded to values, you can return proxy object in your getAPI function to bind all api functions simultaneously.
```js
export default function getAPI(thirdParty, params) {
if (!thirdParty)
return () =>
new Proxy(
{},
{
get: (_, prop) => {
// Possible to mock differently for different props
return noop;
},
}
);

return () => ({
api_key() {
// third-party is defined here for sure :)
console.log(thirdParty);
},
});
}
```
### 💡 Micro api instructions
> You can access all of you apis via `this` keyword
Expand All @@ -230,34 +255,60 @@ export default function getAPI(sound, params) {
> It's better to start name of this internal functions with `_`
## core
### 💡 The `this` problem in API object
### Options
In a case you see this keyword usage in third-party API
you must specifying `this` something other than returned API object.
The following examples is for howler integration using react-aptor:
#### ref _`required`_
```js
{
// ❌ It doesn't work
state: howler.state,

// 🆗 this is Okay
state: howler.state.bind(howler),
// 🆗 this is also Okay
state: () => howler.state(),
// 🆗 this is also Okay, too
state() {
return howler.state();
}
}
```
## core
### **ref** _`required`_
The react **useRef** or **createRef** ref instance which has been passed throw **react.forwardRef** method.
your api will be stored in this ref.
#### configuration _`required`_
### **configuration** _`required`_
- ##### instantiate _`required`_
- ### **instantiate** _`required`_
> function(node, params): Instance
A function that receives probable bounded-node and params. It then returns an instance of your third-party.
- ##### getAPI _`required`_
- ### **destroy**
> function(previousInstance, params): void
A function that receives previous created instance and params. It is useful when you want to perform the cleanup before new instance creation. e.g. **remove event listeners**, **free up allocated memories**, **destroy internally** & etc
- ### **getAPI** _`required`_
> function(Instance, params): ApiObject
A function which receives instance of you third-party and params. It then returns a key-value pair object for api handlers.
- ##### params `any`
- ### **params** `any`
Params can have any arbitrary type and can be used with props or pre-defined options.
#### deps `[]`
### **deps** `[]`
React dependencies array for re-instantiating your third-party-packages. It will call `instantiate` with latest node, params when ever shallow comparison for with the previous deps array finds inequality.
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"lint": "eslint src/**/*.ts",
"lint:fix": "yarn lint --fix",
"lint:types": "tsc --noEmit",
"prebuild": "yarn clean",
"build:cjs": "tsc",
"build:es": "tsc -m esNext --outDir esm",
"build": "yarn build:cjs && yarn build:es",
Expand Down
28 changes: 13 additions & 15 deletions src/useAptor.ts
Original file line number Diff line number Diff line change
@@ -1,25 +1,19 @@
import {
useEffect,
useImperativeHandle,
useState,
useRef,
ForwardedRef,
RefObject,
useMemo,
} from 'react';
import { useEffect, useImperativeHandle, useState, useRef, RefObject, useMemo, Ref } from 'react';

// types:misc
type Nullable<T> = T | null;
// types:api
export type SingleAPI = (...args: any[]) => any;
export type APIObject = { [apiName: string]: SingleAPI };
export type APIObject = Record<string, any>; // function, class, ... as api-value
export type APIGenerator = () => APIObject;
export type GetAPI<T> = (instance: Nullable<T>, prams?: any) => APIGenerator;
// types:configuration
export type Instantiate<T> = (node: Nullable<HTMLElement>, params?: any) => Nullable<T>;
export type Destroy<T> = (instance: Nullable<T>, params?: any) => void;

export interface AptorConfiguration<T> {
getAPI: GetAPI<T>;
instantiate: Instantiate<T>;
destroy: Destroy<T>;
params?: any;
}

Expand All @@ -31,16 +25,20 @@ export interface AptorConfiguration<T> {
* @return domRef - can be bound to dom element
*/
export default function useAptor<T>(
ref: ForwardedRef<APIObject>,
ref: Ref<APIObject>,
configuration: AptorConfiguration<T>,
deps = []
deps: any[] = []
): RefObject<HTMLElement> {
const [instance, setInstance] = useState<Nullable<T>>(null);
const domRef = useRef<Nullable<HTMLElement>>(null);
const { instantiate, getAPI, params } = configuration;
const { instantiate, destroy, getAPI, params } = configuration;

useEffect(() => {
setInstance(instantiate(domRef.current, params));
const instanceReference = instantiate(domRef.current, params);
setInstance(instanceReference);
return () => {
if (destroy) destroy(instanceReference, params);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, deps);

Expand Down
7 changes: 2 additions & 5 deletions tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,10 @@
"lib": ["es2018", "dom"],
"importHelpers": true
},
"include": ["src"],
"exclude": [
"node_modules",
"lib",
"esm",
"tests",
"stories",
"jest.config.ts",
"jest.config.*.ts"
"esm"
]
}

0 comments on commit 5e2c798

Please sign in to comment.