Git Product home page Git Product logo

intro-to-redux-library-codealong's Introduction

Intro to Redux: Reading Data from State

Objectives

  • Use the createStore() method provided by the Redux library.

Introduction

In the previous section, we used a createStore() method that we wrote, and passed a reducer to it. We used the dispatch method from the store to dispatch actions and update the state.

Now let's think about which part of our application would belong in the official Redux library -- that is, which part of our codebase would be common to all applications. Well, probably not the reducer as our reducers seem unique to each React & Redux application. The reducers are unique because sometimes we have reducers that would add or remove items, or add or remove users, or edit users, etc. What these actions are and how the reducer manages the state is customized. Thus, the reducer would not be part of the Redux library that other developers would use to build their application.

The createStore(), method however is generic across Redux applications. It always returns a store (given a reducer) that will have a dispatch method and a getState method.

So from now on, we will import our createStore() method from the official Redux library. Normally, to install Redux into a React application, you need to install two packages, redux and react-redux, by running npm install redux && npm install react-redux. These are already included in this lesson's package.json file, so all you need to do is run npm install && npm start to get started.

In this code along, we'll be building a simple counter application that displays the value of the counter along with a button to increment it.

Step 1: Setting Up The Store

First things first, we'll use Redux to initialize our store and pass it down to our top-level container component.

Redux provides a function, createStore(), that, when invoked, returns an instance of the Redux store for us. We want to import createStore() in our src/index.js file, where ReactDOM renders our application, and then use that function to create the store.

// ./src/index.js

import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux"; /* code change */
import counterReducer from "./reducers/counterReducer.js";
import App from "./App";
import "./index.css";

const store = createStore(counterReducer); /* code change */

ReactDOM.render(<App />, document.getElementById("root"));

Now, with the above set up, let's pass store down to App as a prop so it can access the Redux store.

// ./src/index.js

import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import counterReducer from "./reducers/counterReducer.js";
import App from "./App";
import "./index.css";

const store = createStore(counterReducer);


ReactDOM.render(
    <App store={store} />  /* code change */,
  document.getElementById("root")
);

So, to recap, just like we did previously, we call our createStore() method in src/index.js. We pass our createStore() method a reducer, and then we pass our newly created store to our App component as a prop. You can find the reducer in ./src/reducers/counterReducer.js:

// ./src/reducers/counterReducer.js

export default function counterReducer(
	state = {
		clicks: 0
	},
	action
) {
	switch (action.type) {
		case "INCREASE_COUNT":
			return {
				clicks: state.clicks + 1
			}
		default:
			return state;
	}
}

Each time an action with type 'INCREASE_COUNT' is dispatched to the reducer, the value of the counter is incremented.

Instead of having all of our functions encapsulated in a closure within index.js as we did while building our own Redux setup, we've now separated out the reducer function, giving it a relevant name, counterReducer, and let the Redux library take care of our createStore function. These two pieces are both imported into src/index.js and used to create store.

Once we've created the store and passed it to the App component as a prop, we can access it using this.props.store:

// ./src/App.js

import React, { Component } from "react";
import "./App.css";

class App extends Component {
	handleOnClick = () => {
		this.props.store.dispatch({
		  type: 'INCREASE_COUNT',
		});
	  }

	render() {
		return (
			<div className="App">
				<button onClick={this.handleOnClick}>Click</button>
				<p>{this.props.store.getState().clicks}</p>
			</div>
		);
	}
}

export default App;

As you recall, the store contains two methods: dispatch and getState. We use the getState method in our render method to get the current state so we can display it on the page. We also have an event handler that calls the dispatch method, passing in our action, when the button is clicked.

Now, with this code in place, if you boot up the app you should see a button on the page, followed by a zero. Then, if you click on the button... nothing happens. So what's gone wrong here? Well, we've done the work necessary to create the store and made it accessible to our app so the state is updated when we click the button, but we haven't yet done all the work necessary to get our React and Redux libraries communicating with each other properly so the page re-renders once the state is updated. We'll tackle that in the next lesson. In the meantime, how do we know our state is getting updated? Let's get some feedback so we can find out.

Add Logging to Our Reducer

First, let's log our action and the new state. So we'll change the reducer as follows:

// ./src/reducers/counterReducer

export default function counterReducer(
  state = {
    clicks: 0
  },
  action
) {
  console.log(action);
  switch (action.type) {
    case "INCREASE_COUNT":
      console.log("Current state.clicks %s", state.clicks);
      console.log("Updating state.clicks to %s", state.clicks + 1);
      return {
        clicks: state.clicks + 1
      };

    default:
      console.log("Initial state.clicks: %s", state.clicks);
      return state;
  }
}

Ok, so this may look like a lot, but really all we're doing is adding some logging behavior. At the top of the function, we are logging the action. After the case statement, we are logging our current state first, followed by the value the state is being updated to. Under the default case statement, we can just log the previous state because this state will be unchanged.

