Added MPJ state in the browser draft

This commit is contained in:
Daniel J. Summers 2018-08-24 22:27:22 -05:00
parent f3c061b0e5
commit 67b75f9631

View File

@ -0,0 +1,72 @@
---
layout: post
title: "A Tour of myPrayerJournal: State in the Browser"
date: 2018-08-26 12:15:00
author: Daniel
categories:
- [ Programming, JavaScript, Vue ]
- [ Series, A Tour of myPrayerJournal ]
tags:
- action
- components
- facebook
- flux
- javascript
- mutation
- react
- redux
- script
- state
- vue
- vuex
---
_NOTES:_
- _This is post 3 in a series; see [the introduction][intro] for all of them, and the requirements for which this software was built._
- _Links that start with the text "mpj:" are links to the 1.0.0 tag (1.0 release) of myPrayerJournal, unless otherwise noted._
Flux (a pattern that originated at Facebook) defines state, as well as actions that can mutate that state. Redux is the most popular implementation of that pattern, and naturally works very well with React. However, other JavaScript framewoks use this pattern, as it ensures that state is managed sanely. (Well, the state is sane, but so is the developer!)
As part of Vue, the [Vuex][] component is a flux implementation for Vue that provides a standard way of managing state. They explain it in much more detail, so if the concept is a new one, you may want to read their "What is Vuex?" page before you continue. Once you are ready, let's continue and take a look at how it's used in myPrayerJournal.
## Defining the State
The store ([mpj:store/index.js][store]) exports a single new `Vuex.Store` instance, with its `state` property defining the items it will track, along with initial values for those items. This represents the initial state of the app, and is run whenever the browser is refreshed.
In looking at our store, there are 4 items that are tracked; two items are related to authentication, and two are related to the journal. As part of authentication (which will get a further exploration in its own post), we store the user's profile and identity token in local storage; the initial values for those items attempt to access those values. The two journal related items are simply initialized to an empty state.
## Mutating the State
There are a few guiding principles for mutations in Vuex. First, they must be defined as part of the `mutations` property in the store; outside code cannot simply change one state value to another without going through a mutation. Second, they must be synchronous; mutations must be a fast operation, and must be accomplished in sequence, to prevent race conditions and other inconsistencies. Third, mutations cannot be called directly; mutations are "committed" against the current store. Mutations receive the current state as their first parameter, and can receive as many other parameters as necessary.
_(Side note: although these functions are called "mutations," Vuex is actually replacing the state on every call. This enables some really cool time-traveling debugging, as tools can replay states and their transformations.)_
So, what do you do when you need to run an asynchronous process - like, say, calling an API to get the requests for the journal? These processes are called actions, and are defined on the `actions` property of the store. Actions receive an object that has the state, but it also has a `commit` property that can be used to commit mutations.
If you look at [line 87 of store/index.js][line87], you'll see the above concepts put into action<a href="#note-1"><sup>1</sup></a> as a user's journal is loaded. This one action can commit up to 4 mutations of state. The first clears out whatever was in the journal before, commits the `LOADED_JOURNAL` mutation with an empty object. The second sets the `isLoadingJournal` property to `true` via the `LOADING_JOURNAL` mutation. The third, called if the API call resolves successfully, commits the `LOADED_JOURNAL` mutation with the results. The fourth, called whether it works or not, commits `LOADING_JOURNAL` again, this time with `false` as the parameter.
A note about the names of our mutations and actions - the Vuex team recommends defining constants for mutations and actions, to ensure that they are defined the same way in both the store, and in the code that's calling it. This code follows their recommendations, and those are defined in `action-types.js` and `mutation-types.js` in the `store` directory.
## Using the Store
So, we have this nice data store with a finite number of ways it can be mutated, but we have yet to use it. Since we looked at loading the journal, let's use it as our example ([mpj:Journal.vue][Journal.vue]). Beginning on line 49, we wrap up the computed properties with `...mapState`, which exposes data items from the store as properties on the component. Just below that, the `created` function calls into the store, exposed as `$store` on the component instance, to execute the `LOAD_JOURNAL` action.
The template uses the mapped state properties to control the display. On lines 4 and 5, we display one thing if the `isLoadingJournal` property is true, and another (which is really the rest of the template) if it is not. Line 12 uses the `journal` property to display a `RequestCard` ([mpj:RequestCard.vue][RequestCard.vue]) for each request in the journal.
I mentioned developer sanity above, and in the last post, I said that the logic that has `RequestCard` making the decision on whether it should show, instead of `Journal` deciding which ones it should show, would make sense. This is where we put those pieces together. The Vuex store is reactive; when data from it is rendered into the app, Vue will update the rendering if the store is changed. So, `Journal` simply displays a "hang on" note when the journal is loading, and "all the requests" once it's loaded. `RequestCard` only displays if the request should be displayed. And, the entire "brains" behind this is the thing that starts the entire process, the call to the `LOAD_JOURNAL` action. We aren't moving things around, we're simply displaying the state of things as they are!
`RequestListItem` ([mpj:RequestListItem.vue][RLI.vue]) is another component that bases its display off state. It will always provide a button to view the full request, but based on whether the request is answered, snoozed, or in a recurrence period, it will display the appropriate buttons and informational text. It is used by the `ActiveRequests`, `SnoozedRequests`, and `AnsweredRequests` list components, and those components are very simple; `ActiveRequests` and `SnoozedRequests` simply operate on the already-loaded journal, while `AnsweredRequests` pulls its data when the page is loaded.
<p>&nbsp;</p>
We've now toured our stateful front end; next time, we'll take a look at the API we use to get data into it.
---
<a name="note-1"><sup>1</sup></a> _Pun not originally intended, but it is now!_
[Vuex]: https://vuex.vuejs.org
[store]: https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/store/index.js "app/src/store/index.js | myPrayerJournal | GitHub"
[line87]: https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/store/index.js#L87 "app/src/store/index.js (line 87) | myPrayerJournal | GitHub"
[Journal.vue]: https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/components/Journal.vue "app/src/components/Journal.vue | myPrayerJournal | GitHub"
[RequestCard.vue]: https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/components/request/RequestCard.vue "app/src/components/request/RequestCard.vue | myPrayerJournal | GitHub"
[RLI.vue]: https://github.com/bit-badger/myPrayerJournal/blob/1.0.0/src/app/src/components/request/RequestListItem.vue "app/src/components/request/RequestListItem.vue | myPrayerJournal | GitHub"