diff --git a/src/React.AspNet/HtmlHelperExtensions.cs b/src/React.AspNet/HtmlHelperExtensions.cs
index bf427a7e1..d40fb32ef 100644
--- a/src/React.AspNet/HtmlHelperExtensions.cs
+++ b/src/React.AspNet/HtmlHelperExtensions.cs
@@ -104,8 +104,10 @@ public static IHtmlString React(
/// HTML tag to wrap the component in. Defaults to <div>
/// ID to use for the container HTML tag. Defaults to an auto-generated ID
/// Skip rendering server-side and only output client-side initialisation code. Defaults to false
+ /// Skip rendering React specific data-attributes, container and client-side initialisation during server side rendering. Defaults to false
/// HTML class(es) to set on the container tag
/// A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)
+ /// Functions to call during component render
/// The component's HTML
public static IHtmlString ReactWithInit(
this IHtmlHelper htmlHelper,
@@ -114,8 +116,10 @@ public static IHtmlString ReactWithInit(
string htmlTag = null,
string containerId = null,
bool clientOnly = false,
+ bool serverOnly = false,
string containerClass = null,
- Action exceptionHandler = null
+ Action exceptionHandler = null,
+ IRenderFunctions renderFunctions = null
)
{
try
@@ -133,11 +137,11 @@ public static IHtmlString ReactWithInit(
return RenderToString(writer =>
{
- reactComponent.RenderHtml(writer, clientOnly, exceptionHandler: exceptionHandler);
+ reactComponent.RenderHtml(writer, clientOnly, serverOnly, exceptionHandler: exceptionHandler, renderFunctions);
writer.WriteLine();
- WriteScriptTag(writer, bodyWriter => reactComponent.RenderJavaScript(bodyWriter));
+ WriteScriptTag(writer, bodyWriter => reactComponent.RenderJavaScript(bodyWriter, waitForDOMContentLoad: true));
});
-
+
}
finally
{
diff --git a/src/React.Core/IReactComponent.cs b/src/React.Core/IReactComponent.cs
index 3ba68a128..6e4e8127b 100644
--- a/src/React.Core/IReactComponent.cs
+++ b/src/React.Core/IReactComponent.cs
@@ -56,14 +56,6 @@ public interface IReactComponent
/// HTML
string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action exceptionHandler = null, IRenderFunctions renderFunctions = null);
- ///
- /// Renders the JavaScript required to initialise this component client-side. This will
- /// initialise the React component, which includes attach event handlers to the
- /// server-rendered HTML.
- ///
- /// JavaScript
- string RenderJavaScript();
-
///
/// Renders the HTML for this component. This will execute the component server-side and
/// return the rendered HTML.
@@ -82,6 +74,14 @@ public interface IReactComponent
/// server-rendered HTML.
///
/// JavaScript
- void RenderJavaScript(TextWriter writer);
+ string RenderJavaScript(bool waitForDOMContentLoad);
+
+ ///
+ /// Renders the JavaScript required to initialise this component client-side. This will
+ /// initialise the React component, which includes attach event handlers to the
+ /// server-rendered HTML.
+ ///
+ /// JavaScript
+ void RenderJavaScript(TextWriter writer, bool waitForDOMContentLoad);
}
}
diff --git a/src/React.Core/ReactComponent.cs b/src/React.Core/ReactComponent.cs
index 6ad394361..9b54f10ff 100644
--- a/src/React.Core/ReactComponent.cs
+++ b/src/React.Core/ReactComponent.cs
@@ -231,9 +231,9 @@ public virtual void RenderHtml(TextWriter writer, bool renderContainerOnly = fal
/// server-rendered HTML.
///
/// JavaScript
- public virtual string RenderJavaScript()
+ public virtual string RenderJavaScript(bool waitForDOMContentLoad)
{
- return GetStringFromWriter(renderJsWriter => RenderJavaScript(renderJsWriter));
+ return GetStringFromWriter(renderJsWriter => RenderJavaScript(renderJsWriter, waitForDOMContentLoad));
}
///
@@ -242,15 +242,26 @@ public virtual string RenderJavaScript()
/// server-rendered HTML.
///
/// The to which the content is written
+ /// Delays the component init until the page load event fires. Useful if the component script tags are located after the call to Html.ReactWithInit.
/// JavaScript
- public virtual void RenderJavaScript(TextWriter writer)
+ public virtual void RenderJavaScript(TextWriter writer, bool waitForDOMContentLoad)
{
+ if (waitForDOMContentLoad)
+ {
+ writer.Write("window.addEventListener('DOMContentLoaded', function() {");
+ }
+
writer.Write(
!_configuration.UseServerSideRendering || ClientOnly ? "ReactDOM.render(" : "ReactDOM.hydrate(");
WriteComponentInitialiser(writer);
writer.Write(", document.getElementById(\"");
writer.Write(ContainerId);
writer.Write("\"))");
+
+ if (waitForDOMContentLoad)
+ {
+ writer.Write("});");
+ }
}
///
diff --git a/src/React.Core/ReactEnvironment.cs b/src/React.Core/ReactEnvironment.cs
index 9609aa29a..9c1a10db5 100644
--- a/src/React.Core/ReactEnvironment.cs
+++ b/src/React.Core/ReactEnvironment.cs
@@ -342,7 +342,7 @@ public virtual void GetInitJavaScript(TextWriter writer, bool clientOnly = false
{
if (!component.ServerOnly)
{
- component.RenderJavaScript(writer);
+ component.RenderJavaScript(writer, waitForDOMContentLoad: false);
writer.WriteLine(';');
}
}
diff --git a/src/React.Router/ReactRouterComponent.cs b/src/React.Router/ReactRouterComponent.cs
index 6cfe54858..932b69652 100644
--- a/src/React.Router/ReactRouterComponent.cs
+++ b/src/React.Router/ReactRouterComponent.cs
@@ -93,13 +93,23 @@ protected override void WriteComponentInitialiser(TextWriter writer)
/// Client side React Router does not need context nor explicit path parameter.
///
/// JavaScript
- public override void RenderJavaScript(TextWriter writer)
+ public override void RenderJavaScript(TextWriter writer, bool waitForDOMContentLoad)
{
+ if (waitForDOMContentLoad)
+ {
+ writer.Write("window.addEventListener('DOMContentLoaded', function() {");
+ }
+
writer.Write("ReactDOM.hydrate(");
base.WriteComponentInitialiser(writer);
writer.Write(", document.getElementById(\"");
writer.Write(ContainerId);
writer.Write("\"))");
+
+ if (waitForDOMContentLoad)
+ {
+ writer.Write("});");
+ }
}
}
}
diff --git a/tests/React.Tests/Core/ReactComponentTest.cs b/tests/React.Tests/Core/ReactComponentTest.cs
index 982bf9ba2..1a0cbc93f 100644
--- a/tests/React.Tests/Core/ReactComponentTest.cs
+++ b/tests/React.Tests/Core/ReactComponentTest.cs
@@ -6,6 +6,7 @@
*/
using System;
+using System.IO;
using JavaScriptEngineSwitcher.Core;
using Moq;
using React.Exceptions;
@@ -204,7 +205,7 @@ public void RenderJavaScriptShouldCallRenderComponent()
{
Props = new { hello = "World" }
};
- var result = component.RenderJavaScript();
+ var result = component.RenderJavaScript(false);
Assert.Equal(
@"ReactDOM.hydrate(React.createElement(Foo, {""hello"":""World""}), document.getElementById(""container""))",
@@ -224,7 +225,7 @@ public void RenderJavaScriptShouldCallRenderComponentWithReactDOMRender()
ClientOnly = true,
Props = new { hello = "World" }
};
- var result = component.RenderJavaScript();
+ var result = component.RenderJavaScript(false);
Assert.Equal(
@"ReactDOM.render(React.createElement(Foo, {""hello"":""World""}), document.getElementById(""container""))",
@@ -244,7 +245,7 @@ public void RenderJavaScriptShouldCallRenderComponentwithReactDOMHydrate()
ClientOnly = false,
Props = new { hello = "World" }
};
- var result = component.RenderJavaScript();
+ var result = component.RenderJavaScript(false);
Assert.Equal(
@"ReactDOM.hydrate(React.createElement(Foo, {""hello"":""World""}), document.getElementById(""container""))",
@@ -265,14 +266,37 @@ public void RenderJavaScriptShouldCallRenderComponentWithReactDomRenderWhenSsrDi
ClientOnly = false,
Props = new {hello = "World"}
};
- var result = component.RenderJavaScript();
-
+ var result = component.RenderJavaScript(false);
+
Assert.Equal(
@"ReactDOM.render(React.createElement(Foo, {""hello"":""World""}), document.getElementById(""container""))",
result
);
}
+ [Fact]
+ public void RenderJavaScriptShouldHandleWaitForContentLoad()
+ {
+ var environment = new Mock();
+ var config = CreateDefaultConfigMock();
+ config.SetupGet(x => x.UseServerSideRendering).Returns(false);
+
+ var reactIdGenerator = new Mock();
+ var component = new ReactComponent(environment.Object, config.Object, reactIdGenerator.Object, "Foo", "container")
+ {
+ ClientOnly = false,
+ Props = new {hello = "World"}
+ };
+ using (var writer = new StringWriter())
+ {
+ component.RenderJavaScript(writer, waitForDOMContentLoad: true);
+ Assert.Equal(
+ @"window.addEventListener('DOMContentLoaded', function() {ReactDOM.render(React.createElement(Foo, {""hello"":""World""}), document.getElementById(""container""))});",
+ writer.ToString()
+ );
+ }
+ }
+
[Theory]
[InlineData("Foo", true)]
[InlineData("Foo.Bar", true)]
diff --git a/tests/React.Tests/Core/ReactEnvironmentTest.cs b/tests/React.Tests/Core/ReactEnvironmentTest.cs
index 5253f1ffe..4eab8bf5d 100644
--- a/tests/React.Tests/Core/ReactEnvironmentTest.cs
+++ b/tests/React.Tests/Core/ReactEnvironmentTest.cs
@@ -13,6 +13,7 @@
using Moq;
using Xunit;
using React.Exceptions;
+using System.IO;
namespace React.Tests.Core
{
@@ -125,6 +126,21 @@ public void CreatesIReactComponent()
Assert.Equal(";" + Environment.NewLine, environment.GetInitJavaScript());
}
+ [Fact]
+ public void GetInitJavaScript()
+ {
+ var mocks = new Mocks();
+ var environment = mocks.CreateReactEnvironment();
+
+ var component = new Mock();
+
+ component.Setup(x => x.RenderJavaScript(It.IsAny(), It.IsAny())).Callback((TextWriter writer, bool waitForDOMContentLoad) => writer.Write(waitForDOMContentLoad ? "waiting for page load JS" : "JS")).Verifiable();
+
+ environment.CreateComponent(component.Object);
+
+ Assert.Equal("JS;" + Environment.NewLine, environment.GetInitJavaScript());
+ }
+
[Fact]
public void ServerSideOnlyComponentRendersNoJavaScript()
{
diff --git a/tests/React.Tests/Mvc/HtmlHelperExtensionsTests.cs b/tests/React.Tests/Mvc/HtmlHelperExtensionsTests.cs
index a24015c9c..b08ceffe5 100644
--- a/tests/React.Tests/Mvc/HtmlHelperExtensionsTests.cs
+++ b/tests/React.Tests/Mvc/HtmlHelperExtensionsTests.cs
@@ -38,7 +38,7 @@ public void ReactWithInitShouldReturnHtmlAndScript()
component.Setup(x => x.RenderHtml(It.IsAny(), false, false, null, null))
.Callback((TextWriter writer, bool renderContainerOnly, bool renderServerOnly, Action exceptionHandler, IRenderFunctions renderFunctions) => writer.Write("HTML"));
- component.Setup(x => x.RenderJavaScript(It.IsAny())).Callback((TextWriter writer) => writer.Write("JS"));
+ component.Setup(x => x.RenderJavaScript(It.IsAny(), It.IsAny())).Callback((TextWriter writer, bool waitForDOMContentLoad) => writer.Write(waitForDOMContentLoad ? "waiting for page load JS" : "JS"));
var environment = ConfigureMockEnvironment();
environment.Setup(x => x.CreateComponent(
@@ -57,11 +57,28 @@ public void ReactWithInitShouldReturnHtmlAndScript()
).ToHtmlString();
Assert.Equal(
- "HTML" + System.Environment.NewLine + "",
+ "HTML" + System.Environment.NewLine + "",
result.ToString()
);
}
+ [Fact]
+ public void GetInitJavaScriptReturns()
+ {
+ var component = new Mock();
+
+ var environment = ConfigureMockEnvironment();
+
+ environment.Setup(x => x.GetInitJavaScript(It.IsAny(), It.IsAny())).Callback((TextWriter writer, bool clientOnly) => writer.Write("JS"));
+
+ var renderJSResult = HtmlHelperExtensions.ReactInitJavaScript(htmlHelper: null, clientOnly: false);
+
+ Assert.Equal(
+ "",
+ renderJSResult.ToString()
+ );
+ }
+
[Fact]
public void ScriptNonceIsReturned()
{
@@ -77,7 +94,7 @@ public void ScriptNonceIsReturned()
component.Setup(x => x.RenderHtml(It.IsAny(), false, false, null, null))
.Callback((TextWriter writer, bool renderContainerOnly, bool renderServerOnly, Action exceptionHandle, IRenderFunctions renderFunctions) => writer.Write("HTML")).Verifiable();
- component.Setup(x => x.RenderJavaScript(It.IsAny())).Callback((TextWriter writer) => writer.Write("JS")).Verifiable();
+ component.Setup(x => x.RenderJavaScript(It.IsAny(), It.IsAny())).Callback((TextWriter writer, bool waitForDOMContentLoad) => writer.Write(waitForDOMContentLoad ? "waiting for page load JS" : "JS")).Verifiable();
var config = new Mock();
@@ -101,7 +118,7 @@ public void ScriptNonceIsReturned()
).ToHtmlString();
Assert.Equal(
- "HTML" + System.Environment.NewLine + "",
+ "HTML" + System.Environment.NewLine + "",
result.ToString()
);
@@ -116,7 +133,7 @@ public void ScriptNonceIsReturned()
).ToHtmlString();
Assert.Equal(
- "HTML" + System.Environment.NewLine + "",
+ "HTML" + System.Environment.NewLine + "",
result.ToString()
);
}
@@ -217,7 +234,7 @@ public void RenderFunctionsCalledNonLazily()
fakeRenderFunctions.Setup(x => x.TransformRenderedHtml(It.IsAny())).Returns("HTML");
component.Setup(x => x.RenderHtml(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny>(), It.IsAny()))
- .Callback((TextWriter writer, bool renderContainerOnly, bool renderServerOnly, Action exceptionHandler, IRenderFunctions renderFunctions) =>
+ .Callback((TextWriter writer, bool renderContainerOnly, bool renderServerOnly, Action exceptionHandler, IRenderFunctions renderFunctions) =>
{
renderFunctions.PreRender(_ => "one");
writer.Write(renderFunctions.TransformRenderedHtml("HTML"));
diff --git a/tests/React.Tests/Router/ReactRouterComponentTest.cs b/tests/React.Tests/Router/ReactRouterComponentTest.cs
index 42f43d23d..954aee794 100644
--- a/tests/React.Tests/Router/ReactRouterComponentTest.cs
+++ b/tests/React.Tests/Router/ReactRouterComponentTest.cs
@@ -27,13 +27,32 @@ public void RenderJavaScriptShouldNotIncludeContextOrPath()
{
Props = new { hello = "World" }
};
- var result = component.RenderJavaScript();
+ var result = component.RenderJavaScript(false);
Assert.Equal(
@"ReactDOM.hydrate(React.createElement(Foo, {""hello"":""World""}), document.getElementById(""container""))",
result
);
}
+
+ [Fact]
+ public void RenderJavaScriptShouldHandleWaitForContentLoad()
+ {
+ var environment = new Mock();
+ var config = new Mock();
+ var reactIdGenerator = new Mock();
+
+ var component = new ReactRouterComponent(environment.Object, config.Object, reactIdGenerator.Object, "Foo", "container", "/bar")
+ {
+ Props = new { hello = "World" }
+ };
+ var result = component.RenderJavaScript(true);
+
+ Assert.Equal(
+ @"window.addEventListener('DOMContentLoaded', function() {ReactDOM.hydrate(React.createElement(Foo, {""hello"":""World""}), document.getElementById(""container""))});",
+ result
+ );
+ }
}
}
#endif