score:0

In your data example, there are multiple items with the same type (Ex: list).

This kind of nested objects is not recommended by Redux because you won't be able to optimize your rendering nor optimize the data invalidation.

One way to optimize is to flatten your data in order to look like this:

{
  isFetching: false,
  didInvalidate: false,
  data: [
    { type: 'list', id: 1, name: 'test' },
    { type: 'list', id: 2, name: 'test1' },
    { type: 'map', id: 3, name: 'test' },
    { type: 'map', id: 4, name: 'test1' },
    { type: 'list', id: 5, name: 'test' },
    { type: 'list', id: 6, name: 'test1' },
    { type: 'list', id: 7, name: 'test' },
    { type: 'list', id: 8, name: 'test1' },
  ]
}

app.js

class App extends React.Component {
  render() {
    return (
      <div>
        <ListComponent />
        <MapComponent />
      </div>
    );
  }
}

selectors.js

Use the reselect library in order to avoid useless rerender.

This selector is shared because you'll be able to use it for ListComponent, MapComponent and all your other component to find the items corresponding to your type.

import { createSelector } from 'reselect';

export const typeElementsSelector = createSelector(
  state => state.allElements.data,
  (allElements, props) => allElements.find(element => element.type === props.type),
);

ListComponent.js

The trick here is to only pass the element id and let the children check when to rerender.

Note that ListComponent will rerender only and only if there is any modification in the listElements value. Examples:

  • There is now a new item of a type 'list'
  • There is one removed item of type 'list'
  • One of the current 'list' item were modified

Code:

import React from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';

import { typeElementsSelector } from './selectors';

class ListComponent extends React.Component {
  render() {
    if (!this.props.listElements.length) return (<div>Empty</div>);

    return (
      <div>
        <h2>{this.props.listElements.length} list elements</h2>
        <div>
          {this.props.listElements.map(element => (
            <ListComponentItem key={`list-${element.id}`} id={element.id} />
          ))}
        </div>
      </div>
    );
  }
}

const mapStateToProps = (reducers, props) => ({
  listElements: typeElementsSelector(reducers, props),
});

export default connect(mapStateToProps)(ListComponent);

ListComponentItem.js

You can now display whatever you what you want. The component will rerender only and only if this item in the reducer has been modified

import React from 'react';
import { connect } from 'react-redux';
import { createSelector } from 'reselect';

class ListComponentItem extends React.Component {
  render() {
    return <div>{this.props.name}</div>
  }
}

const elementSelector = createSelector(
  state => state.allElements.data,
  (allElements, props) => allElements.find(element => element.id === props.id),
);

const mapStateToProps = (reducers, props) => ({
  element: elementSelector(reducers, props),
});

export default connect(mapStateToProps)(ListComponentItem);

The other way is to create a reducer for each type and use the same logic as above.

{
  listElements: {
    isFetching: false,
    didInvalidate: false,
    data: [
      { id: 1, name: 'test' },
      { id: 2, name: 'test1' },
      { id: 5, name: 'test' },
      { id: 6, name: 'test1' },
      { id: 7, name: 'test' },
      { id: 8, name: 'test1' },
    ]
  },
  mapElements: {
    isFetching: false,
    didInvalidate: false,
    data: [
      { id: 3, name: 'test' },
      { id: 4, name: 'test1' },
    ]
  },
}

Remember that your API might send you data formatted in a kind of way but you can reformat them as you desire.

An interesting article talking about this kind of optimization: https://medium.com/dailyjs/react-is-slow-react-is-fast-optimizing-react-apps-in-practice-394176a11fba

If you want to go further with data normalization best practices in redux: https://github.com/paularmstrong/normalizr

Hope it helps.

score:6

You can do the loop inside your thunk action and split the data into seperate reducers.

You Action could look something like this:

// action thunk
export function getScreenElements() {
  return function (dispatch) {
    // get data from server here
    const dataFromServer = fetch('url');
    // do your looping here and dispatch each data acording to its type
    dataFromServer.map(obj => {
      switch (obj.type) {
        case 'list':
          dispatch({
            type: 'LIST_RECEIVED',
            listElements: obj
          });

        case 'map':
          dispatch({
            type: 'MAP_RECEIVED',
            mapElements: obj
          });

        // ....
      }
    });
  }
}

Your separate reducers could look something like this:

// map reducer
export default function map(state = initialState, action) {
  if (action.type === 'MAP_RECEIVED') {
    return {
      ...state,
      mapElements: action.mapElements,
    }
  }
}


// list reducer
export default function list(state = initialState, action) {
  if (action.type === 'LIST_RECEIVED') {
    return {
      ...state,
      listElements: action.listElements,
    }
  }
}

And then you can render it like this:

// inside your component render mehtod
render(){
  const { list, map } = this.props; // redux data passed as props from the seperate reducers
  return (
    <div>
      <ListComponent elements={list}>
        <MapComponent elements={map}>
    </div>
  );
}

Edit
As a followup to your comment:

If I put allData to parent and pass allData.xyz to component in render when any data change parent will also reRender as it’s props is changed

Well, there is nothing wrong with Parent being re-rendered. when the render method of component runs it doesn't mean it will re-render to the physical DOM,It will update or override the object of the virtual DOM and then will check for differences between the physical and virtual DOM via the Reconciliation and The Diffing Algorithm to determine when, how and where should the physical DOM get updated.
It can update only certain attributes of DOM elements without re-render them, React Only Updates What’s Necessary.

There is also the option of React.PureComponent but i strongly advice against that in your case.


Related Query

More Query from same tag