Skip to content

Commit

Permalink
add-use-state: Creare useState magic
Browse files Browse the repository at this point in the history
  • Loading branch information
MaquinaTech committed Oct 27, 2024
1 parent 7e636ec commit 6083971
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 0 deletions.
2 changes: 2 additions & 0 deletions packages/alpinejs/src/alpine.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { debounce } from './utils/debounce'
import { throttle } from './utils/throttle'
import { setStyles } from './utils/styles'
import { entangle } from './entangle'
import { useState } from './useState'
import { nextTick } from './nextTick'
import { walk } from './utils/walk'
import { plugin } from './plugin'
Expand Down Expand Up @@ -63,6 +64,7 @@ let Alpine = {
evaluate,
initTree,
nextTick,
useState,
prefixed,
prefix,
plugin,
Expand Down
4 changes: 4 additions & 0 deletions packages/alpinejs/src/magics/$useState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { useState } from '../useState'
import { magic } from '../magics'

magic('useState', (initialValue) => useState(initialValue))
2 changes: 2 additions & 0 deletions packages/alpinejs/src/magics/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { warn } from '../utils/warn'
import { magic } from '../magics'

import './$useState'
import './$nextTick'
import './$dispatch'
import './$watch'
Expand All @@ -14,6 +15,7 @@ import './$el'
// Register warnings for people using plugin syntaxes and not loading the plugin itself:
warnMissingPluginMagic('Focus', 'focus', 'focus')
warnMissingPluginMagic('Persist', 'persist', 'persist')
warnMissingPluginMagic('useState()', 'useState', 'use-state')

function warnMissingPluginMagic(name, magicName, slug) {
magic(magicName, (el) => warn(`You can't use [$${magicName}] without first installing the "${name}" plugin here: https://alpinejs.dev/plugins/${slug}`, el))
Expand Down
14 changes: 14 additions & 0 deletions packages/alpinejs/src/useState.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export function useState(initialState = '') {
let state = Alpine.reactive({ value: initialState });

const setState = (newValue) => {
state.value = typeof newValue === 'function' ? newValue(state.value) : newValue;
};

return {
get state() {
return state.value;
},
setState
};
}
82 changes: 82 additions & 0 deletions packages/docs/src/en/magics/useState.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
---
order: 10
prefix: $
title: useState
---

# $useState

`$useState` is a magic function that allows you to read and set data in variables.

```alpine
<div x-data="{ title: $useState('Hello') }">
<button
@click="title.setState('Hello World!')"
x-text="title.state"
></button>
</div>
```

In the example above, the default value of `title` is set using `$useState('Hello')`. The variable is updated with `title.setState('Hello World!')`, and its value is accessed with `title.state`.

## Initial State

You can initialize the state with any value, including objects and arrays:

```alpine
<div x-data="{ user: $useState({ name: 'John', age: 30 }) }">
<button
@click="user.setState({ name: 'Jane', age: 25 })"
x-text="user.state.name"
></button>
</div>
```

## Reactive Updates

The state is reactive, meaning any changes to the state will automatically update the DOM elements that depend on it. This reactivity extends deeply, so if you pass the state variable as a parameter and modify it within a function, the changes will propagate and update the DOM as if it were an input/output variable.

```alpine
<div x-data="{ count: $useState(0) }">
<button
@click="increment(count)"
x-text="count.state"
></button>
</div>
<script>
function increment(state) {
state.setState(state.state + 1);
}
</script>
```

In this example, the `increment` function takes the state variable `count` as a parameter and updates its value. The DOM automatically reflects the updated state.

## Accessing State

You can access the current state using the `.state` property and update it using the `.setState` method.

## Example with Array

```alpine
<div x-data="{ items: $useState(['Item 1', 'Item 2']) }">
<button
@click="items.setState([...items.state, 'Item 3'])"
x-text="items.state.join(', ')"
></button>
</div>
```

In this example, a new item is added to the array, and the DOM updates to reflect the change.

## Benefits

### Input/Output Variables

One of the key benefits of using `$useState` is the ability to treat state variables as input/output variables. This means you can pass them around in functions and have their changes automatically propagate through the DOM, enhancing the reactivity of your application.

### Enhanced Security

Another significant advantage is that `$useState` helps in complying with Content Security Policy (CSP) guidelines. By avoiding inline scripts and using safer methods to manage state, your application becomes more secure and less vulnerable to certain types of attacks.

40 changes: 40 additions & 0 deletions tests/cypress/integration/magics/$useState.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { haveAttr, html, test } from '../../utils'

test('useState initializes state with the given initial value',
html`
<div x-data="{ state: $useState('testValue') }" x-init="$el.setAttribute('x-data', state)">
</div>
`,
({ get }) => {
cy.wait(1000) // Espera 1 segundo para asegurarte de que Alpine.js se haya inicializado
get('[x-data]').should(haveAttr('x-data', 'testValue'))
}
)

test('useState updates state correctly',
html`
<div x-data="{ state: $useState('initialValue') }" x-init="$el.setAttribute('x-data', state)">
<button @click="state('updatedValue')">Update</button>
</div>
`,
({ get }) => {
cy.wait(1000) // Espera 1 segundo para asegurarte de que Alpine.js se haya inicializado
get('[x-data]').should(haveAttr('x-data', 'initialValue'))
get('button').click()
get('[x-data]').should(haveAttr('x-data', 'updatedValue'))
}
)

test('useState reacts to state changes',
html`
<div x-data="{ state: $useState('initialValue') }" x-init="$el.setAttribute('x-data', state)">
<button @click="state('updatedValue')">Update</button>
</div>
`,
({ get }) => {
cy.wait(1000) // Espera 1 segundo para asegurarte de que Alpine.js se haya inicializado
get('[x-data]').should(haveAttr('x-data', 'initialValue'))
get('button').click()
get('[x-data]').should(haveAttr('x-data', 'updatedValue'))
}
)

0 comments on commit 6083971

Please sign in to comment.