tqr_aupa_atleti
tqr_aupa_atleti

Reputation: 148

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!

Upvotes: 3

Views: 1916

Answers (2)

Dong Do
Dong Do

Reputation: 56

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

Upvotes: 0

DDS
DDS

Reputation: 4375

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.

Upvotes: 1

Related Questions