Skip to content

Instantly share code, notes, and snippets.

@ericlamb89
Created June 13, 2017 22:25
Show Gist options
  • Save ericlamb89/e03bf4420b7e6c3d57a38dfbfed0d954 to your computer and use it in GitHub Desktop.
Save ericlamb89/e03bf4420b7e6c3d57a38dfbfed0d954 to your computer and use it in GitHub Desktop.

React Concepts

What is It?

  • Javascript lib used to create HTML that is shown to user in a browser
  • Use it to create components --> snippets of code that produce HTML
  • can nest components to make complex applications simply

Props

  • Passed into a component on creation
  • Functional Component: props object is an argument
  • Class-based Component: props available anywhere as this.props
Null Props

Error: cannot read property id of undefined

Often happens when using downward data flow, if parent objects can't fetch data fast enough (child component is rendering before it can load data).

State

  • Javascript object that records / reacts to user events
  • Each class-based component has state whereas functional components display something constant w/no state
  • When state changes, re-renders componenet and all of its children
  • Always need to initialize state object in constructor
  • Always use this.setState to modify state (unless in constructor)

Constructors

  • All classes have a constructor
  • Only function called automatically when a new instance of a class is created
  • super() calls constructor of extended class super(props)

Controlled Component

Form element where value of input is set by state of component, not the other way around.

Binding Context

When you pass a callback to an input like so (onChange):

onInputChange(event){
	this.setState({term: event.target.value})
}
<input 
	placeholder="Get a five-day forecast in your favorite cities"
	className="form-control"
	value={this.state.term}
	onChange={this.onInputChange} 
/>

You will get error: Uncaught TypeError: Cannot read property 'setState' of undefined

this context is incorrect. this.setState() will not work because this is not the component ---> it will be some random unknown this.

To fix this, need to bind the correct context in the contructor:

constructor(props){
	super(props);

	this.state = { term: ''};

	this.onInputChange = this.onInputChange.bind(this);
}

#####More on bind()

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind

