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

02. Connecting Components with Redux

Okay, now I want to upgrade my todo apps. Let's say we want to create a profile page and display whatever name we insert into my todo pages. How to make our pages communicate each other? Redux is one solution!. Treats use Redux for its global state management. You may need to learn about Redux Concept, before you start this tutorial.

To use Redux in Treats projects, you don't need to add Provider and creating store for your app, because Treats will handle it. You just need to connect your component to Redux, define your reducer, and combine the reducers under _redux filesystem hooks. Redux is not mandatory in Treats. If you want to disable Redux, you can configure it in treats.config.js as instructed here.

Enough with the concept, let's put our hands to it!

Creating Redux Action, Types, and Initial State

  1. Create profile folder under page. You may use Treats generator.
/* src/page/profile-page/profile.js */

import React, { Component } from "react";
import { injectIntl } from "@treats/intl";

import { connect } from "@treats/redux";
import style from "./profile.css";

/**
*  Profile Page for Treats Tutorial
* @param props React props
* @param props.name
* 
*/
class Profile extends Component {
    constructor(props) {
        super(props);

        this.state = {
            name: ""
        }
    }

    handleFormSubmit = e => {
        e.preventDefault();
        const { name } = this.state;

        //Save your component to Redux
    }

    handleInputChange = e => {
        this.setState({
            name: e.target.value
        })
    }

    render() {
        const { intl, name } = 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>
            </div>
        )
    }
}

const mapStateToProps = state => ({
    /** YOUR MAP STATE TO PROPS FUNCTION */
});
const mapDispatchToProps = dispatch => ({
    /** YOUR MAP DISPATCH TO PROPS FUNCTION */
});

export default connect(mapStateToProps,mapDispatchToProps)(injectIntl(Profile));

But where can I get the name props? Good question! We will get the name props from Redux state, and that's what mapStateToProps for. But we will implement it after creating our action and reducers.

  1. Create Redux boilerplate for profile. You can use Treats generator. In this tutorial, we will create it under ./src/redux.
src
|-- ...
|-- page
|-- redux
    |-- profile-page
        |-- action.js
        |-- index.js
        |-- initial-state.js
        |-- reducer.js
        |-- type.js
/* src/redux/profile-page/index.js */

import profileReducer from "./reducer";

export { default as profileActions } from "./action";
export { default as profileTypes } from "./type";
export { default as profileInitialState } from "./initial-state";

export default profileReducer;
  1. Add Redux's action type definition in type.js.
/* src/redux/profile-page/type.js */
const types = {
    /* Some other types */,
    UPDATE_NAME: "UPDATE_NAME"
};

export default types;
  1. Implement our action in action.js.
/* src/redux/profile-page/action.js */

import types from "./type";

/* Some other actions */

const updateName = name => ({
  type: types[UPDATE_NAME],
  name
})

export default {
    /* Some other actions */,
    updateName
};
  1. Add action handler in reducer.js
/* src/redux/profile-page/reducer.js */

import initialState from "./initial-state";
import types from "./type";

const profileActionHandlers = {
    /* Some other reducers */,
    [types.UPDATE_NAME]: (state, action) => ({
        ...state,
        name: action.name
    })
};

const profileReducer = (state = initialState, action) =>
    profileActionHandlers[action.type] ? profileActionHandlers[action.type](state, action) : state;

export default profileReducer;
  1. We need a proper initial state (not undefined), so let's add some initial state in initial-state.js.
/* src/redux/profile-page/initial-state.js */

const initialState = {
    /* Some other states */,
    name: "User"
};

export default initialState;
  1. Alright, all set in our Redux part. Let's continue into the next section.

Tweaking The Filesystem Hooks

Treats provide filesystem hooks for easy custom configuration and Redux is one of it. We already create our reducers for profile page, but the whole app only need a single reducer, since Treats only have a single store. Fortunately, there's some helpful method called combineReducers and Treats already wrap it in its dependencies.

  1. Create folder _redux under src and create index.js inside it.
src
|-- ...
|-- _redux
    |-- reducer.js
  1. Write your reducer.js file. In this step we will also add some alias to our apps in treats.config.js so instead of writing ../redux/profile-page we will write @redux/profile-page.
// treats.config.js
const path = require("path");

const config = {
    ....
    alias: {
        "@page": path.resolve(__dirname, "./src/page"),
        "@redux": path.resolve(__dirname, "./src/redux")
    }
};

