Skip to content

Instantly share code, notes, and snippets.

@asmagin
Last active March 15, 2019 14:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save asmagin/6e6d0949282d1fc9da86ea0960d75faf to your computer and use it in GitHub Desktop.
Save asmagin/6e6d0949282d1fc9da86ea0960d75faf to your computer and use it in GitHub Desktop.
Sitecore Redux solution
using JavaScriptEngineSwitcher.Core;
using React;
using React.TinyIoC;
namespace Smagin.Alex.React
{
/// <summary>
/// Handles registration of core ReactJS.NET components.
/// </summary>
public class AssemblyRegistration : IAssemblyRegistration
{
/// <summary>
/// Gets the IoC container. Try to avoid using this and always use constructor injection.
/// This should only be used at the root level of an object heirarchy.
/// </summary>
public static TinyIoCContainer Container
{
get { return TinyIoCContainer.Current; }
}
/// <summary>
/// Registers standard components in the React IoC container
/// </summary>
/// <param name="container">Container to register components in</param>
public void Register(TinyIoCContainer container)
{
// One instance shared for the whole app
container.Register<IReactEnvironment, ReduxEnvironment>().AsPerRequestSingleton();
}
}
}
import React from 'react'
import ReactDOM from 'react-dom'
import ReactDOMServer from 'react-dom/server'
import reduxSc from '../redux-sc';
import headerComp from './header';
import simpleContentComp from './simple-content';
import counterComp from './counter';
import timerComp from './timer';
const decorate = element => (
{
element,
renderToDOM(props, node) {
const render = () => {
var el = React.createElement(element, { store: reduxSc.store, dispatch: reduxSc.dispatch, data: props.data, placeholders: props.placeholders });
ReactDOM.render(el, document.getElementById(node))
}
render();
reduxSc.store.subscribe(render);
},
renderToString(props) {
var el = React.createElement(element, { store: reduxSc.store, dispatch: reduxSc.dispatch, data: props.data, placeholders: props.placeholders });
return ReactDOMServer.renderToString(el)
},
renderToStaticMarkup(props) {
var el = React.createElement(element, { store: reduxSc.store, dispatch: reduxSc.dispatch, data: props.data, placeholders: props.placeholders });
return ReactDOMServer.renderToStaticMarkup(el)
},
getPlaceholders(){
if(typeof(element) === 'undefined'
|| typeof(element.prototype.placeholders) === 'undefined') {
return '';
}
return JSON.stringify(element.prototype.placeholders)
}
}
)
// exports
export const header = decorate(headerComp);
export const simpleContent = decorate(simpleContentComp);
export const counter = decorate(counterComp);
export const timer = decorate(timerComp);
export function onIncrement() {
return {
type: 'INCREMENT'
};
}
export function onDecrement() {
return {
type: 'DECREMENT'
};
}
const counter = (state = 0, action) => {
switch (action.type) {
case 'INCREMENT':
return state + 1
case 'DECREMENT':
return state - 1
default:
return state
}
}
export default counter;
import React from 'react'
import { Grid, Row, Col } from 'react-bootstrap'
import * as actionCreators from '../../actions/counter';
const counter = React.createClass({
propTypes: {
data: React.PropTypes.shape({
title: React.PropTypes.string
})
},
getDefaultProps: function () {
return {
data: {
'title': 'This is counter'
}
};
},
render: function() {
const dispatch = (typeof(this.props.dispatch) !== 'undefined')
? this.props.dispatch
: () => {};
const onIncrement = () => {
return dispatch(actionCreators.onIncrement())
};
const onDecrement = () => {
return dispatch(actionCreators.onDecrement())
};
const state = (typeof(this.props.store) !== 'undefined')
? this.props.store.getState()
: {counter: 'NaN'}; // so that is visible
return (
<Grid>
<Row>
<Col xs={12} md={12} style={{fontSize: '20px'}}>
<h3><div dangerouslySetInnerHTML={{ __html: this.props.data.title }} /></h3>
<button onClick={onDecrement} style={{padding: '0 10px'}}> - </button>
<strong style={{padding: '0 20px'}}>{ state.counter }</strong>
<button onClick={onIncrement} style={{padding: '0 10px'}}> + </button>
</Col>
</Row>
</Grid>
)
}
});
export default counter;
using JavaScriptEngineSwitcher.Core;
using React;
using React.Exceptions;
namespace Smagin.Alex.React
{
public class ReduxComponent : ReactComponent
{
public ReduxComponent(IReactEnvironment environment, IReactSiteConfiguration configuration, string componentName,
string containerId) : base(environment, configuration, componentName, containerId)
{
}
public override string RenderHtml(bool renderContainerOnly = false, bool renderServerOnly = false)
{
if (!_configuration.UseServerSideRendering)
{
renderContainerOnly = true;
}
if (!renderContainerOnly)
{
EnsureComponentExists();
}
try
{
var html = string.Empty;
if (!renderContainerOnly)
{
var reactRenderCommand = renderServerOnly
? $"{ComponentName}.renderToStaticMarkup({_serializedProps})"
: $"{ComponentName}.renderToString({_serializedProps})";
html = _environment.Execute<string>(reactRenderCommand);
}
string attributes = string.Format("id=\"{0}\"", ContainerId);
if (!string.IsNullOrEmpty(ContainerClass))
{
attributes += string.Format(" class=\"{0}\"", ContainerClass);
}
return string.Format(
"<{2} {0}>{1}</{2}>",
attributes,
html,
ContainerTag
);
}
catch (JsRuntimeException ex)
{
throw new ReactServerRenderingException(string.Format(
"Error while rendering \"{0}\" to \"{2}\": {1}",
ComponentName,
ex.Message,
ContainerId
));
}
}
public override string RenderJavaScript()
{
return $"{ComponentName}.renderToDOM({_serializedProps}, '{ContainerId}')";
}
}
}
using React;
using System.Text;
using System.Linq;
namespace Smagin.Alex.React
{
public class ReduxEnvironment : ReactEnvironment
{
public ReduxEnvironment(IJavaScriptEngineFactory engineFactory, IReactSiteConfiguration config, ICache cache,
IFileSystem fileSystem, IFileCacheHash fileCacheHash)
: base(engineFactory, config, cache, fileSystem, fileCacheHash)
{
}
public override IReactComponent CreateComponent<T>(string componentName, T props, string containerId = null,
bool clientOnly = false)
{
if (!clientOnly)
{
EnsureUserScriptsLoaded();
}
var component = new ReduxComponent(this, _config, componentName, containerId)
{
Props = props
};
_components.Add(component);
return component;
}
public override string GetInitJavaScript(bool clientOnly = false)
{
var fullScript = new StringBuilder();
// Propagate any server-side console.log calls to corresponding client-side calls.
if (!clientOnly)
{
var consoleCalls = Execute<string>("console.getCalls()");
fullScript.Append(consoleCalls);
}
foreach (var component in _components.Reverse())
{
fullScript.Append(component.RenderJavaScript());
fullScript.AppendLine(";");
}
return fullScript.ToString();
}
}
}
import { createStore } from 'redux';
import reducer from '../reducers/common';
const store = (typeof(window) !== 'undefined')
? createStore(reducer, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__())
: createStore(reducer);
const initialState = JSON.stringify(store.getState());
const reduxSc = {
initialState: initialState,
store: store,
dispatch: store.dispatch
};
export default reduxSc;
export function onTick() {
return {
type: 'TICK'
};
}
const timer = (state = 0, action) => {
switch (action.type) {
case 'TICK':
return state + 1
default:
return state
}
}
export default timer;
import React from 'react'
import { Grid, Row, Col } from 'react-bootstrap'
import * as actionCreators from '../../actions/timer';
const timer = React.createClass({
propTypes: {
data: React.PropTypes.shape({
title: React.PropTypes.string
})
},
getDefaultProps: function () {
return {
data: {
'title': 'This is timer'
}
};
},
getDispatch: function(){
return (typeof(this.props.dispatch) !== 'undefined')
? this.props.dispatch
: () => { };
},
tick: function() {
return this.getDispatch()(actionCreators.onTick())
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
const state = (typeof(this.props.store) !== 'undefined')
? this.props.store.getState()
: { timer: 0 }; // so that is visible
return (
<Grid>
<Row>
<Col xs={12} md={12} style={{fontSize: '20px'}}>
<h3><div dangerouslySetInnerHTML={{ __html: this.props.data.title }} /> { state.timer }s</h3>
</Col>
</Row>
</Grid>
)
}
});
export default timer;
// webpack config
module.exports = {
...
output: {
...
library: '[name]__ext',
...
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment