Skip to content
This repository was archived by the owner on Feb 15, 2025. It is now read-only.

Commit 7760b6b

Browse files
committed
created basic integration docs
1 parent 79f9632 commit 7760b6b

File tree

7 files changed

+326
-103
lines changed

7 files changed

+326
-103
lines changed

docs/Interfaces.md

Lines changed: 0 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -172,68 +172,6 @@ For example Agile Logs are by default purple.
172172

173173

174174

175-
## `StorageMethods`
176-
177-
The `StorageMethodsInterface` is used in the creation of a Agile [Storage](./packages/core/features/storage/Introduction.md) Interface.
178-
Here is a Typescript Interface for quick reference,
179-
however each property will be explained in more detail below.
180-
```ts
181-
export interface StorageMethodsInterface {
182-
get: (key: string) => any;
183-
set: (key: string, value: any) => void;
184-
remove: (key: string) => void;
185-
}
186-
```
187-
188-
<br/>
189-
190-
#### `get`
191-
192-
Method to get a specific value at `primaryKey` from the external Storage.
193-
```ts
194-
myStorage.get("item1"); // Calls the here defined get method
195-
```
196-
197-
| Type | Default | Required |
198-
|--------------------------|-----------|----------|
199-
| `(key: string) => any` | undefined | Yes |
200-
201-
<br/>
202-
203-
#### `set`
204-
205-
Method to set a specific value at `primaryKey` into the external Storage.
206-
```ts
207-
myStorage.set("item1", {my: "value"}); // Calls the here defined set method
208-
```
209-
210-
| Type | Default | Required |
211-
|---------------------------------------|-----------|----------|
212-
| `(key: string, value: any) => void` | undefined | Yes |
213-
214-
<br/>
215-
216-
#### `remove`
217-
218-
Method to remove a specific value at `primaryKey` from the external Storage.
219-
```ts
220-
myStorage.remove("item1"); // Calls the here defined remove method
221-
```
222-
223-
| Type | Default | Required |
224-
|----------------------------|-----------|----------|
225-
| `(key: string) => void` | undefined | Yes |
226-
227-
228-
229-
<br/>
230-
231-
---
232-
233-
<br/>
234-
235-
236-
237175
## `StateIngestConfig`
238176

239177
The `StateIngestConfigInterface` is used as configuration object in functions like `set()` or `undo()`.

docs/packages/core/features/integration/Introduction.md

Lines changed: 230 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,31 +5,37 @@ sidebar_label: Introduction
55
slug: /core/integration
66
---
77

