1. Getting off the ground

1.1. The basics

A good place to start is file structure - it will in many ways define how our website will work. We better sort this out right away.

Remembering goal #3 (server rendering) we will have client, server and shared directories. I think the names a pretty self explanatory. We will also create directory called public with a subdirectory js. js directory will contain a bundle made by Webpack that the browsers will use.

We won't bother ourselves with contents of server and client directories at this point. Our focus will be on shared directory, in which our actual React app will live.

Common way to structure files in React/Redux application is to have directory for you actions, directory for constants, one for the reducers, one for containers, etc. We could go down this road, but let's remember goal #2 - modularity.

Imagine you've created some new functionality, a list of projects, for example. It has reducer, actions, higher-order component (HoC further on), presentational components. How many directories will you have to update?

Instead we will try and make something like shared/modules/blog, shared/modules/gallery, shared/modules/page. These directories will contain everything - reducers, actions, HoCs and presentational components.

Adding a new module to the app will be a matter of adding it's reducer to the root reducer and rendering it in an appropriate place.

There's also part of codebase dedicated to Redux - creating root reducer, store, etc. We will create directory called redux and put everything there.

In the root of our shared folder we will have Root.jsx, which will be the entry point to our application.

In this section we will try make all this work with some predefined data. This data wil come from shared/data.json file. It will contain list of galleries, list of blog posts and content for a text page (we will have only one at this point).

Each module will contain files actions.js, container.jsx for HoC, reducer.js and subdirectory components for presentational components. Let's take a look on it:

Directories structure

Let's take a look at /shared/modules/galleries/container.jsx:

import React from 'react';
import { connect } from 'react-redux';

import GalleriesList from './components/GalleriesList.jsx';

class GalleriesContainer extends React.Component {
    render() {
        return (
            <GalleriesList galleries={this.props.galleries}/>
        );
    }
}

const mapStateToProps = state => ({
    galleries: state.galleries
});

export default connect(mapStateToProps)(GalleriesContainer);

Not much to see here yet - we create GalleriesContainer class, connect it to the galleries part of app's state with connect() function from react-redux package and make it render presentational component with list of galleries from it's props.

Let's look at /shared/modules/galleries/reducer.js:

const initialState = {
    galleries: []
};

export default function galleries(state = initialState, action = {}) {
    let newState;

    switch(action.type) {
        default:
            newState = state;
            break;
    }

    return newState;
}

It does nothing at all! At this point we need it only so that GalleriesContainer can access “state.galleries” array.

actions.js is completely empty. Right now we want to test our modular file structure, so we are just rendering mock content and have nothing to act on or with.

All other modules are almost exactly the same. We also have index module, that renders links to all pages of our app using react-router's Link component:

/shared/modules/index/components/Index.jsx:

import React from 'react';
import { Link } from 'react-router';

const Index = () => {
    return (
        <div>
            <ul>
                <li><Link to="/blog">Blog</Link></li>
                <li><Link to="/galleries">Galleries</Link></li>
                <li><Link to="/page">Page</Link></li>
            </ul>
        </div>
    );
};

export default Index;

In redux directory we see two files: configureStore.js (nothing fancy there, regular redux stuff) and rootReducer.js, that is a bit more interesting:

import { routerReducer } from 'react-router-redux';
import { combineReducers } from 'redux';

import blog from '../modules/blog/reducer.js';
import galleries from '../modules/galleries/reducer.js';
import index from '../modules/index/reducer.js';
import page from '../modules/page/reducer.js';

const reducer = combineReducers({
    blog,
    galleries,
    index,
    page,
    routing: routerReducer
});

export default reducer;

Here we import reducers from all our modules and combine them into a root reducer.

So it seems that this modular file structure will work without problems for our React app.

You can find all code of this section here: https://github.com/graymur/another-react-universtal-tutorial. Clone the repo, switch to branch "1.1", run npm install and npm start commands and navigate to http://localhost:3000. You will also need babel-node installed.

1.2. Adding app's state.

We've got ourselves a working prototype, but it lacks many things. Menu is hardcoded on index page, we have to press "Back" button in the browser all the time, while menu is supposed to be on every page. Most websites have header and footer and possibly other elements that appear on all pages - and we want those!

Right now, though, we can't do that - our app's state consists of states of modules. Each module renders it's own content and should worry only about that - not about headers, footers, menus and other stuff. We need some module/component that will take care of those things without bothering our precious decoupled modules. It should be rendered on every page regardless of the current module.

Fortunately, React allows us to achieve this without any problems. We can just wrap our routing with a new component:

/shared/Root.jsx

...
export default function Root() {
    return (
        <Provider store={store}>
            <App>
                <Router history={history}>
                    <Route path="/blog" component={Blog}/>
                    <Route path="/galleries" component={Galleries}/>
                    <Route path="/" component={Index}/>
                    <Route path="*" component={Page}/>
                </Router>
            </App>
        </Provider>
    );
};

Let's see how App component may look like:

class AppContainer extends React.Component {
    render() {
        return (
            <div>
                <header>
                    <ul>
                        <li><Link to="/blog">Blog</Link></li>
                        <li><Link to="/galleries">Galleries</Link></li>
                        <li><Link to="/page">Page</Link></li>
                    </ul>
                </header>
                {this.props.children}
                <footer>&copy; {(new Date()).getFullYear()}</footer>
            </div>
        );
    }
}
...

App component is a child of Provider component, so we can connect it the store using connect() function. But links and the menu will not work, because App it out of scope of Redux-router. We can fix this easily by leveraging React-router functionality:

...
export default function Root() {
    return (
        <Provider store={store}>
            <Router history={history}>
                <Route component={App}>
                    <Route path="/blog" component={Blog}/>
                    <Route path="/galleries" component={Galleries}/>
                    <Route path="/" component={Index}/>
                    <Route path="*" component={Page}/>
                </Route>
            </Router>
        </Provider>
    );
};

We add another route that wraps other routes and pass App component to it via component property. Now App is in the scope of both Provider and Router and all is peachy - we can use connect() and Link components will work properly.

In App's component JSX we now have

  • header element with menu from Index module (it will appear on every page);

  • footer with copyright and current year;

  • props.children in between them, allowing this component to wrap any number of other components.

While we're at - let's think about our menu. We don't want it to be hardcoded, so we don't have to modify our source code every time we add or remove link. We want it to come from app's backend (from /shared/data.json file at this point). In other words menu has to be rendered dynamically based on the app's state.

This brings us to interesting question - where in our file structure should this App component live? It will have it's own part of app's state with menu items, so it will have a reducer. It means it will have HoC and presentational components. Where did we see this before?

Exactly - this is the same structure as any other module in our app. Let's place it in our /shared/modules directory along with other modules. Here's how our directory structure will look like now:

Directories structure

Let's put our menu into /shared/data.json file like so:

{
    "app": {
        "menu": [{
            "link": "/blog",
            "title": "Blog"
        }, {
            "link": "/galleries",
            "title": "Galleries"
        }, {
            "link": "/page",
            "title": "Page"
        }]
    },
    "galleries: [...],
    "blog": [...],
    "page": {...}
}

And we can now take a look on app module's HoC:

/shared/modules/app/container.jsx

import React from 'react';
import { connect } from 'react-redux';

import App from './components/App.jsx';

class AppContainer extends React.Component {
    render() {
        return <App {...this.props.app} children={this.props.children}/>;
    }
}

const mapStateToProps = state => ({
    app: state.app
});

export default connect(mapStateToProps, null)(AppContainer);

Business as usual here: we define AppContainer component, connect it to the store with connect() function from react-redux and pass properties to presentational component to do it's job. Only interesting thing is that we pass children like this: children={this.props.children}. this.props.children will contain components that are rendered for current route. The are defined in our routing setup in /shared/Root.jsx. We must pass them to our presentational component so it can render them:

/shared/modules/app/components/App.jsx

import React from 'react';
import { Link } from 'react-router';

import Menu from './Menu.jsx';

const App = ({ menu, children }) => {
    return (
        <div>
            <header>
                <Menu items={menu}/>
            </header>
            {children}
            <footer>&copy; {(new Date()).getFullYear()}</footer>
        </div>
    );
};

export default App;

Our presentational component delegates rendering of the menu to Menu component.

Now we have modules that care only about rendering their data and wrapper module that renders header, footer and menu. They don't know anything about each other and are happy about it!

You can find all code of this section here: https://github.com/graymur/another-react-universtal-tutorial. Clone the repo, switch to branch "1.2", run npm install and npm start commands and navigate to http://localhost:3000. You will also need babel-node installed.

results matching ""

    No results matching ""