Skip to content

Commit

Permalink
Merge pull request #2 from newyork-anthonyng/feature/an/server-side-r…
Browse files Browse the repository at this point in the history
…endering

Include an example of server-side rendering
  • Loading branch information
mlrawlings committed Aug 29, 2018
2 parents 33c68c5 + e1a1a95 commit 85ae751
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 3 deletions.
54 changes: 53 additions & 1 deletion README.md
Expand Up @@ -45,4 +45,56 @@ var redux = require('redux');
var counter = require('./reducers');

module.exports = redux.createStore(counter);
```
```

# Server-side rendering

With Marko, we can also send the initial state of your Redux store from the server.

We can pass the initial state as a `global` variable, as shown in the following code:

```js
module.exports = function(req, res) {
var value = parseInt(req.params.value);

res.marko(view, {
$global: {
PRELOADED_STATE: {
value: value,
},
},
});
}
```

When we go to`localhost:8080/server-side/redux/123`, the `123` is used as the initial state of our redux store and passed to the client.

In our Marko component, we can use the initial state like so:

```jsx
import store from '../store';

class {
onCreate(input, out) {
// out.global is received from the $global object from route.js
this.PRELOADED_STATE = out.global.PRELOADED_STATE;

// create redux store on the server-side
this.reduxStore = store.initialize(this.PRELOADED_STATE);
}

onMount() {
// recreate redux store on the client-side
this.reduxStore = store.initialize(this.PRELOADED_STATE);
this.reduxStore.subscribe(() => {
this.forceUpdate();
});
}

dispatch(type) {
this.reduxStore.dispatch({ type: type });
}
}
```

In the above example, the imported store module is slightly different than the previous example. This module exports a Immediately-Invoked Function Expression (IFFE) which returns the Redux store. Using an IFFE allows us to create our Redux store with an initial state from the server, and share it with all other Marko components.
4 changes: 2 additions & 2 deletions routes/index/components/counter.marko
@@ -1,8 +1,8 @@
/**
* This is a presentation component that has no knowledge of Redux.
* When an action needs to be performed it simply emits an event
* that that needs to be handled by a parent container component.
* In this example, the `app` component will handle the `increment and
* that needs to be handled by a parent container component.
* In this example, the `app` component will handle the `increment` and
* `decrement` actions by directly interacting with the Redux store.
*/
class {
Expand Down
40 changes: 40 additions & 0 deletions routes/server-side-redux/components/app.marko
@@ -0,0 +1,40 @@
import store from '../store';

class {
onCreate(input, out) {
// out.global is received from the $global object from route.js
this.PRELOADED_STATE = out.global.PRELOADED_STATE;
// create redux store on the server-side
this.reduxStore = store.initialize(this.PRELOADED_STATE);
}
onMount() {
// recreate redux store on the client-side
this.reduxStore = store.initialize(this.PRELOADED_STATE);
this.reduxStore.subscribe(() => {
this.forceUpdate();
});
}
dispatch(type) {
this.reduxStore.dispatch({ type: type });
}
}

$ var reduxState = {};
/*
* On the client-side, Marko first does a "lightweight re-render" of the UI
* component tree before the `onMount` event is fired. The DOM is not actually
* updated in this "lightweight re-render".
* We check for the existence of `this.reduxStore` because it is first created in
* the `onMount` event.
*/
$ if(typeof component.reduxStore.getState === 'function') {
reduxState = component.reduxStore.getState();
}

<counter
value=reduxState.value
on-increment('dispatch', 'INCREMENT')
on-decrement('dispatch', 'DECREMENT') />
42 changes: 42 additions & 0 deletions routes/server-side-redux/components/counter.marko
@@ -0,0 +1,42 @@
/**
* This is a presentation component that has no knowledge of Redux.
* When an action needs to be performed it simply emits an event
* that needs to be handled by a parent container component.
* In this example, the `app` component will handle the `increment` and
* `decrement` actions by directly interacting with the Redux store.
*/
class {
increment() {
this.emit('increment');
}
decrement() {
this.emit('decrement');
}
incrementIfOdd() {
if (this.input.value % 2 !== 0) {
this.increment();
}
}
incrementAsync() {
setTimeout(() => {
this.increment();
}, 1000);
}
}

<div>
Current count: ${input.value}
<p>
<button on-click('increment')>+</button>
<button on-click('decrement')>-</button>
<button on-click('incrementIfOdd')>
Increment if odd
</button>
<button on-click('incrementAsync')>
Increment async
</button>
</p>
</div>
17 changes: 17 additions & 0 deletions routes/server-side-redux/index.marko
@@ -0,0 +1,17 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Marko + Redux</title>

<!-- CSS includes -->
<lasso-head/>
</head>
<body>
<!-- Top-level UI component: -->
<app/>

<!-- JS includes -->
<lasso-body/>
</body>
</html>
18 changes: 18 additions & 0 deletions routes/server-side-redux/reducers/index.js
@@ -0,0 +1,18 @@
module.exports = function(state, action) {
state = state || {
value: 0
};

switch (action.type) {
case 'INCREMENT':
return {
value: state.value + 1
};
case 'DECREMENT':
return {
value: state.value - 1
};
default:
return state;
}
};
18 changes: 18 additions & 0 deletions routes/server-side-redux/route.js
@@ -0,0 +1,18 @@
var view = require('./index');

module.exports = function(req, res) {
/*
* We can create the initial state of the redux store on the server.
* In this example, the initial state of our store is retrieved from
* the request parameters.
*/
var value = parseInt(req.params.value);

res.marko(view, {
$global: {
PRELOADED_STATE: {
value: value,
},
},
});
};
25 changes: 25 additions & 0 deletions routes/server-side-redux/store.js
@@ -0,0 +1,25 @@
var redux = require('redux');
var counter = require('./reducers');

/*
* We use an Immediately-Invoked Function Expression (IIFE) to hold our
* redux store. This allows us to create a store client-side using the
* preloaded state from the server.
*/
module.exports = (function() {
var store;

function initialize(preloadedState) {
store = redux.createStore(counter, preloadedState);
return store;
}

function getStore() {
return store;
}

return {
initialize: initialize,
getStore: getStore,
};
})();
1 change: 1 addition & 0 deletions server.js
Expand Up @@ -31,6 +31,7 @@ app.use(require('lasso/middleware').serveStatic());

// Map the "/" route to the home page
app.get('/', require('./routes/index/route'));
app.get('/server-side-redux/:value', require('./routes/server-side-redux/route'));

app.listen(port, function(err) {
if (err) {
Expand Down

0 comments on commit 85ae751

Please sign in to comment.