- 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
- Passed into a component on creation
- Functional Component: props object is an argument
- Class-based Component: props available anywhere as this.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).
- 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)
- All classes have a constructor
- Only function called automatically when a new instance of a class is created
super()
calls constructor of extended classsuper(props)
Form element where value of input is set by state of component, not the other way around.
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.
"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
- 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'}
}
In reducers/reducer_books.js
export default function(){
return [
{title: 'Javascript: The Good Parts' },
{title: 'Harry Potter' },
{title: 'The Dark Tower' },
{title: 'Eloquent Ruby' }
]
}
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)
- Connects react components and application state in redux
- "The Bridge"
- "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
- import connect from react-redux
- define class component
- define mapStateToProps() (if necessary -- if don't need application state can skip this and pass null to connect())
- define mapDispatchToProps() -- if don't need actions can leave this out
- call connect with mapStateToProps, mapDispatchToProps and component
Anytime the application state changes:
- mapStateToProps will pass new state to props on the component
- mapDispatchToProps will make sure action gets dispatched to reducers
- 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);
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 intercepts actions before they get to reducers.
Middleware can:
- Let actions pass
- Manipulate actions
- Log actions
- Block actions
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);
When user interacts with a form, reduxForm will record everything in application state.
Adds helper methods on this.props
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
});
Add the action creator in actions/index.js
Import it into the 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
Just a regular old html form
Redux-form comes with a bunch of helpers like handleSubmit
, and fields:{}
const { fields: {title,categories,content} handleSubmit } = this.props;
<form onSubmit={handleSubmit}>
Repeat for every field:
<input type="text" className="form-control" {...title}/>
<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.
- Define a validation function that returns an errors object
- Add the validation function to to reduxForm call config argument
- 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
- 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);
- 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>
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>
)
}
Presents the correct react components when the URL changes.
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'));
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} />
);
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>
);
}
}
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>
Component provided by react-router. Lets you easily navigate your routes.
To set up a Link Component:
import { Link } from 'react-router';
Can apply whatever styling you want :).
<Link to="/posts/new" className="btn btn-primary">
Add a Post
</Link>
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.
import React, { Component, PropTypes } from 'react';
static contextTypes = {
router: PropTypes.object
}
The above code searches all parent components and finds the router object.
Call this.context.router.push('/')
onDeleteClick(){
this.props.deletePost(this.props.params.id)
.then(() => {
this.context.router.push('/');
});
}
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
Automatically get called at various stages of component lifecycle.
Called exactly one time ---> the first time the component is rendered to the DOM, and not again.
this.setState({data: data)}
---> this.setState({ data })
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}) => {
...
}
const url = `https://www.youtube.com/embed/${vdeoId}`;
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)
Use ...
shorthand:
return [...state, action.payload ]
Equivalent to
return state.concat([action.payload]);