56
votes

Perhaps I am not wrapping my head around redux, but all the examples I've seen don't really access state too much between containers and so I haven't seen much usage of store.getState(), but even if you want to dispatch, you need access to store, right?

So, other than importing import store from 'path/to/store/store'

in every file that I want to getState() or "dispatch", how do I get access to that state because if I don't include it, store is undefined.

3

3 Answers

82
votes

In general you want to only make top-level container components ones that have access to the store - they will pass down any necessary data or action dispatches as props to their children components. This is the difference between a "smart" and a "dumb" component - "smart" components know about the Redux store/state, while "dumb" components just get props passed to them and have no idea about the bigger application state.

However, even just passing the store to container components can become tedious. That's why React-Redux provides one component out of the box that wraps your entire application. Check it out in the docs. This is the Provider component and when you wrap your whole app with it, you only pass the store to a component once:

import createStore from '../store';

const store = createStore()

class App extends Component {

  render() {
    return (
      <Provider store={store}>
        <MainAppContainer />
      </Provider>
    )
  }
}

As you can see here, I have a separate configuration file just for my store as there is a lot of modification you can do and for any remotely complex app, you'll find yourself doing the same for things like using compose to apply middleware.

Then any of your remaining "smart" components (generally wrappers) need to listen to the store. This is accomplished using the connect method. This allows you to map pieces of the state to your component properties as well as dispatch actions as properties.

import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from './actionCreators';

const mapStateToProps = function(state){
  return {
    something: state.something,
  }
}

const mapDispatchToProps = function (dispatch) {
  return bindActionCreators({
    getSomething: actionCreators.getSomething,
  }, dispatch)
}

class MainAppContainer extends Component {

    componentDidMount() {
      //now has access to data like this.props.something, which is from store
      //now has access to dispatch actions like this.props.getSomething
    }

    render() {
        //will pass down store data and dispatch actions to child components
        return (
               <div>
                   <ChildComponent1 something={this.props.something} />
                   <ChildComponent2 getSomething={this.props.getSomething} />
               </div>
        )
    }
}

export default connect(mapStateToProps, mapDispatchToProps)(MainAppContainer)

Because you are always passing down dispatch actions and data to your children component as properties, you just reference those on that component with this.props.

Building off the example above, you'll see that because I passed this.props.something to ChildComponent1, it has access to the something data from the store but it does not have access to the getSomething dispatch action. Likewise, ChildComponent2 only has access to the getSomething dispatch action but not the something data. This means that you only expose components to exactly what they need from the store.

For example, because ChildComponent2 was passed down the dispatch action as getSomething, in my onClick I can call this.props.getSomething and it will call the dispatch action without needing any access to the store. In the same way it can continue to pass down getSomething to another child component and that component could call it and/or pass it down and the cycle could continue indefinitely.

class ChildComponent2 extends Component {

    render() {
        return (
            <div>
                <div onClick={this.props.getSomething}>Click me</div>
                <NestedComponent getSomething={this.props.getSomething} />
            </div>
        )
    }
}

Edit from the comments

While this doesn't pertain directly to the question, in the comments you seemed a little confused about actions. I did not actually define the action getSomething here. Instead it is usual in Redux apps to put all of your action definitions in a separate file called actionCreators.js. This contains functions that are named the same as your actions and return an object with a type property and any other methods/data that action requires. For instance, here's a very simple example actionCreators.js file:

export function getSomething() {
    return {
        type: 'GET_SOMETHING',
        payload: {
            something: 'Here is some data'
        }
    }
}

This action type is what your reducer would listen for to know which action was being fired.

15
votes

If you use the react-redux package, you'll end up wrapping your components in a Provider with a store prop. This sets up your single store in a React context, which is then accessed from the connect method in child components. The connect method takes two functions (mapStateToProps and mapDispatchToProps), which are your hooks for getting state from the store and dispatching messages.

Take a look here for more information

0
votes

with mapStateToProps React Component:

import Item from './Item.jsx';
import { createStore } from 'redux';
import { getProduct, addProduct } from '../../actions';
import { connect } from "react-redux";

class Bundles extends React.Component {
  constructor(props) {
    super(props);
  }
  render() {
    var productData = this.props.productData
    return (
      <div>
        <span>
          {
            productData.map(item => (
              <Item item={item} key={item.id} />
            ))
          }

        </span>
      </div >
    )
  }
}

const mapStateToProps = (state) => {
  // console.log(state.getProduct)
  return {
    productData: state.getProduct,
  };
};

export default connect(mapStateToProps)(Bundles);

Product Reducers


const productReducer = (state = data, action) => {
  switch (action.type) {
    case "GET_PRODUCT":
      console.log(state)
      return state
    default:
      return state;
  }
}

export default productReducer;

RootReducer (all reducers combined)

import getProduct from './products';
import getReviews from './reviews';

import { combineReducers } from 'redux';

const allReducers = combineReducers({
  cartReducer, getProduct, getReviews
})

export default allReducers;

Action (action/index.js)

      // console.log('hahahahah')
      return {
        type: 'ADD_PRODUCT',
        payload: n
      }
    }