Top Related Projects
Quick Overview
Undux is a lightweight state management library for React applications. It provides a simple and type-safe way to manage global state in React projects, offering an alternative to more complex state management solutions like Redux.
Pros
- Type-safe: Leverages TypeScript for strong typing and better developer experience
- Simple API: Easy to learn and use, with a minimal learning curve
- Performant: Optimized for efficient updates and rendering
- Flexible: Can be used for both small and large-scale applications
Cons
- Limited ecosystem: Fewer third-party tools and extensions compared to more popular state management libraries
- Less suitable for complex state logic: May not be the best choice for applications with very complex state interactions
- Smaller community: Less community support and resources available compared to more established alternatives
Code Examples
Creating a store:
import { createConnectedStore } from 'undux'
type Store = {
count: number
user: { name: string } | null
}
export default createConnectedStore({
count: 0,
user: null
})
Using the store in a component:
import { Store } from './store'
const Counter = () => {
const store = Store.useStore()
return (
<div>
Count: {store.get('count')}
<button onClick={() => store.set('count')(store.get('count') + 1)}>
Increment
</button>
</div>
)
}
Adding effects:
Store.withEffects(store => {
store.on('count').subscribe(count => {
console.log('Count changed:', count)
})
return store
})
Getting Started
-
Install Undux:
npm install undux
-
Create a store:
import { createConnectedStore } from 'undux' const Store = createConnectedStore({ count: 0 })
-
Use the store in your components:
import { Store } from './store' const MyComponent = () => { const store = Store.useStore() return <div>Count: {store.get('count')}</div> }
-
Update state:
store.set('count')(store.get('count') + 1)
Competitor Comparisons
The Redux Framework
Pros of Rematch
- More comprehensive state management solution with built-in effects and plugins
- Better TypeScript support out of the box
- Larger community and ecosystem with more resources and extensions
Cons of Rematch
- Steeper learning curve due to more concepts and features
- Potentially more boilerplate code for simple use cases
- Heavier bundle size compared to Undux
Code Comparison
Undux:
import { createStore } from 'undux'
const store = createStore({
count: 0
})
store.set('count')(store.get('count') + 1)
Rematch:
import { init } from '@rematch/core'
const store = init({
models: {
count: {
state: 0,
reducers: {
increment: (state) => state + 1
}
}
}
})
store.dispatch.count.increment()
Summary
Rematch offers a more feature-rich state management solution with better TypeScript support and a larger ecosystem. However, it comes with a steeper learning curve and potentially more complexity for simple use cases. Undux, on the other hand, provides a simpler and more lightweight approach to state management, which may be preferable for smaller projects or those new to state management concepts.
Simple, scalable state management.
Pros of MobX
- More mature and widely adopted, with a larger ecosystem and community support
- Offers more advanced features like computed values, reactions, and actions
- Provides better performance for complex state management scenarios
Cons of MobX
- Steeper learning curve due to its more extensive API and concepts
- Requires more boilerplate code for setting up stores and actions
- Can lead to less predictable behavior due to its mutable state approach
Code Comparison
MobX:
import { makeAutoObservable } from "mobx";
class TodoStore {
todos = [];
constructor() {
makeAutoObservable(this);
}
addTodo(text) {
this.todos.push({ text, completed: false });
}
}
Undux:
import { createConnectedStore } from 'undux'
const store = createConnectedStore({
todos: []
})
store.on('todos').subscribe(todos => {
console.log('Todos updated:', todos)
})
Summary
MobX offers a more powerful and flexible state management solution with advanced features and better performance for complex applications. However, it comes with a steeper learning curve and more complexity. Undux, on the other hand, provides a simpler and more straightforward approach to state management, making it easier to learn and use for smaller projects or developers new to state management concepts.
A JS library for predictable global state management
Pros of Redux
- Widely adopted and battle-tested in large-scale applications
- Extensive ecosystem with middleware, developer tools, and integrations
- Predictable state management with a single source of truth
Cons of Redux
- Steeper learning curve and more boilerplate code
- Can be overkill for smaller applications
- Requires careful consideration of performance optimizations for large state trees
Code Comparison
Redux:
const reducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, count: state.count + 1 };
default:
return state;
}
};
Undux:
const store = Store.withLogger(createStore({
count: 0
}));
store.set('count')(store.get('count') + 1);
Redux requires defining reducers and action creators, while Undux offers a more straightforward API for updating state. Redux's approach provides more control and predictability, but Undux's simplicity can be beneficial for smaller projects or rapid prototyping.
Both libraries aim to solve state management problems in React applications, but they differ in complexity and approach. Redux offers a more structured and scalable solution, while Undux focuses on simplicity and ease of use.
🐻 Bear necessities for state management in React
Pros of Zustand
- Simpler API with less boilerplate code
- Better TypeScript support out of the box
- More flexible middleware system for extending functionality
Cons of Zustand
- Less opinionated structure, which may lead to inconsistent patterns in larger projects
- Lacks built-in React DevTools integration (requires additional setup)
Code Comparison
Undux:
import { createConnectedStore } from 'undux'
const store = createConnectedStore({
count: 0
})
export default store
Zustand:
import create from 'zustand'
const useStore = create(set => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 }))
}))
export default useStore
Both Undux and Zustand are state management libraries for React applications, but they differ in their approach and features. Zustand offers a more lightweight and flexible solution, while Undux provides a more structured and opinionated approach. The choice between the two depends on project requirements and developer preferences.
Business logic with ease ☄️
Pros of effector
- More comprehensive state management solution with support for domains, events, and effects
- Better TypeScript support and type inference
- Larger community and ecosystem with more resources and plugins
Cons of effector
- Steeper learning curve due to more concepts and abstractions
- Potentially more boilerplate code for simple use cases
- Larger bundle size compared to undux
Code comparison
undux:
import { createStore } from 'undux'
const store = createStore({
count: 0,
})
store.set('count')(store.get('count') + 1)
effector:
import { createStore, createEvent } from 'effector'
const increment = createEvent()
const $count = createStore(0)
.on(increment, state => state + 1)
increment()
Summary
effector offers a more powerful and flexible state management solution with better TypeScript support, but it comes at the cost of increased complexity and a steeper learning curve. undux provides a simpler API for basic state management needs, making it easier to get started with but potentially limiting for more complex applications. The choice between the two depends on the specific requirements of your project and the level of complexity you're willing to manage.
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual CopilotREADME
Dead simple state management for React
ð Official docs: https://undux.org
Install
# Using Yarn:
yarn add undux
# Or, using NPM:
npm install undux --save
Install (with RxJS v4-)
# Using Yarn:
yarn add undux@^3
# Or, using NPM:
npm install undux@^3 --save
Design Goals
- Complete type-safety, no exceptions
- Super easy to use: forget actions, reducers, dispatchers, containers, etc.
- Familiar abstractions: just
get
andset
Use
1. Create a store
import { createConnectedStore } from 'undux'
// Create a store with an initial value.
export default createConnectedStore({
one: 0,
two: 0,
})
Be sure to define a key for each value in your model, even if the value is initially undefined
.
2. Connect your React components
With React Hooks: useStore
import { useStore } from './MyStore'
// Re-render the component when the store updates.
function MyComponent() {
const store = useStore()
return (
<>
<NumberInput onChange={store.set('one')} value={store.get('one')} />
<NumberInput onChange={store.set('two')} value={store.get('two')} />
Sum: {store.get('one') + store.get('two')}
</>
)
}
function NumberInput({ onChange, value }) {
return (
<input
onChange={(e) => onChange(parseInt(e.target.value, 10))}
type="number"
value={value}
/>
)
}
export default MyComponent
Without React Hooks: withStore
import { withStore } from './MyStore'
// Re-render the component when the store updates.
function MyComponent({ store }) {
return (
<>
<NumberInput onChange={store.set('one')} value={store.get('one')} />
<NumberInput onChange={store.set('two')} value={store.get('two')} />
Sum: {store.get('one') + store.get('two')}
</>
)
}
function NumberInput({ onChange, value }) {
return (
<input
onChange={(e) => onChange(parseInt(e.target.value, 10))}
type="number"
value={value}
/>
)
}
export default withStore(MyComponent)
3. Put your app in an Undux Container
import MyComponent from './MyComponent'
import { Container } from './MyStore'
function MyApp() {
return (
<Container>
<MyComponent />
</Container>
)
}
export default MyApp
That's all there is to it.
Features
Effects
Though Undux automatically re-renders your connected React components for you when the store updates, it also lets you subscribe to changes to specific fields on your store. Undux subscriptions are full Rx observables, so you have fine control over how you react to a change:
import { debounce, filter } from 'rxjs/operators'
store
.on('today')
.pipe(
filter((date) => date.getTime() % 2 === 0), // Only even timestamps.
debounce(100), // Fire at most once every 100ms.
)
.subscribe((date) => console.log('Date changed to', date))
You can even use Effects to trigger a change in response to an update:
store
.on('today')
.pipe(debounce(100))
.subscribe(async (date) => {
const users = await api.get({ since: date })
store.set('users')(users)
})
In order to keep its footprint small, Undux does not come with RxJS out of the box. However, Undux does come with a minimal implementation of parts of RxJS, which interoperates with RxJS operators. To use RxJS operators, you'll need to install RxJS first:
npm install rxjs --save
Partial application
Partially apply the set
function to yield a convenient setter:
const setUsers = store.set('users')
setUsers(['amy'])
setUsers(['amy', 'bob'])
Built-in logger
Undux works out of the box with the Redux Devtools browser extension (download: Chrome, Firefox, React Native). To enable it, just wrap your store with the Redux Devtools plugin:
import { createConnectedStore, withReduxDevtools } from 'undux'
const store = createConnectedStore(initialState, withReduxDevtools)
Redux Devtools has an inspector, a time travel debugger, and jump-to-state built in. All of these features are enabled for Undux as well. It looks like this:
Alternatively, Undux has a simple, console-based debugger built in. Just create your store with withLogger
higher order store, and all model updates (which key was updated, previous value, and new value) will be logged to the console.
To enable the logger, simply import withLogger
and wrap your store with it:
import { createConnectedStore, withLogger } from 'undux'
let store = createConnectedStore(initialState, withLogger)
The logger will produce logs that look like this:
Effects
Undux is easy to modify with effects. Just define a function that takes a store as an argument, adding listeners along the way. For generic plugins that work across different stores, use the .onAll
method to listen on all changes on a store:
// MyStore.ts (if using TypeScript)
import { Effects } from 'undux'
type State = {
// ...
}
export type StoreEffects = Effects<State>
// MyEffects.ts
import { StoreEffects } from './MyStore'
const withLocalStorage: StoreEffects = (store) => {
// Listen on all changes to the store.
store
.onAll()
.subscribe(({ key, value, previousValue }) =>
console.log(key, 'changed from', previousValue, 'to', value),
)
}
Recipes
Creating a store (TypeScript)
// MyStore.ts
import { createConnectedStore, type Effects, type Store } from 'undux'
type State = {
foo: number
bar: string[]
}
const initialState: State = {
foo: 12,
bar: [],
}
export default createConnectedStore(initialState)
// If using effects..
export type StoreEffects = Effects<State>
// If using class components..
export type StoreProps = {
store: Store<State>
}
See full example (in JavaScript, TypeScript, or Flow) here.
Function component (TypeScript)
// MyComponent.ts
import { useStore, type StoreProps } from './MyStore'
type Props = {
foo: number
}
function MyComponent({ foo }: Props) {
const { store } = useStore()
return (
<>
Today is {store.get('today')}
Foo is {foo}
</>
)
}
export default MyComponent
// App.ts
import { Container } from './MyStore'
function App() {
return (
<Container>
<MyComponent foo={3} />
</Container>
)
}
export default App
See full example (in JavaScript, TypeScript, or Flow) here.
Class component (TypeScript)
Undux is as easy to use with class components as with function components.
// MyComponent.ts
import { withStore, type StoreProps } from './MyStore'
type Props = StoreProps & {
foo: number
}
class MyComponent extends React.Component<Props> {
render() {
return (
<>
Today is {this.props.store.get('today')}
Foo is {this.props.foo}
</>
)
}
}
export default withStore(MyComponent)
// App.ts
import { Container } from './MyStore'
function App() {
return (
<Container>
<MyComponent foo={3} />
</Container>
)
}
export default App
See full example (in JavaScript, TypeScript, or Flow) here.
Undux + Hot module reloading
See a full example here.
Undux + TodoMVC
See the Undux TodoMVC example here.
Design philosophy
Goal #1 is total type-safety.
Getting, setting, reading, and listening on model updates is 100% type-safe: use a key that isn't defined in your model or set a key to the wrong type, and you'll get a compile-time error. And connected components and Effects are just as type-safe.
Goal #2 is letting you write as little boilerplate as possible.
Define your model in a single place, and use it anywhere safely. No need to define tedious boilerplate for each field on your model. Container components and action creators are optional - most of the time you don't need them, and can introduce them only where needed as your application grows.
Goal #3 is familiar abstractions.
No need to learn about Actions, Reducers, or any of that. Just call get
and set
, and everything works just as you expect.
Tests
npm test
License
MIT
Top Related Projects
Convert designs to code with AI
Introducing Visual Copilot: A new AI model to turn Figma designs to high quality code using your components.
Try Visual Copilot