Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

React + Redux: Component does not update

Trying out React + Redux, and probably am doing something obviously stupid, because a component that fires an action to fetch data over the network does not get updated (re-rendered) when the data is fetched.

Here are the relevant bits of my code:

The top-level index.js serving as an entry point for the app:

import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { Router, browserHistory } from 'react-router';
import reduxPromise from 'redux-promise';
import createLogger from 'redux-logger';

const logger = createLogger();

import routes from './routes';
import reducers from './reducers';

const createStoreWithMiddleware = applyMiddleware(reduxPromise, logger)(createStore);

ReactDOM.render(
  <Provider store={createStoreWithMiddleware(reducers)}>
    <Router history={browserHistory} routes={routes} />
  </Provider>
  , document.querySelector('.container'));

Top-level container App:

import React, {Component} from 'react';
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as Actions from '../actions';
import Header from '../components/header';
import Showcase from '../components/showcase';

function mapStateToProps(state) {
  return {
    resources: state.resources
  }
}

function mapDispatchToProps(dispatch) {
  return {
    fetchResources: () => {
      dispatch(Actions.fetchResources());
    }
  }
}


class App extends Component {

  render() {
    console.log('props in App', this.props);
    return (
      <div>
        <Header/>
        <Showcase
          fetchResources={this.props.fetchResources}
          resources={this.props.resources}
        />
      </div>
    );
  }
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(App)

Component that triggers an action to sends a request for data when it is about to mount and is supposed to show the fetched data:

import React, {Component} from 'react';
import {connect} from 'react-redux';

class Showcase extends Component {
  constructor(props) {
    super(props);
  }

  componentWillMount() {
    this.props.fetchResources();
  }

  render() {
    console.log('resources', this.props);
    return (
      <div>
        This is showcase
      </div>
    );
  }
}

export default connect(state => ({resources: state.resources}))(Showcase)

Action Creator:

import * as types from '../constants/ActionTypes';
import axios from 'axios';

export function fetchResources() {
  return {
    type: types.FETCH_FIRST,
    payload: axios.get('/sampledata/1.json')
  }
}

Reducer for the fetch action:

import * as types from '../constants/ActionTypes';

export default function resourcesReducer (state={}, action) {
  switch (action.type) {
    case types.FETCH_FIRST:
      console.log('about to return', Object.assign (state, {resources: action.payload.data }))
      return Object.assign (state, {resources: action.payload.data });
    default:
      return state
  }
};

and finally the root reducer:

import { combineReducers } from 'redux';
import navigationReducer from './navigation-reducer';
import resourcesReducer from './resources-reducer';

const rootReducer = combineReducers({
  navigationReducer,
  resourcesReducer
});

export default rootReducer;

So, here is what I am observing. The action to request data is successfully triggered, a request is sent, the reducer receives it when the promise is resolved, and updates the state with the fetched data. At this point, I would expect the top-level App component and the Showcase component to detect that the store has updated, and to re-render, but I do not see it in the console.

Also, I am confused by redux-logger’s console output:

enter image description here

Specifically, I am surprized to see that the state contains reducers from the rootReducer — I don't know if it's right (an example on Redux logger Github page shows a state without reducers). It also seems surprising that the prev state as reported by redux-logger contains the same resourcesReducer object as the next state, although intuitively I would expect prev state to be more or less empty.

Could you please point out what I am doing wrong and how to get React components respond to the state changes?

==================================================

UPDATED:

1) Changed the mapStateToProps function in the App component so that it correctly maps to reducer states:

function mapStateToProps(state) {
  return {
    resources: state.resourcesReducer
  }
}

2) Still passing the resources down to the `Showcase component:

  render() {
    console.log('props in App', this.props);
    return (
      <div>
        <Header navigateActions={this.props.navigateActions}/>
        React simple starter
        <Showcase
          fetchResources={this.props.fetchResources}
          resources={this.props.resources}
        />
      </div>
    );

3) Trying to display resources on the screen by stringifying it to see what’s actually inside this object:

  render() {
    console.log('resources', this.props);
    return (
      <div>
        This is showcase {JSON.stringify(this.props.resources)}
      </div>
    );
  }

See this on the screen: This is showcase {}. The component does not seem to re-render.

Here’s the screenshot of the console showing that App’s props have updated with the values from the next state. Still, that did not cause the component to re-render:

enter image description here

UPDATED AGAIN: And my javascript-fu was poor, too. I did not quite realize that by returning Object.assign (state, {resources: action.payload.data }); I was in fact mutating the state, and that a simple inversion of arguments would let me achieve what I intended. Thanks to this discussion on SO for enlightenment.

like image 406
azangru Avatar asked Mar 15 '16 00:03

azangru


People also ask

Why isn't my React component updating?

There are two common reasons why React might not update a component even though its props have changed: The props weren't updated correctly via setState. The reference to the prop stayed the same.

How do I refresh Redux state?

Just connect the redux state with the component's state by using useSelector hooks, so the component will automatically re-rendered when the redux state changes.

How do you force update a component in React?

Forcing an update on a React class component In any user or system event, you can call the method this. forceUpdate() , which will cause render() to be called on the component, skipping shouldComponentUpdate() , and thus, forcing React to re-evaluate the Virtual DOM and DOM state.

Why is React not updating on state change?

State updates in React are asynchronous; when an update is requested, there is no guarantee that the updates will be made immediately. The updater functions enqueue changes to the component state, but React may delay the changes, updating several components in a single pass.


1 Answers

I am surprized to see that the state contains reducers from the rootReducer

This is how it works. Take a closer look at combineReducers().

const rootReducer = combineReducers({
  navigationReducer,
  resourcesReducer
});

Recognise that it's not a list of parameters; it's a single object parameter. Perhaps it is clearer in verbose syntax:

var rootReducer = combineReducers({
  navigationReducer: navigationReducer,
  resourcesReducer: resourcesReducer
});

The resourcesReducer key points to the state returned by the resourcesReducer() function. That is, the state variable within the resourcesReducer() is just one part of the entire state.

The functions passed to connect() take the entire state as an argument. What yours should actually look like is this:

export default connect(state => ({
  resources: state.resourcesReducer.resources
}))(Showcase);
like image 96
David L. Walsh Avatar answered Sep 21 '22 16:09

David L. Walsh