Skip to content

Commit ecfdbb4

Browse files
Lazily initialize component JS when using Html.ReactWithInit
This allows Output Caching to be used, in #543 Still needs tests and documentation updates
1 parent 340749b commit ecfdbb4

File tree

7 files changed

+61
-28
lines changed

7 files changed

+61
-28
lines changed

Diff for: src/React.AspNet/HtmlHelperExtensions.cs

+9-3
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,11 @@ public static IHtmlString React<T>(
104104
/// <param name="htmlTag">HTML tag to wrap the component in. Defaults to &lt;div&gt;</param>
105105
/// <param name="containerId">ID to use for the container HTML tag. Defaults to an auto-generated ID</param>
106106
/// <param name="clientOnly">Skip rendering server-side and only output client-side initialisation code. Defaults to <c>false</c></param>
107+
/// <param name="serverOnly">Skip rendering React specific data-attributes, container and client-side initialisation during server side rendering. Defaults to <c>false</c></param>
107108
/// <param name="containerClass">HTML class(es) to set on the container tag</param>
108109
/// <param name="exceptionHandler">A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)</param>
110+
/// <param name="renderFunctions"></param>
111+
/// <param name="renderImmediately">Renders immediately, instead after DOMContentLoaded</param>
109112
/// <returns>The component's HTML</returns>
110113
public static IHtmlString ReactWithInit<T>(
111114
this IHtmlHelper htmlHelper,
@@ -114,8 +117,11 @@ public static IHtmlString ReactWithInit<T>(
114117
string htmlTag = null,
115118
string containerId = null,
116119
bool clientOnly = false,
120+
bool serverOnly = false,
117121
string containerClass = null,
118-
Action<Exception, string, string> exceptionHandler = null
122+
Action<Exception, string, string> exceptionHandler = null,
123+
IRenderFunctions renderFunctions = null,
124+
bool renderImmediately = false
119125
)
120126
{
121127
try
@@ -133,9 +139,9 @@ public static IHtmlString ReactWithInit<T>(
133139

134140
return RenderToString(writer =>
135141
{
136-
reactComponent.RenderHtml(writer, clientOnly, exceptionHandler: exceptionHandler);
142+
reactComponent.RenderHtml(writer, clientOnly, serverOnly, exceptionHandler: exceptionHandler, renderFunctions);
137143
writer.WriteLine();
138-
WriteScriptTag(writer, bodyWriter => reactComponent.RenderJavaScript(bodyWriter));
144+
WriteScriptTag(writer, bodyWriter => reactComponent.RenderJavaScript(bodyWriter, renderImmediately));
139145
});
140146

141147
}

Diff for: 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 renderImmediately = false);
8686
}
8787
}

Diff for: src/React.Core/IReactEnvironment.cs

+9-9
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,15 @@ public interface IReactEnvironment
100100
/// <returns>JavaScript for all components</returns>
101101
string GetInitJavaScript(bool clientOnly = false);
102102

103+
/// <summary>
104+
/// Renders the JavaScript required to initialise all components client-side. This will
105+
/// attach event handlers to the server-rendered HTML.
106+
/// </summary>
107+
/// <param name="writer">The <see cref="T:System.IO.TextWriter" /> to which the content is written</param>
108+
/// <param name="clientOnly">True if server-side rendering will be bypassed. Defaults to false.</param>
109+
/// <returns>JavaScript for all components</returns>
110+
void GetInitJavaScript(TextWriter writer, bool clientOnly = false);
111+
103112
/// <summary>
104113
/// Gets the JSX Transformer for this environment.
105114
/// </summary>
@@ -114,14 +123,5 @@ public interface IReactEnvironment
114123
/// Gets the site-wide configuration.
115124
/// </summary>
116125
IReactSiteConfiguration Configuration { get; }
117-
118-
/// <summary>
119-
/// Renders the JavaScript required to initialise all components client-side. This will
120-
/// attach event handlers to the server-rendered HTML.
121-
/// </summary>
122-
/// <param name="writer">The <see cref="T:System.IO.TextWriter" /> to which the content is written</param>
123-
/// <param name="clientOnly">True if server-side rendering will be bypassed. Defaults to false.</param>
124-
/// <returns>JavaScript for all components</returns>
125-
void GetInitJavaScript(TextWriter writer, bool clientOnly = false);
126126
}
127127
}

Diff for: src/React.Core/ReactComponent.cs

+20-3
Original file line numberDiff line numberDiff line change
@@ -139,8 +139,14 @@ public virtual string RenderHtml(bool renderContainerOnly = false, bool renderSe
139139
/// <param name="renderServerOnly">Only renders the common HTML mark up and not any React specific data attributes. Used for server-side only rendering.</param>
140140
/// <param name="exceptionHandler">A custom exception handler that will be called if a component throws during a render. Args: (Exception ex, string componentName, string containerId)</param>
141141
/// <param name="renderFunctions">Functions to call during component render</param>
142+
/// <param name="renderImmediately"></param>
142143
/// <returns>HTML</returns>
143-
public virtual void RenderHtml(TextWriter writer, bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null, IRenderFunctions renderFunctions = null)
144+
public virtual void RenderHtml(
145+
TextWriter writer,
146+
bool renderContainerOnly = false,
147+
bool renderServerOnly = false,
148+
Action<Exception, string, string> exceptionHandler = null,
149+
IRenderFunctions renderFunctions = null)
144150
{
145151
if (!_configuration.UseServerSideRendering)
146152
{
@@ -233,7 +239,7 @@ public virtual void RenderHtml(TextWriter writer, bool renderContainerOnly = fal
233239
/// <returns>JavaScript</returns>
234240
public virtual string RenderJavaScript()
235241
{
236-
return GetStringFromWriter(renderJsWriter => RenderJavaScript(renderJsWriter));
242+
return GetStringFromWriter(renderJsWriter => RenderJavaScript(renderJsWriter, renderImmediately: true));
237243
}
238244

239245
/// <summary>
@@ -242,15 +248,26 @@ public virtual string RenderJavaScript()
242248
/// server-rendered HTML.
243249
/// </summary>
244250
/// <param name="writer">The <see cref="T:System.IO.TextWriter" /> to which the content is written</param>
251+
/// <param name="renderImmediately"></param>
245252
/// <returns>JavaScript</returns>
246-
public virtual void RenderJavaScript(TextWriter writer)
253+
public virtual void RenderJavaScript(TextWriter writer, bool renderImmediately)
247254
{
255+
if (!renderImmediately)
256+
{
257+
writer.Write("window.addEventListener('DOMContentLoaded', function() {");
258+
}
259+
248260
writer.Write(
249261
!_configuration.UseServerSideRendering || ClientOnly ? "ReactDOM.render(" : "ReactDOM.hydrate(");
250262
WriteComponentInitialiser(writer);
251263
writer.Write(", document.getElementById(\"");
252264
writer.Write(ContainerId);
253265
writer.Write("\"))");
266+
267+
if (!renderImmediately)
268+
{
269+
writer.Write("});");
270+
}
254271
}
255272

256273
/// <summary>

Diff for: 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, renderImmediately: true);
346346
writer.WriteLine(';');
347347
}
348348
}

Diff for: 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 renderImmediately)
9797
{
98+
if (!renderImmediately)
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 (!renderImmediately)
110+
{
111+
writer.Write("});");
112+
}
103113
}
104114
}
105115
}

Diff for: tests/React.Tests/Mvc/HtmlHelperExtensionsTests.cs

+2-2
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) => 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) => writer.Write("JS")).Verifiable();
8181

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

0 commit comments

Comments
 (0)