Skip to content

Commit 5b0ed48

Browse files
React helmet (reactjs#615)
* Move render helper functions to more generic namespace * Add react-helmet support * Fix field naming convention This is what happens when you contribute to c# projects with c++ naming conventions at work :) * Remove razor language hint * Fix render error in emotion demo * Fix tests * Fix tests
1 parent e13f8f5 commit 5b0ed48

25 files changed

+301
-95
lines changed

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

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ Add the render helper to the call to `Html.React`:
2121

2222
```
2323
@using React.AspNet
24-
@using React.StylesheetHelpers
24+
@using React.RenderFunctions
2525
2626
@{
2727
var styledComponentsFunctions = new StyledComponentsFunctions();
@@ -98,7 +98,7 @@ Add the render helper to the call to `Html.React`:
9898

9999
```
100100
@using React.AspNet
101-
@using React.StylesheetHelpers
101+
@using React.RenderFunctions
102102
103103
@{
104104
var reactJssFunctions = new ReactJssFunctions();
@@ -187,7 +187,7 @@ Add the render helper to the call to `Html.React`:
187187

188188
```
189189
@using React.AspNet
190-
@using React.StylesheetHelpers
190+
@using React.RenderFunctions
191191
192192
@Html.React("RootComponent", new { exampleProp = "a" }, renderFunctions: new EmotionFunctions())
193193
```

site/jekyll/features/react-helmet.md

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
---
2+
layout: docs
3+
title: React Helmet
4+
---
5+
6+
Just want to see the code? Check out the [sample project](https://github.com/reactjs/React.NET/tree/master/src/React.Sample.Webpack.CoreMvc).
7+
8+
React Helmet is a library that allows setting elements inside the `<head>` tag from anywhere in the render tree.
9+
10+
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).
11+
12+
Expose React Helmet as `global.Helmet`:
13+
14+
```js
15+
require('expose-loader?Helmet!react-helmet');
16+
```
17+
18+
Add the render helper to the call to `Html.React`:
19+
20+
```
21+
@using React.AspNet
22+
@using React.RenderFunctions
23+
24+
@{
25+
var helmetFunctions = new ReactHelmetFunctions();
26+
}
27+
28+
@Html.React("RootComponent", new { exampleProp = "a" }, renderFunctions: helmetFunctions)
29+
30+
@{
31+
ViewBag.HelmetTitle = helmetFunctions.RenderedHelmet.GetValueOrDefault("title");
32+
}
33+
```
34+
35+
In your layout file, render the helmet title that is now in the ViewBag:
36+
37+
```
38+
<!DOCTYPE html>
39+
<html>
40+
<head>
41+
@Html.Raw(ViewBag.HelmetTitle)
42+
<meta charset="utf-8" />
43+
@Html.Raw(ViewBag.ServerStyles)
44+
</head>
45+
<body>
46+
@RenderBody()
47+
</body>
48+
</html>
49+
```

src/React.AspNet/HtmlHelperExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ public static IHtmlString React<T>(
6565
bool serverOnly = false,
6666
string containerClass = null,
6767
Action<Exception, string, string> exceptionHandler = null,
68-
RenderFunctions renderFunctions = null
68+
IRenderFunctions renderFunctions = null
6969
)
7070
{
7171
return new ActionHtmlString(writer =>

src/React.Core/IReactComponent.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ public interface IReactComponent
5656
/// <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>
5757
/// <param name="renderFunctions">Functions to call during component render</param>
5858
/// <returns>HTML</returns>
59-
string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null, RenderFunctions renderFunctions = null);
59+
string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null, IRenderFunctions renderFunctions = null);
6060

6161
/// <summary>
6262
/// Renders the JavaScript required to initialise this component client-side. This will
@@ -76,7 +76,7 @@ public interface IReactComponent
7676
/// <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>
7777
/// <param name="renderFunctions">Functions to call during component render</param>
7878
/// <returns>HTML</returns>
79-
void RenderHtml(TextWriter writer, bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null, RenderFunctions renderFunctions = null);
79+
void RenderHtml(TextWriter writer, bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null, IRenderFunctions renderFunctions = null);
8080

8181
/// <summary>
8282
/// Renders the JavaScript required to initialise this component client-side. This will

src/React.Core/IRenderFunctions.cs

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
using System;
2+
3+
namespace React
4+
{
5+
/// <summary>
6+
/// Functions to execute during a render request.
7+
/// These functions will share the same Javascript context, so state can be passed around via variables.
8+
/// </summary>
9+
public interface IRenderFunctions
10+
{
11+
/// <summary>
12+
/// Executes before component render.
13+
/// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
14+
/// This is useful for setting up variables that will be referenced after the render completes.
15+
/// <param name="executeJs">The func to execute</param>
16+
/// </summary>
17+
void PreRender(Func<string, string> executeJs);
18+
19+
20+
/// <summary>
21+
/// Transforms the React.createElement expression.
22+
/// This is useful for libraries like styled components which require wrapping the root component
23+
/// inside a helper to generate a stylesheet.
24+
/// Example transform: React.createElement(Foo, ...) => wrapComponent(React.createElement(Foo, ...))
25+
/// </summary>
26+
/// <param name="componentToRender">The Javascript expression to wrap</param>
27+
/// <returns>A wrapped expression</returns>
28+
string WrapComponent(string componentToRender);
29+
30+
31+
/// <summary>
32+
/// Transforms the compiled rendered component HTML
33+
/// This is useful for libraries like emotion which take rendered component HTML and output the transformed HTML plus additional style tags
34+
/// </summary>
35+
/// <param name="input">The component HTML</param>
36+
/// <returns>A wrapped expression</returns>
37+
string TransformRenderedHtml(string input);
38+
39+
40+
/// <summary>
41+
/// Executes after component render.
42+
/// It takes a func that accepts a Javascript code expression to evaluate, which returns the result of the expression.
43+
/// This is useful for reading computed state, such as generated stylesheets or a router redirect result.
44+
/// </summary>
45+
/// <param name="executeJs">The func to execute</param>
46+
void PostRender(Func<string, string> executeJs);
47+
}
48+
}

src/React.Core/ReactComponent.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ public ReactComponent(IReactEnvironment environment, IReactSiteConfiguration con
127127
/// <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>
128128
/// <param name="renderFunctions">Functions to call during component render</param>
129129
/// <returns>HTML</returns>
130-
public virtual string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null, RenderFunctions renderFunctions = null)
130+
public virtual string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null, IRenderFunctions renderFunctions = null)
131131
{
132132
return GetStringFromWriter(renderHtmlWriter => RenderHtml(renderHtmlWriter, renderContainerOnly, renderServerOnly, exceptionHandler, renderFunctions));
133133
}
@@ -142,7 +142,7 @@ public virtual string RenderHtml(bool renderContainerOnly = false, bool renderSe
142142
/// <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>
143143
/// <param name="renderFunctions">Functions to call during component render</param>
144144
/// <returns>HTML</returns>
145-
public virtual void RenderHtml(TextWriter writer, bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null, RenderFunctions renderFunctions = null)
145+
public virtual void RenderHtml(TextWriter writer, bool renderContainerOnly = false, bool renderServerOnly = false, Action<Exception, string, string> exceptionHandler = null, IRenderFunctions renderFunctions = null)
146146
{
147147
if (!_configuration.UseServerSideRendering)
148148
{

src/React.Core/StylesheetHelpers/EmotionFunctions.cs renamed to src/React.Core/RenderFunctions/EmotionFunctions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
using System;
22
using System.Text;
33

4-
namespace React.StylesheetHelpers
4+
namespace React.RenderFunctions
55
{
66
/// <summary>
77
/// Render functions for Emotion. https://github.com/emotion-js/emotion
88
/// Requires `emotion-server` to be exposed globally as `EmotionServer`
99
/// </summary>
10-
public class EmotionFunctions : RenderFunctions
10+
public class EmotionFunctions : RenderFunctionsBase
1111
{
1212
/// <summary>
1313
/// Constructor. Supports chained calls to multiple render functions by passing in a set of functions that should be called next.
1414
/// The functions within the provided RenderFunctions will be called *after* this instance's.
1515
/// Supports null as an argument.
1616
/// </summary>
1717
/// <param name="renderFunctions">The chained render functions to call</param>
18-
public EmotionFunctions(RenderFunctions renderFunctions = null)
18+
public EmotionFunctions(IRenderFunctions renderFunctions = null)
1919
: base(renderFunctions)
2020
{
2121
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using Newtonsoft.Json;
5+
6+
namespace React.RenderFunctions
7+
{
8+
/// <summary>
9+
/// Render functions for React-Helmet. https://github.com/nfl/react-helmet
10+
/// Requires `react-helmet` to be exposed globally as `Helmet`
11+
/// </summary>
12+
public class ReactHelmetFunctions : RenderFunctionsBase
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+
/// 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+
25+
/// <summary>
26+
/// Dictionary of Helmet properties, rendered as raw HTML tags
27+
/// Available keys: "base", "bodyAttributes", "htmlAttributes", "link", "meta", "noscript", "script", "style", "title"
28+
/// </summary>
29+
public Dictionary<string, string> RenderedHelmet { get; private set; }
30+
31+
/// <summary>
32+
/// Implementation of PostRender
33+
/// </summary>
34+
/// <param name="executeJs"></param>
35+
protected override void PostRenderCore(Func<string, string> executeJs)
36+
{
37+
var helmetString = executeJs(@"
38+
var helmetResult = Helmet.default.renderStatic();
39+
JSON.stringify(['base', 'bodyAttributes', 'htmlAttributes', 'link', 'meta', 'noscript', 'script', 'style', 'title']
40+
.reduce((mappedResults, helmetKey) => Object.assign(mappedResults, { [helmetKey]: helmetResult[helmetKey] && helmetResult[helmetKey].toString() }), {}));");
41+
42+
RenderedHelmet = JsonConvert.DeserializeObject<Dictionary<string, string>>(helmetString);
43+
}
44+
}
45+
}

src/React.Core/StylesheetHelpers/ReactJssFunctions.cs renamed to src/React.Core/RenderFunctions/ReactJssFunctions.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
11
using System;
22

3-
namespace React.StylesheetHelpers
3+
namespace React.RenderFunctions
44
{
55
/// <summary>
66
/// Render functions for React-JSS. https://github.com/cssinjs/react-jss
77
/// Requires `react-jss` to be exposed globally as `ReactJss`
88
/// </summary>
9-
public class ReactJssFunctions : RenderFunctions
9+
public class ReactJssFunctions : RenderFunctionsBase
1010
{
1111
/// <summary>
1212
/// Constructor. Supports chained calls to multiple render functions by passing in a set of functions that should be called next.
1313
/// The functions within the provided RenderFunctions will be called *after* this instance's.
1414
/// Supports null as an argument.
1515
/// </summary>
1616
/// <param name="renderFunctions">The chained render functions to call</param>
17-
public ReactJssFunctions(RenderFunctions renderFunctions = null)
17+
public ReactJssFunctions(IRenderFunctions renderFunctions = null)
1818
: base(renderFunctions)
1919
{
2020
}

src/React.Core/StylesheetHelpers/StyledComponentsFunctions.cs renamed to src/React.Core/RenderFunctions/StyledComponentsFunctions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,24 @@
11
using System;
22

3-
namespace React.StylesheetHelpers
3+
namespace React.RenderFunctions
44
{
55
/// <summary>
66
/// Render functions for styled components. https://github.com/styled-components/styled-components
77
/// Requires `styled-components` to be exposed globally as `Styled`
88
/// </summary>
9-
public class StyledComponentsFunctions : RenderFunctions
9+
public class StyledComponentsFunctions : RenderFunctionsBase
1010
{
1111
/// <summary>
1212
/// Constructor. Supports chained calls to multiple render functions by passing in a set of functions that should be called next.
1313
/// The functions within the provided RenderFunctions will be called *after* this instance's.
1414
/// Supports null as an argument.
1515
/// </summary>
1616
/// <param name="renderFunctions">The chained render functions to call</param>
17-
public StyledComponentsFunctions(RenderFunctions renderFunctions = null)
17+
public StyledComponentsFunctions(IRenderFunctions renderFunctions = null)
1818
: base(renderFunctions)
1919
{
2020
}
21-
21+
2222
/// <summary>
2323
/// HTML style tag containing the rendered styles
2424
/// </summary>

0 commit comments

Comments
 (0)