Rehash is a lightweight state container based on Redux and heavily inspired by Redux Zero. Instead of a singleton in-memory store, Rehash uses the browser's hash fragment to store your serialized state.
yarn add rehashjs
Import the necessary objects:
import { createStore, JsonSerializer, Provider, connect } from 'rehashjs'
To create a Rehash store, you'll need to define the shape of your state, and the serializer you want to use for each key:
const store = createStore({
count: JsonSerializer,
})
The keys you specify in createStore
will become part of the query string in
the hash fragment. For example, if the store had a value of 10
for the count
key, the hash fragment might look like this:
#?count=10
Rehash comes with two serializers - DateSerializer
, which serializes Date
s
to epoch millisecond strings, and JsonSerializer
, which, well, JSON-serializes
things.
Just like Redux, Rehash uses "actions" to modify application state. Unlike Redux, you don't have to define reducers or action creators - just tell Rehash what your actions are called and provide a reducer implementation.
const actions = {
increment: (state, payload) => ({ count: state.count + payload }),
}
Your reducer implementation receives the application state when the action is
called, but you won't have to worry about that when you're actually calling the
action - the connect
function curries the reducers for you.
The return value from a Rehash reducer is merged into the program state - so you can return the entire state, just like in Redux, or you can return only what's changed.
If your action doesn't have a payload, the second argument is optional:
const actions = {
increment: state => ({ count: state.count + 1 }),
}
Many Rehash applications just need to modify the application's state, with no business logic necessary. If that's the case, you can have Rehash auto-generate your actions:
const actions = store.createActionsFromShape()
The generated actions will have the same names as your state keys.
To connect the store instance to React, use the Provider
component:
class App extends Component {
render() {
return (
<Provider store={store}>
<Counter />
</Provider>
)
}
}
Just like Redux, Rehash provides a connect
function that you can use to
connect your components to the Rehash store.
const mapStateToProps = ({ count }) => ({ count })
const Counter = connect(mapStateToProps)(({ count }) => {
return (
<div>
<h1>Count: {count || 0}</h1>
</div>
)
})
connect
takes two arguments, both optional:
mapStateToProps
, which extracts values from the Rehash store to pass to the component and must return an object,actions
, an object containing actions used by the component
If you don't provide mapStateToProps
, the entire state will be passed to the
component. The actions and the mapStateToProps
return value will be passed to
the component as React props.
Update the state by calling one of your actions, optionally passing in a payload:
const mapStateToProps = ({ count }) => ({ count })
const actions = {
increment: (state, payload) => ({ count: state.count + payload }),
}
const Counter = connect(mapStateToProps, actions)(({ count, increment }) => {
return (
<div>
<h1>Count: {count || 0}</h1>
<button onClick={() => increment(10)}>Increment!</button>
</div>
)
})
connect
automatically binds actions passed to it to the Rehash store, so you
don't have to worry about passing state to the action when you call it. When the
action is called, Rehash will run the "reducer" you specified when you defined
the action. The object the reducer returns will be merged with the existing
Store state and then serialized into the hash fragment.
For the example above, imagine that the hash fragment looked like this:
#?count=10
After clicking the button (and firing the action) with the payload of 10
,
Rehash will run the increment
reducer, which returns a object that looks like
this:
{ count: 20 }
...that object will then be passed to a serializer and the hash fragment will be regenerated with the new value:
#?count=20
Rehash provides a number of serializers out of the box:
Serializer | serialize |
deserialize |
---|---|---|
JsonSerializer |
calls JSON.stringify |
calls JSON.parse |
DateSerializer |
converts a Date object to a string containing millis since the epoch |
safely converts an epoch string to a new Date |
StringSerializer |
safely calls toString() on the input |
returns the input |
Any object with a serialize
and deserialize
method can serve as a Rehash
serializer. Let's make a simple serializer that transforms a Date
object into
an epoch string:
const DateSerializer = {
deserialize: dateString => {
return new Date(Number(dateString))
},
serialize: val => {
return val.getTime().toString()
},
}
When testing your connected components with something like Enzyme, the easiest
way to render your component in the test is to also render a Provider
component, passing in a test store:
describe('myComponent', () => {
describe('when we load the page', () => {
it('renders', () => {
const wrapper = mount(
<Provider store={store}>
<MyComponent />
</Provider>,
)
})
})
})
Rehash provides a createFakeStore
function that creates a store backed by an
in-memory cache and is otherwise fully functional:
import { createFakeStore, JsonSerializer } from 'rehashjs'
const fakeStore = createFakeStore({
count: JsonSerializer,
})
If you want to test store integration without mounting the Provider
, you can
create a fake store and pass it to the connected component's context
using the
options Enzyme provides when rendering:
import { createFakeStore, JsonSerializer } from 'rehashjs'
describe('myComponent', () => {
describe('when we load the page', () => {
it('renders', () => {
const fakeStore = createFakeStore({
count: JsonSerializer,
})
const wrapper = mount(<MyConnectedComponent />, {
context: { store: fakeStore },
childContextTypes: { store: PropTypes.object.isRequired },
})
})
})
})
Alternatively, you can isolate your component by mocking Rehash's connect
function:
jest.mock('rehashjs', () => {
return {
connect: (mapStateToProps, actions) => component => component,
}
})
...and then simply pass props to your component as normal:
describe('myComponent', () => {
describe('when we load the page', () => {
it('renders', () => {
const wrapper = mount(<MyComponent aProp={'aValue'} />)
})
})
})
There's a sample React app in examples/counter
. To start it:
yarn install
yarn start
- Visit
localhost:3000
in your browser
- Async/await?
- Middleware?
- Fork the repo, clone it, and cd into the directory.
- Use Node 8 (
nvm use
) and install packages (yarn
) - Add your feature... and, of course, a test for it.
- Run tests with
yarn test