I am learning redux and I have put up a simple code which uses the store, action and reducer. I am using store.subscribe() to listen for changes in the state and using it to update my local state.
Below is my entire code in index.js:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
var store = createStore(changeState);
var initialState = {
qty: 0,
price: 0
}
function changeState(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
var stateCopy1 = Object.assign({}, state);
stateCopy1.qty = stateCopy1.qty + action.qty;
stateCopy1.price = stateCopy1.price + action.price;
return stateCopy1;
default:
return state;
}
}
class Home extends React.Component {
render() {
return (
<React.Fragment>
<Comp1 /><br />
<Comp2 />
</React.Fragment>
)
}
}
class Comp1 extends React.Component {
increase = () => {
var action = {
type: 'INCREMENT',
qty: 1,
price: 100
}
store.dispatch(action);
}
render() {
return (
<button type="button" onClick={this.increase}>Increase</button>
)
}
}
class Comp2 extends React.Component {
constructor() {
super();
this.state = {
cartQty: 0,
cartPrice: 0
}
}
render() {
store.subscribe(() => {
var globalState = store.getState();
this.setState({cartQty:globalState.qty,cartPrice:globalState.price});
})
return (
<div>
<h1>Total items in cart: {this.state.cartQty}</h1>
<h1>Total price of cart :{this.state.cartPrice}</h1>
</div>
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'));
I want to use react-redux to avoid the local state and subscription. I read about connect() and mapStateToProps() and . But I am unable to figure out how to use them in my below code. How can I implement these parts in my code?
There are few things that you need to take care of.
First, you need to have a react-redux installed and then use Provider component at the top level to which you pass store
Second: You need to connect the components that need to access store
or dispatch
Third: You need to render the connected components
Fourth: You need mapStateToProps
to be passed to container which access state and mapDispatchToProps
to accesses dispatch or make action creators available as props to components. In case you do not pass mapDispatchToProps
to connect
, by default dispatch
prop is made available to the component. You can look at the API docs here
Fifth Create store after defining changeState reducer and initialState
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { connect, Provider } from 'react-redux'
var initialState = {
qty: 0,
price: 0
}
function changeState(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
var stateCopy1 = Object.assign({}, state);
stateCopy1.qty = stateCopy1.qty + action.qty;
stateCopy1.price = stateCopy1.price + action.price;
return stateCopy1;
default:
return state;
}
}
var store = createStore(changeState);
class Comp1 extends React.Component {
increase = () => {
var action = {
type: 'INCREMENT',
qty: 1,
price: 100
}
this.props.dispatch(action);
}
render() {
return (
<button type="button" onClick={this.increase}>Increase</button>
)
}
}
const Comp1Container = connect()(Comp1);
class Comp2 extends React.Component {
render() {
return (
<div>
<h1>Total items in cart: {this.props.cartQty}</h1>
<h1>Total price of cart :{this.props.cartPrice}</h1>
</div>
)
}
}
const mapStateToProps = (state) => {
return {
cartQty:state.qty,
cartPrice: state.price
}
}
const Comp2Container = connect(mapStateToProps)(Comp2);
class Home extends React.Component {
render() {
return (
<React.Fragment>
<Comp1Container /><br />
<Comp2Container />
</React.Fragment>
)
}
}
ReactDOM.render(<Provider store={store}><Home /></Provider>, document.getElementById('root'));
Working demo
Of course you can, first let's make sure concepts are clear.
Redux already has a 'state', so you copying it to your internal state is redundant.
connect()
:
This convenience method is used to map the redux's state, to props in your component. That is, you're not copying the state into another state, you use Redux's state as props, which are inmutable and more like a reference to the real data inside Redux.
It's built with a pattern call hoc, but that's a lesson for another question. The important thing to know about a hoc is that it takes a component as an argument and it returns a new component, an improved one.
mapStateToProps()
:
This will be your way into telling connect
what part of redux's state you want to get inside your component. It's a method that receives the complete redux's state, extracts the properties you want to use, and returns them to be sent as props to your component.
Now you're missing one key part of the ecuation here, which is redux's...
Provider
:
This piece should wrap all of your app ( or the part of it that you want it to has redux access, which usually is all of it ) and is the one in charge of sending redux's store down the tree so connect
can grab it later ( This is achieved through react's context, but that's a meal for another date ).
You get your provider like: import {Provider} from 'react-redux';
and then you give it the store as a prop called.... store
(clever right?)
Enough chit chat right, let's get down to business.
We start with the imports, let's get everything we need:
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import {Provider, connect} from 'react-redux';
We added two more things here the Provider
component and the connect
hoc.
var initialState = {
qty: 0,
price: 0
}
function changeState(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
var stateCopy1 = Object.assign({}, state);
stateCopy1.qty = stateCopy1.qty + action.qty;
stateCopy1.price = stateCopy1.price + action.price;
return stateCopy1;
default:
return state;
}
}
var store = createStore(changeState);
Now, you saw what happened there? Exactly! Nothing. Your reducer can remain as is, we are not moving that, although for a larger app you may want to learn to combine reducers
class Home extends React.Component {
render() {
return (
<Provider store={store}>
<Comp1 /><br />
<Comp2 />
</Provider>
)
}
}
Ok, your Fragment
is gone, I'm sorry about that, but it's now no longer necessary. Fragment
s are used to return two or more components, but since the Provider
is now wrapping the components, there is no need to use a Fragment
.
And about the Provider
, you just need to put it outside of everything and give it your store
. Easy enough.
class Comp1 extends React.Component {
increase = () => {
var action = {
type: 'INCREMENT',
qty: 1,
price: 100
}
store.dispatch(action);
}
render() {
return (
<button type="button" onClick={this.increase}>Increase</button>
)
}
}
In this last component we didn't move anything. Although we should have, look how you're using your store
directly to dispatch
your action
. In a normal app, this component would be in another file, so you wouldn't have access to the store
property. And here comes again our friend connect
which helps us dispatch actions through a function called mapDispatchToProps
you read about it here, but that's also for another day.
And here it comes, what we all were waiting for, the connect
method:
class Comp2 extends React.Component {
render() {
return (
<div>
<h1>Total items in cart: {this.props.qty}</h1>
<h1>Total price of cart :{this.props.price}</h1>
</div>
)
}
}
function mapStateToProps( state ){
return { qty: state.qty, price:state.price }
}
Comp2 = connect(mapStateToProps)(Comp2);
This may be a little bit confusing, so let me explain:
First, we removed everything related to your component's state. We're not using it anymore, cause that's what you wanted right? And also the component is now leaner. And cooler.
But what happened later? well, first we are defining the mapStateToProps
function. And this is translating redux's state to your components state. This time, we sending every single property from redux's state to your component, but in a bigger app this would not be the case, in a bigger app redux would have the state of everything inside, that could be the cart items, the app theme colors, the user's info etc etc. A lot of things, so inside this function we select only the properties we are interested in getting inside our component.
Now the connect call... we are redefining our component, and it's kinda weird, but I'll try to explain.
connect
received our mapStateToProps
method and then our component. And they mated inside connect
and gave birth to another component, this one is a component that will have the component we defined first as a child, and will always send it the parts of the redux's state we asked for as props.
use Provider from react-redux and your store will be available to all your child components
var store = createStore(changeState)
<Provider store={store}>
{child}
</Provider>
Below is what you need to do, i have given an example with comp1 Component,
also check this link https://redux.js.org/basics/usage-with-react helps to get more information.
import React from 'react';
import ReactDOM from 'react-dom';
import { createStore } from 'redux';
import { connect } from 'react-redux'
class Comp1 extends React.Component {
increase = () => {
var action = {
type: 'INCREMENT',
qty: 1,
price: 100
}
store.dispatch(action);
}
render() {
return (
<button type="button" onClick={this.increase}>Increase</button>
)
}
}
mapStateToProps = state =>{
//code to map State to props
}
mapDispatchToProps = dispatch =>{
//code to map dispatch to props
}
export deafult connect(mapStateToProps,mapDispatchToProps)(Comp1);
Code
import {combineReducers, createStore} from 'redux';
import {Provider, connect} from 'react-redux';
//reducer
var initialState = {
qty: 0,
price: 0
}
function cart(state = initialState, action) {
switch (action.type) {
case 'INCREMENT':
var stateCopy1 = Object.assign({}, state);
stateCopy1.qty = stateCopy1.qty + action.qty;
stateCopy1.price = stateCopy1.price + action.price;
return stateCopy1;
default:
return state;
}
}
const rootReducer = combineReducers({cart});
//Actions
const increase = () => {
var action = {
type: 'INCREMENT',
qty: 1,
price: 100
}
}
let Comp1 = (props) => {
return (
<button type="button" onClick={props.increase}>Increase</button>
)
}
Comp1 = connect(null, {increment})(Comp1);
let Comp2 = (props) => {
return (
<div>
<h1>Total items in cart: {props.qty}</h1>
<h1>Total price of cart :{props.price}</h1>
</div>
)
}
const mapStateToProps = ({state}) => state.cart
Comp2 = connect(mapStateToProps)(Comp2);
//create store
const store = createStore(rootReducer);
class Home extends React.Component {
render() {
return (
<Provider store={store}>
<Comp1 /><br />
<Comp2 />
</Provider >
)
}
}
ReactDOM.render(<Home />, document.getElementById('root'));
There is for theoretical reference of how to use redux and react-redux based on your example. This should give you idea of how to use both and with little google help you can finish your example I guess.
First, you want to develop a project thats more modular as you move along in your learning curve, it will be easier for you.
So set up a src/index.js
, you already have an index.js
file I am saying make that your root index.js
and then refactor it like so:
import React from "react";
import ReactDOM from "react-dom";
import "./index.scss";
import { Provider } from "react-redux";
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import App from "./components/App";
import reducers from "./reducers";
const store = createStore(reducers, applyMiddleware(thunk));
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.querySelector("#root")
);
So Provider
is at the top of the hierarchy and having implemented Provider
you will then be able to utilize the connect()
function, otherwise you will get an error.
Before we go ahead and implement connect()
though, lets move down the hierarchy, hierarchy very important in React-Redux.
Provider-> App -> child components.
So next up is components/App.js
:
import React from "react";
import "./App.scss";
import Home from "./Home";
const App = () => {
return (
<div className="App">
<Home />
</div>
);
};
export default App;
So let's assume that you are wanting to wire up connect()
to your Home
component, I don't think you specified, but this is what it would look like:
import React from "react";
import { connect } from "react-redux";
import { fetchLocations } from "../actions";
class Home extends React.Component {
componentDidMount() {
this.props.fetchLocations();
}
render() {
return <div>Home List</div>;
}
}
export default connect(
null,
{ fetchLocations }
)(Home);
You didn't talk much about your action creator or your details about your data so I am just making it about collecting a list of Homes since you have a Home
component. So above I imported the connect()
function inside the component and then below I implement it. Pay attention to the fact that I have placed a null
where your mapStateToProps
will eventually be. Again, you did not go into details about your state so if I were setting this up for you, null
is sufficient to get your app working until you are ready to implement mapStateToProps
, but actions you do need so I created that fetchLocations
action and implemented inside connect()
.
For your reducers you can do reducers/locationsReducer.js
:
export default () => {
return 123;
};
And then implement it with your combined reducer like so, reducers/index.js
:
import { combineReducers } from "redux";
import locationsReducer from "./locationsReducer";
export default combineReducers({
locations: locationsReducer
});
I believe you now have a functioning React-Redux application and you should be able to take it away from there.