8-
The `Integration Class` is used to integrate AgileTs into other frameworks like [React](https://reactjs.org/).
8+
:::info
9+
10+
The `Integration Class` is useful for creating an Integration for a specific Framework.
11+
Luckily there already exists some Integrations, so we don't have to built them from scratch.
12+
Check [here](../../../../Frameworks.md) if your required Integration already exists.
13+
14+
:::
15+
16+
The `Integration Class` is an Interface for AgileTs to other Frameworks like [React](https://reactjs.org/).
17+
It is mainly used to cause rerender on Components which has subscribed a particular State for reactivity.
918
```ts
1019
const reactIntegration = new Integration<typeof React, AgileReactComponent>({
1120
key: 'myFramework',
1221
frameworkInstance: MyFramework,
1322
bind() {
14-
// Will be called on the instantiaten of AgileTs
23+
// Will be called as soon as the Integration gets integrated into AgileTs
1524
},
16-
updateMethod(componentInstance, updatedData: Object) {
17-
// Will be called on each Agile Sub Instance mutation
25+
updateMethod(componentInstance, updatedData) {
26+
// Will be called on each Agile Sub Instance mutation (Component based Subscription)
1827
// For instance if MY_STATE value mutates from 'jeff' to 'hans'
19-
// componentInstance: The Component to which the State got subscribed (In React it might be the Class Component)
20-
// updatedData: The data which got updated, for instance {myState: 'hans'}
28+
// Function based Subscriptions use a callback to cause a rerender on Components and therefore don't call 'updateMethod()'
29+
// componentInstance: The Component to which the State got subscribed to (In React it might be the Class Component)
30+
// updatedData: The data which got updated, for instance '{myState: 'hans'}'
2131
}
22-
2332
});
2433
```
2534

35+
## 💾 Example
2636

27-
28-
29-
30-
31-
32-
37+
### 🔵 [`React`](https://reactjs.org/)
38+
Here is how the React Integration looks like.
3339
```ts
3440
import { Agile, flatMerge, Integration } from '@agile-ts/core';
3541
import { AgileReactComponent } from './hocs/AgileHOC';
@@ -42,6 +48,8 @@ const reactIntegration = new Integration<typeof React, AgileReactComponent>({
4248
// Nothing to bind ;D
4349
return Promise.resolve(true);
4450
},
51+
// Used to update State in Class Component (Component based Subscription)
52+
// (Function Components use a Function based Subscription therefore they don't call 'updateMethod()')
4553
updateMethod(componentInstance, updatedData: Object) {
4654
// Merge changes into State if some Data updated otherwise force rerender
4755
if (Object.keys(updatedData).length !== 0) {
@@ -61,6 +69,215 @@ Agile.initialIntegrations.push(reactIntegration);
6169

6270
export default reactIntegration;
6371
```
72+
However, to properly use AgileTs in React Function and Class Components there were some other things to do.
73+
For instance, we had to create the `useAgile` Hook or the `AgileHOC` to properly subscribe States to Components.
74+
So lets take a quick look how a simplified `useAgile` Hook looks like.
75+
```ts
76+
import React from 'react';
77+
import {
78+
Agile,
79+
getAgileInstance,
80+
extractObservers,
81+
Observer,
82+
State,
83+
} from '@agile-ts/core';
84+
85+
function useAgile(deps: Array<State | Collection | Observer | undefined>, agileInstance?: Agile){
86+
// Extract Observers from passed Agile Sub Instances
87+
// Each State has an Observer, which can be subscribed to a Component
88+
// Through such Observer the State is able to trigger rerenders on the subscribed Components
89+
const depsArray = extractObservers(deps);
90+
91+
// Trigger State, used to force Component to rerender
92+
const [, forceRender] = React.useReducer((s) => s + 1, 0);
93+
94+
useEffect(() => {
95+
// Try to get Agile Instance from passed Instances, otherwise drop error
96+
if (!agileInstance) agileInstance = getAgileInstance(depsArray[0]);
97+
if (!agileInstance || !agileInstance.subController) {
98+
Agile.logger.error('Failed to subscribe Component with deps', depsArray);
99+
return;
100+
}
101+
102+
// Remove undefined Observers, since we can't subscirbe a not existing Observer
103+
const observers: Observer[] = depsArray.filter(
104+
(dep): dep is Observer => dep !== undefined
105+
);
106+
107+
// Create Callback based Subscription
108+
// -> whenever a subscribed State mutates, this callback will be called
109+
// or if its an Component based Subscription the 'updateMethod()' in the React Integration (used in AgileHOC)
110+
const subscriptionContainer = agileInstance.subController.subscribeWithSubsArray(
111+
() => {
112+
forceRender();
113+
},
114+
observers,
115+
key
116+
);
117+
118+
// Unsubscribe Callback based Subscription on Unmount (cleaning up, otherwise it can lead to memory leaks)
119+
return () => {
120+
agileInstance?.subController.unsubscribe(subscriptionContainer);
121+
};
122+
}, []);
123+
124+
// Create return value
125+
return depsArray.map((dep) => {
126+
return dep?.value;
127+
});
128+
}
129+
```
130+
131+
132+
## Subscriptions
133+
134+
To properly create an Integration for AgileTs,
135+
we need a basic understanding of how States can be bound/subscribed to Components.
136+
Such subscriptions are important to cause rerender on the Component as soon as the State mutates.
137+
In AgileTs there are two ways of binding a State to a Component:
138+
139+
### `Component` based
140+
141+
A `Component based Subscription` is thought for Components which handle their States in a specific property.
142+
For instance in React Class Components the `this.state` property.
143+
If a State mutates, we simply can merge the changed State values into the Component State property.
144+
This will trigger a rerender.
145+
```ts
146+
const MY_STATE = App.createState('hans', {key: 'myState'});
147+
this.agileInstance().subController.subscribeWithSubsArray(
148+
MyComponent,
149+
[MY_STATE.observer]
150+
);
151+
```
152+
Now if the State mutates
153+
```ts
154+
MY_STATE.set('jeff');
155+
```
156+
the `updateMethod()` defined in the Integration will be called
157+
```ts
158+
// ..
159+
updateMethod(componentInstance, updatedData){
160+
console.log(componentInstance); // Returns 'MyComponent'
161+
console.log(updatedData); // Returns '{myState: 'jeff'}'
162+
}
163+
// ..
164+
```
165+
166+
### `Function` based
167+
168+
A `Function based Subscription` is thought for Components which don't have a specific property to handle States.
169+
So we can't trigger a rerender by mutating the State property of a Component.
170+
Therefore, we came across another solution. A callback function which triggers a rerender on the Component.
171+
This callback function will be called instead of the `updateMethd()`, whenever a bound State mutates.
172+
```ts
173+
const MY_STATE = App.createState('hans', {key: 'myState'});
174+
this.agileInstance().subController.subscribeWithSubsArray(
175+
() => {console.log('Called callback')},
176+
[MY_STATE.observer]
177+
);
178+
```
179+
Now if the State mutates
180+
```ts
181+
MY_STATE.set('jeff');
182+
```
183+
the callback function will be called.
184+
```ts
185+
// console: 'Called callback'
186+
```
187+
188+
189+
## 📭 Props
190+
191+
```ts
192+
new Integration(config);
193+
```
194+
195+
### `config`
196+
197+
A `Integration` takes a required configuration object as its only parameter.
198+
Here is a Typescript Interface of the configuration object for quick reference,
199+
however each property will be explained in more detail below.
200+
```ts
201+
export interface CreateIntegrationConfig<F = any, C = any>
202+
extends IntegrationMethods<C> {
203+
key: string;
204+
frameworkInstance?: F;
205+
}
206+
207+
// or without extending
208+
209+
export interface CreateIntegrationConfig<F = any, C = any> {
210+
key: string;
211+
frameworkInstance?: F;
212+
bind?: (agileInstance: Agile) => Promise<boolean>;
213+
updateMethod?: (componentInstance: C, updatedData: Object) => void;
214+
}
215+
```
216+
217+
<br/>
218+
219+
#### `key`
220+
221+
The property `key/name` should be a unique `string/number` to identify the Integration later.
222+
```ts
223+
new Integration({
224+
key: "myIntegration"
225+
// ..
226+
});
227+
```
228+
229+
| Type | Default | Required |
230+
|--------------------|-------------|----------|
231+
| `string \| number` | undefined | Yes |
232+
233+
<br/>
234+
235+
#### `frameworkInstance`
236+
237+
The Instance of the Framework the Integration represents.
238+
For instance in case of [React](https://reactjs.org) it should be the 'React' Instance.
239+
240+
| Type | Default | Required |
241+
|--------------------|-------------|----------|
242+
| `any` | undefined | No |
243+
244+
<br/>
245+
246+
#### `frameworkInstance`
247+
248+
The Instance of the Framework the Integration represents.
249+
For instance in case of [React](https://reactjs.org) it should be the `React` Instance.
250+
251+
| Type | Default | Required |
252+
|--------------------|-------------|----------|
253+
| `any` | undefined | No |
254+
255+
<br/>
256+
257+
#### `bind`
258+
259+
Will be called as soon as the Integration gets integrated into AgileTs.
260+
It might be used to configure something's in the Framework the Integration represents.
261+
If that's done it should return `true` if the Framework is ready and `false` if it's not.
262+
263+
| Type | Default | Required |
264+
|----------------------------------------------------|-------------|----------|
265+
| `(agileInstance: Agile) => Promise<boolean>` | undefined | No |
266+
267+
<br/>
268+
269+
#### `updateMethod`
270+
271+
Will be called as soon as a to a Component (`componentInstance`) subscribed State mutates.
272+
Be aware that this is only the case if it is a `Component based Subscription`,
273+
since in `Function based Subscription` a callback function will be called to trigger a rerender on the Component.
274+
275+
| Type | Default | Required |
276+
|--------------------------------------------------------|-------------|----------|
277+
| `(componentInstance: C, updatedData: Object) => void` | undefined | No |
278+
279+
280+
64281

65282

66283

docs/packages/core/features/integration/Methods.md

Lines changed: 0 additions & 12 deletions
This file was deleted.

docs/packages/core/features/state/Introduction.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ export interface StateConfigInterface {
9292
<br/>
9393

9494
#### `key`
95+
9596
The optional property `key/name` should be a unique `string/number` to identify the State later.
9697
```ts
9798
const MY_STATE = App.createState("myInitialValue", {
@@ -117,7 +118,6 @@ This property is mainly thought for internal use.
117118

118119
:::
119120

120-
121121
Determines which States depend on the State.
122122
This means if the State gets mutated and ingested into the `runtime`,
123123
the depending States will be ingested into the `runtime` too.

0 commit comments

Comments
 (0)