Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React with Redux Challenges #4

Closed
QuincyLarson opened this issue Jul 1, 2016 · 45 comments
Closed

React with Redux Challenges #4

QuincyLarson opened this issue Jul 1, 2016 · 45 comments

Comments

@QuincyLarson
Copy link
Contributor

QuincyLarson commented Jul 1, 2016

@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:

  1. Challenge description text
  2. Test suite (using the assert method)
  3. The seed code, which is prepopulated in the editor at the beginning of the challenge
  4. A working solution that makes all tests pass
@alayek
Copy link
Member

alayek commented Aug 17, 2016

The pre-requisite for this section would be React ( #2 ) and Redux ( #3 ).

Here's a rough outline prepared by @EliBei

@alayek
Copy link
Member

alayek commented Aug 17, 2016

Create a GroceryForm Component

Challenge 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 submit button.

Instructions

Create a grocery form component as described above. Add a h1 as well, that says "Add Grocery Item" before the input and button component

Note

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 */}
        );
     }
};

@alayek
Copy link
Member

alayek commented Aug 17, 2016

Create a GroceryList Component

Challenge 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.

Instructions

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.

Note

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 */}
      );
   }
};

@alayek
Copy link
Member

alayek commented Aug 17, 2016

Create a GroceryPage Component

Challenge Description

We need to create a component that renders both the GroceryList and GroceryForm components.

Ideally, a user would want to submit a grocery item by typing in the input field of GroceryForm, and as they hit the submit button, the item's name should appear in the GroceryList component.

Instructions

Use import statements to import both GroceryList and GroceryForm items.

Render the GroceryForm first, followed by the GroceryList.

Note

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 */}
      );
   }
};

@alayek
Copy link
Member

alayek commented Aug 17, 2016

Rendering GroceryPage Component

Challenge Description

In this exercise, we would render with react-dom; the component GroceryPage that we had created earlier.

Instructions

Use import statements to import GroceryPage component.

Render the GroceryPage within the HTML element with ID attribute app.

Note

Use document.getElementById() to get the HTML element with ID app.

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

@QuincyLarson
Copy link
Contributor Author

@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! :)

@alayek
Copy link
Member

alayek commented Aug 17, 2016

Mostly @EliBei has done the heavy-lifting. I am merely reviewing the challenges and adding them in order.

@alayek
Copy link
Member

alayek commented Aug 17, 2016

Create onChange() and onSubmit() function

Challenge 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 an onSubmit() function; to handle the submit button click within the GroceryForm 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

Instructions

Create the onSubmit() and onChange() functions inside the GroceryPage component.

Note

Add these after constructor() function, but before the render() function. Notice how the state is defined within constructor().

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>
        );
    }
}

@alayek
Copy link
Member

alayek commented Aug 28, 2016

Bind this

Challenge Description

Earlier, we have created event listener callback such as onSubmit() or onChange().

Since these event handlers are passed down to components via props, the value of context or this 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);

Instructions

Bind the value of this to onSubmit() and onChange() functions inside the GroceryPage component.

Note

You could have also made a call to bind() when passing it down via props, inside the render() 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>
        );
    }
}

@alayek
Copy link
Member

alayek commented Aug 28, 2016

Passing down event handlers to form

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 function eventHandler as this.props.propName.

Instructions

Use the onChange() function inside the GroceryForm render() function properly, so that it handles change in the input text.

Note

Notice how it was passed down from GroceryPage component. Also remember that <input> component accepts an event listener for onChange event. Similarly, submit buttons accepts an event listener for onSubmit() 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>
        )
    }
}

@alayek
Copy link
Member

alayek commented Aug 28, 2016

Passing down List to 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 of Array.prototype object to iterate over an array and create a list of renderable <li>s.

Instructions

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 use key prop with map() 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>
        )
    }
}

@QuincyLarson
Copy link
Contributor Author

@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 😄

@alayek
Copy link
Member

alayek commented Sep 6, 2016

@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.

@QuincyLarson
Copy link
Contributor Author

@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.

@mmhansen
Copy link

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.

@ghost
Copy link

ghost commented Oct 13, 2016

@mmhansen, yeah defiantly!
Let any @FreeCodeCamp/issue-moderators know if you need help 😃

@mmhansen
Copy link

Allright. I'll just try to follow alayek's example.

It looks like you guys use enzyme for testing, right?

@mmhansen
Copy link

mmhansen commented Oct 13, 2016

Validate Props with React.PropTypes

Challenge Description

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.

Instructions

Add Prop Validation for the props passed to the GroceryForm component.

Tips

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

Challenge Seed

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;

Challenge Solution

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.

@ghost
Copy link

