react-bazaar
v0.0.11
Published
A simple boilerplate-less type-safe state store for react
Downloads
2
Maintainers
Readme
react-bazaar
A tiny wrapper for the new React (> 16.3) context API.
This library was written with the goal of easing the use of the context API, providing a no-boilerplate, completely type-safe, state store for react components.
The code samples are TypeScript, but the library works the same with Vanilla JS.
Only compatible with React >16.3
How to use?
Install
npm install react-bazaar
Define your state interface and initial-state object
interface ITodo {
id: number,
description: string,
done: boolean
}
interface IState {
todos: Array<ITodo>
}
const initialState : IState = {
todos: [
{ id: 1, description: "Learn Typescript", done: true },
{ id: 2, description: "Use React-Bazaar", done: false }
]
}
Create a wrapped context object
import { createBazaar } from "react-bazaar"
const context = createBazaar(initialState)
The wrapped context object contains a Provider and a Consumer component. The Provider-component is only needed at the root component from where you want to propagate state. The Consumer-component is needed where-ever you want to consume the global state.
Create the root component
const App = () => (
<context.Provider>
// ... the rest of your app / components
</context.Provider>
)
Consume the state
Render the consumer and give it a function that accepts a state object as first parameter.
// a dumb Todo-Component
interface ITodoComponentProps { todo: ITodo }
const Todo = (props: ITodoComponentProps) =>
<div style={{textDecoration: props.todo.done ? "line-through" : "none"}}>
{props.todo.description}
</div>
// a list of dumb Todo-Components, rendered from the todos in the store
const Todos = () => (
<context.Consumer>
{state => state.todos.map(todo => <Todo todo={todo} />)}
</context.Consumer>
)
The state's inferred type will be that of IState
so you will have complete type-safety and auto-completion.
Update the state
So far, this has just been the normal behaviour of the Context API. The difference lies in the fact that react-bazaar also offers a way to update the global state.
First, create an action. This is a function that receives the global state as first parameter and returns a changed state object. State changes must happen immutable. For convenience, we wrap a function around the action which takes some parameters.
(In this example I use the immutability helper library immer, which provides an easy way to do an immutable change by doing a mutation on a proxied object.)
import { Action } from "react-bazaar"
import produce from "immer"
const toggleTodo = (todoId: number) : Action<IState> => state => produce(state, state =>
{
const todoToToggle = state.todos.find(todo => todo.id === todoId)
if(todoToToggle)
todoToToggle.done = !todoToToggle.done
return state
})
We'll reuse the previous component but add the toggle functionality to it.
interface ITodoComponentProps { todo: ITodo, toggle: { () : void } }
const Todo = (props: ITodoComponentProps) =>
<div
onClick={props.toggle}
style={{textDecoration: props.todo.done ? "underline" : "none"}}>
{props.todo.description}
</div>
const Todos = () => (
<context.Consumer>
{
(state, dispatch) =>
state.todos.map(todo => <Todo todo={todo} toggle={() => dispatch(toggleTodo(todo.id))}/>)
}
</context.Consumer>
)
As you can see, the render props function of the consumer receives a second parameter dispatch
. This is a function which accepts an Action
and will update the state.
Middleware
When creating the context object with createBazaar
, you have the option to pass in an array of middleware functions.
const logDelimiter : Middleware<IState> = action => state =>
{
console.log("----------------------")
const newState = action(state)
console.log("----------------------")
return newState
}
const durationLogger : Middleware<IState> = action => state =>
{
const before = new Date().getTime()
const newState = action(state)
const after = new Date().getTime()
const delta = after - before
console.log(`Action took ${delta} milliseconds.`)
return newState
}
const changeLogger : Middleware<IState> = action => state =>
{
console.log("state before action", state)
const newState = action(state)
console.log("state after action", newState)
return newState
}
const context = createBazaar(initialState, [logDelimiter, durationLogger, changeLogger])
Tips & Tricks
Server Side Rendering
If you want to prefill your store on the server you can just call your actions on your initialState object manually, serialize the result, pass it to the client and use that as your initialState.
let state = initialState
state = toggleTodo(1)(state)
state = toggleTodo(2)(state)
// ...
// use state as your initial store state
That's it
That's all there is to it. Enjoy. Inspired by redux and react-contextual.
Flow-typings-PR welcome.