Pattern for managing multiple reducers inside of a module [Redux] - reactjs

I am new to Redux and am trying to figure out a scaleable way to setup my projects folder/file structure.
Lets say we have a file structure that looks like this:
root/modules/Todos/reducers
In the root of the project there lives a 'rootReducer.js' file which utilizes 'combineReducers()' to create a top-level implementation of the state tree:
[rootReducer.js]
import { combineReducers } from 'redux';
import todos from './modules/Todos/reducers/index.js';
export default combineReducers({
todos: todos
});
Inside of the 'reducers' folder for each module there are multiple reducers:
[root/modules/Todos/reducers]
>index.js
>Todos__addItem
>Todos__removeItem
The 'index.js' file imports all of the reducers for that module and exports a single object:
[index.js]
import { combineReducers } from 'redux';
import addItem from './Todos__addItem.js';
import removeItem from './Todos__removeItem.js';
export default const todos = combineReducers({
addItem: addItem,
removeItem: removeItem
});
Is this the correct use of 'combineReducers()'?
Does this pattern make sense when developing a large scale application?
What are (if any) potential pitfalls that come along with this pattern?
Thanks!

It's definitely not the correct usage of combineReducers. combineReducers is used to delegate management of a specific slice of state to a given function. Your example would actually create slices of state named addItem and removeItem, when what you really want to do is to update the same todos slice of state using those functions in different ways depending on which action was dispatched.
The Redux docs section on "Structuring Reducers" has some information that should help with this, including the section on Using combineReducers.

