Skip to content

Commit

Permalink
feat(hook): useMutableState (#405)
Browse files Browse the repository at this point in the history
* feat(hook): Enter useMutableState

* feat(hook): adds useMutableState tests
  • Loading branch information
antonioru committed Mar 14, 2023
1 parent af23358 commit e8e8998
Show file tree
Hide file tree
Showing 13 changed files with 147 additions and 37 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Expand Up @@ -1008,3 +1008,9 @@ Errored release
- Updates dependencies
- Improves documentation
- Improves types

## [4.0.0] - 2023-03-13

### Adds

- `useMutableState` hook
10 changes: 6 additions & 4 deletions README.md
Expand Up @@ -85,10 +85,7 @@ import useSomeHook from 'beautiful-react-hooks/useSomeHook'

## 🎨 Hooks

* [useQueryParam](docs/useQueryParam.md)
* [useQueryParams](docs/useQueryParams.md)
* [useSearchQuery](docs/useSearchQuery.md)
* [useURLSearchParams](docs/useURLSearchParams.md)
* [useMutableState](docs/useMutableState.md)
* [useInfiniteScroll](docs/useInfiniteScroll.md)
* [useObservable](docs/useObservable.md)
* [useEvent](docs/useEvent.md)
Expand Down Expand Up @@ -130,6 +127,11 @@ import useSomeHook from 'beautiful-react-hooks/useSomeHook'
* [useAudio](docs/useAudio.md)
* [useObjectState](docs/useObjectState.md)
* [useToggle](docs/useToggle.md)
* [useQueryParam](docs/useQueryParam.md)
* [useQueryParams](docs/useQueryParams.md)
* [useSearchQuery](docs/useSearchQuery.md)
* [useURLSearchParams](docs/useURLSearchParams.md)
*

<div>
<p align="center">
Expand Down
10 changes: 6 additions & 4 deletions docs/README.es-ES.md
Expand Up @@ -76,10 +76,8 @@ $ yarn add beautiful-react-hooks

## 🎨 Hooks

* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useInfiniteScroll](useInfiniteScroll.``md)
* [useMutableState](useMutableState.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
* [useGlobalEvent](useGlobalEvent.md)
Expand Down Expand Up @@ -119,6 +117,10 @@ $ yarn add beautiful-react-hooks
* [useAudio](useAudio.md)
* [useObjectState](useObjectState.md)
* [useToggle](useToggle.md)
* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)

<div>
<p align="center">
Expand Down
9 changes: 5 additions & 4 deletions docs/README.it-IT.md
Expand Up @@ -75,10 +75,7 @@ $ yarn add beautiful-react-hooks

## 🎨 Hooks

* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)
* [useMutableState](useMutableState.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down Expand Up @@ -119,6 +116,10 @@ $ yarn add beautiful-react-hooks
* [useAudio](useAudio.md)
* [useObjectState](useObjectState.md)
* [useToggle](useToggle.md)
* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)

<div>
<p align="center">
Expand Down
17 changes: 9 additions & 8 deletions docs/README.jp-JP.md
Expand Up @@ -41,8 +41,7 @@ React のカスタムフックは抽象的なコンポーネントのビジネ
これまでのところ、私たちが作成し、内部で共有されているフックの大半はかなりの頻度でコールバック参照、イベントとコンポーネントのライフサイクルに関して類似する点がある事が分かっています。<br />
この理由から、私たちはそれらの知見を企業や専門家が開発プロセスをスピードアップするのに役立つ(*できれば*)便利な React フックのコレクションとして `beautiful-react-hooks` にまとめました。
<br /><br />
さらに、コードの読みやすさを考慮して、簡潔かつ具体的な API を作成しました。
より大きなチームで使用し、共有できるように、学習曲線を可能な限り低く抑える事が可能です。
さらに、コードの読みやすさを考慮して、簡潔かつ具体的な API を作成しました。 より大きなチームで使用し、共有できるように、学習曲線を可能な限り低く抑える事が可能です。

**-- フックを利用する前に、ドキュメントを確認して下さい! --**

Expand Down Expand Up @@ -76,10 +75,7 @@ $ yarn add beautiful-react-hooks

## 🎨 Hooks

* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)
* [useMutableState](useMutableState.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down Expand Up @@ -120,6 +116,10 @@ $ yarn add beautiful-react-hooks
* [useAudio](useAudio.md)
* [useObjectState](useObjectState.md)
* [useToggle](useToggle.md)
* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)

<div>
<p align="center">
Expand All @@ -131,7 +131,8 @@ $ yarn add beautiful-react-hooks

## Peer dependencies

いくつかのフックはサードパーティライブラリ(rxjs、react-router-dom、redux)の上に構築されているため、それらのライブラリが peer dependencies としてリストされていることに気づくかもしれません。直接的にそれらのフックを使用しない限り、依存関係としてインストールする必要はありません。
いくつかのフックはサードパーティライブラリ(rxjs、react-router-dom、redux)の上に構築されているため、それらのライブラリが peer dependencies
としてリストされていることに気づくかもしれません。直接的にそれらのフックを使用しない限り、依存関係としてインストールする必要はありません。

## コントリビューション

Expand All @@ -143,7 +144,7 @@ $ yarn add beautiful-react-hooks

1. コードのテストを必ず書くようにし、PR を送る前に `npm test``npm build` を実行して問題がない事を確認してください。
2. カスタムフックを作成する場合には、ドキュメントに必ず追加するようにしてください。
(*カスタムフックのドキュメントテンプレートを用意しています [HOOK_DOCUMENTATION_TEMPLATE](../HOOK_DOCUMENTATION_TEMPLATE.md)*).
(*カスタムフックのドキュメントテンプレートを用意しています [HOOK_DOCUMENTATION_TEMPLATE](../HOOK_DOCUMENTATION_TEMPLATE.md)*).