ghost commented Oct 14, 2016

Looks good to me 😃
Don't wrap the comments in an object though and change "Note well!" to "Note:" 😉

@mmhansen
Copy link

mmhansen commented Oct 14, 2016

Make your first action

Challenge Description

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.

Instructions

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.

Tips

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
}

Challenge Seed

function formSubmit (formState) {
{/* Change code below */}


{/* Change code above */}
}

Challenge Solution

function formSubmit (formState) {
  return {
    type: FORM_SUBMIT,
    payload: formState
  }
}

I'm changing the action challenges a little bit.

  1. Make your first action
  2. Make your first action creator
  3. Type constants and exporting action creators

@mmhansen
Copy link

mmhansen commented Oct 14, 2016

Make your first action creator

Challenge Description

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'

Instructions

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.

Tips

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'

Challenge Seed

{/* Change code below */}


{/* Change code above */}

Challenge Solution

function valueChange (groceryItem) {
  return {
    type: VALUE_CHANGE,
    payload: groceryItem  
  }
}

@mmhansen
Copy link

mmhansen commented Oct 14, 2016

Type constants and exporting action creators

Challenge Description

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.

Instructions

Declare string constants for your two action types.
Export your functions.

Tips

A string constant is declared like: const MY_ACTION = 'MY_ACTION'

Challenge Seed

{/* Change code below */}

function valueChange (groceryItem) {
  return {
    type: VALUE_CHANGE,
    payload: groceryItem  
  }
}

function formSubmit (formState) {
  return {
    type: FORM_SUBMIT,
    payload: formState
  }
}

{/* Change code above */}

Challenge Solution

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
  }
}


@mmhansen
Copy link

mmhansen commented Oct 14, 2016

@atjonathan I changed it to 'note'.
I was already resisting the best I could to not say 'Nota Bene!'

@ghost
Copy link

ghost commented Oct 14, 2016

@mmhansen 😂 👍

@mmhansen
Copy link

mmhansen commented Oct 14, 2016

Create a reducer

Challenge Description

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.

Instructions

Write a switch statement inside the reducer whose default case returns state.

Tips

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) {
}

Challenge Seed

function groceries (state, action) {
// Change code below 

// Change code above 
}

Challenge Solution

function groceries (state, action) {
  switch (action.type) {
    default:
    return state;
  }
}

@mmhansen
Copy link

mmhansen commented Oct 14, 2016

Create a VALUE_CHANGE reducer

Challenge Description

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.

Instructions

Make a case named 'VALUE_CHANGE' that will return the state with the new input value

Tips

This will be a little tricky to see at first, but you must use Object.assign. For example

Object.assign( {}, state, { pieceOfState: action.payload } )

Challenge Seed

function groceries (state, action) {
  switch (action.type) {
// Change code below 

// Change code above 
    default:
    return state;
  }
}

Challenge Solution

function groceries (state, action) {
  switch (action.type) {
    case 'VALUE_CHANGE':
      return Object.assign( {}, state, { input: action.payload } )
    default:
      return state;
  }
}

@mmhansen
Copy link

mmhansen commented Oct 14, 2016

Create a FORM_SUBMIT reducer

Challenge Description

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'

Instructions

Make a case in the reducer to handle the FORM_SUBMIT action.

Tips

Note! Object assign is essential.

Challenge Seed

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;
  }
}

Challenge Solution

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;
  }
}

@mmhansen
Copy link

mmhansen commented Oct 14, 2016

Creating a root reducer

Challenge Description

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.

Instructions

Give the combine reducers function an key:value pair, that will handle a part of state with the same name as your reducer.

Tips

If your reducer was myTodoReducer and you wanted it to be able to access the Todo part of state, you would pass Todo: myTodoReducer

Challenge Seed

import { combineReducers } from 'redux'
import groceries from './actions/groceries'

export default combineReducers({
// Change code below 

// Change code above 
})

Challenge Solution

import { combineReducers } from 'redux'
import groceries from './actions/groceries'

export default combineReducers({
  groceries: groceries
})

@QuincyLarson
Copy link
Contributor Author

@mmhansen awesome work. These look great so far!

Just curious - why is the {/* Change code below */} wrapped in curly braces?

@mmhansen
Copy link

@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. 👍

@mmhansen
Copy link

mmhansen commented Oct 15, 2016

Creating a store

Challenge Description

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.

Instructions

Use the createStore function with your rootReducer to make the application state.

Tips

createStore can also be passed an initial state as a second optional argument

Challenge Seed

import rootReducer from '../reducers/index'
import { createStore } from 'redux'

// Your code below

Challenge Solution

import rootReducer from '../reducers/index'
import { createStore } from 'redux'

