Closed QuincyLarson closed 7 years ago
The pre-requisite for this section would be React ( #2 ) and Redux ( #3 ).
Here's a rough outline prepared by @elibei
stateless
components
statefull
components
this
props
down
onChange()
and onSubmit()
to GroceryForm
fromGroceryList
props
with React.PropTypes
actions
addGrocery
actionGroceryReducer
store
store
store
with default (use ES6 default parameters)stateful
components to Redux
react-redux
mapStateToProps
mapDispatchToProps
bindActionCreators
CHECK_GROCERY_ITEM
actionREMOVE_GROCERY_ITEM
actionGroceryForm
ComponentChallenge Description
In this section, we begin with some simple component to submit a list of names.
To keep it simple, let's assume our component would have an
input
field (for text input of name of a grocery item) and a submitbutton
.
Create a grocery form component as described above. Add a
h1
as well, that says"Add Grocery Item"
before theinput
andbutton
componentNote
Don't forget to wrap React component inside top-level
<div>
and</div>
.
Challenge Seed
import React, { Component } from 'react';
export default class GroceryForm extends Component {
constructor(props) {
super(props);
}
render () {
return (
{/* Change code below this line */}
{/* Change code above this line */}
);
}
};
Challenge Tests
// TODO : Use Enzyme
Challenge Solution
import React, { Component } from 'react';
export default class GroceryForm extends Component {
constructor(props) {
super(props);
}
render () {
return (
{/* Change code below this line */}
<div>
<h1>Add Grocery Item</h1>
<input type="text" name="item" />
<input type="submit" value="Submit" />
</div>
{/* Change code above this line */}
);
}
};
GroceryList
ComponentChallenge Description
We have created a form-like component where you can submit the name of a grocery item
And now, we would create the component which renders the list of grocery items submitted via the form.
Create a grocery list component. For now, use
<li>
nested inside<ul>
components, and render three list items -Apple
,Pear
,Cheese
. Hardcode these values for now, we will make them dynamic later.Also add a title using
<h1>
to display the text"Grocery List"
in it.
Don't forget to wrap component elements within a top-level
<div>
and</div>
.
Challenge Seed
import React, { Component } from 'react';
export default class GroceryList extends Component {
constructor(props) {
super(props);
}
render () {
return (
{/* Change code below this line */}
{/* Change code above this line */}
);
};
Challenge Tests
// TODO
Challenge Solution
import React, { Component } from 'react';
export default class GroceryList extends Component {
constructor(props) {
super(props);
}
render () {
return (
{/* Change code below this line */}
<div>
<h1>Grocery List</h1>
<ul>
<li>Apple</li>
<li>Pear</li>
<li>Cheese</li>
</ul>
</div>
{/* Change code above this line */}
);
}
};
GroceryPage
ComponentChallenge Description
We need to create a component that renders both the
GroceryList
andGroceryForm
components.Ideally, a user would want to submit a grocery item by typing in the input field of
GroceryForm
, and as they hit thesubmit
button, the item's name should appear in theGroceryList
component.
Use
import
statements to import bothGroceryList
andGroceryForm
items.Render the
GroceryForm
first, followed by theGroceryList
.
Don't forget to wrap component elements within a top-level
<div>
and</div>
.
Challenge Seed
import React, { Component } from 'react';
// change code below this line
// change code above this line
export default class GroceryPage extends React.Component {
render() {
return (
{/* Change code below this line */}
{/* Change code above this line */}
)
}
}
Challenge Tests
// TODO
Challenge Solution
import React, { Component } from 'react';
// change code below this line
import GroceryForm from './GroceryForm';
import GroceryList from './GroceryList';
// change code above this line
export default class GroceryPage extends Component {
constructor(props) {
super(props);
}
render () {
return (
{/* Change code below this line */}
<div>
<div>
<GroceryForm />
<GroceryList />
</div>
</div>
{/* Change code above this line */}
);
}
};
GroceryPage
ComponentChallenge Description
In this exercise, we would
render
withreact-dom
; the componentGroceryPage
that we had created earlier.
Use
import
statements to importGroceryPage
component.Render the
GroceryPage
within the HTML element with ID attributeapp
.
Use
document.getElementById()
to get the HTML element with IDapp
.
Challenge Seed
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
// change code below
// change code above
Challenge Tests
// TODO : Use Enzyme
Challenge Solution
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
// change code below
import GroceryPage from './components/grocery/GroceryPage.js';
render (
<GroceryPage />,
document.getElementById('app')
);
// change code above
@alayek wow - you are off to a great start with these challenges. Great job making these project-oriented, so they involve gradually layering on functionality. I can't wait to work through these and learn Redux myself! :)
Mostly @elibei has done the heavy-lifting. I am merely reviewing the challenges and adding them in order.
onChange()
and onSubmit()
functionChallenge Description
We have created a list that renders thre items. It's time we add some cool stuff to it, such as, let the user update it.
We would have to create an
onChange()
function, to handle change in text within the input box. We would also have to create anonSubmit()
function; to handle the submit button click within theGroceryForm
component.Because React promotes the idea of hierarchical UIs, these functions should be defined at the topmost level, within
GroceryPage
component. They would be passed down via props to individual components
Create the
onSubmit()
andonChange()
functions inside theGroceryPage
component.
Add these after
constructor()
function, but before therender()
function. Notice how the state is defined withinconstructor()
.
Challenge Seed
import React, { Component } from 'react';
import GroceryForm from './GroceryForm';
import GroceryList from './GroceryList';
export default class GroceryPage extends Component {
constructor(){
super(); // don't remove this line
this.state = {
// notice how the state is structured
// the input field mimics the value in the input box as user types
// the list is an array of all grocery items
input: {value: ''},
list: []
};
}
onChange(event){
// add code below this line
/*
* You can extract the value from event object.
* Use `this.setState()` to set the value of input within state
*/
// add code above this line
}
onSubmit(){
// add code below this line
/*
* Don't forget to set text in the input to ''
* Don't use push to update the list in state
* Use concat, else you would run into race conditions
*/
// add code above this line
}
render() {
return (
<div>
<GroceryForm />
<GroceryList />
</div>
);
}
}
Challenge Tests
// TODO
Challenge Solution
import React, { Component } from 'react';
import GroceryForm from './GroceryForm';
import GroceryList from './GroceryList';
export default class GroceryPage extends Component {
constructor(){
super();
this.state = {
input: {value: ''},
list: []
};
}
onChange(event){
const input = this.state.input;
input.value = event.target.value;
this.setState({input:input});
}
onSubmit(){
const list = this.state.list;
const input = this.state.input;
const newList = list.concat([input.trim()])
input.value = '';
this.setState({list:list,input:input});
}
render() {
return (
<div>
<GroceryForm />
<GroceryList />
</div>
);
}
}
this
Challenge Description
Earlier, we have created event listener callback such as
onSubmit()
oronChange()
.Since these event handlers are passed down to components via
props
, the value of context orthis
would be altered.We need to use
bind()
to bind the context once and for all before passing these functions down the component hierarchy.There are lot of ways to do this; but for now, we shall be using
bind()
inside the constructor of the React component.To bind a context to a function, we use the syntax
this.myFunc = this.myFunc.bind(this);
Bind the value of
this
toonSubmit()
andonChange()
functions inside theGroceryPage
component.
You could have also made a call to
bind()
when passing it down via props, inside therender()
function. But we would be re-binding over and over again every time the component would have rendered by React.
Challenge Seed
import React, { Component } from 'react';
import GroceryForm from './GroceryForm';
import GroceryList from './GroceryList';
export default class GroceryPage extends Component {
constructor(){
super();
this.state = {
input: {value: ''},
list: []
};
/* Add code below this line */
// bind the context to onChange
// bind the context to onSubmit
/* Add code above this line */
}
onChange(event){
const input = this.state.input;
input.value = event.target.value;
this.setState({input:input});
}
onSubmit(){
const list = this.state.list;
const input = this.state.input;
const newList = list.concat([input.trim()])
input.value = '';
this.setState({list:list,input:input});
}
render() {
return (
<div>
<GroceryForm />
<GroceryList />
</div>
);
}
}
Challenge Tests
// TODO
Challenge Solution
import React, { Component } from 'react';
import GroceryForm from './GroceryForm';
import GroceryList from './GroceryList';
export default class GroceryPage extends Component {
constructor(){
super();
this.state = {
input: {value: ''},
list: []
};
/* Add code below this line */
// bind the context to onChange
this.onChange = this.onChange.bind(this);
// bind the context to onSubmit
this.onSubmit = this.onSubmit.bind(this);
/* Add code above this line */
}
onChange(event){
const input = this.state.input;
input.value = event.target.value;
this.setState({input:input});
}
onSubmit(){
const list = this.state.list;
const input = this.state.input;
const newList = list.concat([input.trim()])
input.value = '';
this.setState({list:list,input:input});
}
render() {
return (
<div>
<GroceryForm />
<GroceryList />
</div>
);
}
}
Challenge Description
Now that we have taken care of the context by setting
this
properly; we can go ahead and use them in the child components.Just like values, we can pass down functions via props. This is expected, because in JavaScript, functions are also
first-class
objects.However, each function is a JS expression, so needs to be wrapped around with
{}
.This is what passing down an event handler woulds look like
<ChildComponent propName={this.eventHandler} />
Inside the component declaration of
ChildComponent
, we can access this functioneventHandler
asthis.props.propName
.
Use the
onChange()
function inside theGroceryForm
render()
function properly, so that it handles change in the input text.
Notice how it was passed down from
GroceryPage
component. Also remember that<input>
component accepts an event listener foronChange
event. Similarly, submit buttons accepts an event listener foronSubmit()
event.
Challenge Seed
import React, { Component } from 'react';
import GroceryForm from './GroceryForm';
import GroceryList from './GroceryList';
export default class GroceryPage extends Component {
constructor(){
super();
this.state = {
input: {value: ''},
list: []
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChange(event){
const input = this.state.input;
input.value = event.target.value;
this.setState({input:input});
}
onSubmit(){
const list = this.state.list;
const input = this.state.input;
const newList = list.concat([input.trim()])
input.value = '';
this.setState({list:list,input:input});
}
render() {
return (
<div>
<GroceryForm value={this.state.input.value} valueChange={this.onChange} formSubmit={this.onSubmit}/>
<GroceryList />
</div>
);
}
}
import React, { Component } from 'react';
export default class GroceryForm extends Component {
constructor(props) {
super();
}
render() {
return (
<div>
<h1>Add Grocery Item</h1>
{/* Change code below */}
<input type="text" name="item" value={value}/>
<input type="submit" value="Submit" />
{/* Change code above */}
</div>
)
}
}
Challenge Tests
// TODO
Challenge Solution
import React, { Component } from 'react';
import GroceryForm from './GroceryForm';
import GroceryList from './GroceryList';
export default class GroceryPage extends Component {
constructor(){
super();
this.state = {
input: {value: ''},
list: []
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChange(event){
const input = this.state.input;
input.value = event.target.value;
this.setState({input:input});
}
onSubmit(){
const list = this.state.list;
const input = this.state.input;
const newList = list.concat([input.trim()])
input.value = '';
this.setState({list:list,input:input});
}
render() {
return (
<div>
<GroceryForm />
<GroceryList />
</div>
);
}
}
import React, { Component } from 'react';
export default class GroceryForm extends Component {
constructor(props) {
super();
}
render() {
return (
<div>
<h1>Add Grocery Item</h1>
{/* Change code below */}
<input type="text" name="item" value={value} onClick={this.props.valueChange}/>
<input type="submit" value="Submit" onSubmit={this.props.formSubmit} />
{/* Change code above */}
</div>
)
}
}
GroceryList
Challenge Description
Next, we need to pass the list of items to display to our
GroceryList
component.One thing to keep in mind, is that React expects that every element of an array, should have a unique
key
property; if you want to render them.You can use the
map()
function ofArray.prototype
object to iterate over an array and create a list of renderable<li>
s.
Render the list passed down via props from
GroceryPage
component, using<li>
.Note
Notice how it was passed down from
GroceryPage
component. Also remember that you would have to usekey
prop withmap()
to render the array elements in a list-view.
Challenge Seed
import React, { Component } from 'react';
import GroceryForm from './GroceryForm';
import GroceryList from './GroceryList';
export default class GroceryPage extends Component {
constructor(){
super();
this.state = {
input: {value: ''},
list: []
};
this.onChange = this.onChange.bind(this);
this.onSubmit = this.onSubmit.bind(this);
}
onChange(event){
const input = this.state.input;
input.value = event.target.value;
this.setState({input:input});
}
onSubmit(){
const list = this.state.list;
const input = this.state.input;
const newList = list.concat([input.trim()])
input.value = '';
this.setState({list:list,input:input});
}
render() {
return (
<div>
<GroceryForm value={this.state.input.value} valueChange={this.onChange} formSubmit={this.onSubmit}/>
<GroceryList list={this.state.list} />
</div>
);
}
}
import React, { Component } from 'react';
export default class GroceryList extends Component {
constructor(props) {
super();
}
render() {
return (
<div>
<h1>Grocery List</h1>
<ul>
{/* Add code below */}
{/* Add code above */}
</ul>
</div>
)
}
}
Challenge Tests
// TODO
Challenge Solution
import React, { Component } from 'react';
import GroceryForm from './GroceryForm';
import GroceryList from './GroceryList';
export default class GroceryPage extends Component {
constructor(){
super();
this.state = {
input: {value: ''},
list: []
};
// bind the context to onChange
this.onChange = this.onChange.bind(this);
// bind the context to onSubmit
this.onSubmit = this.onSubmit.bind(this);
}
onChange(event){
const input = this.state.input;
input.value = event.target.value;
this.setState({input:input});
}
onSubmit(){
const list = this.state.list;
const input = this.state.input;
const newList = list.concat([input.trim()])
input.value = '';
this.setState({list:list,input:input});
}
render() {
return (
<div>
<GroceryForm value={this.state.input.value} valueChange={this.onChange} formSubmit={this.onSubmit}/>
<GroceryList list={this.state.list} />
</div>
);
}
}
import React, { Component } from 'react';
export default class GroceryList extends Component {
constructor(props) {
super();
}
render() {
return (
<div>
<h1>Grocery List</h1>
<ul>
{/* Change code below */}
{list.map((item,index) =>
<li key={index} >{item}</li>
)}
{/* Change code above */}
</ul>
</div>
)
}
}
@alayek this is also off to a great start! Keep it up. Let me know if I can do anything to help. I can help you find someone else who's good with React if you want 😄
@QuincyLarson that would be great! I really could use some help with this.
I was thinking first few problems can be simply moved to React. Because they use only React concepts, and nothing about a state containers. But we have the challenges ready (thanks to @elibei ), and I will submit the rest of them here soon.
@alayek sure - we can look into redistributing the challenges once we get them done. The main thing is we need to get this Enzyme library working ASAP so we can create tests properly.
Does this still need some help?
It'd be my first open source adventure, but I've got a good handle on react-redux and could make the challenges.
@mmhansen, yeah defiantly! Let any @FreeCodeCamp/issue-moderators know if you need help 😃
Allright. I'll just try to follow alayek's example.
It looks like you guys use enzyme for testing, right?
As your app grows it's helpful to make sure that it is behaving how it is supposed to.
To make sure that everything works right, React includes a type validator.
This means that when you expect a certain prop to get passed down to your component, you will be able to check if it what you expect it to be. When the validation passes, everything will be fine. If it does not pass, your app will give you an error.
The basic Prop Types are: bool, func, array, number, object, string, and symbol. If you know that the app will break if it doesn't have a certain prop passed to it, then it is good to add the 'isRequired' selector to your propTypes object.
Because we passed down our valueChange and formSubmit functions to our GroceryForm component we know what to expect. This is the perfect place to use Prop Validation.
Add Prop Validation for the props passed to the GroceryForm component.
An example:
MyComponent.propTypes = { myArrayProp: React.PropTypes.array myFunctionProp: React.PropTypes.func.isRequired }
This should be placed after the component, but before the export!
Note! The object name is propTypes, but the property is PropTypes
import React, { Component } from 'react';
class GroceryForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input type="text" name="item" value={value} onClick={this.props.valueChange}/>
<input type="submit" value="Submit" onSubmit={this.props.formSubmit} />
</div>
)
}
}
{/* Change code below */}
{/* Change code above */}
export default GroceryForm;
import React, { Component } from 'react';
class GroceryForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input type="text" name="item" value={value} onClick={this.props.valueChange}/>
<input type="submit" value="Submit" onSubmit={this.props.formSubmit} />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
export default GroceryForm;
Is the quality okay? @atjonathan @QuincyLarson @Em-Ant @HKuz or whoever else is looking at these things. If it is all-right, I'll go ahead with making some more.
Looks good to me :smiley: Don't wrap the comments in an object though and change "Note well!" to "Note:" :wink:
Actions are uniform objects. They require only one key, which is named 'type'
For example:{ type: ADD_TODO }
The purpose of an action is to create a uniform way of telling the reducers how to change your application state.
Make an action for handling the onSubmit event of your Grocery Form Component.
Or in plain terms, return an object with a type FORM_SUBMIT, and has a payload key with the form state as the value.
Sometimes you will want to have more than only a type key. Then your object could look something like this:
{ type: ACTION_NAME, payload: string }
function formSubmit (formState) {
{/* Change code below */}
{/* Change code above */}
}
function formSubmit (formState) {
return {
type: FORM_SUBMIT,
payload: formState
}
}
I'm changing the action challenges a little bit.
This challenge is very similar to the last one, but we will now let you make the whole function. Functions that return actions are called 'action creators'
Make an action creator with name 'valueChange' that takes an argument 'groceryItem' that returns an action with a type and payload following the previous challenge's convention.
The convention for actions is to have a type and payload key. the type value should be in all caps and separate words by an underscore such as 'FREE_CODE_CAMP_ROCKS'
{/* Change code below */}
{/* Change code above */}
function valueChange (groceryItem) {
return {
type: VALUE_CHANGE,
payload: groceryItem
}
}
You may have noticed that the type properties were not strings, but variables. This is because we declare our types as string constants above our actions creators, or if there is enough of them, we move it to a designated constant file. In short, keeping action types as constants will help catch typos more quickly, as well being able to keep track of what actions were removed or added in commits.
Another important thing about action creators is that we want to use them throughout our application. because this file with actions will not just contain one action, we are not able to use the export default syntax. Use export in front of each function instead.
Declare string constants for your two action types. Export your functions.
A string constant is declared like: const MY_ACTION = 'MY_ACTION'
{/* Change code below */}
function valueChange (groceryItem) {
return {
type: VALUE_CHANGE,
payload: groceryItem
}
}
function formSubmit (formState) {
return {
type: FORM_SUBMIT,
payload: formState
}
}
{/* Change code above */}
const VALUE_CHANGE = 'VALUE_CHANGE'
const FORM_SUBMIT = 'FORM_SUBMIT'
export function valueChange (groceryItem) {
return {
type: VALUE_CHANGE,
payload: groceryItem
}
}
export function formSubmit (formState) {
return {
type: FORM_SUBMIT,
payload: formState
}
}
@atjonathan I changed it to 'note'. I was already resisting the best I could to not say 'Nota Bene!'
@mmhansen 😂 👍
Now that we have made action creators to send our actions, we need them to go to the reducers. The reducer is how we change our application state. Let's take a look at an example reducer
function groceries (state, action) { }
Reducers receive state and an action as the two arguments. State is your current application state, and the action is defines how you want to change it. Each reducer will handle many action.types so we will use a switch statement to determine what we want to do.
Write a switch statement inside the reducer whose default case returns state.
The switch statement is going to be handling the action.type that we made in our action creators, so the switch should look like:
switch (action.type) { }
function groceries (state, action) {
// Change code below
// Change code above
}
function groceries (state, action) {
switch (action.type) {
default:
return state;
}
}
Let's first handle our VALUE_CHANGE action. Remember that this action controls the input for a new grocery item.
The reducer will do exactly the same, but instead of using a function on the component, it will make its change through a reducer!To have our action creator change the state, it's going to need a part to change, so let's assume we will make a part of state called 'input' that will hold whatever is being typed into our input element.
Make a case named 'VALUE_CHANGE' that will return the state with the new input value
This will be a little tricky to see at first, but you must use Object.assign. For example
Object.assign( {}, state, { pieceOfState: action.payload } )
function groceries (state, action) {
switch (action.type) {
// Change code below
// Change code above
default:
return state;
}
}
function groceries (state, action) {
switch (action.type) {
case 'VALUE_CHANGE':
return Object.assign( {}, state, { input: action.payload } )
default:
return state;
}
}
There is one more action to handle, the FORM_SUBMIT. Remember the purpose of this action is to append a string to an array. Assume the portion of our state that will contain the array will be called 'list'
Make a case in the reducer to handle the FORM_SUBMIT action.
Note! Object assign is essential.
function groceries (state, action) {
switch (action.type) {
case 'VALUE_CHANGE':
return Object.assign( {}, state, { input: action.payload } );
// Change code below
// Change code above
default:
return state;
}
}
function groceries (state, action) {
switch (action.type) {
case 'VALUE_CHANGE':
return Object.assign( {}, state, { input: action.payload } );
case 'FORM_SUBIMT':
return Object.assign( {}, state, { list: [ ...state.list, action.payload] }
default:
return state;
}
}
As your app grows more complex, you'll want to split your reducing function into separate functions, each managing independent parts of the state. The combineReducers helper function turns an object whose values are different reducing functions into a single reducing function Combine reducers takes one argument that is an object.
Give the combine reducers function an key:value pair, that will handle a part of state with the same name as your reducer.
If your reducer was myTodoReducer and you wanted it to be able to access the Todo part of state, you would pass
Todo: myTodoReducer
import { combineReducers } from 'redux'
import groceries from './actions/groceries'
export default combineReducers({
// Change code below
// Change code above
})
import { combineReducers } from 'redux'
import groceries from './actions/groceries'
export default combineReducers({
groceries: groceries
})
@mmhansen awesome work. These look great so far!
Just curious - why is the {/* Change code below */} wrapped in curly braces?
@QuincyLarson oops. I just had that copied from the jsx sections, but we don't need it anymore in the redux parts. Thanks for the good eye. 👍
A store holds the whole state tree of your application. The only way to change the state inside it is to dispatch an action on it. A store is not a class. It's just an object with a few methods on it. To create it, pass your root reducing function as an argument to createStore.
Use the createStore function with your rootReducer to make the application state.
createStore can also be passed an initial state as a second optional argument
import rootReducer from '../reducers/index'
import { createStore } from 'redux'
// Your code below
import rootReducer from '../reducers/index'
import { createStore } from 'redux'
const store = createStore(rootReducer)
Middlewares are functions that are part of the redux system. When you send an action to the reducers, it goes through the middleware. Middleware can be extremely helpful, we're using thunk as an example which allows you to resolve promises (like ajax calls) inside of redux. This allows other actions to go through while you are waiting for the API response. Another very popular redux middleware is logger, which will console.log all of the actions. This shows how your state changes very simply.
Pass the applyMiddleware function with thunk as an argument as the second argument of the createStore function.
Remember that applyMiddleware is a function that takes a middleware as its only argument.
import rootReducer from '../reducers/index'
import { createStore, applyMiddleware } from 'redux'
import { thunk } from 'thunk'
const store = createStore(rootReducer)
import rootReducer from '../reducers/index'
import { createStore, applyMiddleware } from 'redux'
import { thunk } from 'thunk'
const store = createStore(rootReducer, applyMiddleware(thunk))
Let's start to bring this together with our grocery list app. We need a way for the state that we have in redux to get to the application. Redux provides a component called Provider that will do just that!
Make the Grocery page component be wrapped by two
elements. Pass the opening a prop with name store and give that prop a value of the store you just made.
Remember your closing tag: </ Provider> Note: The comma that separates the component from the document.getElementById has been moved to the next line not removed. You must not forget it in your own program.
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import rootReducer from '../reducers/index'
import { createStore, applyMiddleware } from 'redux'
import { thunk } from 'thunk'
import { Provider } from 'react-redux'
import GroceryPage from './components/grocery/GroceryPage.js';
const store = createStore(rootReducer, applyMiddleware(thunk))
render (
// change code below
// change code above
<GroceryPage />
// change code below
// change code above
, document.getElementById('app')
);
import 'babel-polyfill';
import React from 'react';
import { render } from 'react-dom';
import rootReducer from '../reducers/index'
import { createStore, applyMiddleware } from 'redux'
import { thunk } from 'thunk'
import { Provider } from 'react-redux'
import GroceryPage from './components/grocery/GroceryPage.js';
const store = createStore(rootReducer, applyMiddleware(thunk))
render (
<Provider store={store}
<GroceryPage />
</Provider>
, document.getElementById('app')
);
The provider lets us pass down props from the top level component. In larger applications, this isnt effective, because we might have lots of components and we don't want to spend all that time passing the props down in every single one. In comes connect to save the day! Connect from react-redux lets us get the props wherever we are in the application, simply by passing our component into it. Connect is a curried function. This simply means that instead of taking lots of arguments in one function, it returns a function that takes an argument after you put the first argument in.
For connect, the first function takes two arguments that we will look at next challenge, for now just pass it null, null. The second is the component.
Use the connect function to connect your GroceryForm component to the redux state.
A curried function is passed arguments like this: connect( arg1, arg2 )( myComponent )
import React, { Component } from 'react';
import { connect } from react-redux;
class GroceryForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input type="text" name="item" value={value} onClick={this.props.valueChange}/>
<input type="submit" value="Submit" onSubmit={this.props.formSubmit} />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
// change code below
export default connect()();
import React, { Component } from 'react';
import { connect } from react-redux;
class GroceryForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input type="text" name="item" value={value} onClick={this.props.valueChange}/>
<input type="submit" value="Submit" onSubmit={this.props.formSubmit} />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
export default connect( null, null )( GroceryForm );
We've got our Component using connect, but we need to tell connect to give it a piece of the state. State is the first argument to the connect function. We don't want one big thing named state in every one of our connected components, we want something more natural, like pieces of state as props. So we need to make a function that takes state as an argument and returns an object with keys as the prop names and values as prop values. This is what we do with mapStateToProps.
Make mapStateToProps return an object with key input and with the state.groceries.input portion of state
This function takes the whole state as an argument. Remember that we defined the portion of state with our input and list in it as groceries. So we access input by going through state.grocieries
import React, { Component } from 'react';
import { connect } from react-redux;
class GroceryForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input type="text" name="item" value={value} onClick={this.props.valueChange}/>
<input type="submit" value="Submit" onSubmit={this.props.formSubmit} />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
const mapStateToProps = (state) => {
return {
// change code below
// change code above
}
}
export default connect( mapStateToProps, null )( GroceryForm );
import React, { Component } from 'react';
import { connect } from react-redux;
class GroceryForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input type="text" name="item" value={value} onClick={this.props.valueChange}/>
<input type="submit" value="Submit" onSubmit={this.props.formSubmit} />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
const mapStateToProps = (state) => {
return {
input: state.groceries.input
}
}
export default connect( mapStateToProps, null )( GroceryForm );
The second argument that connect takes is the actions that we will dispatch to redux state. In this small application, it is useful to import all of the actions because they come from one file. We can use the * es2015 syntax to import all actions. For example: import * as lemons from '../my/lemon/cache' This would make all of the functions accessible as lemons.myFunction
Import all as actions from the index file in '../actions/'
Look at the bottom to see that we've added actions into connect for you.
import React, { Component } from 'react';
import { connect } from react-redux;
// change code below
// change code above
class GroceryForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input type="text" name="item" value={value} onClick={this.props.valueChange}/>
<input type="submit" value="Submit" onSubmit={this.props.formSubmit} />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
const mapStateToProps = (state) => {
return {
input: state.groceries.input
}
}
export default connect( mapStateToProps, actions )( GroceryForm );
import React, { Component } from 'react';
import { connect } from react-redux;
import * as actions from '../actions/index.js'
class GroceryForm extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input type="text" name="item" value={value} onClick={this.props.valueChange}/>
<input type="submit" value="Submit" onSubmit={this.props.formSubmit} />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
const mapStateToProps = (state) => {
return {
input: state.groceries.input
}
}
export default connect( mapStateToProps, actions )( GroceryForm );
We are using redux universal application state now, so lets get rid of our component state.
Remove the constructor.
Notice how we've replaced the onClick handler with our new prop action from redux connect!
import React, { Component } from 'react';
import { connect } from react-redux;
import * as actions from '../actions/index.js'
class GroceryForm extends Component {
// change code below
constructor(props) {
super(props);
}
// change code above
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input
type="text"
name="item"
value={value}
onClick={this.props.actions.valueChange.bind(this}/>
<input
type="submit"
value="Submit"
onSubmit={ } />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
const mapStateToProps = (state) => {
return {
input: state.groceries.input
}
}
export default connect( mapStateToProps, actions )( GroceryForm );
import React, { Component } from 'react';
import { connect } from react-redux;
import * as actions from '../actions/index.js'
class GroceryForm extends Component {
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input
type="text"
name="item"
value={value}
onClick={this.props.actions.valueChange.bind(this}/>
<input
type="submit"
value="Submit"
onSubmit={ } />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
const mapStateToProps = (state) => {
return {
input: state.groceries.input
}
}
export default connect( mapStateToProps, actions )( GroceryForm );
The action creators will be making actions all the way over in our redux state, so make sure to bind(this) so it stays inside the component we want.
Add the handleSubmit action to our input field
Look at the valueChange function, yours will be very similar
import React, { Component } from 'react';
import { connect } from react-redux;
import * as actions from '../actions/index.js'
class GroceryForm extends Component {
// change code below
constructor(props) {
super(props);
}
// change code above
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input
type="text"
name="item"
value={value}
onClick={this.props.actions.valueChange.bind(this}/>
<input
type="submit"
value="Submit"
onSubmit={
// change code below
// change code above
} />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
const mapStateToProps = (state) => {
return {
input: state.groceries.input
}
}
export default connect( mapStateToProps, actions )( GroceryForm );
import React, { Component } from 'react';
import { connect } from react-redux;
import * as actions from '../actions/index.js'
class GroceryForm extends Component {
render() {
return (
<div>
<h1>Add Grocery Item</h1>
<input
type="text"
name="item"
value={value}
onClick={this.props.actions.valueChange.bind(this}/>
<input
type="submit"
value="Submit"
onSubmit={this.props.actions.formSubmit.bind(this} />
</div>
)
}
}
GroceryForm.propTypes = {
valueChange: React.PropTypes.func.isRequired,
formSubmit: React.PropTypes.func.isRequired
}
const mapStateToProps = (state) => {
return {
input: state.groceries.input
}
}
export default connect( mapStateToProps, actions )( GroceryForm );
Let's get a little more practice making actions and reducers. We want to be able to click on a grocery item and remove it.
Return an action that has a type 'REMOVE_GROCERY' and with a payload of the grocery you want to remove
Remember that actions are just a name for a uniform object that redux likes
export function removeGrocery ( grocery ) {
return
// your code below
// your code above
}
export function removeGrocery ( grocery ) {
return {
type: 'REMOVE_GROCERY',
payload: grocery
}
}
We've written this up mostly for you because it is a little tough to grasp at first. Notice that all reducers must be pure functions!
Write a return statement in the filter function that will return all of the array except the one we passed in the action.
Remember that Array.filter returns the item when it gets a return value as true We have defined the initial state as {} so that when the application is run, it will have a place to put any changes you make.
export default function ( state = {}, action ) {
switch ( action.type ) {
case 'REMOVE_GROCERY':
return Object.assign( {}, state, items: [
...state.items.filter( item ) {
// your code below
// your code above
}
] )
default:
return state;
}
}
export default function ( state, action ) {
switch ( action.type ) {
case 'REMOVE_GROCERY':
return Object.assign( {}, state, items: [
...state.items.filter( item ) {
return ( item !== action.payload )
}
])
default:
return state;
}
}
Let's revisit the rootReducer. When your application gets a bigger, you will want to pass little pieces of the state to each of your reducers. This makes the functions much easier to write!
We've imported the new reducer, chores, for you. Add it to the combine reducers function.
import { combineReducers } from 'redux'
import groceries from './reducers/groceries'
import chores from './reducers/chores'
export default combineReducers({
groceries: groceries,
// change code below
// change code above
})
import { combineReducers } from 'redux'
import groceries from './reducers/groceries'
import chores from './reducers/chores'
export default combineReducers({
groceries: groceries,
chores: chores
})
I'm not 100% clear on what some of the challenges mean simply by their name in the earlier post that lists them.
If you think an addition is necessary, please let me know and I will do my best to accommodate it.
I realized it is pretty difficult to write these challenges and make sure that everything lines up, in part because react-redux involves a lot of working parts, and also because I picked up halfway through where someone left off. I would really appreciate a critical look at the challenges.
@QuincyLarson
@mmhansen wow - you work quickly!
@alayek what are your thoughts on this? Do you think he correctly interpreted the goals of each of the challenge names? Could you take a look at his challenges?
@all Please see the React Issues thread about an update we've made to the development of these React/Redux challenges.
Thanks everyone! I'm closing this thread because our alpha React + Redux challenges are live. Read about them here: https://forum.freecodecamp.com/t/alpha-of-free-code-camps-react-redux-challenges-is-now-live/64492
@alayek is in charge of coordinating the creation of these challenges.
Here are the challenges we have currently planned:
For each challenge, please reply to this GitHub issue with: