Skip to content

Commit 30ac8d5

Browse files
Lazily initialize component JS when using Html.ReactWithInit
1 parent 60a61e9 commit 30ac8d5

File tree

7 files changed

+63
-18
lines changed

7 files changed

+63
-18
lines changed

src/React.AspNet/HtmlHelperExtensions.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ public static IHtmlString ReactWithInit<T>(
135135
{
136136
reactComponent.RenderHtml(writer, clientOnly, exceptionHandler: exceptionHandler);
137137
writer.WriteLine();
138-
WriteScriptTag(writer, bodyWriter => reactComponent.RenderJavaScript(bodyWriter));
138+
WriteScriptTag(writer, bodyWriter => reactComponent.RenderJavaScript(bodyWriter, waitForDOMContentLoad: true));
139139
});
140140

141141
}

src/React.Core/IReactComponent.cs

+9-9
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,6 @@ public interface IReactComponent
5656
/// <returns>HTML</returns>
5757
string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null, IRenderFunctions renderFunctions = null);
5858

59-
/// <summary>
60-
/// Renders the JavaScript required to initialise this component client-side. This will
61-
/// initialise the React component, which includes attach event handlers to the
62-
/// server-rendered HTML.
63-
/// </summary>
64-
/// <returns>JavaScript</returns>
65-
string RenderJavaScript();
66-
6759
/// <summary>
6860
/// Renders the HTML for this component. This will execute the component server-side and
6961
/// return the rendered HTML.
@@ -82,6 +74,14 @@ public interface IReactComponent
8274
/// server-rendered HTML.
8375
/// </summary>
8476
/// <returns>JavaScript</returns>
85-
void RenderJavaScript(TextWriter writer);
77+
string RenderJavaScript();
78+
79+
/// <summary>
80+
/// Renders the JavaScript required to initialise this component client-side. This will
81+
/// initialise the React component, which includes attach event handlers to the
82+
/// server-rendered HTML.
83+
/// </summary>
84+
/// <returns>JavaScript</returns>
85+
void RenderJavaScript(TextWriter writer, bool waitForDOMContentLoad);
8686
}
8787
}

src/React.Core/ReactComponent.cs

+13-2
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ public virtual void RenderHtml(TextWriter writer, bool renderContainerOnly = fal
233233
/// <returns>JavaScript</returns>
234234
public virtual string RenderJavaScript()
235235
{
236-
return GetStringFromWriter(renderJsWriter => RenderJavaScript(renderJsWriter));
236+
return GetStringFromWriter(renderJsWriter => RenderJavaScript(renderJsWriter, waitForDOMContentLoad: false));
237237
}
238238

239239
/// <summary>
@@ -242,15 +242,26 @@ public virtual string RenderJavaScript()
242242
/// server-rendered HTML.
243243
/// </summary>
244244
/// <param name="writer">The <see cref="T:System.IO.TextWriter" /> to which the content is written</param>
245+
/// <param name="waitForDOMContentLoad">Delays the component init until the page load event fires. Useful if the component script tags are located after the call to Html.ReactWithInit. </param>
245246
/// <returns>JavaScript</returns>
246-
public virtual void RenderJavaScript(TextWriter writer)
247+
public virtual void RenderJavaScript(TextWriter writer, bool waitForDOMContentLoad)
247248
{
249+
if (waitForDOMContentLoad)
250+
{
251+
writer.Write("window.addEventListener('DOMContentLoaded', function() {");
252+
}
253+
248254
writer.Write(
249255
!_configuration.UseServerSideRendering || ClientOnly ? "ReactDOM.render(" : "ReactDOM.hydrate(");
250256
WriteComponentInitialiser(writer);
251257
writer.Write(", document.getElementById(\"");
252258
writer.Write(ContainerId);
253259
writer.Write("\"))");
260+
261+
if (waitForDOMContentLoad)
262+
{
263+
writer.Write("});");
264+
}
254265
}
255266

256267
/// <summary>

src/React.Core/ReactEnvironment.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -342,7 +342,7 @@ public virtual void GetInitJavaScript(TextWriter writer, bool clientOnly = false
342342
{
343343
if (!component.ServerOnly)
344344
{
345-
component.RenderJavaScript(writer);
345+
component.RenderJavaScript(writer, waitForDOMContentLoad: true);
346346
writer.WriteLine(';');
347347
}
348348
}

src/React.Router/ReactRouterComponent.cs

+11-1
Original file line numberDiff line numberDiff line change
@@ -93,13 +93,23 @@ protected override void WriteComponentInitialiser(TextWriter writer)
9393
/// Client side React Router does not need context nor explicit path parameter.
9494
/// </summary>
9595
/// <returns>JavaScript</returns>
96-
public override void RenderJavaScript(TextWriter writer)
96+
public override void RenderJavaScript(TextWriter writer, bool waitForDOMContentLoad)
9797
{
98+
if (waitForDOMContentLoad)
99+
{
100+
writer.Write("window.addEventListener('DOMContentLoaded', function() {");
101+
}
102+
98103
writer.Write("ReactDOM.hydrate(");
99104
base.WriteComponentInitialiser(writer);
100105
writer.Write(", document.getElementById(\"");
101106
writer.Write(ContainerId);
102107
writer.Write("\"))");
108+
109+
if (waitForDOMContentLoad)
110+
{
111+
writer.Write("});");
112+
}
103113
}
104114
}
105115
}

tests/React.Tests/Core/ReactComponentTest.cs

+25-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
*/
77

88
using System;
9+
using System.IO;
910
using JavaScriptEngineSwitcher.Core;
1011
using Moq;
1112
using React.Exceptions;
@@ -266,13 +267,36 @@ public void RenderJavaScriptShouldCallRenderComponentWithReactDomRenderWhenSsrDi
266267
Props = new {hello = "World"}
267268
};
268269
var result = component.RenderJavaScript();
269-
270+
270271
Assert.Equal(
271272
@"ReactDOM.render(React.createElement(Foo, {""hello"":""World""}), document.getElementById(""container""))",
272273
result
273274
);
274275
}
275276

277+
[Fact]
278+
public void RenderJavaScriptShouldHandleWaitForContentLoad()
279+
{
280+
var environment = new Mock<IReactEnvironment>();
281+
var config = CreateDefaultConfigMock();
282+
config.SetupGet(x => x.UseServerSideRendering).Returns(false);
283+
284+
var reactIdGenerator = new Mock<IReactIdGenerator>();
285+
var component = new ReactComponent(environment.Object, config.Object, reactIdGenerator.Object, "Foo", "container")
286+
{
287+
ClientOnly = false,
288+
Props = new {hello = "World"}
289+
};
290+
using (var writer = new StringWriter())
291+
{
292+
component.RenderJavaScript(writer, waitForDOMContentLoad: true);
293+
Assert.Equal(
294+
@"window.addEventListener('DOMContentLoaded', function() {ReactDOM.render(React.createElement(Foo, {""hello"":""World""}), document.getElementById(""container""))});",
295+
writer.ToString()
296+
);
297+
}
298+
}
299+
276300
[Theory]
277301
[InlineData("Foo", true)]
278302
[InlineData("Foo.Bar", true)]

tests/React.Tests/Mvc/HtmlHelperExtensionsTests.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public void ReactWithInitShouldReturnHtmlAndScript()
3838
component.Setup(x => x.RenderHtml(It.IsAny<TextWriter>(), false, false, null, null))
3939
.Callback((TextWriter writer, bool renderContainerOnly, bool renderServerOnly, Action<Exception, string, string> exceptionHandler, IRenderFunctions renderFunctions) => writer.Write("HTML"));
4040

41-
component.Setup(x => x.RenderJavaScript(It.IsAny<TextWriter>())).Callback((TextWriter writer) => writer.Write("JS"));
41+
component.Setup(x => x.RenderJavaScript(It.IsAny<TextWriter>(), It.IsAny<bool>())).Callback((TextWriter writer, bool waitForDOMContentLoad) => writer.Write("JS"));
4242

4343
var environment = ConfigureMockEnvironment();
4444
environment.Setup(x => x.CreateComponent(
@@ -77,7 +77,7 @@ public void ScriptNonceIsReturned()
7777
component.Setup(x => x.RenderHtml(It.IsAny<TextWriter>(), false, false, null, null))
7878
.Callback((TextWriter writer, bool renderContainerOnly, bool renderServerOnly, Action<Exception, string, string> exceptionHandle, IRenderFunctions renderFunctions) => writer.Write("HTML")).Verifiable();
7979

80-
component.Setup(x => x.RenderJavaScript(It.IsAny<TextWriter>())).Callback((TextWriter writer) => writer.Write("JS")).Verifiable();
80+
component.Setup(x => x.RenderJavaScript(It.IsAny<TextWriter>(), It.IsAny<bool>())).Callback((TextWriter writer, bool waitForDOMContentLoad) => writer.Write("JS")).Verifiable();
8181

8282
var config = new Mock<IReactSiteConfiguration>();
8383

@@ -217,7 +217,7 @@ public void RenderFunctionsCalledNonLazily()
217217
fakeRenderFunctions.Setup(x => x.TransformRenderedHtml(It.IsAny<string>())).Returns("HTML");
218218

219219
component.Setup(x => x.RenderHtml(It.IsAny<TextWriter>(), It.IsAny<bool>(), It.IsAny<bool>(), It.IsAny<Action<Exception, string, string>>(), It.IsAny<IRenderFunctions>()))
220-
.Callback((TextWriter writer, bool renderContainerOnly, bool renderServerOnly, Action<Exception, string, string> exceptionHandler, IRenderFunctions renderFunctions) =>
220+
.Callback((TextWriter writer, bool renderContainerOnly, bool renderServerOnly, Action<Exception, string, string> exceptionHandler, IRenderFunctions renderFunctions) =>
221221
{
222222
renderFunctions.PreRender(_ => "one");
223223
writer.Write(renderFunctions.TransformRenderedHtml("HTML"));

0 commit comments

Comments
 (0)