Skip to content

Commit

Permalink
Merge pull request #14 from borntorun/feature-remove-script
Browse files Browse the repository at this point in the history
Add option (removeOnUnmount) to delete/remove tag script on component unmount
  • Loading branch information
dozoisch authored Mar 24, 2017
2 parents ffe524d + 9bfc899 commit 8505c25
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 15 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The api is very simple `makeAsyncScriptLoader(Component, scriptUrl, options)`. W
- exposeFuncs: Array of String. It'll create a function that will call the child component with the same name. It passes arguments and return value.
- callbackName: If the scripts calls a global function when loaded, provide the callback name here. It'll be autoregistered on the window.
- globalName: If wanted, provide the globalName of the loaded script. It'll be injected on the component with the same name *(ex: "grecaptcha")*
- removeOnUnmount: Boolean **default=false**: If set to true removes the script tag on the component unmount

You can retrieve the child component using the fonction called `getComponent()`.

Expand Down
49 changes: 34 additions & 15 deletions src/async-script-loader.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ export default function makeAsyncScript(Component, scriptURL, options) {

if (SCRIPT_MAP.has(scriptURL)) {
let entry = SCRIPT_MAP.get(scriptURL);
if (entry.loaded || entry.errored) {
if (entry && (entry.loaded || entry.errored)) {
this.asyncScriptLoaderHandleLoad(entry);
return;
}
Expand Down Expand Up @@ -90,30 +90,35 @@ export default function makeAsyncScript(Component, scriptURL, options) {

script.onload = () => {
let mapEntry = SCRIPT_MAP.get(scriptURL);
mapEntry.loaded = true;
callObserverFuncAndRemoveObserver( (observer) => {
if (callbackName) {
return false;
}
observer(mapEntry);
return true;
});
if (mapEntry) {
mapEntry.loaded = true;
callObserverFuncAndRemoveObserver( (observer) => {
if (callbackName) {
return false;
}
observer(mapEntry);
return true;
});
}
};
script.onerror = (event) => {
let mapEntry = SCRIPT_MAP.get(scriptURL);
mapEntry.errored = true;
callObserverFuncAndRemoveObserver( (observer) => {
observer(mapEntry);
return true;
});
if (mapEntry) {
mapEntry.errored = true;
callObserverFuncAndRemoveObserver( (observer) => {
observer(mapEntry);
return true;
});
}
};

// (old) MSIE browsers may call "onreadystatechange" instead of "onload"
script.onreadystatechange = () => {
if (this.readyState === "loaded") {
// wait for other events, then call onload if default onload hadn't been called
window.setTimeout(() => {
if (SCRIPT_MAP.get(scriptURL).loaded !== true) {
const mapEntry = SCRIPT_MAP.get(scriptURL);
if (mapEntry && mapEntry.loaded !== true) {
script.onload();
}
}, 0);
Expand All @@ -128,10 +133,24 @@ export default function makeAsyncScript(Component, scriptURL, options) {
},

componentWillUnmount() {
// Remove tag script
if (options.removeOnUnmount === true) {
const allScripts = document.getElementsByTagName("script");
for(let i = 0; i < allScripts.length; i += 1) {
if (allScripts[i].src.indexOf(scriptURL) > -1) {
if (allScripts[i].parentNode) {
allScripts[i].parentNode.removeChild(allScripts[i]);
}
}
}
}
// Clean the observer entry
let mapEntry = SCRIPT_MAP.get(scriptURL);
if (mapEntry) {
mapEntry.observers.delete(this.asyncScriptLoaderGetScriptLoaderID());
if (options.removeOnUnmount === true) {
SCRIPT_MAP.delete(scriptURL);
}
}
},

Expand Down
30 changes: 30 additions & 0 deletions test/async-script-loader-spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ const MockedComponent = React.createClass({
},
});

const hasScript = () => {
let as=document.getElementsByTagName("script");
for(let i = 0; i < as.length; i+=1) {
if (as[i].src.indexOf("http://example.com") > -1) {
return true;
}
}
return false;
}

describe("AsyncScriptLoader", () => {
it("should be imported successfully", () => {
assert.isNotNull(makeAsyncScriptLoader);
Expand Down Expand Up @@ -51,4 +61,24 @@ describe("AsyncScriptLoader", () => {
);
instance.callsACallback(done);
});
it("should not remove tag script on removeOnUnmount option not set", () => {
let ComponentWrapper = makeAsyncScriptLoader(MockedComponent, "http://example.com");
let instance = ReactTestUtils.renderIntoDocument(
<ComponentWrapper />
);
assert.equal(hasScript(), true);
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(instance));
instance.componentWillUnmount();
assert.equal(hasScript(), true);
});
it("should remove tag script on removeOnUnmount option set to true", () => {
let ComponentWrapper = makeAsyncScriptLoader(MockedComponent, "http://example.com", { removeOnUnmount: true });
let instance = ReactTestUtils.renderIntoDocument(
<ComponentWrapper />
);
assert.equal(hasScript(), true);
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(instance));
instance.componentWillUnmount();
assert.equal(hasScript(), false);
});
});

0 comments on commit 8505c25

Please sign in to comment.