Skip to content

Commit

Permalink
feat: initial implementation
Browse files Browse the repository at this point in the history
Initial implementation of useLazyState

BREAKING CHANGE: Initial implementation
  • Loading branch information
carlosbaraza committed Sep 19, 2022
1 parent 6f6aac1 commit 487dc4e
Show file tree
Hide file tree
Showing 5 changed files with 208 additions and 44 deletions.
103 changes: 69 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# my-package-name
# use-lazy-state

[![npm package][npm-img]][npm-url]
[![Build Status][build-img]][build-url]
Expand All @@ -8,55 +8,90 @@
[![Commitizen Friendly][commitizen-img]][commitizen-url]
[![Semantic Release][semantic-release-img]][semantic-release-url]

> My awesome module
> Simple `useLazyState` hook. Similar to `useState` but each component using the state needs to opt-in the state changes. This is very useful to prevent re-rendering an entire tree when only a child should be actually re-rendered.
## Install

```bash
npm install my-package-name
npm install use-lazy-state
```

## Usage

```ts
import { myPackage } from 'my-package-name';
Create a state in the parent. Changes to the array will not trigger a re-render in the parent, because it is not using `useState`:

myPackage('hello');
//=> 'hello from my package'
```

## API
```tsx
import { useLazyState } from 'use-lazy-state';

### myPackage(input, options?)
const Parent = () => {
const numberList = useLazyState([0, 0, 0]);

#### input
return (
<div>
<Child state={numberList} index={0} />
<Child state={numberList} index={1} />
<Child state={numberList} index={2} />
</div>
);
};
```

Type: `string`
Opt-in the state changes with `state.useState`. Re-renders will only trigger when the state returned from the getter has changed (uses `===` to check changes):

```tsx
type Props = {
numberList: UseLazyState<number[]>;
index: number;
};

const Child = ({ numberList, index }: Props) => {
const n = numberList.useState(s => s[index]);

const addOne = () => {
numberList.setState(prev => {
prev[index] = prev[index] + 1;
return [...prev];
});
};

return (
<div>
Number: {n}
<button onClick={addOne}>+1 [{index}]</button>
</div>
);
};
```

Lorem ipsum.
The `Child` component would only re-render when the state returned from your getter actually changes.

#### options
## Demo

