@francisco.ruiz/global-state
v0.2.0
Published
Lightweight package to handle the global state of our React-based-applications via the new React Context API
Downloads
1
Maintainers
Readme
Global State
Lightweight package to handle the global state of our React-based-applications via the new React Context API.
Table of Contents
Motivation
Although right now we have very little needs regarding this topic (we moslty work on business logic-focused products), we have a common need for the reasons we'll review in the sections below.
Current issues
- At this moment, we have one single way to share data ("global state of our business logic") between different components, but it's performed within the Domain through the
streamify
decorator. - There are some data that we don't want to mix with our business logic: information from the browser, user interface or even user preferences.
- We use a lot the ugly pattern of passing down data throughout the whole tree, component by componet... annoying right?
Why React Context
- It's basically React, no any other 3rd-party libraries needed.
- Ease to use and very well documented.
- Very little boilerplate (almost none) in order to start using it. So different regarding other 3rd-party libraries like Redux.
- We avoid unnecessary renders by using a context provider that is a
PureComponent
and handles the "global state" locally, so that any other unneeded prop is passed down, just consumers will get the new updated values.
API Reference
Regarding this point, it's important to say the final implementation is completely based in a 3rd-party utility: https://github.com/dai-shi/react-context-global-state. That means at this moment it fits the most needs we have an also fulfill all my expectations I've had in this project. However, it doesn't mean we can adapt it by modifying the output of our library, or even create our own rewritten library. At the end of the day, the main ideas stand within that code.
Top-Level Exports
createGlobalState
, whose input is a plain object, and for each property a context will be created.- input:
{Object} initialState
- output:
{Object} {StateProvider, StateConsumer, stateItemConsumers, stateItemUpdaters}
- input:
withGlobalStateFactory
, a small utility to, once passed a neededGlobalStateConsumer
, wrap any component setting a new prop with the global state value required.
Example of Usage
Install
npm install @francisco.ruiz/global-state --save
Create your own state wrapper
I swear it's the hardest part 😛 Let's create it in the root directory and name it GlobalState
:
// GlobalState.js
import {createGlobalState, withGlobalStateFactory} from '@francisco.ruiz/global-state'
// Set your initial state.
const initialState = {
theme: 'dark',
language: 'en',
notifications: []
}
// Create your global state.
const {
StateProvider: GlobalStateProvider,
StateConsumer: GlobalStateConsumer,
stateItemUpdaters
} = createGlobalState(initialState)
// Set your actions in order to update your global state.
const actions = {
toggleTheme: () => {
const update = stateItemUpdaters.theme
update(theme => (theme === 'light' ? 'dark' : 'light'))
},
updateLanguage: language => {
const update = stateItemUpdaters.language
update(() => language)
},
pushNotification: notification => {
const update = stateItemUpdaters.notifications
update(notifications => [...notifications, notification])
}
}
// Generate your own HOC in order to consume easily the global state by any context.
// Call it by passing first the context and then the component: ´withGlobalState('theme', Component)´
const withGlobalState = withGlobalStateFactory(GlobalStateConsumer)
// Then export it all.
export {GlobalStateProvider, GlobalStateConsumer, withGlobalState, actions}
Provide your app
Do it where you render your app, let's say in your main file:
// index.js
import React from 'react'
import ReactDOM from 'react-dom'
import {BrowserRouter} from 'react-router-dom'
import {GlobalStateProvider} from './GlobalState'
import App from './App'
import './index.scss'
ReactDOM.render(
<GlobalStateProvider>
<BrowserRouter>
<App />
</BrowserRouter>
</GlobalStateProvider>,
document.getElementById('root')
)
Consume the global state
You can do it in the component you want, but let's watch an example within your app component:
// App.js
import React from 'react'
import PropTypes from 'prop-types'
import classNames from 'classnames'
import {withGlobalState, actions} from './GlobalState'
function App({title, theme}) {
const isDark = theme === 'dark'
return (
<div className={classNames('mt-App', {
'is-dark': isDark
})}>
<h1 className="mt-App-title">{title}</h1>
<div className="mt-App-toggleTheme">
Toggle theme
<input
type="checkbox"
checked={isDark}
onChange={actions.toggleTheme}
/>
</div>
</div>
)
}
App.propTypes = {
title: PropTypes.string,
theme: PropTypes.string
}
App.defaultProps = {
title: 'Hello World!'
}
export default withGlobalState('theme', App)
And now, enjoy! 🎉
Things to be considered
- If you're using a
BrowserRouter
wrapping your app you'll have to place yourGlobalStateProvider
into an upper level because it uses aPureComponent
and it won't let theBrowserRouter
to propagate the changes to the app components. - Within the context of Schibsted Spain, we recommend to wrap the global state and publish it as a npm package.
- On the global state creation, try to separate the initial state from the actions and the creation itself in different files.
Demo
You can see it working in the project demo I've made for the Front End Enabler test: https://francisco-ruiz-enabler-test.now.sh/
In this project you can toggle the theme between their light and dark modes and also navigate through the main funnel (list and detail) while the browser scroll is kept at its last position on the same page type.
Documentation
- https://github.com/reactjs/rfcs/blob/master/text/0002-new-version-of-context.md
- https://reactjs.org/blog/2018/03/29/react-v-16-3.html#official-context-api
- https://reactjs.org/docs/context.html
- https://github.com/dai-shi/react-context-global-state
- https://medium.com/@dai_shi/react-global-state-by-context-api-5b3efa8acc6b
- https://github.com/drcmda/react-contextual/blob/master/PITFALLS.md
- https://frontarm.com/articles/when-context-replaces-redux/
- https://frontarm.com/articles/react-context-performance/
Next steps
- Split actions by context.
- Accept to send multiple global state contexts at a time when calling
withGlobalState
, for instance:withGlobalState(['theme', 'language'], Component)
. - Generate specific consumer HOCs for each context, e.g.:
withTheme
,withBrowser
. - Support decorators and even React Hooks 😄