Now, refresh your app, and give it a shot. When the page loads, you should see the initial action being logged, along with the initial value of the counter, 0. This is coming from our default case. Then, when you click the button, you should see the INCREASE_COUNT action logged along with the current state and the state we are updating to. We know that we are dispatching actions because each time we click the button, we can see that the call to this.props.store.dispatch({ type: 'INCREASE_COUNT' }) is hitting our reducer, and we can also see the value of the current state increasing each time. So things are happening.

Redux DevTools

There is this amazing piece of software that allows us to nicely view the state of our store and each action that is dispatched. (In actuality, the software can do a lot more for us than that; you can read up on it here: redux-devtools-extension.) Ok, so let's get to incorporating the devtools. In fact, every time we use the Redux library going forward, we should make sure we incorporate devtools. Otherwise, you are flying blind.

First, just Google for Redux Devtools Chrome. You should find the Chrome extension for Redux. Please download it, and refresh Chrome. To verify that you have successfully installed the extension, go to your developer console in Google Chrome (press command+shift+c to pull it up). In the top bar you will see a couple of arrows. Click those arrows, and if you see Redux in the dropdown, you have properly installed the Chrome extension. Step one is done.

Second, we need to tell our application to communicate with this extension. Doing so is pretty easy. Let's change the arguments to our createStore method to the following:

// ./src/index.js

import React from "react";
import ReactDOM from "react-dom";
import { createStore } from "redux";
import counterReducer from "./reducers/counterReducer";
import App from "./App";
import "./index.css";

const store = createStore(
  counterReducer,
  window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__()
); /* code change */

ReactDOM.render(
  <App store={store} />,
  document.getElementById("root")
);

Ok, notice that we are still passing through our reducer to the createStore method. The second argument is accessing our browser to find a method called __REDUX_DEVTOOLS_EXTENSION__. Now let's open the Redux Devtools (press command+shift+c, click on the arrows at the top right, and select the extension in the dropdown). You should see the initial action, @@INIT in the inspector. Now click on the tab that says state. You should see { clicks: 0 }. If you do, it means that your app is now communicating with the devtool. Each time you click on the button in your application, you should see the action name (INCREASE_COUNT) and the updated state show up in the devtools.

Whew!

Summary

In this lesson, we saw how to use the createStore() method. We saw that we can rely on the Redux library to provide this method, and that we still need to write our own reducer to tell the store what the new state should be given a particular action. We saw that when using the createStore() method and passing through a reducer, we are able to change the state just as we did previously. We were able to see these changes by hooking our application up to a Chrome extension called Redux Devtools, and then providing the correct configuration.

intro-to-redux-library-codealong's People

Contributors

achasveachas avatar dependabot[bot] avatar gj avatar graciemcguire avatar ihollander avatar jeffkatzy avatar johannkerr avatar kwebster2 avatar lizbur10 avatar lukeghenco avatar maxwellbenton avatar morgvanny avatar sylwiavargas avatar vibraniumforge avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

intro-to-redux-library-codealong's Issues

Same issue as #13 except the correct solution is this


ReactDOM.render( <Provider store={store}>
  <App />
</Provider>,
  document.getElementById('root')
);

It's key that <App /> is on its own line. See this stackoverflow for more https://stackoverflow.com/questions/36735397/react-error-failed-proptype-invalid-prop-children-supplied-to-provider-ex

It always returns

"Return" should be "returns" in the sentence below:
"It always return a store (given a reducer) that will have a dispatch method and a getState method."

Thank you.

Fix grammar

This paragraph has the problem: It's in the section Step 1: Setting Up The Store
Redux provides a function, createStore(), that, when invoked, returns an instance of the Redux store for us. So we can use that method to create a store. We want to import createStore() in our src/index.js file, where ReactDOM renders our application.

Change to:
So we can use that method to create a store, we want to import createStore() in our src/index.js file, where ReactDOM renders our application.

mocha test wrappers just take in .first and .last instead of by name

Noticed that if you switch the order of the userinput and hometown input the tests don't pass because of lines like these:

let usernameInput = wrapper.find('input').first(); // this line just takes the first input
usernameInput.simulate('change', { target: { value: 'Bob', name: 'username' } });
let hometownInput = wrapper.find({ type: 'text' }).last(); //this line just takes the last input.

Lesson not working

