@harlem/extension-compose
Advanced tools
Comparing version 2.1.0 to 2.1.1
{ | ||
"name": "@harlem/extension-compose", | ||
"amdName": "harlemCompose", | ||
"version": "2.1.0", | ||
"version": "2.1.1", | ||
"license": "MIT", | ||
@@ -39,6 +39,6 @@ "author": "Andrew Courtice <andrewcourtice@users.noreply.github.com>", | ||
"build": "yarn run --top-level tsup", | ||
"prepublish": "yarn build" | ||
"prepublishOnly": "yarn build" | ||
}, | ||
"dependencies": { | ||
"@harlem/utilities": "^2.1.0" | ||
"@harlem/utilities": "^2.1.1" | ||
}, | ||
@@ -50,5 +50,6 @@ "peerDependencies": { | ||
"devDependencies": { | ||
"@harlem/core": "^2.1.0", | ||
"@harlem/core": "^2.1.1", | ||
"vue": "^3.2.0" | ||
} | ||
}, | ||
"gitHead": "c72c6733c53bea4c04a4c519326697c4a00cada3" | ||
} |
214
README.md
@@ -5,2 +5,214 @@ # Harlem Compose Extension | ||
This is the official compose extension for Harlem. | ||
This is the official compose extension for Harlem. The compose extension adds the ability to create simple read/write operations without having to explicitly define a mutation. This extension also helps to reduce boilerplate code in components when definining writable computeds that simply change a single state value. | ||
## Getting Started | ||
Follow the steps below to get started using the compose extension. | ||
### Installation | ||
Before installing this extension make sure you have installed `@harlem/core`. | ||
```bash | ||
yarn add @harlem/extension-action | ||
# or | ||
npm install @harlem/extension-action | ||
``` | ||
### Registration | ||
To get started simply register this extension with the store you wish to extend. | ||
```typescript | ||
import composeExtension from '@harlem/extension-compose'; | ||
import { | ||
createStore | ||
} from '@harlem/core'; | ||
const STATE = { | ||
firstName: 'Jane', | ||
lastName: 'Smith' | ||
}; | ||
const { | ||
state, | ||
getter, | ||
mutation, | ||
useState, | ||
computeState | ||
} = createStore('example', STATE, { | ||
extensions: [ | ||
composeExtension() | ||
] | ||
}); | ||
``` | ||
The compose extension adds several new methods to the store instance (highlighted above). | ||
## Usage | ||
### Basic | ||
The most basic way to use the compose extension is to use the `computeState` method. The `computeState` method creates a writable computed by which you can read/write state directly. Take the following store as an example: | ||
```typescript | ||
// store.ts | ||
const { | ||
state, | ||
computeState | ||
} = createStore('example', { | ||
details: { | ||
name: '' | ||
} | ||
}, { | ||
extensions: [ | ||
composeExtension() | ||
] | ||
}); | ||
``` | ||
If all you need to do is update the name field it can be cumbersome to write a mutation and a computed (in your component) just to update a simple field. Without the compose extension your code might look something like this: | ||
```typescript | ||
// store.ts | ||
const setName = mutation('set-name', (state, name) => state.details.name = name); | ||
``` | ||
And in your component: | ||
```vue | ||
<template> | ||
<input type="text" v-model="name" /> | ||
</template> | ||
<script lang="ts" setup> | ||
import { | ||
state, | ||
setName | ||
} from './store'; | ||
const name = computed({ | ||
get: () => state.details.name, | ||
set: name => setName(name) | ||
}); | ||
</script> | ||
``` | ||
While it isn't a lot of code, it can still be cumbersome to write for lots of places where all you are doing is directly reading/writing state. The equivalent code using the compose extension would be: | ||
```typescript | ||
// store.ts | ||
// No mutation necessary :) | ||
``` | ||
And in your component: | ||
```vue | ||
<template> | ||
<input type="text" v-model="name" /> | ||
</template> | ||
<script lang="ts" setup> | ||
import { | ||
computeState | ||
} from './store'; | ||
const name = computeState(state => state.details.name); | ||
</script> | ||
``` | ||
This drastically simplifies basic read/write operations. For auditability purposes you can even still specify the mutation name so you can see it in the devtools - just specify the mutation name as the second argument to the `computeState` method: | ||
```typescript | ||
const name = computeState(state => state.details.name, 'set-name'); | ||
``` | ||
If no mutation name is specified, one will be automatically generated from the path. In this case it would be: `compose:root/details/name`. | ||
### Advanced | ||
The more advanced usage of the compose extension is using the `useState` method. The `useState` method is designed to mimic React's `useState` method (or SolidJS's `createSignal`). The `useState` method returns a tuple with a get method and a set method. Given the same store structure as above: | ||
```typescript | ||
const [ | ||
getName, | ||
setName | ||
] = useState(state => state.details.name); | ||
getName(); | ||
setName('Phil'); | ||
``` | ||
This is useful for having more granular control over the read/write cycle as opposed to a computed automatically updating when any dependencies change (in this case, `name` on state). | ||
As with `computeState`, `useState` also accepts a mutation name as a second argument: | ||
```typescript | ||
const [ | ||
getName, | ||
setName | ||
] = useState(state => state.details.name, 'set-name'); | ||
``` | ||
## Considerations | ||
Please keep the following points in mind when using this extension: | ||
### Avoid transforms in the accessor function | ||
```typescript | ||
// This will work | ||
computeState(state => state.details.name); | ||
// This will not | ||
computeState(state => state.details.name.split(' ')); | ||
``` | ||
### Data structures still have readonly properties | ||
```typescript | ||
const details = computeState(state => state.details); | ||
// This will work | ||
details.value = { | ||
name: 'Phil' | ||
}; | ||
// This will not | ||
details.value.name = 'Phil' | ||
// Properties of the details object are still readonly | ||
// This is the same for arrays, maps, sets etc. | ||
``` | ||
### Avoid explicitly defining indexes in the accessor function | ||
```typescript | ||
// Although this is technically possible, it is strongly discouraged | ||
const firstRoleId = computeState(state => state.details.roles[0].id); | ||
// This will always update the id of the first item in the array | ||
firstRoleId.value = 5; | ||
``` | ||
### Avoid traversing multiple branches of state in the accessor function | ||
```typescript | ||
// This will generate an incorrect read/write path | ||
const name = computeState(state => { | ||
const something = state.something.id; | ||
return state.details.name; | ||
}); | ||
// Internally, Harlem uses a proxy object to determine which path in state you are traversing to | ||
// Traversing multiple state paths in the accessor function will assume you are trying to access: | ||
// something/id/details/name | ||
// As opposed to just: | ||
// details/name | ||
``` |
@@ -1,2 +0,4 @@ | ||
import { DeepReadonly } from '@vue/reactivity'; | ||
import { | ||
DeepReadonly, | ||
} from '@vue/reactivity'; | ||
@@ -3,0 +5,0 @@ export type Accessor<TState, TValue> = (state: TState) => TValue; |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
32026
16
424
218
Updated@harlem/utilities@^2.1.1