I'm trying to cache the rendered markup of the App
component. I know that this is somehow "against the rules" but I'm in a server-less environment (chrome-extension). Upon page load i want to inject the cached App
markup into the DOM. The expected result is similar to the experience of having the react-component rendererd on a server. Very much as described here: /.
To illustrate my usecase, I have updated the Thinking in react example:
- App
- FilterableProductTable
- SearchBar
- ProductTable (containing from
reflux
store in state)- ProductCategoryRow
- ProductRow
- FilterableProductTable
As is expected, neither componentDidUpdate
nor componentWillUpdate
are called in App
.
Is it possible to detect updated child components in the App
component in a sane way? Preferably without modifying the child component classes?
I would like to avoid moving props/state to App
.
I'm trying to cache the rendered markup of the App
component. I know that this is somehow "against the rules" but I'm in a server-less environment (chrome-extension). Upon page load i want to inject the cached App
markup into the DOM. The expected result is similar to the experience of having the react-component rendererd on a server. Very much as described here: http://www.tabforacause.org/blog/2015/01/29/using-reactjs-and-application-cache-fast-synced-app/.
To illustrate my usecase, I have updated the Thinking in react example:
- App
- FilterableProductTable
- SearchBar
- ProductTable (containing from
reflux
store in state)- ProductCategoryRow
- ProductRow
- FilterableProductTable
As is expected, neither componentDidUpdate
nor componentWillUpdate
are called in App
.
Is it possible to detect updated child components in the App
component in a sane way? Preferably without modifying the child component classes?
I would like to avoid moving props/state to App
.
6 Answers
Reset to default 6I came up with a solution, that works as a drop in solution (without modifying child-components, or having knowledge of the whole app state, e.g.: Flux pattern):
App
can be wrapped in a Component that uses the MutationObserver
to track actual changes in the DOM.
You could define a callback in App which is passed down through its child hierarchy via props, to be triggered if the componentDidUpdate method of the child is called. This might get messy if you have a deep hierarchy with a lot of children, though.
I had a situation where I wanted to do this.setProps(…)
in unit tests (when the component rendered without a parent). But it causes an error if done when there is a parent.
My workaround was simply to set a prop like <MyComponent renderingWithoutParentForTest={true} />
in the unit test, and using that prop for a condition.
I recognize that this is ugly, though. In this particular situation it seemed to make sense.
The React documentation suggests two ways to deal with child-to-parent communication. The first one has been mentioned, which is to pass a function(s) down as props through the hierarchy from the parent and then call them in the child component.
Child-To-Parent Communication: https://facebook.github.io/react/tips/communicate-between-components.html
The second is to use a global event system. You could build your own event system that could work for these purposes fairly easily. It might look something like this:
var GlobalEventSystem = {
events: {},
subscribe: function(action, fn) {
events[action] = fn;
},
trigger: function(action, args) {
events[action].call(null, args);
}
};
var ParentComponent = React.createClass({
componentDidMount: function() {
GlobalEventSystem.subscribe("childAction", functionToBeCalledWhenChildTriggers);
},
functionToBeCalledWhenChildTriggers: function() {
// Do things
}
)};
var DeeplyNestedChildComponent = React.createClass({
actionThatHappensThatShouldTrigger: function() {
GlobalEventSystem.trigger("childAction");
}
});
This would function somewhat similarly to the Flux pattern. Using a Flux architecture might help solve your problem because the idea of view components subscribing to events is an important part of Flux. So you would have your parent component subscribe to some event in your Store(s) which would have been triggered by the child component.
if you have bigger app, event system is a lot better solution then passing props.
Think as flux recommends. component -> action -> dispatcher -> store
In store you would have your state. You would register callbacks of components to store. You fire action from any component and any other component, that is listening for store's changes are getting data. No matter how you change your hierarchy you always get you data where it's required.
dispatcher.js:
var Promise = require('es6-promise').Promise;
var assign = require('object-assign');
var _callbacks = [];
var _promises = [];
var Dispatcher = function () {
};
Dispatcher.prototype = assign({}, Dispatcher.prototype, {
/**
* Register a Store's callback so that it may be invoked by an action.
* @param {function} callback The callback to be registered.
* @return {number} The index of the callback within the _callbacks array.
*/
register: function (callback) {
_callbacks.push(callback);
return _callbacks.length - 1;
},
/**
* dispatch
* @param {object} payload The data from the action.
*/
dispatch: function (payload) {
var resolves = [];
var rejects = [];
_promises = _callbacks.map(function (_, i) {
return new Promise(function (resolve, reject) {
resolves[i] = resolve;
rejects[i] = reject;
});
});
_callbacks.forEach(function (callback, i) {
Promise.resolve(callback(payload)).then(function () {
resolves[i](payload);
}, function () {
rejects[i](new Error('#2gf243 Dispatcher callback unsuccessful'));
});
});
_promises = [];
}
});
module.exports = Dispatcher;
some store sample:
const AppDispatcher = require('./../dispatchers/AppDispatcher.js');
const EventEmitter = require('events').EventEmitter;
const AgentsConstants = require('./../constants/AgentsConstants.js');
const assign = require('object-assign');
const EVENT_SHOW_ADD_AGENT_FORM = 'EVENT_SHOW_ADD_AGENT_FORM';
const EVENT_SHOW_EDIT_AGENT_FORM = 'EVENT_SHOW_EDIT_AGENT_FORM';
const AgentsStore = assign({}, EventEmitter.prototype, {
emitShowAgentsAddForm: function (data) {
this.emit(EVENT_SHOW_ADD_AGENT_FORM, data);
},
addShowAgentsAddListener: function (cb) {
this.on(EVENT_SHOW_ADD_AGENT_FORM, cb);
},
removeShowAgentsAddListener: function (cb) {
this.removeListener(EVENT_SHOW_ADD_AGENT_FORM, cb);
}
});
AppDispatcher.register(function (action) {
switch (action.actionType) {
case AgentsConstants.AGENTS_SHOW_FORM_EDIT:
AgentsStore.emitShowAgentsEditForm(action.data);
break;
case AgentsConstants.AGENTS_SHOW_FORM_ADD:
AgentsStore.emitShowAgentsAddForm(action.data);
break;
}
});
module.exports = AgentsStore;
actions file:
var AppDispatcher = require('./../dispatchers/AppDispatcher.js');
var AgentsConstants = require('./../constants/AgentsConstants.js');
var AgentsActions = {
show_add_agent_form: function (data) {
AppDispatcher.dispatch({
actionType: AgentsConstants.AGENTS_SHOW_FORM_ADD,
data: data
});
},
show_edit_agent_form: function (data) {
AppDispatcher.dispatch({
actionType: AgentsConstants.AGENTS_SHOW_FORM_EDIT,
data: data
});
},
}
module.exports = AgentsActions;
in some component you are like:
...
componentDidMount: function () {
AgentsStore.addShowAgentsAddListener(this.handleChange);
},
componentWillUnmount: function () {
AgentsStore.removeShowAgentsAddListener(this.handleChange);
},
...
this code is kind of old but it works well and you can definitely get idea of how stuff work
you can use React.Children.count if you are interested only to know when children number is changed or you can access to the each children React.Children.map/forEach.
see this example (I'm using it in a useEffect hook, but you can use it in a componentDidMount or DidUpdate)
const BigBrother = props => {
const { children } = props;
const childrenIds = React.Children.map(children, child => {
return child ? child.props.myId : null;
}).filter(v => v !== null);
useEffect(() => {
// do something here
}, [childrenIds.join("__")]);
return (
<div>
<h2>I'm the big brother</h2>
<div>{children}</div>
</div>
}
then you can use it like this (use a dynamic list tho!)
<BigBrother>
<LilBrother myId="libindi" />
<LilBrother myId="lisoko" />
<LilBrother myId="likunza" />
</BigBrother>
App
component would need to know about child components changing? – WiredPrairie Commented Mar 13, 2015 at 21:02