diff --git a/examples/with-refnux/README.md b/examples/with-refnux/README.md new file mode 100644 index 00000000..c838da6f --- /dev/null +++ b/examples/with-refnux/README.md @@ -0,0 +1,54 @@ + +# Refnux example + +## How to use + +Download the example [or clone the repo](https://github.com/zeit/next.js): + +```bash +curl https://codeload.github.com/zeit/next.js/tar.gz/master | tar -xz --strip=2 next.js-master/examples/with-refnux +cd with-refnux +``` + +Install it and run: + +```bash +npm install +npm run dev +``` + +Deploy it to the cloud with [now](https://zeit.co/now) ([download](https://zeit.co/download)) + +```bash +now +``` + + +## The idea behind the example + +This example, just like `with-react` and `with-mobx` examples, shows how to manage a global state in your web-application. +In this case we are using refnux which is an alternative, simpler, purely functional store state manager. + +We have two very similar pages (page1.js, page2.js). They both + +- show the current application state, including a simple counter value +- have a link to jump from one page to the other +- have an 'increment' button to increment the state of the counter +(it triggers the `counterIncrement` action) + +When running the example, please, increment the counter and note how moving from page 1 to page 2 and back the state is persisted. +Reloading any of the pages will restore the initial state coming from the server. + + +### Implementation details + +Each page uses `withRefnux` helper which wraps the page in a Provider component. +Page components are connected to the state using refnux `connect` function. + +In the `store/` directory you can see a simple implmentation of + +- a `getInitialstate` function that returns the initial state of the store. It's used only on SSR +- a `counterIncrement` action that increments the counter state whe user pushes the corresponding button +- a `setTitle` action that's used to set page title during pages `getInitialProps` + +If you have any comment / question / pull requests please refer to the [original repository](https://github.com/davibe/next.js-example-with-refnux) where this example is developed and maintained. \ No newline at end of file diff --git a/examples/with-refnux/helpers/getStore.js b/examples/with-refnux/helpers/getStore.js new file mode 100644 index 00000000..86a41560 --- /dev/null +++ b/examples/with-refnux/helpers/getStore.js @@ -0,0 +1,18 @@ +import { createStore } from 'refnux' + +let storeMemoized = null + +const getStore = (initialState) => { + let store = null + if (typeof window == 'undefined') { + store = createStore(initialState) + } else { + if (!storeMemoized) { + storeMemoized = createStore(initialState) + } + store = storeMemoized + } + return store +} + +export default getStore \ No newline at end of file diff --git a/examples/with-refnux/helpers/withRefnux.js b/examples/with-refnux/helpers/withRefnux.js new file mode 100644 index 00000000..19fe6922 --- /dev/null +++ b/examples/with-refnux/helpers/withRefnux.js @@ -0,0 +1,42 @@ +import { Provider} from 'refnux' + +import getStore from './getStore' + +// The `withRefnux` "decorator" +// - wraps the given Component in a refnux Provider component +// - creates a `store` for the Provider handling different server side / client side cases +// - runs wrapped component `getInitialProps` as expected +// - passes `store` to Component's `getInitialProps` so that it can dispatch actions + +const withRefnux = (getInitialState, Component) => { + + const Wrapper = (props) => { + var store = props.store + // if getInitialProps was executed on the server we get a store + // that's missing non-serializable functions. + // Because of this we need to recreate the store based on the + // state coming from the server. + if (!store.dispatch) { + store = getStore(props.store.state) + } + return } + /> + } + + Wrapper.getInitialProps = async function (context) { + const store = getStore(getInitialState()) + var componentProps = {} + // honor wrapped component getInitialProps + if (Component.getInitialProps) { + componentProps = await Component.getInitialProps({ ...context, store }) + } + return { store, componentProps } + } + + return Wrapper +} + + +export default withRefnux \ No newline at end of file diff --git a/examples/with-refnux/package.json b/examples/with-refnux/package.json new file mode 100644 index 00000000..8942fb4a --- /dev/null +++ b/examples/with-refnux/package.json @@ -0,0 +1,17 @@ +{ + "name": "next.js-example-with-refnux", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "next": "next" + }, + "author": "", + "license": "ISC", + "dependencies": { + "next": "^2.0.0-beta.31", + "react": "^15.4.2", + "react-dom": "^15.4.2", + "refnux": "^1.3.0" + } +} diff --git a/examples/with-refnux/pages/page1.js b/examples/with-refnux/pages/page1.js new file mode 100644 index 00000000..836379d1 --- /dev/null +++ b/examples/with-refnux/pages/page1.js @@ -0,0 +1,28 @@ +import { connect } from 'refnux' +import Link from 'next/link' + +import withRefnux from '../helpers/withRefnux' +import getInitialState from '../store/getInitialState' + +// actions +import counterIncrement from '../store/counterIncrement' +import setTitle from '../store/setTitle' + +const Page1 = connect( + (state, dispatch) => +
+

