Progress on my DnD Initiative Tracker app has been steady. I’ve been working on it for about two months in my spare time. It’s been a bit slow, but that’s fine when you’re learning.
The application has slightly grown, previously it had a pre-set encounter, with some players and enemies. Now, the application has the following feature set:
- The home screen shows your players and encounters
- You can create a player (including level, HP and key attributes)
- You can create an encounter, which has a name and a list of enemies
- Starting an encounter navigates to a ‘roll for initiative’ screen
- During an encounter you control damage, healing and death saving throws
- After an encounter a summary screen is displayed.
Building a relatively decent-sized application has taught me a lot about the Redux ecosystem. I’d like to follow up on my “Initial Thoughts on Redux” post with some of the things that I have discovered along the way.
In my previous post I highlighted how you should work to keep your state as normalised as possible. As your application grows this becomes incredibly important. It reduces redundancy but also complexity. Flattening your state model means that changes to one particular section of your state are localised to one reducer. You can utilise a library such as node-uuid to generate uuids for your entities. Previously I was generating ids in a rather silly way:
If you were to persist your state, either to a server or local storage, a subsequent reload of the application would reset this
idCount variable to 0, and you’ll start getting id clashes. So design with the future in mind and use proper uuids:
Selectors as an Interface
The reselect library is primarily intended for Redux applications suffering from performance problems. Its idea is to create memoizable selectors - getters that retrieve data from your state tree. However, in of itself it is a useful tool to expose an more readable interface over your reducers.
Selectors are a chain of functions. They begin simply, accepting the state tree as an argument:
Then they build up into more formal selectors. You specify, as arguments to a
createSelector call, which selectors to accept as input. When they change, the resultant function is called:
Selectors can be called within your typical
mapStateToProps function, but I’ve also found them useful if I need to do anything complicated with an asynchronous action creator. For example, when the user wishes to start an encounter, we to take the enemies and players, and transform them into
Combatant objects. I do this as an action, but I use a selector to do the actual fetch work. This is all done asynchronously via
redux-thunk, so that you have access to the state:
You may not wish to use selectors for literally every piece of state access - indeed such a choice should be profiled in my opinion! - but thinking of selectors as a formal interface to your data increases the simplicity of accessing your state data. Especially when it’s incredibly normalised!
Separate your domain and UI state
In addition to your domain state, comprising your business models, there’s also the UI state. UI state that is transient, such as whether an element is focused, or what current working value of an
<input> is. This data may be considered purely UI, so why bother tracking it outside of a React component? It’s perfectly valid to store data outside of Redux’s state tree, if you feel this is necessary. Alternatively, you may decide to track your UI state within the Redux state tree, in which case you’ll have to expend a lot of effort plugging up
onBlahChange handlers for every UI component. It makes for a rather complex React tree.
I had this issue with my application. I used a form to handle adding a player and an enemy. In one case I hooked up every input component to dispatch an action. In the other, I decided to not bother and simply gathered all of the data together in an action creator fired
onSubmit. Thankfully I discovered the redux-form library, which acts as a higher-order component, and does all the legwork for you. You declare the form’s fields (and optionally its initial values), identify the form by a unique key, and wrap it all up:
In your root reducer, add in the redux-form reducer:
The library will handle dispatching actions to this isolated part of the state tree, so you don’t have to.
There are other libraries that take this approach too, such as redux-ui. It uses decorators to hydrate UI components with state from your reducers. These libraries touch upon the same idea - if you’re going to use your state tree to store UI state, ensure that it’s isolated from the domain parts of your state tree. Then when it comes to persisting your state tree, you can simply chop off the UI parts and only persist the domain logic.
The application is not yet complete. There’s a whole bunch of small things that I need to fix, ranging from small bugs to UI polishing. I have a few new features to add, such as taking the monsters from the 5th Edition System Reference Document and allowing users to add these to their encounters, rather than building afresh. This will then allow me to calculate encounter difficulties, based on the enemy challenge rating. I hope that adding new features will open up new avenues to explore within the Redux ecosystem.