react-svelte-stores
Advanced tools
Comparing version 0.1.1 to 0.1.2
{ | ||
"name": "react-svelte-stores", | ||
"version": "0.1.1", | ||
"version": "0.1.2", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "build/index.js", |
164
README.md
# react-svelte-stores | ||
Truly Reactive Stores for React | ||
Truly Reactive Stores for React. | ||
Inspired by [Svelte](https://svelte.dev/tutorial/writable-stores) | ||
`npm i react-svelte-stores` | ||
@@ -8,88 +10,148 @@ | ||
* Separate your UI and business logic. Keep your component trees and your state trees clean and readable. | ||
* Write less React, write more TypeScript/JavaScript. Your code will be reusable in Svelte, React, Preact, etc. and much easier to unit test. Global state management is built in because state lives in your store objects, and your components are subscribed to them behind the scenes. Truly reactive stores! | ||
* No need for `Provider`, `mapStateToProps`, `mapDispatchToProps`, etc. Just plug your store into a react-svelte-stores hook and write a concise component that is a pure function of your store's state. "Dispatch actions" by calling your custom store object methods. | ||
* You can still write reducers if you really want to! [go to example] | ||
* [Less code, fewer bugs](https://blog.codinghorror.com/the-best-code-is-no-code-at-all/). Static-typing and unit-testability are cherries on top! | ||
- I really enjoy using Svelte for rapid prototyping. Svelte's stores, like most Svelte features, allow you to focus on features, not boilerplate. | ||
#### Pros and Cons (compared to Redux) | ||
* One store (made up of multiple reducers) vs multiple stores is a non-issue. If you need one action to affect two stores, you can easily compose them with an action creator [go to example]. [Facebook's Flux architecture works this way](https://s3-us-west-2.amazonaws.com/samuel-blog/flux-explain.png), but with a dispatcher rather than store method composition like in react-svelte-stores. | ||
- Gateway drug to Svelte, or a way for people who already love Svelte to write Svelte-like code in React. | ||
* Redux is [stringly-typed](https://wiki.c2.com/?StringlyTyped), react-svelte-stores are strongly-typed. This makes debugging Redux easier, but defining action constants, typing your action constants, defining action types, and creating action creators is extreme overkill for all but the most expansive front end projects. With react-svelte-stores, you are given an update function with which you map the previous state to the next state. | ||
- I wanted to use a state management library with persistence built-in. Use `persisted` (for localStorage) and `persistedAsync` (for AsyncStorage) to create a store that automatically serializes and rehydrates its state. | ||
* The Redux ecosystem comes with really good async middleware such as Redux-Observable and Redux-Saga. With react-svelte-stores, you have to write your own async logic. This is both a pro and a con. You get more freedom, but you have to put more thought into your architecure and documentation to make your app maintainable. To help with this issue, react-svelte-stores provide `asyncUpdate`, which don't exist in Svelte stores, so you don't have to write async IIFEs in your update functions. | ||
- I wanted to use Svelte stores with React Native and with TypeScript. | ||
* As of v1.0.0, react-svelte-stores dont't support time-travel debugging. I'm going to look into it, as this is the 'killer app' for Redux in my opinion. If that's something you really need right now, there's a workaround (as long as you're willing to write reducers) [go to example] | ||
- No need for `Provider`, `mapStateToProps`, `mapDispatchToProps`, etc. Just plug your store into a react-svelte-stores hook and write a concise component that is a pure function of your store's state. "Dispatch actions" by calling your custom store object methods. | ||
- [Less code, fewer bugs](https://blog.codinghorror.com/the-best-code-is-no-code-at-all/). Static-typing and unit-testability are cherries on top! | ||
- \*When I'm ready to make a "final version"/"production quality" application, I reach for Redux and Redux Observable. Redux's debugging experience and predictabilty are unmatched. However, react-svelte-stores and vanilla RxJS make development so easy that I use them for all prototypes/PoCs. | ||
## Usage | ||
* Split your app's state tree into small and readable stores. You should split stores in the same way you'd split reducers in Redux. | ||
- react-svelte-stores are meant for applications that require shared state, but will never grow to need Redux or other more robust state management libraries. I would place it in between the React Context API and Redux in terms of power. | ||
### hooks | ||
- I recommend using react-svelte-stores when you have a narrow and shallow state tree, or a wide and shallow state tree. When you have deep state trees and find yourself needing performance optimizations such as memoized selectors (compatible w/ react-svelte-stores), you should use Redux. React-Redux has a lot of performance benefits that react-svelte-stores will never match because I use it for prototyping. | ||
#### useStoreState hook | ||
## Examples | ||
```js | ||
import { useStoreState } from "react-svelte-stores"; | ||
import { counter } from "./stores/counter"; | ||
### Easy React Native Autocomplete Search | ||
const Counter = () => { | ||
const count = useStoreState(counter); | ||
`src/components/searchInput` | ||
```ts | ||
import React, { FC } from 'react' | ||
import { View, Text, TextInput } from 'react-native' | ||
import { searchStore } from '../stores/searchStore' | ||
const SearchInput: FC = () => { | ||
const searchTerm = useSelectedStoreState(searchStore, state => state.searchTerm); | ||
return ( | ||
<div> | ||
<p>{count}</p> | ||
<button onClick={counter.increment}>+</button> | ||
<button onClick={counter.decrement}>-</button> | ||
<button onClick={counter.reset}>Reset</button> | ||
</div> | ||
<View> | ||
<TextInput | ||
placeholder="search" | ||
value={searchTerm} | ||
onChangeText={searchStore.setSearchTerm} | ||
/> | ||
</View> | ||
); | ||
}; | ||
} | ||
``` | ||
`src/components/searchResults` | ||
```ts | ||
import React, { FC } from 'react' | ||
import { View, FlatList } from 'react-native' | ||
import { searchStore } from '../stores/searchStore' | ||
const SearchResults: FC = () => { | ||
const searchResults = useSelectedStoreState(searchStore, state => state.searchResults); | ||
return ( | ||
<FlatList | ||
data={searchResults} | ||
keyExtractor={item => item.id} | ||
renderItem={({item}) => <YourItemComponent item={item} />} | ||
/> | ||
); | ||
} | ||
``` | ||
#### Primitive Store | ||
`src/stores/searchStore` | ||
```ts | ||
import { writable } from "react-svelte-stores"; | ||
import { persistedAsync } from 'react-svelte-stores'; | ||
import { AsyncStorage } from 'react-native'; | ||
const createCounter = (initialValue: number) => { | ||
const { subscribe, update, set } = writable(initialValue); | ||
interface ISearchStoreState { | ||
searchTerm: string; | ||
loading: boolean; | ||
searchResults: Array<SearchResult> | ||
} | ||
const defaultSearchStoreState = { | ||
searchTerm: "", | ||
loading: false, | ||
searchResults:[] | ||
}; | ||
const createSearchStore = (initialState: ISearchStoreState) => { | ||
const { subscribe, update, set } = persistedAsync( | ||
initialState, | ||
"@yourApp/searchStore", | ||
AsyncStorage | ||
); | ||
const searchTerm$: Subject<string> = new Subject(); | ||
const autocomplete$ = searchTerm$.pipe( | ||
filter(searchTerm => searchTerm.length > 0), | ||
debounceTime(700), | ||
distinctUntilChanged(), | ||
switchMap(searchTerm => | ||
ajax.getJSON(`https://yourSearchApiHere.com/${searchTerm}`).pipe( | ||
catchError(err => { | ||
console.log(err); | ||
return of(err); | ||
}) | ||
) | ||
) | ||
); | ||
autocomplete$.subscribe((response: Array<SearchResult>) => update(state => ({ | ||
...state, | ||
loading: false | ||
searchResults: response | ||
})); | ||
return { | ||
subscribe, | ||
incrementBy: (incrementor: number) => update(count => count + incrementor), | ||
increment: () => update(count => count + 1), | ||
decrement: () => update(count => count - 1), | ||
reset: () => set(initialValue) | ||
setSearchTerm: (searchTerm: string) => { | ||
update(state => ({ | ||
...state, | ||
loading: searchTerm.length ? true : false, | ||
searchTerm | ||
})); | ||
searchTerm$.next(searchTerm); | ||
} | ||
}; | ||
}; | ||
export const counter = createCounter(7); | ||
export const searchStore = createSearchStore(defaultSearchStoreState); | ||
``` | ||
#### Complex State Tree Store | ||
## API Reference | ||
* Shallow copy the state tree, or use immer, with `useSelectedStoreState` to prevent unnecessary re-renders of for unchanged selections. | ||
### Hooks | ||
#### useSubscribedState hook | ||
#### `useStoreState(store: IStore<T>): T` | ||
### writable stores | ||
#### `useSelectedStoreState(store: IStore<T>, selector: <T, R>(state: T) => R): R` | ||
#### update | ||
* Compatible with reselect | ||
#### asyncUpdate | ||
### Stores | ||
### readable stores | ||
#### `writable(initialState: T): IWritableStore<T>` | ||
### derived stores | ||
#### `readable(initialState: T, setCallback?: ReadableSetCallback<T>): IReadableStore<T>` | ||
### custom stores | ||
#### `persisted(initialState: T, storeKey: string, throttleMs?: number): IWritableStore<T>` | ||
### persisted stores | ||
#### `persistedAsync(initialState: T, storeKey: string, AsyncStorage: AsyncStorageStatic, throttleMs?: number): IWritableStore<T>` | ||
### debugging | ||
TODO: loggin with proxy | ||
TODO: TodoMVC | ||
Custom stores must expose the subscribe function to be usable with hooks. |
@@ -76,2 +76,7 @@ import { useStoreState, useSelectedStoreState } from "./hooks"; | ||
}); | ||
// TODO | ||
// test("selector deps", () => { | ||
// const store = writable(todoState) | ||
// }) | ||
}); |
@@ -1,2 +0,2 @@ | ||
import { useState, useLayoutEffect } from "react"; | ||
import { useState, useLayoutEffect, useEffect } from "react"; | ||
import { IStore } from "./types"; | ||
@@ -23,3 +23,4 @@ import { get } from "./stores"; | ||
store: IStore<T>, | ||
selector: Selector<T, R> | ||
selector: Selector<T, R>, | ||
selectorDeps?: React.DependencyList | ||
) => { | ||
@@ -34,3 +35,10 @@ const [state, setState] = useState<R>(selector(get(store))); | ||
useEffect( | ||
() => { | ||
setState(selector(get(store))); | ||
}, | ||
selectorDeps ? selectorDeps : [] | ||
); | ||
return state; | ||
}; |
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
33912
661
157