I know, I know – this is a very controversial topic in React sphere. In this article I am not going to recommend you any state management library (Redux and MobX folks can put your pitch-forks down), nor am I recommending anything “revolutionary”, but something rather interesting, and mildly unconventional that I discovered. Read along and let me know what do you feel about this state management technique and why or why wouldn’t you use it!
I have heard all sorts of opinion on global state management in React. I know folks who wouldn’t start their project unless they have Redux setup. I have also seen people hating the boilerplate Redux introduces, so much that now solutions like MobX, which advertise their simplicity, have a cult of their own. Then there are people who belong to neither of these schools, and would rather use React Context with Hooks or even plain old component state – Building complex applications with just vanilla component state and avoiding all this fanciness is a talent in itself 😉
Thinking global state
So what is a global state anyway ? Essentially you are looking at the ability to have a central store which holds all the necessary state information, and some kind of API that let’s your components read the state as well as inform them of state changes.
You, of course, don’t necessarily need a library to achieve this. You could use the concept of prop-drilling and somehow manage to pass a global state, in the form of prop, to the whole app tree! It works, not necessarily the best scalable, maintainable, neat, readable solution out there but it works.
So, at end of the day, the concept of global state essentially boils down to storing all the mutable changes at single place, and notifying your component of these changes – this simpliefied description immediately makes me think of web sockets and message-passing through native window.postMessage(). if every time I make changes to the state, if I communicate to all the components which depend on the state the new value, my problem is solved, right ? This immediately made me explore the use of window.postMessage() – the issue with window.postMessage() is that it’s meant for cross-origin communication and your app and its components are mostly going to render within same-origin so I want something like postMessage() but for same-origin!
Enter BroadcastChannel
Prior to exploring this idea of React global state management through a native web API – I had never heard of BroadcastChannel – I had worked enough with window.postMessage() but this was something new.
BroadcastChannel API enables one-to-many communication between windows, tabs, iframes contexts residing on same origin. It’s quite a like window.postMessage() but for same origin! Exactly what I needed. It let’s you broadcast any arbitrary message to the interested parties on the same origin.
You could send messages to different “channels” – think of a message bus on the webpage – and then listen and react to the incoming messages on different channels.
BroadcastChannel API is quite simple – you don’t have to store contexts like you do in window.postMessage(),
const cName = new BroadcastChannel('user_name'); cName.onmessage = (e) => console.log(e.data); // prints `Avinesh Singh` cName.postMessage('Avinesh Singh');
Making a Todo app
So let’s make a Todo app which uses BroadcastChannel for state management, to earn some brownie points I am going to modify Redux’s official example Todo app. 😀
The todo app has three components,
- Text box to enter new todo item
- Ability to filter all, completed and pending todo items
- Clicking on individual todo items marks them “complete” or “incomplete”
Every time I add an item to the todo-list, I send a message on ADD_TODO
channel with the item text. When I receive a message, I append todos
array with a new object in the form of { text: 'Pat the Cat', completed: false }
Every time I switch between All
, Active
and Completed
filters, I send a message on SET_VISIBILITY_FILTER
channel with the filter I just selected. Upon receiving the message, I update the value of filter
key in the state object to one of the possible values – SHOW_ALL
, SHOW_COMPLETED
or SHOW_ACTIVE
for All
, Completed
and Active
respectively.
Lastly, every time I click on an individual item on the todo list, I send a message on TOGGLE_TODO
with the text of todo item I clicked on. When I receive the message, I map
through todos
array and when I find the item, I set todo.completed
to !todo.completed
Everytime there is a state change, I send the new mutated state on MAIN
channel and all the components can listen and update their internal state according to news state incoming on this channel. Components can also request for current state by sending any arbitrary message on GET_STATE
channel, this is helpful when the component loads for the first time.
Here’s structure of state variable,
const state = {
todos: [ { text: 'Pat the Cat', completed: false } ],
filter: 'SHOW_ALL`
}
Full code is available at avin3sh/statesman repo.
When a component that relies on state loads, you could use Effect hook to call a separate channel called GET_STATE
to obtain current state and then either listen to channel MAIN
to get entire app state to act upon, or specific channel for individual state changes, for example listening only on SET_VISIBILITY_FITLER
to act only on filter related state changes.
Those who have familiarity with Redux would realize that “channels” are quite similar to “Actions” in Redux, and listenBroadcasts()
function is quite similar to “Reducers”.
In an earlier commit I am also using window.localStorage to persist changes across browser/tab reloads!
Another interesting bit here is that state changes are across all the open browser windows/tabs – no more inconsistencies while adding items in your shopping cart 😉
The flow of message can be visualized like following,
Conclusion
Maintaining global app state in React using BroadcastChannel Web API is very much possible, and is, arguably, more neater than other techniques which which do not utilize external libraries. You could write your wrappers around the web API and maybe end-up with a custom robust alternative of Redux, MobX, and other state management libraries – maybes that’s a good idea for my next project.
Amazing article. Will definitely give this a try.