1
votes

I have a problem with my redux app: it correctly dispatches the relevant action and updates the state, but for some reason the UI doesn't get updated. 99% of questions about this seem to be caused because the state actually gets mutated, but I'm pretty sure that's not the case. Here are the relevant files:

Container component:

import React from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import PropTypes from 'prop-types';
import requestEvents from 'actions/feedActions';
import { FeedElement, feedElementTypes } from '../components/feed/feedElement';

class Feed extends React.Component {
  componentWillMount() {
    this.props.requestEvents();
  }
  render() {
    console.log('Rendering feed');
    const listOfFeedElements = this.props.listOfFeedElements;
    let elementsToDisplay;
    if (listOfFeedElements.length === 0) {
      elementsToDisplay = <li><p> No elements to display</p></li>;
    } else {
      const numOfElementsToDisplay = listOfFeedElements.length <= 10 ?
      listOfFeedElements.length : 10;

      const sortedElementsToDisplay = listOfFeedElements.concat().sort(
      (e1, e2) => e1.timestamp - e2.timeStamp);

      elementsToDisplay =
      sortedElementsToDisplay
        .slice(0, numOfElementsToDisplay)
        .map(el => FeedElement(el));
    }

    return (
      <div className="socialFeedContainer">
        <h1> Social feed </h1>
        <ol>
          {elementsToDisplay}
        </ol>
      </div>

    );
  }
}

Feed.propTypes = {
  requestEvents: PropTypes.func.isRequired,
  listOfFeedElements: PropTypes.arrayOf(PropTypes.shape({
    timeStamp: PropTypes.number.isRequired,
    type: PropTypes.oneOf(Object.keys(feedElementTypes)).isRequired,
    targetDeck: PropTypes.string.isRequired,
    concernedUser: PropTypes.string.isRequired,
    viewed: PropTypes.bool.isRequired })),
};

Feed.defaultProps = {
  listOfFeedElements: [],
};


function mapDispatchToProps(dispatch) {
  return bindActionCreators({ requestEvents }, dispatch);
}

function mapStateToProps(state) {
  const { feedState } = state.local.feed.listOfFeedElements;
  return {
    feedState,
  };
}

export default connect(mapStateToProps, mapDispatchToProps)(Feed);

Reducers:

import Immutable from 'seamless-immutable';
import { types } from 'actions/feedActions';

const initialState = Immutable({
  listOfFeedElements: [],
  sentRequestForList: false,
  receivedList: false,
});

function feedReducer(state = initialState, action) {
  switch (action.type) {
    case types.REQUEST_FEED_ELEMENTS:
      return Immutable.merge(state, {
        sentRequestForList: true,
        receivedList: false,
      });
    case types.RECEIVED_LIST:
      return Immutable.merge(state, {
        sentRequestForList: false,
        receivedList: true,
        listOfFeedElements: action.payload.listOfNotifications,
      });
    default:
      return state;
  }
}

export default feedReducer;

Saga:

import { takeLatest, put } from 'redux-saga/effects';

import { types } from 'actions/feedActions';
import feedApiCall from './feedApiCall';

export function* getFeedFlow() {
  try {
    console.log('sent request for feed');

    const listOfNotifications = feedApiCall();

    yield put({
      type: types.RECEIVED_LIST,
      payload: {
        listOfNotifications,
      },
    });
  } catch (error) {
    yield put({
      type: types.RECEPTION_ERROR,
      payload: {
        message: error.message,
        statusCode: error.statusCode,
      },
    });
  }
}

function* feedUpdateWatcher() {
  yield takeLatest(types.REQUEST_FEED_ELEMENTS, getFeedFlow);
}

export default feedUpdateWatcher;

The actions get dispatched and the state modified (In the end I have a list with the components). However the component renders only once, as I can check from the call to console.log.

1
I'm still rusty when it comes to redux, but in your mapStateToProps you seem to be binding a property feedState while your component renders a property listOfFeedElements. - Gimby
which action is called REQUEST_FEED_ELEMENTS or RECEIVED_LIST ? - Julien TASSIN
@Gimby: Nice catch, but it's not the problem. The console.log('Rendering feed'); only prints once, which means that the rendering function is not even called a second time. - Gloomy
@JulienTASSIN : REQUEST_FEED_ELEMENTS is called when the component gets mounted. It's captured by the saga middleware and causes an API call. The saga middleware then dispatches a RECEIVED_LIST action on reception of the response. - Gloomy

1 Answers

1
votes

The root component looks quite suspiciously.

First, proceeding from a code and agreements on naming, FeedElement the class looks as React, however it is used as simple function in map. Whether there was no similar look in view of something:

elementsToDisplay =
      sortedElementsToDisplay
        .slice(0, numOfElementsToDisplay)
        .map(el => (<FeedElement {...el} />));

Secondly, you return object const {feedState} = state.local.feed.listOfFeedElements; and further you address this.props.listOfFeedElements - such field in principle just doesn't exist. And from where a part of state.local.feed undertakes?

In addition, time you is used by redux and saga, it is better to make generally a pure functional part and to get rid of a call of componentWillMount which not really corresponds to the expected architecture.