Skip to content

Commit 038ef55

Browse files
Improve chaining server-render helper functions
It was always a bit clunky that the constructor was used for this. Now, there is a first-class ChainedRenderFunctions type that can be used to accomplish the same thing.
1 parent 9acdf40 commit 038ef55

File tree

13 files changed

+135
-156
lines changed

13 files changed

+135
-156
lines changed

site/jekyll/features/css-in-js.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ CSS-in-JS is a technique for declaring styles within components. ReactJS.NET sup
99

1010
Make sure ReactJS.NET is up to date. You will need at least ReactJS.NET 4.0 (which is in public beta at the time of writing).
1111

12+
If you're using more than one CSS-in-JS library in your project, we've got you covered! Just pass mutliple server-render helper functions into `ChainedRenderFunctions`, and they will be called in the order they are passed in.
13+
1214
### [Styled Components](https://github.com/styled-components/styled-components)
1315

1416
Expose styled-components as `global.Styled`:
@@ -27,7 +29,7 @@ Add the render helper to the call to `Html.React`:
2729
var styledComponentsFunctions = new StyledComponentsFunctions();
2830
}
2931
30-
@Html.React("RootComponent", new { exampleProp = "a" }, renderFunctions: styledComponentsFunctions)
32+
@Html.React("RootComponent", new { exampleProp = "a" }, renderFunctions: new ChainedRenderFunctions(styledComponentsFunctions))
3133
3234
@{
3335
ViewBag.ServerStyles = styledComponentsFunctions.RenderedStyles;
@@ -104,7 +106,7 @@ Add the render helper to the call to `Html.React`:
104106
var reactJssFunctions = new ReactJssFunctions();
105107
}
106108
107-
@Html.React("RootComponent", new { exampleProp = "a" }, renderFunctions: reactJssFunctions)
109+
@Html.React("RootComponent", new { exampleProp = "a" }, renderFunctions: new ChainedRenderFunctions(reactJssFunctions))
108110
109111
@{
110112
ViewBag.ServerStyles = reactJssFunctions.RenderedStyles;
@@ -189,7 +191,7 @@ Add the render helper to the call to `Html.React`:
189191
@using React.AspNet
190192
@using React.RenderFunctions
191193
192-
@Html.React("RootComponent", new { exampleProp = "a" }, renderFunctions: new EmotionFunctions())
194+
@Html.React("RootComponent", new { exampleProp = "a" }, renderFunctions: new ChainedRenderFunctions(new EmotionFunctions()))
193195
```
194196

195197
You're now ready to declare styles inside components:

site/jekyll/features/react-helmet.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ Add the render helper to the call to `Html.React`:
2525
var helmetFunctions = new ReactHelmetFunctions();
2626
}
2727
28-
@Html.React("RootComponent", new { exampleProp = "a" }, renderFunctions: helmetFunctions)
28+
@Html.React("RootComponent", new { exampleProp = "a" }, renderFunctions: new ChainedRenderFunctions(helmetFunctions))
2929
3030
@{
3131
ViewBag.HelmetTitle = helmetFunctions.RenderedHelmet.GetValueOrDefault("title");
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
using System;
2+
using System.Text;
3+
4+
namespace React.RenderFunctions
5+
{
6+
/// <summary>
7+
/// Helper to chain functions to be executed during server-side rendering.
8+
/// For instance, React Router and React Helmet can both be used together using this class.
9+
/// </summary>
10+
public class ChainedRenderFunctions : IRenderFunctions
11+
{
12+
private readonly IRenderFunctions[] _chainedFunctions;
13+
14+
/// <summary>
15+
/// Constructor. Supports chained calls to multiple render functions by passing in a set of functions that should be called next.
16+
/// </summary>
17+
/// <param name="chainedFunctions">The chained render functions to call</param>
18+
public ChainedRenderFunctions(params IRenderFunctions[] chainedFunctions)
19+
{
20+
_chainedFunctions = chainedFunctions;
21+
}
22+
23+
/// <summary>
24+
/// Executes before component render.
25+
/// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
26+
/// This is useful for setting up variables that will be referenced after the render completes.
27+
/// <param name="executeJs">The func to execute</param>
28+
/// </summary>
29+
public void PreRender(Func<string, string> executeJs)
30+
{
31+
foreach (var chainedFunction in _chainedFunctions)
32+
{
33+
chainedFunction.PreRender(executeJs);
34+
}
35+
}
36+
37+
38+
/// <summary>
39+
/// Transforms the React.createElement expression.
40+
/// This is useful for libraries like styled components which require wrapping the root component
41+
/// inside a helper to generate a stylesheet.
42+
/// Example transform: React.createElement(Foo, ...) => wrapComponent(React.createElement(Foo, ...))
43+
/// </summary>
44+
/// <param name="componentToRender">The Javascript expression to wrap</param>
45+
/// <returns>A wrapped expression</returns>
46+
public string WrapComponent(string componentToRender)
47+
{
48+
string wrappedComponent = componentToRender;
49+
50+
foreach (var chainedFunction in _chainedFunctions)
51+
{
52+
wrappedComponent = chainedFunction.WrapComponent(wrappedComponent);
53+
}
54+
55+
return wrappedComponent;
56+
}
57+
58+
59+
/// <summary>
60+
/// Transforms the compiled rendered component HTML
61+
/// This is useful for libraries like emotion which take rendered component HTML and output the transformed HTML plus additional style tags
62+
/// </summary>
63+
/// <param name="input">The component HTML</param>
64+
/// <returns>A wrapped expression</returns>
65+
public string TransformRenderedHtml(string input)
66+
{
67+
string renderedHtml = input;
68+
69+
foreach (var chainedFunction in _chainedFunctions)
70+
{
71+
renderedHtml = chainedFunction.TransformRenderedHtml(renderedHtml);
72+
}
73+
74+
return renderedHtml;
75+
}
76+
77+
78+
/// <summary>
79+
/// Executes after component render.
80+
/// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
81+
/// This is useful for reading computed state, such as generated stylesheets or a router redirect result.
82+
/// </summary>
83+
/// <param name="executeJs">The func to execute</param>
84+
public void PostRender(Func<string, string> executeJs)
85+
{
86+
foreach (var chainedFunction in _chainedFunctions)
87+
{
88+
chainedFunction.PostRender(executeJs);
89+
}
90+
}
91+
}
92+
}

src/React.Core/RenderFunctions/EmotionFunctions.cs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,12 @@ namespace React.RenderFunctions
99
/// </summary>
1010
public class EmotionFunctions : RenderFunctionsBase
1111
{
12-
/// <summary>
13-
/// Constructor. Supports chained calls to multiple render functions by passing in a set of functions that should be called next.
14-
/// The functions within the provided RenderFunctions will be called *after* this instance's.
15-
/// Supports null as an argument.
16-
/// </summary>
17-
/// <param name="renderFunctions">The chained render functions to call</param>
18-
public EmotionFunctions(IRenderFunctions renderFunctions = null)
19-
: base(renderFunctions)
20-
{
21-
}
22-
2312
/// <summary>
2413
/// Implementation of TransformRenderedHtml
2514
/// </summary>
2615
/// <param name="input"></param>
2716
/// <returns></returns>
28-
protected override string TransformRenderedHtmlCore(string input)
17+
public override string TransformRenderedHtml(string input)
2918
{
3019
return $"EmotionServer.renderStylesToString({input})";
3120
}

src/React.Core/RenderFunctions/ReactHelmetFunctions.cs

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,6 @@ namespace React.RenderFunctions
1111
/// </summary>
1212
public class ReactHelmetFunctions : RenderFunctionsBase
1313
{
14-
/// <summary>
15-
/// Constructor. Supports chained calls to multiple render functions by passing in a set of functions that should be called next.
16-
/// The functions within the provided RenderFunctions will be called *after* this instance's.
17-
/// Supports null as an argument.
18-
/// </summary>
19-
/// <param name="renderFunctions">The chained render functions to call</param>
20-
public ReactHelmetFunctions(IRenderFunctions renderFunctions = null)
21-
: base(renderFunctions)
22-
{
23-
}
24-
2514
/// <summary>
2615
/// Dictionary of Helmet properties, rendered as raw HTML tags
2716
/// Available keys: "base", "bodyAttributes", "htmlAttributes", "link", "meta", "noscript", "script", "style", "title"
@@ -32,7 +21,7 @@ public ReactHelmetFunctions(IRenderFunctions renderFunctions = null)
3221
/// Implementation of PostRender
3322
/// </summary>
3423
/// <param name="executeJs"></param>
35-
protected override void PostRenderCore(Func<string, string> executeJs)
24+
public override void PostRender(Func<string, string> executeJs)
3625
{
3726
var helmetString = executeJs(@"
3827
var helmetResult = Helmet.default.renderStatic();

src/React.Core/RenderFunctions/ReactJssFunctions.cs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,6 @@ namespace React.RenderFunctions
88
/// </summary>
99
public class ReactJssFunctions : RenderFunctionsBase
1010
{
11-
/// <summary>
12-
/// Constructor. Supports chained calls to multiple render functions by passing in a set of functions that should be called next.
13-
/// The functions within the provided RenderFunctions will be called *after* this instance's.
14-
/// Supports null as an argument.
15-
/// </summary>
16-
/// <param name="renderFunctions">The chained render functions to call</param>
17-
public ReactJssFunctions(IRenderFunctions renderFunctions = null)
18-
: base(renderFunctions)
19-
{
20-
}
21-
2211
/// <summary>
2312
/// HTML style tag containing the rendered styles
2413
/// </summary>
@@ -28,7 +17,7 @@ public ReactJssFunctions(IRenderFunctions renderFunctions = null)
2817
/// Implementation of PreRender
2918
/// </summary>
3019
/// <param name="executeJs"></param>
31-
protected override void PreRenderCore(Func<string, string> executeJs)
20+
public override void PreRender(Func<string, string> executeJs)
3221
{
3322
executeJs("var reactJssProps = { registry: new ReactJss.SheetsRegistry() };");
3423
}
@@ -38,7 +27,7 @@ protected override void PreRenderCore(Func<string, string> executeJs)
3827
/// </summary>
3928
/// <param name="componentToRender"></param>
4029
/// <returns></returns>
41-
protected override string WrapComponentCore(string componentToRender)
30+
public override string WrapComponent(string componentToRender)
4231
{
4332
return ($"React.createElement(ReactJss.JssProvider, reactJssProps, ({componentToRender}))");
4433
}
@@ -47,7 +36,7 @@ protected override string WrapComponentCore(string componentToRender)
4736
/// Implementation of PostRender
4837
/// </summary>
4938
/// <param name="executeJs"></param>
50-
protected override void PostRenderCore(Func<string, string> executeJs)
39+
public override void PostRender(Func<string, string> executeJs)
5140
{
5241
RenderedStyles = $"<style type=\"text/css\" id=\"server-side-styles\">{executeJs("reactJssProps.registry.toString()")}</style>";
5342
}

src/React.Core/RenderFunctions/StyledComponentsFunctions.cs

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,17 +8,6 @@ namespace React.RenderFunctions
88
/// </summary>
99
public class StyledComponentsFunctions : RenderFunctionsBase
1010
{
11-
/// <summary>
12-
/// Constructor. Supports chained calls to multiple render functions by passing in a set of functions that should be called next.
13-
/// The functions within the provided RenderFunctions will be called *after* this instance's.
14-
/// Supports null as an argument.
15-
/// </summary>
16-
/// <param name="renderFunctions">The chained render functions to call</param>
17-
public StyledComponentsFunctions(IRenderFunctions renderFunctions = null)
18-
: base(renderFunctions)
19-
{
20-
}
21-
2211
/// <summary>
2312
/// HTML style tag containing the rendered styles
2413
/// </summary>
@@ -28,7 +17,7 @@ public StyledComponentsFunctions(IRenderFunctions renderFunctions = null)
2817
/// Implementation of PreRender
2918
/// </summary>
3019
/// <param name="executeJs"></param>
31-
protected override void PreRenderCore(Func<string, string> executeJs)
20+
public override void PreRender(Func<string, string> executeJs)
3221
{
3322
executeJs("var serverStyleSheet = new Styled.ServerStyleSheet();");
3423
}
@@ -38,7 +27,7 @@ protected override void PreRenderCore(Func<string, string> executeJs)
3827
/// </summary>
3928
/// <param name="componentToRender"></param>
4029
/// <returns></returns>
41-
protected override string WrapComponentCore(string componentToRender)
30+
public override string WrapComponent(string componentToRender)
4231
{
4332
return ($"serverStyleSheet.collectStyles({componentToRender})");
4433
}
@@ -47,7 +36,7 @@ protected override string WrapComponentCore(string componentToRender)
4736
/// Implementation of PostRender
4837
/// </summary>
4938
/// <param name="executeJs"></param>
50-
protected override void PostRenderCore(Func<string, string> executeJs)
39+
public override void PostRender(Func<string, string> executeJs)
5140
{
5241
RenderedStyles = executeJs("serverStyleSheet.getStyleTags()");
5342
}

src/React.Core/RenderFunctionsBase.cs

Lines changed: 4 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -8,62 +8,14 @@ namespace React
88
/// </summary>
99
public abstract class RenderFunctionsBase : IRenderFunctions
1010
{
11-
private readonly IRenderFunctions _renderFunctions;
12-
13-
/// <summary>
14-
/// Constructor. Supports chained calls to multiple render functions by passing in a set of functions that should be called next.
15-
/// The functions within the provided RenderFunctions will be called *after* this instance's.
16-
/// Supports null as an argument.
17-
/// </summary>
18-
/// <param name="renderFunctions">The chained render functions to call</param>
19-
protected RenderFunctionsBase(IRenderFunctions renderFunctions)
20-
{
21-
_renderFunctions = renderFunctions;
22-
}
23-
24-
/// <summary>
25-
/// Implementation of PreRender
26-
/// </summary>
27-
/// <param name="executeJs"></param>
28-
protected virtual void PreRenderCore(Func<string, string> executeJs)
29-
{
30-
}
31-
32-
/// <summary>
33-
/// Implementation of WrapComponent
34-
/// </summary>
35-
/// <param name="componentToRender"></param>
36-
/// <returns></returns>
37-
protected virtual string WrapComponentCore(string componentToRender)
38-
{
39-
return componentToRender;
40-
}
41-
42-
/// <summary>
43-
/// Implementation of TransformRenderedHtml
44-
/// </summary>
45-
/// <param name="input"></param>
46-
/// <returns></returns>
47-
protected virtual string TransformRenderedHtmlCore(string input) => input;
48-
49-
/// <summary>
50-
/// Implementation of PostRender
51-
/// </summary>
52-
/// <param name="executeJs"></param>
53-
protected virtual void PostRenderCore(Func<string, string> executeJs)
54-
{
55-
}
56-
5711
/// <summary>
5812
/// Executes before component render.
5913
/// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
6014
/// This is useful for setting up variables that will be referenced after the render completes.
6115
/// <param name="executeJs">The func to execute</param>
6216
/// </summary>
63-
public void PreRender(Func<string, string> executeJs)
17+
public virtual void PreRender(Func<string, string> executeJs)
6418
{
65-
PreRenderCore(executeJs);
66-
_renderFunctions?.PreRender(executeJs);
6719
}
6820

6921

@@ -75,12 +27,7 @@ public void PreRender(Func<string, string> executeJs)
7527
/// </summary>
7628
/// <param name="componentToRender">The Javascript expression to wrap</param>
7729
/// <returns>A wrapped expression</returns>
78-
public string WrapComponent(string componentToRender)
79-
{
80-
return _renderFunctions == null
81-
? WrapComponentCore(componentToRender)
82-
: _renderFunctions.WrapComponent(WrapComponentCore(componentToRender));
83-
}
30+
public virtual string WrapComponent(string componentToRender) => componentToRender;
8431

8532

8633
/// <summary>
@@ -89,12 +36,7 @@ public string WrapComponent(string componentToRender)
8936
/// </summary>
9037
/// <param name="input">The component HTML</param>
9138
/// <returns>A wrapped expression</returns>
92-
public string TransformRenderedHtml(string input)
93-
{
94-
return _renderFunctions == null
95-
? TransformRenderedHtmlCore(input)
96-
: _renderFunctions.TransformRenderedHtml(TransformRenderedHtmlCore(input));
97-
}
39+
public virtual string TransformRenderedHtml(string input) => input;
9840

9941

10042
/// <summary>
@@ -103,10 +45,8 @@ public string TransformRenderedHtml(string input)
10345
/// This is useful for reading computed state, such as generated stylesheets or a router redirect result.
10446
/// </summary>
10547
/// <param name="executeJs">The func to execute</param>
106-
public void PostRender(Func<string, string> executeJs)
48+
public virtual void PostRender(Func<string, string> executeJs)
10749
{
108-
PostRenderCore(executeJs);
109-
_renderFunctions?.PostRender(executeJs);
11050
}
11151
}
11252
}

0 commit comments

Comments
 (0)