Skip to content

Commit 18a4f5b

Browse files
authored
Merge pull request #693 from reactjs/fix-turbolinks-load-order
fix(UJS) support cases when Turbolinks is loaded after UJS
2 parents 0fdcf3d + bac14a5 commit 18a4f5b

File tree

13 files changed

+304
-61
lines changed

13 files changed

+304
-61
lines changed

README.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -245,7 +245,14 @@ You can use this when the DOM is modified by AJAX calls or modal windows.
245245

246246
`ReactRailsUJS` will automatically mount components on `<%= react_component(...) %>` tags and unmount them when appropriate.
247247

248-
Be sure to load `react_ujs` _after_ these libraries so that it can detect them.
248+
If you need to re-detect events, you can call `detectEvents`:
249+
250+
```js
251+
// Remove previous event handlers and add new ones:
252+
ReactRailsUJS.detectEvents()
253+
```
254+
255+
For example, if `Turbolinks` is loaded _after_ `ReactRailsUJS`, you'll need to call this again. This function removes previous handlers before adding new ones, so it's safe to call as often as needed.
249256

250257
### `getConstructor`
251258

lib/assets/javascripts/react_ujs.js

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,37 @@ var turbolinksClassicEvents = __webpack_require__(10)
117117
// see what things are globally available
118118
// and setup event handlers to those things
119119
module.exports = function(ujs) {
120+
121+
if (ujs.handleEvent) {
122+
// We're calling this a second time -- remove previous handlers
123+
turbolinksClassicEvents.teardown(ujs)
124+
turbolinksEvents.teardown(ujs);
125+
turbolinksClassicDeprecatedEvents.teardown(ujs);
126+
pjaxEvents.teardown(ujs);
127+
nativeEvents.teardown(ujs);
128+
}
129+
120130
if (ujs.jQuery) {
121131
ujs.handleEvent = function(eventName, callback) {
122132
ujs.jQuery(document).on(eventName, callback);
123133
};
124-
} else {
134+
ujs.removeEvent = function(eventName, callback) {
135+
ujs.jQuery(document).off(eventName, callback);
136+
}
137+
} else if ('addEventListener' in window) {
125138
ujs.handleEvent = function(eventName, callback) {
126139
document.addEventListener(eventName, callback);
127140
};
141+
ujs.removeEvent = function(eventName, callback) {
142+
document.removeEventListener(eventName, callback);
143+
};
144+
} else {
145+
ujs.handleEvent = function(eventName, callback) {
146+
window.attachEvent(eventName, callback);
147+
};
148+
ujs.removeEvent = function(eventName, callback) {
149+
window.detachEvent(eventName, callback);
150+
};
128151
}
129152

130153
// Detect which kind of events to set up:
@@ -251,8 +274,11 @@ var ReactRailsUJS = {
251274
// the default is ReactRailsUJS.ComponentGlobal
252275
getConstructor: constructorFromGlobal,
253276

254-
useContext: function(req) {
255-
this.getConstructor = constructorFromRequireContextWithGlobalFallback(req)
277+
// Given a Webpack `require.context`,
278+
// try finding components with `require`,
279+
// then falling back to global lookup.
280+
useContext: function(requireContext) {
281+
this.getConstructor = constructorFromRequireContextWithGlobalFallback(requireContext)
256282
},
257283

258284
// Render `componentName` with `props` to a string,
@@ -298,11 +324,36 @@ var ReactRailsUJS = {
298324
ReactDOM.unmountComponentAtNode(node);
299325
}
300326
},
327+
328+
// Check the global context for installed libraries
329+
// and figure out which library to hook up to (pjax, Turbolinks, jQuery)
330+
// This is called on load, but you can call it again if needed
331+
// (It will unmount itself)
332+
detectEvents: function() {
333+
detectEvents(this)
334+
},
335+
}
336+
337+
// These stable references are so that handlers can be added and removed:
338+
ReactRailsUJS.handleMount = function(e) {
339+
var target = undefined;
340+
if (e && e.target) {
341+
target = e.target;
342+
}
343+
ReactRailsUJS.mountComponents(target);
344+
}
345+
ReactRailsUJS.handleUnmount = function(e) {
346+
var target = undefined;
347+
if (e && e.target) {
348+
target = e.target;
349+
}
350+
ReactRailsUJS.unmountComponents(target);
301351
}
302352

353+
303354
if (typeof window !== "undefined") {
304355
// Only setup events for browser (not server-rendering)
305-
detectEvents(ReactRailsUJS)
356+
ReactRailsUJS.detectEvents()
306357
}
307358

308359
// It's a bit of a no-no to populate the global namespace,
@@ -324,12 +375,22 @@ module.exports = {
324375
setup: function(ujs) {
325376
if (ujs.jQuery) {
326377
// Use jQuery if it's present:
327-
ujs.jQuery(function() { ujs.mountComponents() });
378+
ujs.handleEvent("ready", ujs.handleMount);
328379
} else if ('addEventListener' in window) {
329-
document.addEventListener('DOMContentLoaded', function() { ujs.mountComponents() });
380+
ujs.handleEvent('DOMContentLoaded', ujs.handleMount);
330381
} else {
331382
// add support to IE8 without jQuery
332-
window.attachEvent('onload', function() { ujs.mountComponents() });
383+
ujs.handleEvent('onload', ujs.handleMount);
384+
}
385+
},
386+
387+
teardown: function(ujs) {
388+
if (ujs.jQuery) {
389+
ujs.removeEvent("ready", ujs.handleMount);
390+
} else if ('addEventListener' in window) {
391+
ujs.removeEvent('DOMContentLoaded', ujs.handleMount);
392+
} else {
393+
ujs.removeEvent('onload', ujs.handleMount);
333394
}
334395
}
335396
}
@@ -342,10 +403,16 @@ module.exports = {
342403
module.exports = {
343404
// pjax support
344405
setup: function(ujs) {
345-
ujs.handleEvent('ready', function() { ujs.mountComponents() });
346-
ujs.handleEvent('pjax:end', function(e) { ujs.mountComponents(e.target) });
347-
ujs.handleEvent('pjax:beforeReplace', function(e) { ujs.unmountComponents(e.target) });
348-
}
406+
ujs.handleEvent('ready', ujs.handleMount);
407+
ujs.handleEvent('pjax:end', ujs.handleMount);
408+
ujs.handleEvent('pjax:beforeReplace', ujs.handleUnmount);
409+
},
410+
411+
teardown: function() {
412+
ujs.removeEvent('ready', ujs.handleMount);
413+
ujs.removeEvent('pjax:end', ujs.handleMount);
414+
ujs.removeEvent('pjax:beforeReplace', ujs.handleUnmount);
415+
},
349416
}
350417

351418

@@ -356,9 +423,15 @@ module.exports = {
356423
module.exports = {
357424
// Turbolinks 5+ got rid of named events (?!)
358425
setup: function(ujs) {
359-
ujs.handleEvent('DOMContentLoaded', function() { ujs.mountComponents() })
360-
ujs.handleEvent('turbolinks:render', function() { ujs.mountComponents() })
361-
ujs.handleEvent('turbolinks:before-render', function() { ujs.unmountComponents() })
426+
ujs.handleEvent('DOMContentLoaded', ujs.handleMount)
427+
ujs.handleEvent('turbolinks:render', ujs.handleMount)
428+
ujs.handleEvent('turbolinks:before-render', ujs.handleUnmount)
429+
},
430+
431+
teardown: function(ujs) {
432+
ujs.removeEvent('DOMContentLoaded', ujs.handleMount)
433+
ujs.removeEvent('turbolinks:render', ujs.handleMount)
434+
ujs.removeEvent('turbolinks:before-render', ujs.handleUnmount)
362435
},
363436
}
364437

@@ -371,8 +444,12 @@ module.exports = {
371444
// Attach handlers to Turbolinks-Classic events
372445
// for mounting and unmounting components
373446
setup: function(ujs) {
374-
ujs.handleEvent(Turbolinks.EVENTS.CHANGE, function() { ujs.mountComponents() });
375-
ujs.handleEvent(Turbolinks.EVENTS.BEFORE_UNLOAD, function() { ujs.unmountComponents() });
447+
ujs.handleEvent(Turbolinks.EVENTS.CHANGE, ujs.handleMount);
448+
ujs.handleEvent(Turbolinks.EVENTS.BEFORE_UNLOAD, ujs.handleUnmount);
449+
},
450+
teardown: function(ujs) {
451+
ujs.removeEvent(Turbolinks.EVENTS.CHANGE, ujs.handleMount);
452+
ujs.removeEvent(Turbolinks.EVENTS.BEFORE_UNLOAD, ujs.handleUnmount);
376453
}
377454
}
378455

@@ -388,8 +465,12 @@ module.exports = {
388465
// https://github.com/reactjs/react-rails/issues/87
389466
setup: function(ujs) {
390467
Turbolinks.pagesCached(0)
391-
ujs.handleEvent('page:change', function() { ujs.mountComponents() });
392-
ujs.handleEvent('page:receive', function() { ujs.unmountComponents() });
468+
ujs.handleEvent('page:change', ujs.handleMount);
469+
ujs.handleEvent('page:receive', ujs.handleUnmount);
470+
},
471+
teardown: function(ujs) {
472+
ujs.removeEvent('page:change', ujs.handleMount);
473+
ujs.removeEvent('page:receive', ujs.handleUnmount);
393474
}
394475
}
395476

react_ujs/dist/react_ujs.js

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -117,14 +117,37 @@ var turbolinksClassicEvents = __webpack_require__(10)
117117
// see what things are globally available
118118
// and setup event handlers to those things
119119
module.exports = function(ujs) {
120+
121+
if (ujs.handleEvent) {
122+
// We're calling this a second time -- remove previous handlers
123+
turbolinksClassicEvents.teardown(ujs)
124+
turbolinksEvents.teardown(ujs);
125+
turbolinksClassicDeprecatedEvents.teardown(ujs);
126+
pjaxEvents.teardown(ujs);
127+
nativeEvents.teardown(ujs);
128+
}
129+
120130
if (ujs.jQuery) {
121131
ujs.handleEvent = function(eventName, callback) {
122132
ujs.jQuery(document).on(eventName, callback);
123133
};
124-
} else {
134+
ujs.removeEvent = function(eventName, callback) {
135+
ujs.jQuery(document).off(eventName, callback);
136+
}
137+
} else if ('addEventListener' in window) {
125138
ujs.handleEvent = function(eventName, callback) {
126139
document.addEventListener(eventName, callback);
127140
};
141+
ujs.removeEvent = function(eventName, callback) {
142+
document.removeEventListener(eventName, callback);
143+
};
144+
} else {
145+
ujs.handleEvent = function(eventName, callback) {
146+
window.attachEvent(eventName, callback);
147+
};
148+
ujs.removeEvent = function(eventName, callback) {
149+
window.detachEvent(eventName, callback);
150+
};
128151
}
129152

130153
// Detect which kind of events to set up:
@@ -251,8 +274,11 @@ var ReactRailsUJS = {
251274
// the default is ReactRailsUJS.ComponentGlobal
252275
getConstructor: constructorFromGlobal,
253276

254-
useContext: function(req) {
255-
this.getConstructor = constructorFromRequireContextWithGlobalFallback(req)
277+
// Given a Webpack `require.context`,
278+
// try finding components with `require`,
279+
// then falling back to global lookup.
280+
useContext: function(requireContext) {
281+
this.getConstructor = constructorFromRequireContextWithGlobalFallback(requireContext)
256282
},
257283

258284
// Render `componentName` with `props` to a string,
@@ -298,11 +324,36 @@ var ReactRailsUJS = {
298324
ReactDOM.unmountComponentAtNode(node);
299325
}
300326
},
327+
328+
// Check the global context for installed libraries
329+
// and figure out which library to hook up to (pjax, Turbolinks, jQuery)
330+
// This is called on load, but you can call it again if needed
331+
// (It will unmount itself)
332+
detectEvents: function() {
333+
detectEvents(this)
334+
},
335+
}
336+
337+
// These stable references are so that handlers can be added and removed:
338+
ReactRailsUJS.handleMount = function(e) {
339+
var target = undefined;
340+
if (e && e.target) {
341+
target = e.target;
342+
}
343+
ReactRailsUJS.mountComponents(target);
344+
}
345+
ReactRailsUJS.handleUnmount = function(e) {
346+
var target = undefined;
347+
if (e && e.target) {
348+
target = e.target;
349+
}
350+
ReactRailsUJS.unmountComponents(target);
301351
}
302352

353+
303354
if (typeof window !== "undefined") {
304355
// Only setup events for browser (not server-rendering)
305-
detectEvents(ReactRailsUJS)
356+
ReactRailsUJS.detectEvents()
306357
}
307358

308359
// It's a bit of a no-no to populate the global namespace,
@@ -324,12 +375,22 @@ module.exports = {
324375
setup: function(ujs) {
325376
if (ujs.jQuery) {
326377
// Use jQuery if it's present:
327-
ujs.jQuery(function() { ujs.mountComponents() });
378+
ujs.handleEvent("ready", ujs.handleMount);
328379
} else if ('addEventListener' in window) {
329-
document.addEventListener('DOMContentLoaded', function() { ujs.mountComponents() });
380+
ujs.handleEvent('DOMContentLoaded', ujs.handleMount);
330381
} else {
331382
// add support to IE8 without jQuery
332-
window.attachEvent('onload', function() { ujs.mountComponents() });
383+
ujs.handleEvent('onload', ujs.handleMount);
384+
}
385+
},
386+
387+
teardown: function(ujs) {
388+
if (ujs.jQuery) {
389+
ujs.removeEvent("ready", ujs.handleMount);
390+
} else if ('addEventListener' in window) {
391+
ujs.removeEvent('DOMContentLoaded', ujs.handleMount);
392+
} else {
393+
ujs.removeEvent('onload', ujs.handleMount);
333394
}
334395
}
335396
}
@@ -342,10 +403,16 @@ module.exports = {
342403
module.exports = {
343404
// pjax support
344405
setup: function(ujs) {
345-
ujs.handleEvent('ready', function() { ujs.mountComponents() });
346-
ujs.handleEvent('pjax:end', function(e) { ujs.mountComponents(e.target) });
347-
ujs.handleEvent('pjax:beforeReplace', function(e) { ujs.unmountComponents(e.target) });
348-
}
406+
ujs.handleEvent('ready', ujs.handleMount);
407+
ujs.handleEvent('pjax:end', ujs.handleMount);
408+
ujs.handleEvent('pjax:beforeReplace', ujs.handleUnmount);
409+
},
410+
411+
teardown: function() {
412+
ujs.removeEvent('ready', ujs.handleMount);
413+
ujs.removeEvent('pjax:end', ujs.handleMount);
414+
ujs.removeEvent('pjax:beforeReplace', ujs.handleUnmount);
415+
},
349416
}
350417

351418

@@ -356,9 +423,15 @@ module.exports = {
356423
module.exports = {
357424
// Turbolinks 5+ got rid of named events (?!)
358425
setup: function(ujs) {
359-
ujs.handleEvent('DOMContentLoaded', function() { ujs.mountComponents() })
360-
ujs.handleEvent('turbolinks:render', function() { ujs.mountComponents() })
361-
ujs.handleEvent('turbolinks:before-render', function() { ujs.unmountComponents() })
426+
ujs.handleEvent('DOMContentLoaded', ujs.handleMount)
427+
ujs.handleEvent('turbolinks:render', ujs.handleMount)
428+
ujs.handleEvent('turbolinks:before-render', ujs.handleUnmount)
429+
},
430+
431+
teardown: function(ujs) {
432+
ujs.removeEvent('DOMContentLoaded', ujs.handleMount)
433+
ujs.removeEvent('turbolinks:render', ujs.handleMount)
434+
ujs.removeEvent('turbolinks:before-render', ujs.handleUnmount)
362435
},
363436
}
364437

@@ -371,8 +444,12 @@ module.exports = {
371444
// Attach handlers to Turbolinks-Classic events
372445
// for mounting and unmounting components
373446
setup: function(ujs) {
374-
ujs.handleEvent(Turbolinks.EVENTS.CHANGE, function() { ujs.mountComponents() });
375-
ujs.handleEvent(Turbolinks.EVENTS.BEFORE_UNLOAD, function() { ujs.unmountComponents() });
447+
ujs.handleEvent(Turbolinks.EVENTS.CHANGE, ujs.handleMount);
448+
ujs.handleEvent(Turbolinks.EVENTS.BEFORE_UNLOAD, ujs.handleUnmount);
449+
},
450+
teardown: function(ujs) {
451+
ujs.removeEvent(Turbolinks.EVENTS.CHANGE, ujs.handleMount);
452+
ujs.removeEvent(Turbolinks.EVENTS.BEFORE_UNLOAD, ujs.handleUnmount);
376453
}
377454
}
378455

@@ -388,8 +465,12 @@ module.exports = {
388465
// https://github.com/reactjs/react-rails/issues/87
389466
setup: function(ujs) {
390467
Turbolinks.pagesCached(0)
391-
ujs.handleEvent('page:change', function() { ujs.mountComponents() });
392-
ujs.handleEvent('page:receive', function() { ujs.unmountComponents() });
468+
ujs.handleEvent('page:change', ujs.handleMount);
469+
ujs.handleEvent('page:receive', ujs.handleUnmount);
470+
},
471+
teardown: function(ujs) {
472+
ujs.removeEvent('page:change', ujs.handleMount);
473+
ujs.removeEvent('page:receive', ujs.handleUnmount);
393474
}
394475
}
395476

0 commit comments

Comments
 (0)