Search code examples
reactjsreduxreact-reduxreact-routernavigation

How do I stop state store data from accumulating in a redux component every time I navigate to it using react router


Okay, caveat is that I'm very very new to redux. I'm doing a course on it atm and I'm trying to step outside the box a little and generate a fairly standard website using the wordpress API and Redux. I appreciate that redux is generally meant for larger things but this seems like a useful first step in the learning process.

I have a series of components which list out posts, pages and different types of custom posts taken from the wordpress API and I navigate between these using react-router-dom.

The problem is that every time I go back to a component/view the list of posts or pages is rendered again so, for example, the first time I go there the list might be: test post 1, test post 2, the second time it would be: test post 1, test post 2, test post 1, test post 2, the third time: test post 1, test post 2, test post 1, test post 2, test post 1, test post 2 etc etc etc.

The reason for this is obvious, each time the component is rendered the data gets pulled from the store and rendered, however, as the entire app doesn't rerender as it would be with plain old reactjs, it doesn't cleared.

My question, of course is what's the best way of going about fixing this. I've read some kind of related posts which advise attaching some kind of condition to the component to check whether the data is already present but I've no idea how to do this and can't find out how. My attempts haven't worked because it seems that any var returned from componentDidMount is not seen in the render method.

Thanks in advance.

Code is below:

src/index.js

import React from "react";
import { BrowserRouter as Router } from 'react-router-dom';
import { render } from "react-dom";
import { Provider } from "react-redux";
import store from "./js/store/index";
import App from "./js/components/App";
render(
  <Router>
    <Provider store={store}>
      <App />
    </Provider>
  </Router>,
  document.getElementById("root")
);

src/js/index.js

import store from "../js/store/index";
window.store = store;

src/js/store/index.js

import { createStore, applyMiddleware, compose } from "redux";
import rootReducer from "../reducers/index";
import thunk from "redux-thunk";
const storeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const store = createStore(
  rootReducer,
  storeEnhancers(applyMiddleware(thunk))
);
export default store;

src/js/reducers/index.js

import { POSTS_LOADED } from "../constants/action-types";
import { PAGES_LOADED } from "../constants/action-types";

const initialState = {
  posts: [],
  pages: [],
  banner_slides: [],
  portfolio_items: []
};

function rootReducer(state = initialState, action) {

    switch (action.type) {
      case 'POSTS_LOADED':
          return Object.assign({}, state, {
            posts: state.posts.concat(action.payload)
          });

      case 'PAGES_LOADED':
          return Object.assign({}, state, {
            pages: state.pages.concat(action.payload)
          });

      default: 
          return state;
    }
}

export default rootReducer;

src/js/actions/index.js

export function getWordpress(endpoint) {
    return function(dispatch) {
        return fetch("http://localhost/all_projects/react-wpapi/my_portfolio_site/wordpress/wp-json/wp/v2/" + endpoint )
            .then(response => response.json())
            .then(json => { 
            dispatch({ type: endpoint.toUpperCase() + "_LOADED", payload: json });
        });
    };
}

src/js/constants/action-types.js

export const ADD_ARTICLE = "ADD_ARTICLE";
export const POSTS_LOADED = "POSTS_LOADED";
export const PAGES_LOADED = "PAGES_LOADED";

src/js/components/app.js

import React from "react";
import { Route, Switch, Redirect } from 'react-router-dom';

import Header from "./Header/Header";
import Posts from "./Posts";
import Pages from "./Pages";
import BannerSlides from "./BannerSlides";
import PortfolioItems from "./PortfolioItems";

const App = () => (
    <div>
        <Header />
        <Route render = {({ location }) => (
            <Switch location={location}>
            <Route 
                exact path="/posts"
                component={Posts} 
            />   
            <Route 
                exact path="/pages"
                component={Pages} 
            />   
            </Switch>
        )} />
    </div>
);
export default App;

src/js/components/Posts.js

import React, { Component } from "react";
import { connect } from "react-redux";
import { getWordpress } from "../actions/index";
export class Posts extends Component {

  componentDidMount() {
      this.props.getWordpress('posts');
      let test = 1;
      return test;
  }

  render() {
    console.log("test: ", test); // not defined
      if (test !== 1) { 
          return (
            <ul>
              {this.props.posts.map(item => ( 
                <li key={item.id}>{item.title.rendered}</li>
              ))}
            </ul>
          );
      }
  }
}
function mapStateToProps(state) {
    return {
        posts: state.posts.slice(0, 10)
    };
}
export default connect(
  mapStateToProps,
  { getWordpress }
)(Posts);

Solution

  • The problem was that, every time you were fetching data, you were adding it to previous data in the array. That's why it was duplicating over time. Just assign instead of adding it in your reducer

    function rootReducer(state = initialState, action) {
    switch (action.type) {
      case 'POSTS_LOADED':
          return {
            ...state,
            posts: action.payload
          };
    
      case 'PAGES_LOADED':
          return {
            ...state,
            pages: action.payload
          };
    
      default: 
          return state;
     }
    }