const store = createStore(rootReducer)

@mmhansen
Copy link

mmhansen commented Oct 15, 2016

Using middlewares

Challenge Description

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.

Instructions

Pass the applyMiddleware function with thunk as an argument as the second argument of the createStore function.

Tips

Remember that applyMiddleware is a function that takes a middleware as its only argument.

Challenge Seed

import rootReducer from '../reducers/index'
import { createStore, applyMiddleware } from 'redux'
import { thunk } from 'thunk'

const store = createStore(rootReducer)

Challenge Solution

import rootReducer from '../reducers/index'
import { createStore, applyMiddleware } from 'redux'
import { thunk } from 'thunk'

const store = createStore(rootReducer, applyMiddleware(thunk))

@mmhansen
Copy link

Setup Provider

Challenge Description

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!

Instructions

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.

Tips

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.

Challenge Seed

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')
);

Challenge Solution

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')
);

@mmhansen
Copy link

mmhansen commented Oct 15, 2016

Using connect

Challenge Description

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.

Instructions

Use the connect function to connect your GroceryForm component to the redux state.

Tips

A curried function is passed arguments like this: connect( arg1, arg2 )( myComponent )

Challenge Seed

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()();

Challenge Solution

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 );

@mmhansen
Copy link

mmhansen commented Oct 15, 2016

mapStateToProps

Challenge Description

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.

Instructions

Make mapStateToProps return an object with key input and with the state.groceries.input portion of state

Tips

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

Challenge Seed

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 );

Challenge Solution

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 );

@mmhansen
Copy link

mmhansen commented Oct 15, 2016

Adding actions to connect

Challenge Description

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

Instructions

Import all as actions from the index file in '../actions/'

Tips

Look at the bottom to see that we've added actions into connect for you.

Challenge Seed

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 );

Challenge Solution

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 );

@mmhansen
Copy link

mmhansen commented Oct 16, 2016

Remove Component State

Challenge Description

We are using redux universal application state now, so lets get rid of our component state.

Instructions

Remove the constructor.

Tips

Notice how we've replaced the onClick handler with our new prop action from redux connect!

Challenge Seed

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 );

Challenge Solution

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 );

@mmhansen
Copy link

mmhansen commented Oct 16, 2016

Bind action creators

Challenge Description

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.

Instructions

Add the handleSubmit action to our input field

Tips

Look at the valueChange function, yours will be very similar

Challenge Seed

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 );

Challenge Solution

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 );

@mmhansen
Copy link

mmhansen commented Oct 16, 2016

Create 'REMOVE_GROCERY' action

Challenge Description

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.

Instructions

Return an action that has a type 'REMOVE_GROCERY' and with a payload of the grocery you want to remove

Tips

Remember that actions are just a name for a uniform object that redux likes

Challenge Seed

export function removeGrocery ( grocery ) {
  return 
// your code below

// your code above
}

Challenge Solution

export function removeGrocery ( grocery ) {
  return {
    type: 'REMOVE_GROCERY',
    payload: grocery
  }
}

@mmhansen
Copy link

mmhansen commented Oct 16, 2016

Create 'REMOVE_GROCERY' reducer

Challenge Description

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!

Instructions

Write a return statement in the filter function that will return all of the array except the one we passed in the action.

Tips

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.

Challenge Seed

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;
  }
}

Challenge Solution

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;
  }
}

@mmhansen
Copy link

Combining reducers

Challenge Description

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!

Instructions

We've imported the new reducer, chores, for you. Add it to the combine reducers function.

Tips

Challenge Seed

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
})

Challenge Solution

import { combineReducers } from 'redux'
import groceries from './reducers/groceries'
import chores from './reducers/chores'

export default combineReducers({
  groceries: groceries,
  chores: chores
})

@mmhansen
Copy link

mmhansen commented Oct 16, 2016

Checklist

  • Validate props with react.PropTypes
  • Make your first action
  • Make your first action creator
  • Type constants and exporting your action creators
  • Create a reducer
  • Create VALUE_CHANGE reducer
  • Create FORM_SUBMIT reducer
  • Create a root reducer
  • Creating store
  • Using middlewares
  • Setup provider
  • Using connect
  • mapStateToProps
  • Addding actions to connect
  • Remove component state
  • bindActionCreators
  • Create 'REMOVE_GROCERY' action
  • Create 'REMOVE_GROCERY' reducer
  • Combining reducers

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

@QuincyLarson
Copy link
Contributor Author

@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?

@bonham000
Copy link

@ALL Please see the React Issues thread about an update we've made to the development of these React/Redux challenges.

@QuincyLarson
Copy link
Contributor Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants