Treats

Treats

  • Getting Started

›Tutorial

Getting Started

  • Installation

Tutorial

  • 01. Creating Your First Page
  • 02. Using Redux
  • 03. Using GraphQL
  • 04. Fetch Data for SSR
  • 05. Adding Addons

Main Concepts

  • Overview
  • Routing
  • Localization
  • Code-splitting
  • Redux
  • GraphQL Client
  • Middlewares
  • Helpers
  • Server-side Events
  • Server-side Template
  • Server-side Rendering
  • Custom Server App
  • Custom Client Initialization
  • Custom React App
  • Runtime Config
  • Build Config
  • Environment Variables
  • Code Generator
  • Scripts
  • Addons
  • Typescript
  • Workbox

API Reference

  • Overview
  • Filesystem Hooks
  • Components
  • Server
  • Client
  • Router
  • Intl
  • Locale Data
  • Helmet
  • Redux
  • Graphql

Authoring Addons

  • Overview
  • Helpers
  • Middlewares
  • Generators
  • Wrapping Up

Addons

  • Treats Addons List

Contributing

  • How To Contribute

FAQ

  • FAQs

04. Fetching Data for Server-Side-Render

SEO matters. We need to think about our page to be as meaningful as possible. Score to SSR and Treats! Treats supports SSR, but did you realize that all data still not server-side rendered? Well to put it simple it's because we didn't configure the GraphQL SSR. On the different side, when you are not using GraphQL, there's no one would give the data to Treats server. This tutorial will show you how to fetch data for SSR in Treats.

Let's add some case for this tutorial. How about adding country of origin buttons in our profile page? We will get countries data from https://restcountries.eu/rest/v2/all. When user click on a country button, it will update user's nationality and store it to Redux. Now let's get going to our profile page.

Fetch Data from API

The key concept of fetching data via SSR is using static async methods to dispatch action. Read more about SSR concept here.

  1. Add getInitialState in our profile.js
/* src/page/profile-page/profile.js */

class Profile extends Component {
    static async getInitialState({ router, serverContext }) {
        const { req, reduxStore } = serverContext;
        return reduxStore.dispatch(profileActions.getInitialState());
    }
    
    ...
}
  1. Next, we need to implement getInitialState in our src/redux/profile-page/action.js, along with getCountries, getCountriesLoading, getCountriesSuccess, and getCountriesError. You will need to install axios to help us with the request.
~$ npm install axios
/* src/redux/profile-page/action.js */

/* Other import */
import axios from "axios"

/* Other methods */
const getCountriesLoading = () => ({
    type: types["GET_COUNTRIES_LOADING"]
});

const getCountriesSuccess = response => ({
    type: types["GET_COUNTRIES_SUCCESS"],
    response
});

const getCountriesError = () => ({
    type: types["GET_COUNTRIES_ERROR"]
});

const getCountries = () => dispatch => {
    dispatch(getCountriesLoading());

    return axios.get("https://restcountries.eu/rest/v2/all")
    .then(response => {
        const parsedResponse = response.data.map(country => ({
            code: country.alpha2Code,
            name: country.name
        }))
        dispatch(getCountriesSuccess(parsedResponse));
    }).catch(err => {
        dispatch(getCountriesError());
        console.error(err);
    });
};

const getInitialState = () => dispatch => {
    return Promise.all([dispatch(getCountries())])
}

export default {
    /* Other export */
    getCountries,
    getInitialState
}

As the name implies, the getCountries is used to get list of countries from https://restcountries.eu/rest/v2/all. The getCountries(Loading|Success|Error) are action generators for updating data fetch status [success, error, loading] in Redux.

  1. Adding action means we need to define the types, reducer, and initial-state.
/* src/redux/profile-page/type.js */

const types = {
    /* Other types */
    "GET_COUNTRIES_LOADING": "GET_COUNTRIES_LOADING",
    "GET_COUNTRIES_SUCCESS": "GET_COUNTRIES_SUCCESS",
    "GET_COUNTRIES_ERROR": "GET_COUNTRIES_ERROR"
};

export default types;
/* src/redux/profile-page/initial-state.js */

const initialState = {
    name: "User",
    countries: {
        status: "loading",
        data: []
    }
};

export default initialState;
/*  src/redux/profile-page/reducer.js */

...
const profileActionHandlers = {
    /* Other handlers */
    [types.GET_COUNTRIES_LOADING]: state => ({
        ...state,
        countries: {
            status: "loading",
            data: []
        }
    }),
    [types.GET_COUNTRIES_SUCCESS]: (state, action) => ({
        ...state,
        countries: {
            status: "success",
            data: action.response
        }
    }),
    [types.GET_COUNTRIES_ERROR]: state => ({
        ...state,
        countries: {
            status: "error",
            data: []
        }
    })
};
...