{state.title}

+

Current state: {JSON.stringify(state, null, 2)}

+ + +
+) + +Page1.getInitialProps = async function (context) { + const {store} = context + // dispatch actions to store to set it up for this page / route + store.dispatch(setTitle('Page 1')) + return {} // we have a store, we don't need props! +} + +export default withRefnux(getInitialState, Page1) \ No newline at end of file diff --git a/examples/with-refnux/pages/page2.js b/examples/with-refnux/pages/page2.js new file mode 100644 index 00000000..44586fd5 --- /dev/null +++ b/examples/with-refnux/pages/page2.js @@ -0,0 +1,28 @@ +import { connect } from 'refnux' +import Link from 'next/link' + +import withRefnux from '../helpers/withRefnux' +import getInitialState from '../store/getInitialState' + +// actions +import counterIncrement from '../store/counterIncrement' +import setTitle from '../store/setTitle' + +const Page2 = connect( + (state, dispatch) => +
+

{state.title}

+

Current state: {JSON.stringify(state, null, 2)}

+ + +
+) + +Page2.getInitialProps = async function (context) { + const {store} = context + store.dispatch(setTitle('Page 2')) + return {} +} + + +export default withRefnux(getInitialState, Page2) \ No newline at end of file diff --git a/examples/with-refnux/store/counterIncrement.js b/examples/with-refnux/store/counterIncrement.js new file mode 100644 index 00000000..697705a5 --- /dev/null +++ b/examples/with-refnux/store/counterIncrement.js @@ -0,0 +1,6 @@ + +const counterIncrement = ({counter}, dispatch) => { + return { counter: counter + 1 } +} + +export default counterIncrement \ No newline at end of file diff --git a/examples/with-refnux/store/getInitialState.js b/examples/with-refnux/store/getInitialState.js new file mode 100644 index 00000000..fe4620d9 --- /dev/null +++ b/examples/with-refnux/store/getInitialState.js @@ -0,0 +1,8 @@ +const getInitialState = () => { + return { + title: '', + counter: 0 + } +} + +export default getInitialState \ No newline at end of file diff --git a/examples/with-refnux/store/getStore.js b/examples/with-refnux/store/getStore.js new file mode 100644 index 00000000..c4c2b632 --- /dev/null +++ b/examples/with-refnux/store/getStore.js @@ -0,0 +1,16 @@ +import { createStore } from 'refnux' + +const storeInitialState = { counter: 0, key: 'value' } + +const getStore = () => { + let store = null + if (typeof window == 'undefined') { + store = createStore(storeInitialState) + } else { + store = window.store || createStore(storeInitialState) + window.store = store + } + return store +} + +export default getStore \ No newline at end of file diff --git a/examples/with-refnux/store/setTitle.js b/examples/with-refnux/store/setTitle.js new file mode 100644 index 00000000..18220795 --- /dev/null +++ b/examples/with-refnux/store/setTitle.js @@ -0,0 +1,6 @@ + +const setTitle = (newTitle) => ({title}) => { + return { title: newTitle } +} + +export default setTitle \ No newline at end of file