Type: `object`
- [CodeSandbox demo](https://codesandbox.io/s/uselazystate-5ti537?file=/src/useLazyState.ts)

##### postfix
## API

Type: `string`
Default: `rainbows`
```
export type UseLazyState<T> = {
_stateRef: any;
setState: (action: T | UseLazyStateSetter<T>) => void;
useState: <D = T>(getter?: (state: T) => D) => D;
};
Lorem ipsum.
export type UseLazyStateSetter<T> = (prev: T) => T;
```

[build-img]:https://github.com/carlosbaraza/use-lazy-state/actions/workflows/release.yml/badge.svg
[build-url]:https://github.com/carlosbaraza/use-lazy-state/actions/workflows/release.yml
[downloads-img]:https://img.shields.io/npm/dt/use-lazy-state
[downloads-url]:https://www.npmtrends.com/use-lazy-state
[npm-img]:https://img.shields.io/npm/v/use-lazy-state
[npm-url]:https://www.npmjs.com/package/use-lazy-state
[issues-img]:https://img.shields.io/github/issues/carlosbaraza/use-lazy-state
[issues-url]:https://github.com/carlosbaraza/use-lazy-state/issues
[codecov-img]:https://codecov.io/gh/carlosbaraza/use-lazy-state/branch/main/graph/badge.svg
[codecov-url]:https://codecov.io/gh/carlosbaraza/use-lazy-state
[semantic-release-img]:https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
[semantic-release-url]:https://github.com/semantic-release/semantic-release
[commitizen-img]:https://img.shields.io/badge/commitizen-friendly-brightgreen.svg
[commitizen-url]:http://commitizen.github.io/cz-cli/
[build-img]: https://github.com/carlosbaraza/use-lazy-state/actions/workflows/release.yml/badge.svg
[build-url]: https://github.com/carlosbaraza/use-lazy-state/actions/workflows/release.yml
[downloads-img]: https://img.shields.io/npm/dt/use-lazy-state
[downloads-url]: https://www.npmtrends.com/use-lazy-state
[npm-img]: https://img.shields.io/npm/v/use-lazy-state
[npm-url]: https://www.npmjs.com/package/use-lazy-state
[issues-img]: https://img.shields.io/github/issues/carlosbaraza/use-lazy-state
[issues-url]: https://github.com/carlosbaraza/use-lazy-state/issues
[codecov-img]: https://codecov.io/gh/carlosbaraza/use-lazy-state/branch/main/graph/badge.svg
[codecov-url]: https://codecov.io/gh/carlosbaraza/use-lazy-state
[semantic-release-img]: https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg
[semantic-release-url]: https://github.com/semantic-release/semantic-release
[commitizen-img]: https://img.shields.io/badge/commitizen-friendly-brightgreen.svg
[commitizen-url]: http://commitizen.github.io/cz-cli/
59 changes: 59 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@ryansonshine/cz-conventional-changelog": "^3.3.4",
"@types/jest": "^27.5.2",
"@types/node": "^12.20.11",
"@types/react": "^18.0.20",
"@typescript-eslint/eslint-plugin": "^4.22.0",
"@typescript-eslint/parser": "^4.22.0",
"conventional-changelog-conventionalcommits": "^5.0.0",
Expand Down
77 changes: 76 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,76 @@
export const myPackage = (taco = ''): string => `${taco} from my package`;
import { useEffect, useRef, useState } from 'react';

export type UseLazyStateSetter<T> = (prev: T) => T;
export type UseLazyState<T> = {
_stateRef: any;
setState: (action: T | UseLazyStateSetter<T>) => void;
useState: <D = T>(getter?: (state: T) => D) => D;
};

type Subscriber = {
id: number;
triggerUpdate: () => void;
};

export const useLazyState = <T>(initialState: T): UseLazyState<T> => {
const subscriberId = useRef<number>(0);
const ref = useRef<T>(initialState);

const subscribers = useRef<Subscriber[]>([]);

const setState: UseLazyState<T>['setState'] = action => {
if (typeof action === 'function') {
ref.current = (action as UseLazyStateSetter<T>)(ref.current);
} else {
const state = action;
ref.current = state;
}
for (const subscriber of subscribers.current) {
subscriber.triggerUpdate();
}
};

const _useState = function _useState<D = T>(_getter?: (state: T) => D): D {
const getter =
typeof _getter === 'undefined' ? (state: T): T => state : _getter;

if (typeof getter !== 'function')
throw new Error('Getter must be a function');

const innerInitialState = getter(initialState);

// stateRef to maintain a reference to an object that triggerUpdate could point to
const currentStateRef = useRef<any>(innerInitialState);

// useState to trigger react rendering when it is updated
const [currentState, setCurrentState] = useState(innerInitialState);

// Subscribe the hook to get global updates when changes happen in the state
useEffect(() => {
const id = subscriberId.current;
subscribers.current.push({
id,
triggerUpdate: () => {
const newState = getter(ref.current);
const currentState = currentStateRef.current;
if (currentState && newState === currentState) return; // no need to update state
// update state
currentStateRef.current = newState;
setCurrentState(newState);
},
});
subscriberId.current = subscriberId.current + 1;
return () => {
subscribers.current = subscribers.current.filter(s => s.id !== id);
};
}, []);

return currentState as D;
};

return {
_stateRef: ref,
setState,
useState: _useState,
};
};
12 changes: 3 additions & 9 deletions test/index.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
import { myPackage } from '../src';

describe('index', () => {
describe('myPackage', () => {
it('should return a string containing the message', () => {
const message = 'Hello';

const result = myPackage(message);

expect(result).toMatch(message);
describe('useLazyState', () => {
it('true === true', () => {
expect(true).toBe(true);
});
});
});

0 comments on commit 487dc4e

Please sign in to comment.