<h4>Repository
<ul>
<li><a href="https://github.com/facebook/react" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">React Github Page
<p dir="auto">Previously on this series, we learned more about state management in large applications and the benefits we could gain by adopting an architecture like Flux. We also add Redux application functionality to our FireLiners app complete with actions and Sagas.
<p dir="auto">Today, we'll do just a little bit more. We'll be going in-depth into more state management practices and we'll setup dynamic data that we'll be using to populate our feed. Finally, we'll add functionality that allows us to persist our state to local storage.
<h4>Difficulty
<ul>
<li>Advanced
<h4>What Will I Learn?
<p dir="auto">By the time we get to the end of this tutorial, you should have understood the following concepts:
<ul>
<li>Working with forms in a Redux-Saga application.
<li>Updating immutable records with sagas.
<li>Keeping client data easily accessible by persisting to LocalStorage with <code>redux-localstorage-middleware.
<li>Dynamically transitioning to any view within our React application.
<h4>Requirements
<ul>
<li><a href="https://nodejs.org" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Node.js 8.0 or greater.
<li><a href="https://npmjs.com" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">NPM 3.0 or greater.
<li><a href="https://yarnpkg.com" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Yarn package manager
<li><a href="https://github.com" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Git
<li><a href="https://github.com/creatrixity/fireliners" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Previous code on this series
<li>An intermediate level of <a href="https://reactjs.org" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">ReactJS knowledge.
<li>Keen interest and patience.
<h4>Introduction
<p dir="auto">Welcome back, friend! Last time around, we did a lot of awesome stuff. This time around, we'll be up to even much more. Before we proceed any further, let's remember our current project structure.
<ul>
<li><p dir="auto"><strong>fire-liners/
<ul>
<li>config/...
<li>node_modules/...
<li>public/...
<li>scripts/...
<li><strong>src/
<ul>
<li>components/
<ul>
<li>Header/
<ul>
<li>index.js
<li>logo.svg
<li>containers/
<ul>
<li>App/
<ul>
<li>App.test.js
<li>index.js
<li>constants.js
<li>reducer.js
<li>screens/
<ul>
<li>Home /
<ul>
<li>index.js
<li>constants.js
<li>actions.js
<li>Loading /
<ul>
<li>index.js
<li>services/
<ul>
<li>DataService/
<ul>
<li>index.js<br />
+index.js
<li>registerServiceWorker.js
<li>package.json
<p dir="auto"><a href="https://steemit.com/utopian-io/@creatrixity/pt-1-build-a-css-in-js-react-app-with-styled-components-and-priceline-design-system" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Last time around we explored many new ideas about state management. We talked about actions, action dispatchers, sagas and a whole lot of new ideas. We also setup our app to start using those concepts. Today, we'll continue applying our knowledge to our application. We'll be adding functionality that allows the creation of new quotes. We'll be visually highlighting the latest quote and we'll also explore local storage as a viable store for our application (for now).
<p dir="auto"><img src="https://images.hive.blog/768x0/https://preview.ibb.co/ixMvfy/A_Storage_Story.jpg" alt="The Storage Story" srcset="https://images.hive.blog/768x0/https://preview.ibb.co/ixMvfy/A_Storage_Story.jpg 1x, https://images.hive.blog/1536x0/https://preview.ibb.co/ixMvfy/A_Storage_Story.jpg 2x" />
<h3>Adding New Liner Entries Efficiently.
<p dir="auto">Let's get straight to it. We'll be adding functionality to the form available at <code>/add. We'll also be working on multiple form inputs. Let's create <code>src/screens/AddLine/index.js (if you are yet to do so) and get to work. We'll be importing our dependencies for this module. We'll start off by importing React and it's <code>Component subclass. We'll also be using the <code>connect method from <code>react-redux to add our store state and dispatch methods to the <code>this.props property. We'll also use the <code>Box, <code>Flex, <code>Label, <code>Select, <code>RedButton and <code>Text Priceline components to construct our UI. We'll import the <code>addLiner method from <code>src/containers/Home/actions.js (which we'll soon create) as it will help us add a new "liner" (or quote, if you prefer) to the store. Finally, we also import the <code>getAppState method from the <code>reducer.js file at <code>src/containers/App to help us retrieve the current application state and make it available to this module.
<pre><code>import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Box, Flex, Label, Select, RedButton, Text } from 'pcln-design-system';
import Textarea from '../../components/Form/Textarea';
import { addLiner } from '../Home/actions';
import { getAppState } from '../../containers/App/reducer';
<p dir="auto">Next, we'll be adding some code to our <code>AddLiner class. We'll add a constructor to the class. Within our constructor, we'll first follow a common React practice and inherit from React's <code>Component constructor by calling the <code>super method with any provided <code>props as the argument. We'll also be making the <code>this.handleSubmit method accessible throughout our class. We then set a default state for our <code>AddLine component. You may ask, <em>"I thought we were supposed to use Redux for state? Why are we then setting state in this component?" Well, simply because we use Redux doesn't mean we can't use regular state management. We use regular state management for state that is supposed to remain <em>private. For instance, we have two fields:
<ul>
<li>A Select field with a list of authors we can use for any given quote.
<li>A Textbox where we can add the quote.
<p dir="auto">We'll save them into the component's state and later, we'll send them to the store. We are setting a default quote just to keep things from getting boring.
<pre><code>class AddLine extends Component {
constructor (props) {
super(props);
this.handleSubmit = this.handleSubmit.bind(this);
this.state = {
author: 'Immortal Technique',
body: 'This is the point from which, I die or succeed; Living the struggle, I know I'm alive when I bleed.'
}
}
}
<p dir="auto">Next, in our class, we'll add a render method. We'd like to render a select box pre-filled with a list of authors within our form. To do this, we must first go through an array of authors and we'll create an <code><option></option> element for each author. We can do this by calling the <code>map method on <code>this.props.author (undefined at the moment).
<pre><code>render () {
let authors = this.props.authors.map((author, index) => (
<option value={author.name} key={index}>{ author.name }</option>
));
}
<p dir="auto">Next, we construct a little UI with the Priceline Design System Components. We create a form and we assign the <code>this.handleSubmit method as its submit event handler. We also assign an inline handler method to the onChange method of the <code><Select/> component that simply sets the <code>author state value to the selected author. We also make sure the select box selects an author by default. We do this by mapping the <code>value attribute to the <code>this.state.value option. We also set the <code>state.body property to a new value anytime the value of the Text box changes. Finally, we show a pretty red button that triggers the submit action.
<pre><code> return (
<Flex mt={4} justify="center" alignItems="center">
<Flex flexDirection="column" width={[ 0.8, 0.8, 0.5 ]}>
<Text bold mb={3} fontSize={3}>Add. The Dopest Lines. Ever.</Text>
<Box mb={3}>
<form onSubmit={this.handleSubmit}>
<Flex flexDirection="column" mb={3}>
<Label mb={2}>Author</Label>
<Select onChange={e => this.setState({
author: e.target.value
})} placeholder="Which cat dropped this line?" value={this.state.author}>
{authors}
</Select>
</Flex>
<Flex flexDirection="column" mb={3}>
<Label mb={2}>Lyrics</Label>
<Textarea rows={7} value={this.state.body} onChange={e => this.setState({
body: e.target.value
})} placeholder="Spit that line here, dawg..."></Textarea>
</Flex>
<RedButton type="submit">Save and go back</RedButton>
</form>
</Box>
</Flex>
</Flex>
)
<p dir="auto">With our render method done, we simply need to create the submit event handler. We use <code>e.preventDefault to prevent our form from actually trying to send information to a server as that's not the behaviour we'd like. We also use the <code>Array.prototype.reduce method to calculate the largest numeric id within our <code>liners collection and we then simply add 1 to the id obtained to get our new id. We then call the <code>addLiner method which is available on the <code>this.props. We supply the new id, author and body to this method for processing. Finally, we go back to the index by calling <code>this.props.history.push('/') which will take us to the root.
<pre><code> handleSubmit (e) {
e.preventDefault();
let newID = this.props.liners.reduce((maxId, liner) => Math.max(maxId, liner.id), 0) + 1;
this.props.addLiner({
id: newID,
author: this.state.author,
body: this.state.body
})
this.props.history.push('/')
}
<p dir="auto">We then need to create our <code>mapStateToProps and <code>mapDispatchToProps functions that we get to pass to Redux's <code>connect method. We get to add the <code>liners and <code>authors props to the component.
<pre><code>const mapStateToProps = (state) => {
return {
liners: getAppState(state).get('liners'),
authors: getAppState(state).get('authors')
}
}
const mapDispatchToProps = (dispatch) => {
return {
addLiner: data => dispatch(addLiner(data))
}
}
export default connect(mapStateToProps, mapDispatchToProps)(AddLine);
<p dir="auto">It's now time to create the <code>addLiner method in <code>src/containers/Home/actions.js. We'll create the <code>actions.js file at <code>src/containers/Home. We'll import our constants and we'll use them in the action object.
<pre><code>import { ADD_LINER_REQUEST, ADD_LINERS_REQUEST, ADD_AUTHORS_REQUEST } from './constants';
export const fetchLinersRequest = data => {
return {
type: ADD_LINERS_REQUEST,
data
}
}
export const addLiner = data => {
return {
type: ADD_LINER_REQUEST,
data
}
}
<p dir="auto">We then need to update our saga at the Home screen. We'll edit <code>src/screens/Home/saga.js. We'll setup the dependencies. We get to import the <code>all method that combines multiple saga functions. We also import the <code>call method that helps us resolve any promises. We use the <code>put method to send an action to the Redux store. We also use the <code>takeLatest method to take the latest available saga action.
<p dir="auto">Next, we import the <code>getLinersData method and a couple of constants.
<pre><code>import { all, call, put, takeLatest } from 'redux-saga/effects';
import { getLinersData } from '../../services/DataService';
import { ADD_LINERS_REQUEST, ADD_LINER_REQUEST } from './constants';
import { SET_LINERS_DATA, ADD_LINER } from '../../containers/App/constants';
<p dir="auto">We get to our <code>addLiner method now. We simply use this as a wrapper that calls our put method with a type and some data.
<pre><code>export function* addLiner (data) {
return yield put({
type: ADD_LINER,
payload: data
})
}
<p dir="auto">...Then in the <code>root method, we simply take the latest <code>ADD_LINER_REQUEST and call the <code>addLiner method above.
<pre><code>export default function* root() {
yield all([
takeLatest(ADD_LINERS_REQUEST, fetchLiners),
takeLatest(ADD_LINER_REQUEST, addLiner)
]);
}
<p dir="auto">We then need to update our reducer at <code>src/containers/App/reducer.js and add some code. We'll be using the <code>fromJS method from ImmutableJS to turn Javascript objects or arrays to immutable ones. In our <code>AppReducer function, we set the <code>liners state property to an array that comprises of the initial liners data along with the new data.
<pre><code>import { fromJS } from 'immutable';
import {ADD_LINER } from './constants';
const initialState = fromJS({
liners: [],
})
<p dir="auto">We then add our reducer code.
<pre><code>const AppReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_LINER:
return state.set("liners", fromJS([state.get('liners').push(action.payload.data)]));
default:
return state
}
}
<p dir="auto">We've just completed functionality that enables us to add some new quotes easily. But, it's not enough, as we'll also need a way to keep our authors dynamic. Let's tackle that.
<h4>Dynamically Fetching Authors.
<p dir="auto">We'd like to add a means of keeping our authors list dynamic. This is important, because along in this series, we'll be adding functionality that allows us to create authors dynamically.
<p dir="auto"><strong>The Game Plan
<p dir="auto">We need to fetch our authors <em>immediately our app starts. This means using a lifecycle event method to run the action that fetches our authors. That means we'll be modifying the <code>App root component a little to allow us add new behavior.
<p dir="auto">Let's edit <code>src/containers/App/index.js and get to work. We'll be adding some new dependencies. We'll be using the <code>withRouter method from <code>react-router as we'd like to be able to access some router based properties within our component. We'll also be importing the <code>fetchAuthorsRequest from the Home screen actions.
<pre><code>import { withRouter } from 'react-router';
import { connect } from 'react-redux';
import { fetchAuthorsRequest } from '../../screens/Home/actions';
<p dir="auto">We'll need to add the <code>componentDidMount lifecycle method. We'll be calling <code>this.props.fetchAuthors in this method. We'll define this method below.
<pre><code>class App extends React.Component {
componentDidMount() {
this.props.fetchAuthors()
}
<p dir="auto">Next, we'll run the usual <code>mapStateToProps and <code>mapDispatchToProps where we get to define the <code>fetchAuthors method that will make the request to our saga telling it, "Hey, we want all the authors you've got".
<pre><code>const mapStateToProps = (state) => {
return {}
}
const mapDispatchToProps = (dispatch) => {
return {
fetchAuthors: data => dispatch(fetchAuthorsRequest(data))
}
}
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(App));
<p dir="auto">Next, we must define the recently used <code>fetchAuthorsRequest action dispatcher. We'll add the <code>fetchAuthorsRequest method to the <code>actions.js file at <code>src/screens/Home. We're simply importing a couple of constants and we then define our action object. This will dispatch the <code>ADD_AUTHORS_REQUEST action to our saga.
<pre><code>import { ADD_LINER_REQUEST, ADD_LINERS_REQUEST, ADD_AUTHORS_REQUEST } from './constants';
export const fetchAuthorsRequest = () => {
return {
type: ADD_AUTHORS_REQUEST
}
}
<p dir="auto">Next, we'll add a saga for this request. We'll define the <code>fetchAuthors generator function at <code>src/screens/Home/saga.js. We'll start off by importing the <code>getAuthorsData method from the Data Service we created previously. We also import a couple of constants we'll be using. We then use the special <code>call method provided by Redux-Saga to resolve the promise returned by the <code>getAuthorsData method. We then call the <code>put method providing it with some required parameters (type and payload).
<pre><code>import { getAuthorsData } from '../../services/DataService';
import { ADD_AUTHORS_REQUEST } from './constants';
import { SET_AUTHORS_DATA } from '../../containers/App/constants';
export function* fetchAuthors (payload) {
const response = yield call(getAuthorsData);
return yield put({
type: SET_AUTHORS_DATA,
payload: {
data: response
}
})
}
<p dir="auto">We'll quickly define the imported constants. Editing <code>src/containers/App/constants.js.
<pre><code>export const SET_AUTHORS_DATA = 'app/SET_AUTHORS_DATA';
<p dir="auto">Also editing <code>src/screens/Home/constants.js.
<p dir="auto">``js<br />
export const ADD_AUTHORS_REQUEST = 'app/ADD_AUTHORS_REQUEST';
<pre><code>
We'll be defining the `getAuthorsData` method at the Data Service. Let's edit `src/services/DataService/index.js`. We'll be creating the `authors.json` file at `src/assets/data` shortly. In the meantime, we import the JSON data from it, and we resolve that data in a promise that we return.
```js
import authorsData from "../../assets/data/authors.json";
export const getAuthorsData = (id = null) => {
if (id) {
let author = authorsData.filter(author => author.id === id);
return new Promise(resolve => resolve(author));
}
return new Promise(resolve => resolve(authorsData));
};
<p dir="auto">It's now time to create our <code>authors.json along with the information. Let's use this file as a base to build on. Create <code>src/assets/data/authors.json and add test data.
<pre><code>[{
"name": "Immortal Technique",
"government_name": "Felipe Andres Coronel",
"photo": "immortal-technique.jpg"
},
{
"name": "Eminem",
"government_name": "Marshall Mathers",
"photo": "eminem.jpg"
},
{
"name": "Andre 3000",
"photo": "andre-3000.jpg"
}]
<p dir="auto">Great! All we need to do now is add a reducer for this action. We'll start by importing the <code>SET_AUTHORS_DATA constant. We then have the <code>authors property of the initial state as being an empty object. We check for our <code>SET_AUTHORS_DATA action and if our action was called we simply set the erstwhile empty <code>authors property in the <code>initialState array to the action's payload data.
<pre><code>import { SET_AUTHORS_DATA } from './constants';
const initialState = fromJS({
// ...Previous code here.
authors: []
})
const AppReducer = (state = initialState, action) => {
switch (action.type) {
case SET_AUTHORS_DATA:
return state.set('authors', action.payload.data)
// ...Previous code goes here.
default:
return state
}
}
<p dir="auto">Heading back to our browser tab at <code>http://localhost:3000, we see our list of authors show up in the select box. That's awesome! If we hit the submit button, we get to see our new entry with the selected author. Isn't that great?
<p dir="auto"><img src="https://images.hive.blog/0x0/https://cdn.steemitimages.com/DQmSi9h899VwckeohzJbuxwSTK17YzE5w8jo7PLjNby6SCh/Add-Liner.gif" alt="Dynamic entry adding in action" />
<h4>Persisting Data To LocalStorage.
<p dir="auto">Earlier, we installed <code>redux-localstorage. We need it for persisting data to the local storage. We can apply the middleware by updating some code at our Redux store. We first start by importing the <code>compose Redux method we also import the <code>redux-localstorage middleware. In our <code>createStoreWithMiddleware method call, we use the <code>compose method to compose our <code>persistState function. We provide <code>undefined as the first argument. This makes sure we sync all of our Redux store to local storage. We also define the <code>slicer, <code>serialize, <code>deserialize and <code>merge methods.
<p dir="auto">We'll go through the functionality of these methods:
<ul>
<li><p dir="auto"><strong>The <code>slicer method: returns a portion of the state.
<li><p dir="auto"><strong>The <code>serialize method: can be used to perform an action before the data is saved or serialized to local storage.
<li><p dir="auto"><strong>The <code>deserialize method: is used to perform an action <em>before data is retrieved or deserialized from local storage.
<li><p dir="auto"><strong>The <code>merge method: is used to merge the initial state and the persisted state. Since we're using immutable, we're merging the persisted data along with the initial state data through Immutable's <code>mergeDeep method.
<pre><code>import { compose } from 'redux';
import persistState from 'redux-localstorage'
const store = createStoreWithMiddleware(
rootReducer,
initialState,
compose(
persistState(undefined, {
slicer: (paths) => (state) => state,
serialize: (subset) => JSON.stringify(subset),
deserialize: (serializedData) => fromJS(JSON.parse(serializedData)),
merge: (initialState, persistedState) => initialState.mergeDeep(persistedState)
})
)
)
<p dir="auto">We'll stop at this juncture now. We've done a lot of work and we deserve some ice cream now.
<h4>Conclusion
<p dir="auto">In this tutorial, we covered multiple concepts. We learned more about state management in large applications. We also wrote some more code to add more functionality. We then added the ability to persist our redux store to local storage.
<p dir="auto">In our next tutorial, we'll explore route based filtering from our store.
<h4>Resources
<ul>
<li><a href="https://redux-saga.js.org/docs/introduction/" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">An Introduction to Redux Saga
<li><a href="https://github.com/elgerlambert/redux-localstorage" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Redux LocalStorage Middleware Introduction
<li><a href="https://github.com/creatrixity/fire-liners" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">FireLiners Github Code
<h4>Curriculum
<ul>
<li><p dir="auto"><a href="https://steemit.com/utopian-io/@creatrixity/pt-1-build-a-css-in-js-react-app-with-styled-components-and-priceline-design-system" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Part 1: Build a CSS-in-JS React App with Styled Components and Priceline Design System
<li><p dir="auto"><a href="https://steemit.com/utopian-io/@creatrixity/part-2-build-a-css-in-js-react-app-with-styled-components-and-priceline-design-system" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Part 2: Build a CSS-in-JS React App with Styled Components and Priceline Design System
<li><p dir="auto"><a href="https://steemit.com/utopian-io/@creatrixity/part-3-build-a-css-in-js-react-app-with-styled-components-and-priceline-design-system" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Part 3: Build a CSS-in-JS React App with Styled Components and Priceline Design System
Thanks for the contribution!
Your contribution has been evaluated according to Utopian policies and guidelines, as well as a predefined set of questions pertaining to the category.
To view those questions and the relevant answers related to your post, click here.
Chat with us on Discord.
[utopian-moderator]Need help? Write a ticket on https://support.utopian.io/.
Thanks for contributing on Utopian.
We’re already looking forward to your next contribution!Hey @creatrixity
Contributing on Utopian
Learn how to contribute on our website or by watching this tutorial on Youtube.
Want to chat? Join us on Discord https://discord.gg/h52nFrV.
Vote for Utopian Witness!
That's really good contribution. I want to give you some advices to make better contributions:
I hope these advices will help you for your next contributions.
Congratulations @creatrixity! You have completed some achievement on Steemit and have been rewarded with new badge(s) :
<p dir="auto"><a href="http://steemitboard.com/@creatrixity" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link"><img src="https://images.hive.blog/768x0/https://steemitimages.com/70x80/http://steemitboard.com/notifications/payout.png" srcset="https://images.hive.blog/768x0/https://steemitimages.com/70x80/http://steemitboard.com/notifications/payout.png 1x, https://images.hive.blog/1536x0/https://steemitimages.com/70x80/http://steemitboard.com/notifications/payout.png 2x" /> Award for the total payout received <p dir="auto"><sub><em>Click on the badge to view your Board of Honor.<br /> <sub><em>If you no longer want to receive notifications, reply to this comment with the word <code>STOP <blockquote> <p dir="auto">Do you like <a href="https://steemit.com/@steemitboard" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">SteemitBoard's project? Then <strong><a href="https://v2.steemconnect.com/sign/account-witness-vote?witness=steemitboard&approve=1" target="_blank" rel="noreferrer noopener" title="This link will take you away from hive.blog" class="external_link">Vote for its witness and <strong>get one more award!