The bind() function creates a new function (a bound function) with the same function body (internal call property in ECMAScript 5 terms) as the function it is being called on (the bound function's target function) with the this value bound to the first argument of bind(), which cannot be overridden. bind() also accepts leading default arguments to provide to the target function when the bound function is called. A bound function may also be constructed using the new operator: doing so acts as though the target function had instead been constructed. The provided this value is ignored, while prepended arguments are provided to the emulated function.

Redux

redux-flow

What is it

"State container"

  • Collection of all of the data that describes the appplication
  • Metadata (e.g. currently selected books)
  • React on the other hand translates the app's data into something the user can understand
  • All of the app's data is contained in a single object "The State" - this is application-level state, not component level state

Reducers

  • A function that returns a piece of the application state
  • Produces the "value" of state

E.g (Generated by books reducer and an activeBook reducer):

{
	books: [{title: 'Harry Potter'},{title: 'Javascript'}],
	activeBook: {title: 'Javascript: The Good Parts'}
}

Step 1: Define Reducer

In reducers/reducer_books.js

export default function(){
	return [
		{title: 'Javascript: The Good Parts' },
		{title: 'Harry Potter' },
		{title: 'The Dark Tower' },
		{title: 'Eloquent Ruby' }
	]
}

Step 2: Wire up reducer to application

Use combineReducers to add reducers as keys in the global application state (rootReducer in the example below)

In reducers/index.js:

import { combineReducers } from 'redux';
import BooksReducer from './reducer_books'

const rootReducer = combineReducers({
  books: BooksReducer
});

export default rootReducer;

React-Redux

(react-redux)

  • Connects react components and application state in redux
  • "The Bridge"

Containers

  • "Smart Components" -> Special React component with a direct connection to the state managed by Redux.
  • Usually separated into a separate directory called "containers" (not in components)
  • Usually the most parent component that cares about a particlar piece of state should be a container
  • If the component needs to call action creators, it should be a container

Defining a Container

  1. import connect from react-redux
  2. define class component
  3. define mapStateToProps() (if necessary -- if don't need application state can skip this and pass null to connect())
  4. define mapDispatchToProps() -- if don't need actions can leave this out
  5. call connect with mapStateToProps, mapDispatchToProps and component

Anytime the application state changes:

  1. mapStateToProps will pass new state to props on the component
  2. mapDispatchToProps will make sure action gets dispatched to reducers
  3. components using the state will automatically re-render.

Example Container

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

class BookList extends Component {
   //defined component
}

function mapStateToProps(state){
	// Whatever is returned will show up as props 
	// inside of component
	return {
		books: state.books
	};

}

export default connect(mapStateToProps)(BookList);

Actions and Action Creators

Any event (user triggered or otherwise) can trigger a call of an Action Creator which returns an Action object

  • Action Creator is a function that returns an action object (has a required type and an optional payload)
  • Action automtically sent to all reducers
  • If reducer cares about that action, it will figure out new value for its part of the application state

Middleware

Middleware intercepts actions before they get to reducers.

Middleware can:

  • Let actions pass
  • Manipulate actions
  • Log actions
  • Block actions

redux-promise

Popular middleware package for redux. Helps with ajax requests. If an action payload is a promise, it will stop the action until the promise resolves. Once it resolves, it will send a new action with the same type and a payload containing the request response.

Install with: $ npm install --save redux promise

Hook into application in index.js

import ReduxPromise from 'redux-promise';
const createStoreWithMiddleware = applyMiddleware(ReduxPromise)(createStore);

Redux Form

When user interacts with a form, reduxForm will record everything in application state.

Adds helper methods on this.props

redux-form

1. Hook it up to reducer

In reducers/index.js

import it:

import { reducer as formReducer } from 'redux-form';

Add it to combine reducers call (form):

const rootReducer = combineReducers({
  posts: PostsReducer
  form: formReducer
});
2. Add an Action creator for it

Add the action creator in actions/index.js

Import it into the component

3. Add reduxForm it to component

Import it and wrap your class export in reduxForm like so:

import { reduxForm } from 'redux-form';

class PostsNew extends Component {
	// blah blah blah
}

export default reduxForm({
	//name of the form
	form: 'PostsNew',
	//array of fields in the form
	fields: ['title','categories','content'],
}, null, { createForm })(PostsNew);

reduxForm works just like connect()()...

Connect: 1st argument is mapStateToProps, 2nd is mapDispatchToProps

ReduxForm: 1st is form config, 2nd is mapStateToProps, 3rd is mapDispatchToProps

3. Build a Form

Just a regular old html form

4. Use Helpers

Redux-form comes with a bunch of helpers like handleSubmit, and fields:{}

const { fields: {title,categories,content} handleSubmit } = this.props;
<form onSubmit={handleSubmit}>
5. Map fields object to form inputs

Repeat for every field:

<input type="text" className="form-control" {...title}/>

6. Define an action creator and pass to handleSubmit

<form onSubmit={handleSubmit(this.props.createPost)}>

In this example, handleSubmit calls this.props.createPost with the values of the form inputs in an object. createPost can then parse that object and do whatever it wants. In this case it makes a POST request to the API with the field values.

Form Validation with Redux Form

  1. Define a validation function that returns an errors object
  2. Add the validation function to to reduxForm call config argument
  3. If the errors object has a key that matches one of the fields in the form, reduxForm assumes the form is not valid, prevents submission, and adds properties to the fields configuration object. Gets
  4. Use the errors object inside of the render method
function validate(values){
	const errors = {};
	if (!values.title){
		errors.title = 'Enter a username';
	}
	return errors;
}
export default reduxForm({
	//name of the form
	form: 'PostsNew',
	//array of fields in the form
	fields: ['title','categories','content'],
	validate
},null,{ createPost })(PostsNew);
  1. In the render method, can display the error for any field like so:
	<div className="form-group">
		<label>Title</label>
		<input type="text" className="form-control" {...title}/>
		<div className="text-help">
		{title.error}
	</div> 

Prevent Default

When using a form, need to prevent form from submitting an http post request (this is standard browser behavior), since typically you will have your own event handler for submit.

Use event.preventDefault() in event handler function like so:

onFormSubmit(event){
		event.preventDefault();
}

render () {
	return (
		<form onSubmit={this.onFormSubmit} 
		</form>
	)
}

React Router

react-router

What is It?

Presents the correct react components when the URL changes.

Setting Up React Router

1. Add React Router to index.js
import { Router, browserHistory } from 'react-router';
import routes from './routes';

// blah blah blah

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <Router history={browserHistory} routes={routes} />
  </Provider>
  , document.querySelector('.container'));

2. Configure routes

Tell react-router the valid routes

routes.js ---> mapping of paths to components:

import React from 'react';
import { Route, IndexRoute } from 'react-router';

import App from './components/app';

export default (
	<Route path="/" component={App} />
);

3. Nesting Routes w/ this.props.children

Below example shows nested routes --> each will show App and Greeting on the page.

Can go to localhost:8080/greet , localhost:8080/greet2, localhost:8080/greet3

In routes.js

const Greeting = () => {
	return <div>Hey there!</div>;
};

export default (
	<Route path="/" component={App}>
		<Route path="greet" component={Greeting} />
		<Route path="greet2" component={Greeting} />
		<Route path="greet3" component={Greeting} />
	</Route>

);

In parent component, app.js, need to add {this.props.children}

import React from 'react';
import { Component } from 'react';

export default class App extends Component {
  render() {
    return (
      <div>
      	React simple starter
      	{this.props.children}
      </div>
    );
  }
}

4. IndexRoute

If the URL only matches the parent component, show this component.

export default (
	<Route path="/" component={App}>
		<IndexRoute component={PostsIndex} />
		<Route path="greet" component={Greeting} />
		<Route path="greet2" component={Greeting} />
		<Route path="greet3" component={Greeting} />
	</Route>

Link Component

Component provided by react-router. Lets you easily navigate your routes.

To set up a Link Component:

1. Import Link Component

import { Link } from 'react-router';

2. Add Link Tag - Specify Route with to="ROUTE"

Can apply whatever styling you want :).

<Link to="/posts/new" className="btn btn-primary">
	Add a Post
</Link>

Context and router.push

Context provides info from parent component in children, without needing to explicitly passing as props.

Good use for this is using the router.push() method, which navigates you directly to a particluar route. This is useful on form submission or any asynchronous request where navigation should not happen unless the the action is successful.

Step 1: Import PropTypes from react

import React, { Component, PropTypes } from 'react';

Step 2: Declare context types object
static contextTypes = {
	router: PropTypes.object
}

The above code searches all parent components and finds the router object.

Step 3:

Call this.context.router.push('/')

onDeleteClick(){
	this.props.deletePost(this.props.params.id)
		.then(() => {
			this.context.router.push('/');
		});
}

Route Params

Add a dynamic route like so:

<Route path="posts/:id" component={PostsShow} />

In the above example, "id" is available to the component as this.props.params.id

Lifecycle Methods

Automatically get called at various stages of component lifecycle.

componentWillMount()

Called exactly one time ---> the first time the component is rendered to the DOM, and not again.

ES6 Syntax Tips

setState() shortcut

this.setState({data: data)} ---> this.setState({ data })

Skip assigning props to consts in component definitions.

Instead of:

const VideoListItem = (props) => {
	const video = props.video;
	return <li>Video</li>
};

Use

const VideoListItem = ({video}) => {
	return <li>Video</li>
};

With multiple props...

const VideoListItem = ({video, onVideoSelect}) => {
 ...
}
Template Strings (string interpolation)
const url = `https://www.youtube.com/embed/${vdeoId}`;
Destructuring

Instead of:

const lon = cityData.city.coord.lon;
const lat = cityData.city.coord.lat;

Use: const {lon, lat} = cityData.city.coord

To use destructuring, property names must be identical (e.g. lon ->, lat->lat)

Spread Operator (...)

Use ... shorthand:

return [...state, action.payload ]

Equivalent to

return state.concat([action.payload]);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment