Gathering detailed insights and metrics for kiss-for-react
Gathering detailed insights and metrics for kiss-for-react
Gathering detailed insights and metrics for kiss-for-react
Gathering detailed insights and metrics for kiss-for-react
kiss-js-bloc
Bloc type state management solution for typescript
kiss-js-bloc-react
Bloc type state management solution for typescript | The react implementation
kiss-state-js
This is the bloc type state management tool for the TypeScript base web application
cra-template-kiss
Template KISS (Keep it small and simple) | template for Create React App
Kiss state management, for React. Visit kissforreact.org for detailed docs and examples.
npm install kiss-for-react
Typescript
Module System
Node Version
NPM Version
TypeScript (95.74%)
Gherkin (4.03%)
JavaScript (0.23%)
Total Downloads
0
Last Day
0
Last Week
0
Last Month
0
Last Year
0
MIT License
1 Stars
7 Commits
1 Watchers
1 Branches
1 Contributors
Updated on May 21, 2025
Latest Version
1.0.3
Package Id
kiss-for-react@1.0.3
Unpacked Size
938.10 kB
Size
146.21 kB
File Count
117
NPM Version
10.9.2
Node Version
18.20.4
Published on
May 21, 2025
Cumulative downloads
Total Downloads
Last Day
0%
NaN
Compared to previous day
Last Week
0%
NaN
Compared to previous week
Last Month
0%
NaN
Compared to previous month
Last Year
0%
NaN
Compared to previous year
Kiss is a state management package for React, launched in May 2025. While new to React, it's a mature solution, having been available for Flutter for years under the name Async Redux (top 8% of packages), battle-tested in hundreds of real-world applications.
The Kiss docs are published at https://kissforreact.org
State management solutions can sometimes overwhelm you with complex concepts and the significant knowledge overhead needed to avoid pitfalls. Kiss is the opposite, as there's no need to be especially clever to make things work. Detailed comparison.
AI ready: Kiss centralizes state and business logic in predictable ways, making it easier for AI models to reason about code. This improves AI-driven generation, and helps you achieve results with surprisingly little effort.
Here are the main concepts:
The store holds all the application state. A few examples:
1// Here, the state is a number 2const store = createStore<number>({initialState: 1}); 3 4// Here, the state is a plain JS object 5const store = createStore({ initialState: {name: 'Mary', age: 25} }); 6 7// Here, the state is an ES6 class object 8class State { constructor(public name: string, public age: number){} } 9const store = createStore<State>({ initialState: new State('Mary', 25) });
To use the store, add it in a StoreProvider
at the top of your component tree.
1function App() { 2 return ( 3 <StoreProvider store={store}> 4 <AppContent /> 5 </StoreProvider> 6 ); 7};
The useAllState
hook lets you access the state from any component.
It will rebuild when the state changes.
1function MyComponent() { 2 const state = useAllState(); 3 4 return <div>{state.name} has {state.age} years old</div>; 5};
The useSelect
hook selects only the part of the state that your component needs.
It will rebuild only when that part changes.
1function MyComponent() { 2 const name = useSelect((state) => state.name); 3 const age = useSelect((state) => state.age); 4 5 return <div>{name} has {age} years old</div>; 6};
The useObject
hook is another alternative that only rebuilds when needed:
1function MyComponent() { 2 3 const state = useObject((state) => { 4 name: state.name, 5 age: state.age 6 }); 7 8 return <div>{state.name} has {state.age} years old</div>; 9};
An action is a class that contain its own reducer.
1class Increment extends Action { 2 3 reduce() { 4 // The reducer has access to the current state 5 return this.state + 1; // It returns a new state 6 }; 7}
The store state is immutable.
The only way to change the store state is by dispatching an action. The action reducer returns a new state, that replaces the old one.
1// Dispatch an action 2store.dispatch(new Increment()); 3 4// Dispatch multiple actions 5store.dispatchAll([new Increment(), new LoadText()]); 6 7// Dispatch an action and wait for it to finish 8await store.dispatchAndWait(new Increment()); 9 10// Dispatch multiple actions and wait for them to finish 11await store.dispatchAndWaitAll([new Increment(), new LoadText()]);
The hooks to dispatch actions are useDispatch
, useDispatchAll
etc.
1function MyComponent() { 2 const dispatch = useDispatch(); 3 4 return ( 5 <Button onClick={() => dispatch(new LoadText())}> 6 Click me! 7 </Button> 8 ); 9};
Or getting the store with useStore
also allows you to dispatch actions:
1function MyComponent() { 2 const store = useStore(); 3 4 return ( 5 <Button onClick={() => store.dispatch(new LoadText())}> 6 Click me! 7 </Button> 8 ); 9};
They can download information from the internet, or do any other async work.
1const store = createStore<string>({initialState: ''});
1class LoadText extends Action { 2 3 // This reducer returns a Promise 4 async reduce() { 5 6 // Download something from the internet 7 let response = await fetch('https://dummyjson.com/todos/1'); 8 let text = await response.text(); 9 10 // Change the state with the downloaded information 11 return (state) => text; 12 }
If something bad happens, you can simply throw an error. In this case, the state will not change. Errors are caught globally and can be handled in a central place, later.
In special, if you throw a UserException
, which is a type provided by Kiss,
a dialog (or other UI) will open automatically, showing the error message to the user.
1class LoadText extends Action { 2 3 async reduce() { 4 let response = await fetch("https://dummyjson.com/todos/random/1"); 5 if (!response.ok) throw new UserException("Failed to load."); 6 7 let text = await response.text(); 8 return (state) => text; 9 }
To show a spinner while an asynchronous action is running, use isWaiting(action)
.
To show an error message inside the component, use isFailed(action)
.
1function MyComponent() { 2 3 const isWaiting = useIsWaiting(LoadText); 4 const isFailed = useIsFailed(LoadText); 5 const state = useAllState(); 6 7 if (isWaiting) return <CircularProgress /> 8 if (isFailed) return <p>Loading failed...</p>; 9 return <p>{state}</p>; 10}
You can use dispatchAndWait
to dispatch an action and wait for it to finish.
1class LoadTextAndIncrement extends Action { 2 3 async reduce() { 4 5 // Dispatch and wait for the action to finish 6 await this.dispatchAndWait(new LoadText()); 7 8 // Only then, increment the state 9 return (state) => state.copy({ count: state.count + 1 }); 10 } 11}
You can also dispatch actions in parallel and wait for them to finish:
1class BuyAndSell extends Action { 2 3 async reduce() { 4 5 // Dispatch and wait for both actions to finish 6 await this.dispatchAndWaitAll([ 7 new BuyAction('IBM'), 8 new SellAction('TSLA') 9 ]); 10 11 return (state) => state.copy({ 12 message: `New cash balance is ${this.state.cash}` 13 }); 14 } 15}
You can also use waitCondition
to wait until the state
changes in a certain way:
1class SellStockForPrice extends Action { 2 constructor(public stock: string, public price: number) { super(); } 3 4 async reduce() { 5 6 // Wait until the stock price is higher than the limit price 7 await this.waitCondition( 8 (state) => state.stocks.getPrice(this.stock) >= this.price 9 ); 10 11 // Only then, post the sell order to the backend 12 let amount = await postSellOrder(this.stock); 13 14 return (state) => 15 state.copy( 16 stocks: state.stocks.setAmount(this.stock, amount), 17 ); 18 } 19}
It's easy to add your own reusable "features" to your actions, but they come out of the box with some interesting ones:
To prevent an action from being dispatched while it's already running,
add the nonReentrant
property to your action class and set it to true
.
1class LoadText extends Action { 2 nonReentrant = true; 3 4 reduce() { ... } 5}
If an action fails, to retry it a few times with exponential backoff,
add the retry
property to your action class.
1class LoadText extends Action { 2 retry = {on: true} 3 4 reduce() { ... } 5}
And you can specify the retry policy:
1class LoadText extends Action { 2 3 retry = { 4 initialDelay: 350, // Millisecond delay before the first attempt 5 maxRetries: 3, // Number of retries before giving up 6 multiplier: 2, // Delay increase factor for each retry 7 maxDelay: 5000, // Max millisecond delay between retries 8 } 9 10 reduce() { ... } 11}
To limit how often an action occurs in response to rapid inputs, you can add a debounce
property
to your action class. For example, when a user types in a search bar, debouncing ensures that not
every keystroke triggers a server request. Instead, it waits until the user pauses typing before
acting.
1class SearchText extends Action { 2 constructor(public searchTerm: string) { super(); } 3 4 debounce = 300 // Milliseconds 5 6 async reduce() { 7 let result = await loadJson('https://example.com/?q=', searchTerm); 8 return (state) => state.copy({searchResult: result}); 9 } 10}
To prevent an action from running too frequently, you can add a throttle
property to your
action class. This means that once the action runs it's considered fresh, and it won't run
again for a set period of time, even if you try to dispatch it.
After this period ends, the action is considered stale and is ready to run again.
1class LoadPrices extends Action { 2 3 throttle = 5000 // Milliseconds 4 5 async reduce() { 6 let result = await loadJson('https://example.com/prices'); 7 return (state) => state.copy({prices: result}); 8 } 9}
Automatically checks if there is an internet connection before running the action. If there is no internet, the action aborts. Optionally, it can show a dialog to the user saying something like: "There is no Internet, please verify your connection".
1class LoadPrices extends Action { 2 3 checkInternet = { dialog: true } 4 5 async reduce() { ... } 6}
To provide instant feedback on actions that save information to the server, this feature immediately applies state changes as if they were already successful, before confirming with the server. If the server update fails, the change is rolled back and, optionally, a notification can inform the user of the issue.
1class SaveName extends Action { 2 3 optimisticUpdate = { ... } 4 5 async reduce() { ... } 6}
You can add a persistor
to save the state to the local device disk.
It supports serializing JavaScript objects and ES6 classes out of the box.
1const store = createStore<State>({ 2 persistor: new Persistor(), 3});
Just dispatch actions and wait for them to finish. Then, verify the new state or check if some error was thrown.
1class State { 2 constructor( 3 public items: string[], 4 public selectedItem: number 5 ) {} 6} 7 8test('Selecting an item', async () => { 9 10 const store = createStore<State>({ 11 initialState: new State(['A', 'B', 'C'], -1); 12 }); 13 14 // Should select item 2 15 await store.dispatchAndWait(new SelectItem(2)); 16 expect(store.state.selectedItem).toBe('B'); 17 18 // Fail to select item 42 19 let status = await store.dispatchAndWait(new SelectItem(42)); 20 expect(status.originalError).toBeInstanceOf(UserException); 21});
If you are the Team Lead, you set up the app's infrastructure in a central place, and allow your developers to concentrate solely on the business logic.
You can add a stateObserver
to collect app metrics, an errorObserver
to log errors,
an actionObserver
to print information to the console during development,
and a globalWrapError
to catch all errors.
1const store = createStore<string>({ 2 stateObserver: (action, prevState, newState, error, count) => { ... }, 3 errorObserver: (error, action, store) => { ... } 4 actionObserver: (action, count, ini) => { ... } 5 globalWrapError: (error) => { ... } 6});
For example, here we handle FirestoreError
errors thrown by Firebase.
We convert them into UserException
errors, which are built-in types that
automatically show a message to the user in an error dialog:
1globalWrapError: (error: any) => { 2 return (error instanceof FirestoreError) 3 ? UserException('Error connecting to Firebase') 4 : error; 5 }
The Team Lead may create a base action class that all actions will extend, and add some common functionality to it. For example, add getter shortcuts to important parts of the state, and selectors to help find information.
1class State { 2 items: Item[]; 3 selectedItem: number; 4} 5 6export abstract class Action extends KissAction<State> { 7 8 // Getter shortcuts 9 get items() { return this.state.items; } 10 get selectedItem() { return this.state.selectedItem; } 11 12 // Selectors 13 findById(id) { return this.items.find((item) => item.id === id); } 14 get selectedIndex() { return this.items.indexOf(this.selectedItem); } 15 searchByText(text) { return this.items.find((item) => item.text.includes(text)); } 16}
Now, all actions can use them to access the state in their reducers:
1class SelectItem extends Action { 2 constructor(public id: number) { super(); } 3 4 reduce() { 5 let item = this.findById(this.id); 6 if (item === undefined) throw new Error('Item not found'); 7 return this.state.copy({selectedItem: item}); 8 } 9}
Complete docs: https://kissforreact.org
Created by Marcelo Glasberg (GitHub, LinkedIn)
No vulnerabilities found.
No security vulnerabilities found.