module.exports = config;
/* src/_redux/reducer.js */

import { combineReducers } from "@treats/redux";
/* Other reducer */
import ProfileReducer from "@redux/profile-page";

export default combineReducers({
    /* Other reducer */
    profile: ProfileReducer
});
  1. All set for filesystem hooks, now let's get back to our unfinished components.

mapStateToProps and mapDispatchToProps

Okay in the first section, we promise to implement our mapStateToProps. Here we are:

  1. Implement our mapStateToProps in profile.js
/* src/page/profile-page/profile.js */

...

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

  1. Realize that our submit button doesn't do anything when clicked? Since we will push our new name to Redux, we are gonna dispatch updateName we create earlier. Here, we will need to implement mapDispatchToProps.
/* src/page/profile-page/profile.js */

/* Other import */
import { profileActions } from "@redux/profile-page";

class Profile extends Component {
    
    handleFormSubmit = e => {
        e.preventDefault();
        const { name } = this.state,
            { onFormSubmit } = this.props;

        //Save your component to Redux

        if (onFormSubmit) {
            onFormSubmit(name);
        }
    }

    /* Other class method */
}

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

const mapDispatchToProps = dispatch => ({
    onFormSubmit: name => {
        dispatch(profileActions.updateName(name))
    }
});
  1. All set to our components! Now check your redux page

Registering Page To Routes

Feels something missing in our route? You're sharp!. We need to register it to our route before testing it. Just like the previous tutorial, you need to add it in path.js, route.js, and module.js under _route.

  1. Add profile route name to path.js. The route is up to you, but we will assign it with /profile for this tutorial
/* src/_route/path.js */

export const TODO = "/";
export const PROFILE = "/profile";
  1. Add your route config in route.js. Also delete disabled: true in todo config, since it will trigger redirect. Redirect will cause Redux state back to initial state.
import { TODO, PROFILE } from "./path";

const route = [
    {
        name: "todo",
        path: TODO,
        exact: true
    },
    {
        name: "profile",
        path: PROFILE,
        exact: true,
    }
];

export default route;
  1. Connect your path and your page in module.js
import Todo from "@page/todo";
import Profile from "@page/profile-page";

import { TODO, PROFILE } from "./path";

const module = {
    [TODO]: Todo,
    [PROFILE]: Profile
};

export default module;
  1. Next, add some navigation button that connect our pages. Note: please use Link instead of <a> tag, because Redux state will be reset to initial state when we redirect using <a> tag.
/* src/page/todo/todo.js */

/* Other import */
import Link from "@treats/component/link";

class Todo extends Component {
    /* Other method */

    render() {
        const { keyword, items } = this.state,
            { intl } = this.props;
        return (
            <div className={style.todo_form}>
                <Link href="/profile" isPush>Change Name</Link>
                <br />
                <FormattedMessage
                    id="todo_title" 
                    values={{
                        name: "User",
                        value2: 20
                    }}
                />
                ...
            </div>
        )
    }
}
/* src/page/profile-page/profile.js */

/* Other import */
import Link from "@treats/component/link";

class Profile extends Component {
    /* Other method */

    render() {
        const { name } = this.props,
            { name: temporaryName } = this.state;
        return(
            <div>
                ...
                <Link href="/" isPush>Back to Todo</Link>
            </div>
        )
    }
}
  1. To show the name in todo.js, we need to connect todo.js with Redux. We only need to connect todo.js with mapStateToProps since todo page will not be able to change the state. By connecting todo component, todo component will have name in its props.
/* src/page/todo/todo.js */

class Todo extends Component {
    ...

    render() {
        const { keyword, items } = this.state,
            { intl, name } = this.props;
        return (
            <div className={style.todo_form}>
                <Link href="/profile" isPush>Change Name</Link>
                <br />
                <FormattedMessage
                    id="todo_title" 
                    values={{
                        name: name,
                        value2: 20
                    }}
                />
                ...
            </div>
        )
    }
}

const mapStateToProps = state => ({
    name: state.profile.name
})

export default connect(mapStateToProps)(injectIntl(Todo));

Now, the name on title will be changed after you change your name in profile page.

← 01. Creating Your First Page03. Fetch and Submit Data with GraphQL →
Tokopedia Open Source
Copyright © 2019 Tokopedia OSS