react-redux-url-state
v0.0.12
Published
URL state with Redux and React
Downloads
5
Readme
URL state in React Applications
This introductory article, that will be followed by tutorial, how to develop your own solution for URL state of your application.
There are many tools for handling the state in React SPA applications. The URL state however seems to be rather left behind.
If you do a quick research, you will see, there are some libraries approaching the URL state handling. But the functionality they offer is mostly bound to ad hoc use cases, that don’t have to be acceptable for your individual requirements.
When I was looking for the solution for our business requirements, those offered by recent libraries were often close to our needs. But never close enough.
One of the closest solutions was the Spotify’s one called RLS.
https://github.com/spotify/redux-location-state
The behaviour was close to what I needed. But still little different. Not applicable to my requirements.
So, I looked at the license (https://github.com/spotify/redux-location-state/blob/master/LICENSE) and set off to write my own solution based on its codebase.
Inspired by already present solutions, I started writing something, that fits the needs of my use case, but still generic enough, to keep the possibility of covering also other possible scenarios.
You can read more about individual types of the SPA state in this article http://borakpetr.cz/blog/state-management-in-react-applications?lang=en
react-redux-url-state npm package
This library is supposed to work together with Redux and History libraries as peer dependencies.
It will check the search string at the time of the initial load of the application, handling the URL as single source of truth.
During the lifecycle of the application, it maps the parts of your Redux state, defined in the separate config, to the URL.
Which means, that it keeps the state projected to the URL and the resulted URL will set up the values in the state of your application, once you copy it and pass it to the browsers address bar.
Installation
npm install react-redux-url-state --save
Sample application using react-redux-url-state
You can get the sample application using this library here
https://github.com/PetrBorak/deep-url-app
After downloading, run the following to start the example SPA:
npm install
npm run start
Setup
Here is example of minimalistic setup of the store with the library
import { createBrowserHistory } from 'history';
import { createStore, applyMiddleware, compose, combineReducers } from 'redux';
import {createReduxLocationActions, listenForHistoryChange } from 'react-redux-url-state';
import { paramSetup, mapLocationToState } from './init.deep.url';
import { reducer } from './store'
export const history = createBrowserHistory();
const { locationMiddleware, reducersWithLocation } = createReduxLocationActions(paramSetup, mapLocationToState, (history as any), combineReducers({
url: reducer
}));
const middleware = applyMiddleware(locationMiddleware);
export const composeEnhancers = compose;
export const store = createStore(reducersWithLocation, composeEnhancers(middleware));
listenForHistoryChange(store, (history as any));
On line 3 you import the following exports from react-redux-url-state:
- createReduxLocationActions
- Creates the enhaced middleware and reducer that will be used to setup the Redux store
- listenForHistoryChange
- Will link the history library with the store
On line 4 you import setup objects, that are used to setup the react-redux-url-state
- paramSetup
- Primary setup file for definition of which parts of the Redux state will be handled by the react-redux-url-state library
- mapLocationToState
- This is ordinary Redux reducer, which maps the changes in the url search string to state.
Now the react-redux-url-state library is setup and handling your URL as single source of truth for you React/Redux application.
Let’s Elaborate on the individual parts in more detail.
createReduxLocationActions
This function returns enhanced reducer and middleware that needs to be used for the setup of the Redux store. It accepts the following parameters:
- paramsSetup
- mapLocationToState
- history
- Original Store reducer
paramsSetup
This object is the setup of the linking between URL and Redux store. Let’s see what an example paramsSetup can look like
Const testPath = {
"url": {
"stateKey": "url.stateForTheUrl",
"type": "number",
"initialState": -1,
"options": {"shouldPush": true}
}
}
export const paramSetup = {
eventsToIgnore: [LOCATION_CHANGE],
eventToMerge: MERGE_URL_T0_STATE_AND_HYDRATE_URL_FROM_STATE,
'/test-path': testPath,
};
paramsSetup - eventToIgnore property
You can setup events, that will be ignored by the react-redux-url-state middleware, by defining eventsToIgnore property of the paramsSetup object as an array of event types strings.
paramsSetup - eventToMerge property
This property defines an initial event at which the URL passed will be merged into the Redux state. It should be called from container component. This event also starts the process of mapping the defined Redux state to the URL.
paramsSetup definition of paths setup The main functionality of paramsSetup is handled by properties whose key is used as mapper of the path in the URL.
In our paramsSetup we have defined
'/test-path': testPath,
That says to react-redux-url-state library to use the definition defined in the testPath object for URL with path
“/test-path”
Let’s look at testPath config object
Const testPath = {
"item": {
"stateKey": "url.stateForTheUrl",
"type": "number",
"initialState": -1,
"options": {"shouldPush": true}
}
}
The key, the “item” in our example defines that the item key in the URLs query part, will be handling some part of the state in the Redux.
The “item” config object includes the following:
- stateKey
- This is the path in the Redux state, which will be mapped to the URL’s query with key “item”
- It has to include path from the root of the Redux state object down through the objects structure
- In this particular case, the Redux store object can have the following structure
{
url: {
stateForTheUrl: 5
}
}
type
This tells the react-redux-url-state library, what is the type of the query parameter, that will be projected from URL to the Redux state
It is used for stringifying the Redux state value into URL string of the query and for parsing of the URL string to the Redux state
Available types are
array
number
boolean
string
object
As a flag option
As a record
Options * This property allows to define some more specific traits of the library behaviour.
keepOrder * Available for array type. Is set to true, the library will preserve the order of its array items in the state according to order in the URL
parse * You can define you own parser, that will be used to transform values in the URL query into records in the Redux state
serialize * You can define you own serializer, that will be used to transform values in the Redux state to the URL queries
delimiter * symbol that will be used to separate the key from the value in the URL * or symbol that will be used to separate the items in the array projected to the URL
shouldPush * When the state changes, the library will map the state into query. * If shouldPush is true, the resulted URL will be projected by calling history.push(URL) * Otherwise history.replace is used
isFlag * If used for record of type - Object. The items in the state will be projected as the Objects keys in the URL. * But only if the record for the key is true
mapLocationToState
This is pure function, which is used as a regular reducer. This reducer is called each time the URL changes.
In our example application, it looks like this:
export const mapLocationToState = (state: any, location: any) => {
switch (location.pathname) {
case '/test-path':
return merge({}, state, location.query);
default:
return state;
}
};
It takes state and location as parameters. We define, what should be the resulted state based on these two parameters.
createReduxLocationActions – output
Let’s look at this function again.
createReduxLocationActions(paramSetup, mapLocationToState, history, combineReducers({
url: reducer
}));
It takes the
- paramsSetup
- mapLocationToState
- history
- original Redux reducer
And returns:
locationMiddleware and reducersWithLocation
- locationMiddleware * is middleware that must be installed to Redux
- reducersWithLocation
- is original reducer enhanced by the library, that is handed over to Redux during setup
Creation of enhanced Redux store
On the last lines, we have
export const store = createStore(reducersWithLocation, composeEnhancers(middleware));
Here we create the Redux store and pass the enhanced reducer and react-redux-url-states middleware.
Linking with history library
The last thing, we need to do, is to link the whole react-redux-url-state engine to History library This is, what the last line in the example does
listenForHistoryChange(store, (history as any));
We are done
Let’s talk about the common flow
URL state and common flow
When user copies the deep url to the Adress Bar, the engine needs to project it to the state. When the user interacts with the application, the engine needs to project the observed state back to the URL.
The common flow starts always by entering the URL. However, some state changes can occur during the initial load of the application. At least the action of the Redux store to initialize to its initial values takes place in the beginning. We don’t want this initial state to overwrite our URL. The URL should be the source of truth.
For that reason, the react-redux-url-state library waits for signal, to start operating. The signal is defined in the setup object:
export const paramSetup = {
eventsToIgnore: [LOCATION_CHANGE],
eventToMerge: MERGE_URL_T0_STATE_AND_HYDRATE_URL_FROM_STATE,
'/test-path': testPath,
};
It’s the eventToMerge property. It’s simple string for the action creator. Before the library starts its magic, the action with the type defined by this property must be dispatched. Before that, any change in the store can take place and the library will ignore it. It’s recommended for this action to be dispatched from some container component close to the root of the application.
Conclusion
Now we should be able to handle the URL in the application as a primary source of truth. Which means, propagating the state changes to the URL when needed and, at same time, creating stateful URLs, which can be used to preserve the state of the application and be reused by copying it.
What can be added? Well, it’s not even a library. This solution takes only 8 mid-sized files.
In am going to write a step by step tutorial, in which I will show you how to implement your own solution, similar to what is outlined in this article.