Connecting Component with New Redux State

Now that we already implement the getInitialState, we need to add interaction to our profile page. As you can see from above implementations, we will store our data into our Redux state (countries). Therefore, we need to get the data via props.

  1. Now, get back to our profile.js and render the data. Also update the mapStateToProps and mapDispatchToProps.
class Profile extends Component {
    /* Other methods */

    render() {
        const { intl, name, nation, countries: { status, data } } = this.props,
            { name: temporaryName } = this.state;
        return(
            <div>
                <h3>Hello, {name}</h3>

                <form onSubmit={this.handleFormSubmit}>
                    <input value={temporaryName} onChange={this.handleInputChange} />
                    <button>{intl.formatMessage({id: "submit"})}</button>
                </form>

                {status === "success" && data.map(country => (
                    <button
                        key={country.code}
                        className={nation === country.name ? style.button_selected : undefined}
                        onClick={() => this.handleCountryChange(country.name)}
                    >
                        {country.name}
                    </button>
                ))}
                <br />
                <Link href="/" isPush>Back to Todo</Link>
            </div>
        )
    }
}

const mapStateToProps = state => ({
    name: state.profile.name,
    nation: state.profile.nation,
    countries: state.profile.countries
});

const mapDispatchToProps = dispatch => ({
    onFormSubmit: name => {
        dispatch(profileActions.updateName(name))
    },
    onNationUpdate: country => {
        dispatch(profileActions.updateNation(country))
    },
    onMount: () => {
        dispatch(profileActions.getCountries())
    }
});
  1. We also need to update the state, when user click on a country button. Simply add the implement this handleCountryChange method inside your Profile class.
class Profile extends Component {
    /* Other methods */

    handleCountryChange = country => {
        const { onNationUpdate } = this.props;

        if (onNationUpdate) {
            onNationUpdate(country);
        }
    }

    ...
}
  1. Oops! Almost forgot to add the updateNation in our action.js. Let's add it on action.js, type.js, initial-state.js, and reducer.js
/* src/redux/profile-page/action.js */

/* Other actions */
const updateNation = country => ({
    type: types["UPDATE_NATION"],
    nation: country
});

export default {
    /* Other actions */
    updateNation,
}
/* src/redux/profile-page/type.js */

const types = {
    /* Other types */
    "UPDATE_NATION": "UPDATE_NATION",
};

export default types;
/* src/redux/profile-page/initial-state.js */

const initialState = {
    name: "User",
    nation: "",
    countries: {
        status: "loading",
        data: []
    }
};

export default initialState;
/* src/redux/profile-page/reducer.js */

...
const profileActionHandlers = {
    /* Other reducers */
    [types.UPDATE_NATION]: (state, action) => ({
        ...state,
        nation: action.nation
    }),
};
...
  1. It's confusing to distinguish between selected button and unselected one. So let's add css in profile.css for selected button.
.button_selected {
    background-color: #0f0
}
  1. Right now, you should see your profile page is already server-side-rendered. To prove this, you can right-click on your page and click on "View Page Source". You should see that the countries data is rendered along with the components.

Showing the Country Value on Todo Page

Now that we have set "nation" value in Redux state. We can use it in our Todo page.

  1. Change the <FormattedMessage> second value to "nation". Also update mapStateToProps so it could pass the state into component props.
const Todo = ({ intl, name, nation }) => (
    <div>
        <Link href="/profile" isPush>Change Name</Link>
        <br />
        <FormattedMessage
            id="todo_title" 
            values={{
                name,
                nation
            }}
        />
        ...
    </div>
)

const mapStateToProps = state => ({
    name: state.profile.name,
    nation: state.profile.nation
})
  1. Change our locale JSON value, so it could show some related message
/* src/_locale/id.json */

{
    "todo_title": "Rencana Saya. Nama saya: {name}. Saya dari: {nation}",
    /* Other locale */
}
/* src/_locale/en.json */

{
    "todo_title": "My Todo List. My name is: {name}. I am from: {nation}",
    /* Other locale */
}

Great job! We just add some functionality to our todo apps. Also, we manage to learn some of Treats concept by practice. Keep exploring Treats features by reading the main-concepts and API references section. If you happen to find issues, don't hesitate to write the issues on our Github.

← 03. Fetch and Submit Data with GraphQL05. Adding Addons on Treats Server →
Tokopedia Open Source
Copyright © 2019 Tokopedia OSS