Batch Updating in React
Friday, November 6th 2015
This post talks about how React components perform updates for different types of events and how
we can get these updates to behave in a way that is ideal when using AJAX actions that update multiple Flux stores.
SyntheticEvents
React components will batch their updates inside of Events.
This means that when a component's setState
function is called
multiple times inside of an OnClick
event, the component will only call render
once (if shouldComponentUpdate
returns true
).
This is exactly the behavior I want. I only want to interact with stores that are in a complete, stable, and ready state.
AJAX and timeout events
Unfortunately, AJAX and window.timeout events are not batched like SyntheticEvents.
It is common for my code to update multiple stores from a single AJAX response.
Bugs crop up by using the default behavior of re-rendering on each call to setState
.
Luckily, there is a pattern to batch these updates and it is pretty simple to do to.
I batch the updates by using a single store that sets a isLoading
boolean
at the beginning of the AJAX event call and doesn't clear it until all the stores have finished their updates.
The main react component will use this
isLoading
boolean inside of its shouldComponentUpdate
function. The logic will say to not update if the current state and the next
state is loading.
This isLoading
acts as a lock and batches all the stores updates into one single render call.
This holds the invariant in our components that stores will be in a complete and stable state during render calls.
UI Lock Example
Here is a partially implemented version of a loading lock that performs batch updates for AJAX events.
AppDispatcher.dispatch({
type: 'AJAX_ACTION',
});
_isLoading = false;
var AppStore = {
isLoading() {
return _isLoading;
},
};
AppStore.dispatchToken = AppDispatcher.register(action => {
switch (action.type) {
case 'AJAX_ACTION':
_isLoading = true;
AppStore.emitChange();
break;
case 'AJAX_ACTION_SUCCESS':
AppDispatcher.waitFor([OtherStore1.dispatchToken, OtherStore2.dispatchToken]);
_isLoading = false;
AppStore.emitChange();
break;
}
});
var OtherStore1 = {
// Store state...
};
OtherStore1.dispatchToken = AppDispatcher.register(action => {
switch (action.type) {
case 'AJAX_ACTION_SUCCESS':
// Update state
OtherStore1.emitChange();
break;
}
});
var OtherStore2 = {
// Store state...
};
OtherStore2.dispatchToken = AppDispatcher.register(action => {
switch (action.type) {
case 'AJAX_ACTION_SUCCESS':
// Update state
OtherStore2.emitChange();
break;
}
});
function getState() {
return {
storeState1: OtherStore1.getAll(),
storeState2: OtherStore2.getAll(),
isLoading: AppStore.isLoading(),
};
}
var App = Reat.createClass({
shouldComponentUpdate(nextProps, nextState) {
var update = true;
if (this.state.isLoading && nextState.isLoading) {
update = false;
}
return update;
},
render() {
// Stores are in a stable complete state invariant holds! \o/
},
});
Encrypting secrets with knife-solo
Wednesday, October 21st 2015
We've had some secrets laying around in our database that needed to be changed and encrypted.
We have been using chef to deploy our service. With chef data bags we can encrypt and decrypt our secrets.
There is one problem though. It expects you to have a chef server.
However, we are using a pretty simple setup with no chef server at the moment.
Luckily there is knife-solo and knife-solo_data_bag.
Create a data bag named secrets with an item named database
$ knife solo data bag create secrets database --json-file data_bags/secrets/database.json --data-bag-path chef/data_bags/ --secret-file ~/secrets_key
Edit the existing data bag item.
$ knife solo data bag edit secrets database --data-bag-path chef/data_bags/ --secret-file ~/secrets_key
Logging django apps to syslog
Wednesday, May 25th 2015
I spent most of my day filling in the holes of my django/python logging knowledge.
I've never done logging in python or a django app before. And thus our code base, had zero logging in it.
A huge problem when it comes time to debug issues in production.
I read a few articles that I found at Full Stack Python and
also went through the Python doc logging tutorials.
Taking Some Pain out of Python Logging
was an eye opener when Hynek explained that syslog
can capture and record the python log records. Syslog was exactly
what I set out to do (I didn't know it was called syslog. I just knew about /var/logs, so today was a good learning experience).
I am not doing exactly what Hynek does. But it accomplishes my goal of getting our logs into syslog.
Here is the final LOGGING dictionary in settings.py I went with.
# settings.py
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse',
},
'require_debug_true': {
'()': 'django.utils.log.RequireDebugTrue',
},
},
'formatters': {
'verbose': {
'format': '%(process)-5d %(thread)d %(name)-50s %(levelname)-8s %(message)s'
},
'simple': {
'format': '[%(asctime)s] %(name)s %(levelname)s %(message)s',
'datefmt': '%d/%b/%Y %H:%M:%S'
},
},
'handlers': {
'console': {
'level': 'DEBUG',
'filters': ['require_debug_true'],
'class': 'logging.StreamHandler',
'formatter': 'simple'
},
'syslog': {
'level': 'DEBUG',
'class': 'logging.handlers.SysLogHandler',
'facility': 'local7',
'address': '/dev/log',
'formatter': 'verbose'
},
},
'loggers': {
# root logger
'':{
'handlers': ['console', 'syslog'],
'level': 'INFO',
'disabled': False
},
'thingsforwork': {
'handlers': ['console', 'syslog'],
'level': 'DEBUG',
'propagate': False,
},
},
}
Ubuntu 14.04 uses the rocket-fast system for log processing (rsyslog)
and we need to tell it about our local7 facility.
I got this one from serverfault.
# /etc/rsyslog.d/30-thingsforwork.conf
local7.* /var/log/thingsforwork.log
& ~ # This stops local7.* from going anywhere else.
Do a sudo service rsyslog restart
so rsyslog.d can pickup the new config. And ta-da!
# /var/log/thingsforwork.log
May 28 03:19:57 vagrant 11874 140156620121856 thingsforwork.dashboard.views.home DEBUG we are in the home page, this is a debug
May 28 03:19:57 vagrant 11874 140156620121856 thingsforwork.dashboard.views.home INFO We are logging django! e01be3ae-e9d1-49d7-97ac-00c5454f3026
May 28 03:20:02 vagrant 11874 140156620121856 requests.packages.urllib3.connectionpool INFO Starting new HTTP connection (1): dashboard.reviewpush.com
May 28 03:20:02 vagrant 11874 140156605413120 requests.packages.urllib3.connectionpool INFO Starting new HTTPS connection (1): tartan.plaid.com
May 28 03:20:02 vagrant 11874 140156392699648 googleapiclient.discovery INFO URL being requested: GET https://www.googleapis.com/discovery/v1/apis/analytics/v3/rest
May 28 03:20:02 vagrant 11874 140156375914240 requests.packages.urllib3.connectionpool INFO Starting new HTTPS connection (1): quickbooks.api.intuit.com