Tried to open this lesson and this pulls up in IDE
// โ™ฅ learn open intro-to-redux-library-codealong-v-000
Looking for lesson...
/usr/local/rvm/gems/ruby-2.3.1/gems/learn-open-1.2.21/lib/learn_open/lessons/ios_lesson.rb:6:in detect': undefined method any?' for false:FalseClass (NoMethodError)
from /usr/local/rvm/gems/ruby-2.3.1/gems/learn-open-1.2.21/lib/learn_open/lessons.rb:20:in block in classify' from /usr/local/rvm/gems/ruby-2.3.1/gems/learn-open-1.2.21/lib/learn_open/lessons.rb:19:in each'
from /usr/local/rvm/gems/ruby-2.3.1/gems/learn-open-1.2.21/lib/learn_open/lessons.rb:19:in find' from /usr/local/rvm/gems/ruby-2.3.1/gems/learn-open-1.2.21/lib/learn_open/lessons.rb:19:in classify'
from /usr/local/rvm/gems/ruby-2.3.1/gems/learn-open-1.2.21/lib/learn_open/opener.rb:36:in run' from /usr/local/rvm/gems/ruby-2.3.1/gems/learn-open-1.2.21/lib/learn_open/opener.rb:11:in run'
from /usr/local/rvm/gems/ruby-2.3.1/gems/learn-open-1.2.21/bin/learn-open:7:in <top (required)>' from /usr/local/rvm/gems/ruby-2.3.1/bin/learn-open:23:in load'
from /usr/local/rvm/gems/ruby-2.3.1/bin/learn-open:23:in `

'

Redux store update doesn't trigger re-render of component

Following the lab code-along the component does not re-render when dispatching an action. I checked the solution branch and the solution branch is out of date as well and seems to point to an older version of the code-along with an array

ShoppingListItemReducer

You should fix your code so that the return is not items: state.items.concat(state.items.length + 1), but:

`` switch (action.type) {
case 'INCREASE_COUNT':
return Object.assign({}, state, {
items: state.items.concat(state.items.length + 1)
});```

If not, the code in App will not work, for this.props.items will not exist after updating state.

Inconsistent incrementation of item count in lesson produces error

Issue intended for intro-to-redux-library-codealong-v-000

In the initial implementation of the reducer, shoppingListItemReducer, the first case statement returns state.items.concat(state.items.length + 1); which caused this.props.items.length in the render method of class App (/src/App.js) to return undefined.

export default function shoppingListItemReducer(
    state = {
        items: []
    },
    action
) {
    switch (action.type) {
        case 'INCREASE_COUNT':
            return state.items.concat(state.items.length + 1);
 
        default:
            return state;
    }
}

The solution to this error is in the Add Logging to Our Reducer section of this lesson which provides a different method to increment the count.

return Object.assign({}, state, {
                items: state.items.concat(state.items.length + 1)
            });

When using this code, the error disappears and all goes well. Changing the initial code to match the code further along in this code-along would save some time and avoid this error.

ShoppingListItemReducer *still* causes failure

This issue was brought up in November of 2018 and has yet to be resolved. It does appear that someone attempted to resolve the error as the return of ShoppingListItemReducer mysteriously switches halfway through the lesson without mention:

switch (action.type) {
case 'INCREASE_COUNT':
return state.items.concat(state.items.length + 1);

default:
  return state;

}

to

switch (action.type) {
case 'INCREASE_COUNT':
console.log('Current state.items length %s', state.items.length);
console.log('Updating state.items length to %s', state.items.length + 1);
return Object.assign({}, state, {
items: state.items.concat(state.items.length + 1)
});

default:
  console.log('Initial state.items length: %s', state.items.length);
  return state;

}

Note return Object.assign({}...

Unfortunately, this does not seem to be functional. The only code that works for me is:

switch (action.type) {
  case 'INCREASE_COUNT':
	return {
		...state,
		items: state.items.concat(state.items.length + 1)
		}   
  default:
	return state;
}

Same issue as #13 and #16 - ReactDOM.render Error

Solution is:

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  document.getElementById("root")
);

Recreating issue to hopefully bump this, as this issue has persisted since January.

npm install react-redux

This codealong imports react-redux... I couldn't get it to work until I tried npm install react-redux --save (the readme mentioned using npm install react --save)

App won't render in browser with code provided in readme

Using the code the readme provides for index.js errors out with the following:

React.Children.only expected to receive a single React element child.

I had to remove line 15: {' '} to get the page to render.

Full working code:

ReactDOM.render( <Provider store={store}> {' '} <App /> </Provider>, document.getElementById('root') );

Return's an Array Instead of an Object

Line 11 in the first "./src/reducers/shoppingListItemReducer.js" code block reads:

return state.items.concat(state.items.length + 1);

The return line should return an object instead of an array... otherwise, it throws errors. (This is fixed in later code blocks)

Extra ")" causing compiling to fail

After copying and pasting the updated code from the second to last code block in the lesson into shoppingListItemReducer, I get a compiler error:

Failed to compile.

./src/reducers/shoppingListItemReducer.js
Syntax error: Unexpected token, expected ; (17:4)

  15 |                  ...state,
  16 |                  items: state.items.concat(state.items.length + 1)
> 17 |           });
     |            ^
  18 |   
  19 |    default:
  20 |           console.log('Initial state.items length: %s', state.items.length);

It looks like there is an extra ) on line 17.

This should take you to the exact element on the page (line 17 in that code block)
document.querySelector("#js--region-body-content > div > div:nth-child(2) > div > div > div > div > pre:nth-child(29) > code > ol > li:nth-child(17) > span")

Curriculum

This would have been very helpful information back in Using Redux with React. It would have helped with debugging. Perhaps you can change the order.

Props has some issues

TypeError: Cannot read property 'length' of undefined
App.render
src/App.js:14
11 | return (
12 |


13 | Click

14 |

{this.props.items.length}


15 |

16 | );
17 | }

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.