Table of Contents
Having page layout hardcoded works fine but it doesn’t let us dynamically modify it without changing the code. What about if we store the page layout in mongoDB, then request it through GraphQL and create component that could dynamically load the page components.
Luckily for us we already have page layout set up in GraphQL which for the homepage looks like this:
module.exports = [ { id: 'home', url: '/home', layout: [ { span: 12, components: [ { name: "Header" } ] }, { span: 12, components:[ { name: "Home" } ] } ] } ]
It simply shows that the page layout has a header and Home component. We are going to build component that will interpret this and will render a webpage with the requested components. This way we will have the flexibility to:
- dynamically change page layout by adding and removing components without code change
- create new pages on the fly without code change.
And if you want to explore all other layouts you could look into GraphQL project under ./src/models/mock_data/pages
Creating PageLayout component to load our modules.
Let’s change App/index.js
remove all other routes and redirect everything to the new component that we are going to create (line 21).
./src/components/App/index.js
import React, { Component } from 'react'; import PageLayout from '../../containers/PageLayout'; import { ApolloProvider } from 'react-apollo'; import { ApolloClient } from 'apollo-client'; import { HttpLink } from 'apollo-link-http'; import { InMemoryCache } from 'apollo-cache-inmemory'; import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'; import styles from './styles.scss'; export default class App extends Component { render() { const GRAPHQL_URL = 'http://localhost:4001/graphql'; const client = new ApolloClient({ link: new HttpLink({ uri: GRAPHQL_URL }), cache: new InMemoryCache() }); return ( <div className={styles.appWrapper}> <ApolloProvider client={client}> <Router> <Switch> <Route exact path="*" component={PageLayout} /> </Switch> </Router> </ApolloProvider> </div> ); } }
Now let’s continue with creating the component itself.
./src/containers/PageLayout/index.js
import React, { Component } from 'react'; import query from './query'; import { graphql } from 'react-apollo'; class PageLayout extends Component { constructor(props) { super(props); } render() { console.log(this.props.data.getPageByUrl); return ( <div> this is my page layout! </div> ); } } export default graphql(query, { options(props) { return { variables: { url: props.history.location.pathname }, }; }, })(PageLayout);
./src/containers/PageLayout/query.js
import gql from 'graphql-tag'; const query = gql` query getPageByUrl($url: String) { getPageByUrl(url: $url) { id url layout { span components { name } } } } ` export default query;
and when you run the project and look at the browser console you will see the layout structure that we printed out in index.js (line 12)
This basically describes which components and in what order should be rendered for the ‘home’ page.
Hook PageLayot component to render required HTML components.
Create ComponentList sub component with the following contents:
./src/containers/PageLayout/ComponentList/index.js
import Header from '../../../components/Header'; import Home from '../../../components/Home'; import About from '../../../components/About'; import Greetings from '../../../components/Greetings'; import Gallery from '../../../containers/DogsCatalog'; export default { Home: Home, About: About, Greetings: Greetings }
This component simply provide a list of all components that we are going to render in our website.
Now modify PageLayout component to add the list and render the HTML components.
./src/containers/PageLayout/index.js
import React, { Component } from 'react'; import ComponentList from './ComponentList'; import query from './query'; import { graphql } from 'react-apollo'; const styles = require('./styles.scss'); class PageLayout extends Component { constructor(props) { super(props); } render() { if(!this.props.data.getPageByUrl) { return(<p>loading ...</p>); } const allLayout = this.props.data.getPageByUrl.layout.map((layoutList) => { const layout = layoutList.components.map((component, id , components) => { const componentName = component.name; const ChildComponent = ComponentList[componentName]; if(typeof ChildComponent === 'undefined') { return( <div key='{id}' className={styles.error}>Can't find {componentName} component!</div> ); } return ( <ChildComponent key={componentName} /> ); }); return layout; }); return(allLayout); } } export default graphql(query, { options(props) { return { variables: { url: props.history.location.pathname }, }; }, })(PageLayout);
what we just did:
– we added the ComponentList which returns all components on our website
– (Line 19) we are getting the layout for this particular url that contains all components, then we loop through all components (line 20) and create an instances for all of them.
– If the component cannot be found it will show red error message (lines 23-27)
Also add the css which will colorize the error message in red:
./src/containers/PageLayout/styles.scss
.error { background: red; color: white; }
navigate to http://localhost:8080/home and you are probably going to see this screen:
This is actually made intentional, to demonstrate how our error handler works If it can’t find particular component.
Let’s fix this. Add the highlighted lines with the missing components.
./src/containers/PageLayout/ComponentList/index.js
import Header from '../../../components/Header'; import Home from '../../../components/Home'; import About from '../../../components/About'; import Greetings from '../../../components/Greetings'; import Gallery from '../../../containers/DogsCatalog'; export default { Home: Home, About: About, Greetings: Greetings, DogsCatalogWithRedux: DogsCatalog, Header: Header }
Navigate again to http://localhost:8080/home and you should have SPA with 4 pages.