example from https://github.com/suin/redux-multiple-reducers-example
import {counter1, counter2 } from "../../reducers/index"
import CounterApp from "../containers/CounterApp";
const rootReducer = combineReducers({
one:counter1 ,
two:counter2
});
const store = createStore(rootReducer);
class App extends React.Component{
render() {
return (
<Provider store={store}>
<CounterApp />
</Provider>
);
}
Counter1 view
import * as counter1Actions from "../../actions/counter1Actions";
#connect(state => ({
counter1: state.one
}))
export default class Counter1 extends React.Component{
static propTypes = {
counter1: PropTypes.number.isRequired
}
componentDidMount() {
console.info("counter1 component did mount.");
}
onClick() {
console.info("counter1 button was clicked.");
const action = bindActionCreators(counter1Actions, this.props.dispatch);
action.increment();
}
render() {
return (
<div>
<h1>Counter 1</h1>
<button onClick={::this.onClick}>increment</button>
<div>Total: <span>{this.props.counter1}</span></div>
</div>
);
}
}
Counter2 view
import * as counter2Actions from "../../actions/counter2Actions";
#connect(state => ({
counter2: state.two
}))
export default class Counter2 extends React.Component {
static propTypes = {
counter2: PropTypes.number.isRequired
}
componentDidMount() {
console.info("counter2 component did mount.");
}
onClick() {
console.info("counter2 button was clicked.");
const action = bindActionCreators(counter2Actions, this.props.dispatch);
action.increment();
}
render() {
return (
<div>
<h1>Counter 2</h1>
<button onClick={::this.onClick}>increment</button>
<div>Total: <span>{this.props.counter2}</span></div>
</div>
);
}
}
CounterApp
import Counter1 from "../components/Counter1";
import Counter2 from "../components/Counter2";
class CounterApp extends React.Component{
render() {
return (
<div>
<Counter1/>
<Counter2/>
</div>
);
}
}
reducer
export default function counter1(state = initialState, event) {
switch (event.type) {
case "COUNTER1_INCREMENTED":
console.info(`counter1 ack ${event.type}: event =`, event);
return state + 1;
default:
console.warn("counter1 ack unknown event: state =", state, "event =", event);
return state;
}
export default function counter2(state: Object = initialState, event: Object): Object {
switch (event.type) {
case "COUNTER2_INCREMENTED":
console.info(`counter2 ack ${event.type}: event =`, event);
return state + 1;
default:
console.warn("counter2 ack unknown event: state =", state, "event =", event);
return state;
}
}

Related

React, Redux - pass function from component A to other components

import React from "react";
import OtherComponent from "./OtherComponent";
class Main extends React.Component {
constructor(props) {
super(props);
this.runMyFunction = this.runMyFunction.bind(this);
this.myFunction = this.myFunction.bind(this);
}
runMyFunction(event) {
event.preventDefault();
this.myFunction();
}
myFunction() {
return console.log("I was executed in Main.js");
}
render() {
return (
<div>
<OtherComponent runMyFunction={this.runMyFunction} />
</div>
);
}
}
export default Main;
import React from "react";
class OtherComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.runMyFunction();
}
render() {
return (
<div>
<button onClick={this.handleClick} />Click me to execute function from Main </button>
</div>
);
}
}
export default OtherComponent;
I'm new in redux and don't know how to pass and run that function in other component. It was easy not using redux, just pass as props like in example above.
I have folder with actions, components, containers and reducers.
Now I have Main.js where I have
import React from "react";
const Main = ({data, getData}) => {
const myFunction = () => {
return "ok";
};
return (
<div>
<p>This is main component</p>
</div>
);
};
export default Main;
In MainContainer.js I got:
import Main from "../../components/Main/Main";
import { connect } from "react-redux";
import {
getData
} from "../../actions";
function mapStateToProps(state) {
return {
data: state.main.data
};
}
const mapDispatchToProps = (dispatch) => {
return {
getData: () => dispatch(getData())
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(Main);
So how I can run function myFunction() in OtherComponent.js:
import React from "react";
const OtherComponent = ({executeFunctionInMainComponent}) => {
return (
<div>
<button onClick={executeFunctionInMainComponent}>run action</button>
</div>
);
};
export default OtherComponent;
I need to just run, not pass whole function, just execute myFunction in Main.js but action to run this function will came from OtherComponent.
So first i have to mention that i believe that you have a misconception of redux. This isn't to allow for functions created in components to be reused in different locations. This is to move that logic to a reducer outside of your function which would allow it to be used wherever you wired it with {connect} from react-redux. So you will need a couple of files (for clarity). First you're going to need an action file which we'll name myReturnOkAction.
export const myReturnOkAction = (/*optional payload*/) => {
return {
type: 'PRINT_OK',
}
}
Redux Actions
This is what you're going to call in your mapDispatchToProps function where you're going to trigger this event. You're going to have to import it into your OtherComponent so import {myReturnOkAction} from "/*wherever the files exists*/" and to include it in your mapDispatchToProps as okFunction: () => dispatch(myReturnOkAction())
Once you have your action your connect Higher Order Component (HOC) wrapping your main component is going to need a Reducer to modify your current store state as well as do any actions.
export const myReturnOkReducer = (state, action) => {
if(action.type === 'PRINT_OK'){
/*This is where you update your global state*/
/*i.e. return {...store, valueWanted: ok}*/
}else{
return state
}
}
Redux Reducers
So the way that this is going to move is that you're function, somewhere is going to call the action. Once the action is called its going to trigger the reducer and make any changes to the store which you need. Once the reducer has updated the store with new values its then going to update any components which are connected to it through the connect HOC which will cause them to re-render with new information.
Also my favorite image to describe how redux works.
I hope this helps.
I found an answer:
I still can pass as props in redux but I can't do this in this way: OtherComponent = ({executeFunctionInMainComponent}) => {}. I need to do in this way: OtherComponent = (props) => {} and then inside that component I have an access via props.executeFunctionInMainComponent

react redux props undefined on event listener when using mapDispatchToProps

My first time using react/redux and I'm trying to bind a simple action addClick to the 'click' event, but when I click I receive the error:
Uncaught TypeError: Cannot read property 'props' of undefined
My (stripped down) code is:
import {addClick} from './actions'
const mapDispatchToProps = {addClick}
class App extends Component {
componentDidMount() {
document.addEventListener('click', this.props.addClick)
}
componentWillUnmount() {
document.removeEventListener('click', this.props.addClick)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(App)
Before I had it implemented without mapDispatchToProps using an action creator defined within the class and bound to this in the constructor. But I thought part of the point of mapDispatchToProps was to bind the action creator to this (as well as wrapping it in a dispatch)
What am I missing?
Thanks!
From what I can tell from the documentation, the object short-hand (const mapDispatchToProps =
{addClick}) you use for mapDispatchToProps doesn't bind this to anything. It just sees to it that your addClick action creator gets called with dispatch. So that if you, in your component execute addClick(3), then that will result in a call looking like this dispatch(addClick(3)).
I'm not sure why your action creator would need access to this though. Can't you just pass it what ever data it needs as a parameter? So that the call in your component might look like
componentDidMount() {
const {addClick, someOtherProp} = this.props;
document.addEventListener('click', () => addClick(someOtherProp));
}
Do you use props inside addClick action?
Check this example:
import React from "react";
import { render } from "react-dom";
import { connect, Provider } from "react-redux";
import { createStore } from "redux";
function addClick(event) {
return {
type: "CLICK",
payload: `pageX: ${event.pageX} | pageY: ${event.pageY}`
};
}
const mapStateToProps = state => {
return {
clickXY: state
};
};
const mapDispatchToProps = { addClick };
class App extends React.Component {
componentDidMount() {
document.addEventListener("click", this.props.addClick);
}
componentWillUnmount() {
document.removeEventListener("click", this.props.addClick);
}
render() {
return (
<h1>
Click message: {this.props.clickXY}
</h1>
);
}
}
function clickReducer(state = "None", action) {
switch (action.type) {
case "CLICK": {
return action.payload;
}
default:
return state;
}
}
let store = createStore(clickReducer);
const AppContainer = connect(mapStateToProps, mapDispatchToProps)(App);
class Root extends React.Component {
render() {
return (
<Provider store={store}>
<AppContainer />
</Provider>
);
}
}
render(<Root />, document.getElementById("root"));
Link to editor
So bind works well in this code.

React-Redux-Form save data to prop from onChange

I am new react-redux and I am trying to save to props the value entered in from a react-redux-form textbox by using the onChange event which can be passed to another component
My code snippet for the textbox is
<ListItemContent>
<Control component={Textfield} model="somemodel" label="MyLabel"
onChange={this.props}/>
</ListItemContent>
How can I save this value and make this available to other components?
EDIT I have it partly working:
<ListItemContent>
<Control component={Textfield} model="somemodel" label="MyLabel"
onBlur={this.onChangeOfValue}/>
</ListItemContent>
onChangeOfValue = (event) =>
{
this.setState({ newValueToPassAlong: event.target.value}); //newValueToPassAlong is set in constructor
};
.....
let mapStateToProps = (state) => {
return {newValueToGive: state.newValueToPassAlong} //This is undefined
};
export default connect(mapStateToProps)(form)
Further, my componentWillReceiveProps(nextProps) is not being fired when the other component's state changes.
// YOUR TEXTFIELD COMPONENT
import React, { Component } form 'react';
import { reduxForm, Field } from 'redux-form';
import {passValueToOtherComponent} from '../actions/your-actions-index-file';
import { connect } from 'react-redux';
import ListItemContent form 'list-item-content';
class TextField extends Component {
constructor(props) {
super(props);
this.state = {
textFieldValue: '',
}
this.onInputChange = this.onInputChange.bind(this);
}
onInputChange(event) {
var newValue = event.target.value;
this.setState({textFieldValue: newValue});
//when input changes
//call action to update global state...
this.props.passValueToOtherComponent(this.state.textFieldValue)
}
render() {
<form>
<ListItemContent>
<Control component={Textfield} model="somemodel" label="MyLabel"
onChange={this.onInputChange} value={this.state.textFieldValue}/>
</ListItemContent>
</form>
}
}
//ReduxForm wrapper
const wrappedReduxForm = connect(null, {passValueToOtherComponent})(TextField)
export default reduxForm({
form: 'TextField'
})(TextField)
// actions/your-actions-index-file.js
//create an action which will call to update global state
export const NEW_VALUE = "NEW_VALUE"
export function passValueToOtherComponent(value) {
return {
type: CREATE_POST,
payload: value,
}
}
//YOUR NewValue Reducer
//reducer_new_value.js
//create reducer which will accept payload data
import {NEW_VALUE} from '../actions/your-actions-index-file';
const INITIAL_STATE = {
valueToPass: null
};
export default function (state = [], action) {
console.log('Action...' action);
switch (action.type) {
case NEW_VALUE:
return { ...state, valueToPass: action.payload.data}
break;
default:
}
}
//Your Root Reducer
//Because you may have lots of state to manage, a rootReducer is awesome in managing it all
import { combineReducers } from 'redux';
import NewValueReducer from './reducer_new_value';
const rootReducer = combineReducers({
// state: (state = {}) => state
value: NewValueReducer,
});
export default rootReducer;
//finally pass this desired value to your desired Component
import React, {Component} from 'react';
import { connect } from 'react-redux';
class OtherComponent extends Component {
render() {
return (
<div>
<input value= {this.props.texFieldValue}>
</div>
)
}
}
function mapStateToProps(state) {
return { textFieldValue: state.value.valueToPass }
}
export default connect(mapStateToProps)(OtherComponent);
This is something I just typed out. Not sure if it will work, but it covers actions, reducers, and updating values from one component to anohter. Of course, this is a crazy way to do it using react redux. I'm not sure how efficient it would be to call this action on every input change. You might be better off passing the current value of state as a prop to your desired component.
If you have some more questions, I'd be happy to help or point you to some other resources.

Handling updates to store with React/Redux and lifecycle events

I'm using React with Redux on my front end and using the Rails API to handle my backend. At present, I am trying to update a list of articles based on user addition of an article. The ArticleForm component fires an action creator that is successfully updating my ArticleList. However, at present the life cycle method componentWillUpdate is firing continuously making axios requests to Rails, and Rails keeps querying my database and sending back the articleList.
Note: I have tried using shouldComponentUpdate as such to no avail, the DOM doesn't update:
// shouldComponentUpdate(newProps){
// return newProps.articleList !== this.props.articleList
// }
My question is: how can I use React's lifecycle methods to avoid this from happening and only happening when my articleList updates. Am I going down the wrong path using lifecycle methods? I'm fairly new to React/Redux so any and all advice is helpful!
I have the following container:
import React, { Component } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import ArticleForm from './ArticleForm'
import ArticleList from './ArticleList'
import removeArticle from '../actions/removeArticle'
import fetchArticles from '../actions/fetchArticles'
import updateArticleList from '../actions/updateArticleList'
class DumbArticleContainer extends Component {
componentWillMount() {
this.props.fetchArticles()
}
// shouldComponentUpdate(newProps){
// return newProps.articleList !== this.props.articleList
// }
componentWillUpdate(newProps){
if (newProps.articleList.articleList.count !== this.props.articleList.articleList.count){
this.props.updateArticleList()
}
}
render() {
return(
<div>
<ArticleForm />
<ArticleList articleList={this.props.articleList} />
</div>
)
}
}
const ArticleContainer = connect(mapStateToProps, mapDispatchToProps)(DumbArticleContainer)
function mapStateToProps(state) {
return {articleList: state.articleList}
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({removeArticle, fetchArticles, updateArticleList}, dispatch);
}
export default ArticleContainer
here is the ArticleForm
import React, { Component, PropTypes } from 'react'
import { reduxForm } from 'redux-form'
import addArticle from '../actions/addArticle.js'
class ArticleForm extends Component {
constructor(props) {
super(props)
this.state = {disabled: true}
}
/* Most article elements are displayed conditionally based on local state */
toggleState(){
this.setState({
disabled: !this.state.disabled
})
}
handleFormSubmit(props) {
event.preventDefault()
const {resetForm} = this.props
this.props.addArticle(props).then( ()=>{
var router = require('react-router')
router.browserHistory.push('/dashboard')
resetForm()
})
}
render() {
const disabled = this.state.disabled ? 'disabled' : ''
const hidden = this.state.disabled ? 'hidden' : ''
const {fields: {title, url}, handleSubmit} = this.props;
return (
<div className="article-form">
<form onSubmit={handleSubmit(this.handleFormSubmit.bind(this))}>
<button className="article-form-btn"
hidden={!hidden}
onClick={this.toggleState.bind(this)}
>
+ Add Article
</ button>
<input className="article-form-input"
hidden={hidden}
type="textarea"
placeholder="Title"
{...title}
/>
<input className="article-form-input"
hidden={hidden}
type="textarea"
placeholder="Paste Link"
{...url}
/>
{ this.state.disabled
? ''
: <input className="article-form-input"
type="submit"
value="Save"
/>
}
</form>
</div>
);
}
}
export default reduxForm({
form: 'articleForm',
fields: ['title', 'url']
},
null,
{ addArticle })(ArticleForm);
and the ArticleList
import React, { Component, PropTypes } from 'react'
import { connect } from 'react-redux'
import { bindActionCreators } from 'redux'
import removeArticle from '../actions/removeArticle.js'
import fetchArticles from '../actions/fetchArticles'
import { ListGroup } from 'react-bootstrap'
import { ListGroupItem } from 'react-bootstrap'
class Article extends Component {
render(){
var articleList = this.props.articleList.articleList
return(
<div>
<ListGroup>
{ articleList.slice(articleList.length - 10, articleList.length)
.map( (article) => {
return(
<ListGroupItem href="#" header={article.attributes.title}>
{article.attributes.url}
</ListGroupItem>
)}
)}
</ListGroup>
<div> View All Articles </div>
</div>
)
}
}
const ArticleList = connect(mapStateToProps, mapDispatchToProps)(Article)
function mapStateToProps(state) {
return {articleList: state.articleList}
}
function mapDispatchToProps(dispatch) {
return {removeArticle: bindActionCreators({removeArticle}, dispatch),
fetchArticles: bindActionCreators({fetchArticles}, dispatch)
}
}
export default ArticleList
action creator:
So here is my action creator import axios from 'axios'
import axios from 'axios'
function updateArticleList(){
const url = 'http://localhost:3000/api/v1/articles'
return axios.get(url).then( (response)=> {
return {
type: 'UPDATE_ARTICLE_LIST',
payload: response.data
}
})
}
export default updateArticleList
and reducer:
export default function articleReducer(state = {articleList: []}, action) {
switch(action.type){
case 'FETCH_ARTICLES':
return Object.assign({}, state, {articleList: action.payload.data});
case 'UPDATE_ARTICLE_LIST':
return Object.assign({}, state, {articleList: action.payload.data});
default:
return state
}
}
There is no issue with the store nor the action creators nor the reducers, they are all working pretty well. I can't really replicate the hundreds of queries rails is performing but am happy to include other code should anyone need to see it.
Thanks!
Your mapDispatchToProps is using bindActionCreators wrong. Instead of
function mapDispatchToProps(dispatch) {
return {removeArticle: bindActionCreators({removeArticle}, dispatch),
fetchArticles: bindActionCreators({fetchArticles}, dispatch)
}
}
you should use
function mapDispatchToProps(dispatch) {
return bindActionCreators({removeArticle, fetchArticles}, dispatch);
}
bindActionCreators can, as the name suggests, bind more than one action creator.
This probably won't solve your issue but an answer is the only place I could put this nicely.
Note that you'll need to fix how you're using it as well. No more double names.
I'd like to keep a state called shouldUpdateList. Whenever I fire a action that changes the list(add or update an item to the list), I set shouldUpdateList to true. Then,set it back to false whenever I fire ajax action to fetch the list.
The lifecycle event I use to check shouldUpdateList is componentWillReceiveProps, if it's true I fire a fetch action.
EDIT: I mean keep shouldUpdateList state in Redux store. Something like:
const INIT_STATE = {
list: [],
shouldUpdateList: false
}
then
case Action.ADD_NEW:
//set shouldUpdateList to true
case Action.FETCH_LIST:
//set shouldUpdateList to false
lastly, in component
componentWillReceiveProps(nextProps) {
if(nextProps.shouldUpdateList === true) {
//dispatch action FETCH_LIST
}
}

Decoupling React Components and Redux Connect

As seen here I am trying to decouple my app's components as much as I can and make them not aware of any storage or action creator.
The goal is to have them to manage their own state and call functions to emit a change. I have been told that you do this using props.
Considering
// Menu.jsx
import React from 'react'
import { className } from './menu.scss'
import Search from 'components/search'
class Menu extends React.Component {
render () {
return (
<div className={className}>
<a href='#/'>Home</a>
<a href='#/foo'>foo</a>
<a href='#/bar'>bar</a>
<Search />
</div>
)
}
}
And
// Search.jsx
import React from 'react'
import { className } from './search.scss'
class Search extends React.Component {
render () {
let { searchTerm, onSearch } = this.props
return (
<div className={`search ${className}`}>
<p>{searchTerm}</p>
<input
type='search'
onChange={(e) => onSearch(e.target.value)}
value={searchTerm}
/>
</div>
)
}
}
Search.propTypes = {
searchTerm: React.PropTypes.string,
onSearch: React.PropTypes.function
}
export default Search
And reading here I see a smart use of Provider and connect and my implementation would look something like this:
import { bindActionCreators, connect } from 'redux'
import actions from 'actions'
function mapStateToProps (state) {
return {
searchTerm: state.searchTerm
}
}
function mapDispatchToProps (dispatch) {
return bindActionCreators({
dispatchSearchAction: actions.search
}, dispatch)
}
export default connect(mapStateToProps, mapDispatchToProps)(Search)
Assuming I have a store handling searchTerm as part of the global state.
Problem is, where does this code belongs to? If I put it in Search.jsx I will couple actions with the component and more important to redux.
Am I supposed to have two different versions of my component, one decoupled and one connect()ed and have <Menu /> to use it? If yes what would my files tree look like? One file per component or a like a make-all-connected.js ?
In redux, exist a new kind of component that is called containers, this is the component that use connect(mapStateToProps, mapActionsToProps), to pass the state and actions to the current component.
All depends of the use of the component. For example, if you component Search only going to be use with the same state and action, You container could be the same that your component like this:
// Search.jsx
import { connect } from 'redux'
import actions from 'actions'
import React from 'react'
import { className } from './search.scss'
class Search extends React.Component {
render () {
let { searchTerm, onSearch } = this.props
return (
<div className={`search ${className}`}>
<p>{searchTerm}</p>
<input
type='search'
onChange={(e) => onSearch(e.target.value)}
value={searchTerm}
/>
</div>
)
}
}
Search.propTypes = {
searchTerm: React.PropTypes.string,
onSearch: React.PropTypes.function
}
function mapStateToProps ({searchTerm}) {
return {
searchTerm
};
}
const mapDispatchToProps = {
onSearch: actions.search
}
export default connect(mapStateToProps, mapDispatchToProps)(Search)
But if your plan is reuse this component in another containers and the searchTerm or the action are different on the global state. The best way is passing this properties through other containers, and keep the Search component pure. Like this:
// Container1.jsx
import { connect } from 'redux'
import actions from 'actions'
import React, { Component } from 'react'
class Container1 extends Component {
render() {
const { searchTerm, handleOnSearch } = this.props;
return (
<div>
<Search searchTerm={searchTerm} onSearch={handleOnSearch} />
</div>
)
}
}
function mapStateToProps ({interState: {searchTerm}}) {
return {
searchTerm
};
}
const mapDispatchToProps = {
handleOnSearch: actions.search
}
export default connect(mapStateToProps, mapDispatchToProps)(Container1)
// Container2.jsx
import { connect } from 'redux'
import otherActions from 'otheractions'
import React, { Component } from 'react'
class Container2 extends Component {
render() {
const { searchTerm, handleOnSearch } = this.props;
return (
<div>
<Search searchTerm={searchTerm} onSearch={handleOnSearch} />
</div>
)
}
}
function mapStateToProps ({otherState: {searchTerm}}) {
return {
searchTerm
};
}
const mapDispatchToProps = {
handleOnSearch: otherActions.search
}
export default connect(mapStateToProps, mapDispatchToProps)(Container2)
For more information, read the official docs about using redux with react.

Resources