### 利用しているライブラリ

Expand Down
9 changes: 5 additions & 4 deletions docs/README.pl-PL.md
Expand Up @@ -77,10 +77,7 @@ $ yarn add beautiful-react-hooks

## 🎨 Hooki

* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)
* [useMutableState](useMutableState.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down Expand Up @@ -121,6 +118,10 @@ $ yarn add beautiful-react-hooks
* [useAudio](useAudio.md)
* [useObjectState](useObjectState.md)
* [useToggle](useToggle.md)
* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)

<div>
<p align="center">
Expand Down
9 changes: 5 additions & 4 deletions docs/README.pt-BR.md
Expand Up @@ -76,10 +76,7 @@ $ yarn add beautiful-react-hooks

## 🎨 Hooks

* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)
* [useMutableState](useMutableState.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down Expand Up @@ -120,6 +117,10 @@ $ yarn add beautiful-react-hooks
* [useAudio](useAudio.md)
* [useObjectState](useObjectState.md)
* [useToggle](useToggle.md)
* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)

<div>
<p align="center">
Expand Down
9 changes: 5 additions & 4 deletions docs/README.uk-UA.md
Expand Up @@ -76,10 +76,7 @@ $ yarn add beautiful-react-hooks

## 🎨 Хуки

* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)
* [useMutableState](useMutableState.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down Expand Up @@ -120,6 +117,10 @@ $ yarn add beautiful-react-hooks
* [useAudio](useAudio.md)
* [useObjectState](useObjectState.md)
* [useToggle](useToggle.md)
* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)

<div>
<p align="center">
Expand Down
9 changes: 5 additions & 4 deletions docs/README.zh-CN.md
Expand Up @@ -73,10 +73,7 @@ $ yarn add beautiful-react-hooks

## 🎨 Hooks

* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)
* [useMutableState](useMutableState.md)
* [useInfiniteScroll](useInfiniteScroll.md)
* [useObservable](useObservable.md)
* [useEvent](useEvent.md)
Expand Down Expand Up @@ -117,6 +114,10 @@ $ yarn add beautiful-react-hooks
* [useAudio](useAudio.md)
* [useObjectState](useObjectState.md)
* [useToggle](useToggle.md)
* [useQueryParam](useQueryParam.md)
* [useQueryParams](useQueryParams.md)
* [useSearchQuery](useSearchQuery.md)
* [useURLSearchParams](useURLSearchParams.md)

<div>
<p align="center">
Expand Down
35 changes: 35 additions & 0 deletions docs/useMutableState.md
@@ -0,0 +1,35 @@
# useMutableState

This hook provides mutable states that trigger the component to re-render. It offers similar functionality to Svelte's reactivity, enabling
developers to write more efficient and concise code.

### Why? 💡

- Improves code streamlining by providing a reactive state that can be used to trigger a rerender

### Basic Usage:

```jsx harmony
import { Typography, Space, Button, Tag } from 'antd';
import useMutableState from 'beautiful-react-hooks/useMutableState';

const TestComponent = () => {
const counter = useMutableState({ value: 0 });

return (
<DisplayDemo title="useMutableState">
<Typography.Paragraph>
Counter: <Tag color="green">{counter.value}</Tag>
</Typography.Paragraph>
<Space>
<Button type="primary" onClick={() => counter.value += 1}>increase</Button>
<Button type="primary" onClick={() => counter.value -= 1}>decrease</Button>
</Space>
</DisplayDemo>
);
};

<TestComponent />
```

<!-- Types -->
2 changes: 1 addition & 1 deletion package.json
Expand Up @@ -335,4 +335,4 @@
"require": "./useAudio.js"
}
}
}
}
22 changes: 22 additions & 0 deletions src/useMutableState.ts
@@ -0,0 +1,22 @@
import { useMemo, useState } from 'react'

/**
* Returns a reactive value that can be used as a state.
*/
const useMutableState = <TValue, TProxied extends Record<string | symbol, TValue>>(initialState: TProxied) => {
if (typeof initialState !== 'object' || initialState === null) throw new Error('The initial state must be an object')

const [, setState] = useState(0)

return useMemo<TProxied>(() => new Proxy(initialState, {
set: (target, prop: keyof TProxied, value: TProxied[keyof TProxied]) => {
if (target && target[prop] !== value) {
target[prop] = value
setState((state) => (state + 1))
}
return true
}
}), [])
}

export default useMutableState
37 changes: 37 additions & 0 deletions test/useMutableState.spec.js
@@ -0,0 +1,37 @@
import React, { useEffect } from 'react'
import { render } from '@testing-library/react'
import { cleanup, renderHook } from '@testing-library/react-hooks'
import useMutableState from '../dist/useMutableState'
import assertHook from './utils/assertHook'

describe('useMutableState', () => {
beforeEach(cleanup)

assertHook(useMutableState)

it('should return an object', () => {
const { result } = renderHook(() => useMutableState({ value: 0 }))

expect(result.current).to.be.an('object').that.has.property('value')
})

it('should re-render when the value changes', () => {
const spy = sinon.spy()

const TestComponent = () => {
const state = useMutableState({ value: 0 })

spy()

useEffect(() => {
state.value = 1
}, [])

return <div>val: {state.value}</div>
}

render(<TestComponent />)

expect(spy.callCount).to.equal(2)
})
})

0 comments on commit e8e8998

Please sign in to comment.