4
\$\begingroup\$

I'm wondering if my approach to creating a filtered array (filteredLaunches) from an array of data (launches) is an anti-pattern, or not? Is there a better way to go about it?

Here is the code:

const initialState = {
  launches: [],
  filteredLaunches: []
};

export default function(state = initialState, action) {

  switch (action.type) {

    case 'getLaunches':
      return Object.assign({}, state, {
        launches: action.launches
      });

    case 'filterLaunches':

      let filteredLaunches = [];

      for (let i = 0; i < state.launches.length; i++) {
        if (action.location === state.launches[i].location.countryCode)
          filteredLaunches.push(state.launches[i]);
      }

      return Object.assign({}, state, {
        filteredLaunches: filteredLaunches
      });

    default:
      return state;
  }
};

\$\endgroup\$
5
\$\begingroup\$

Well, you could use Array.filter to filter the launches.

...
case 'filterLaunches':
  return Object.assign({}, state, {
    filteredLaunches: state.launches.filter(function(launch) {
      return action.location === launch.location.countryCode
    })
  }
default:
 return state;

The other question is, why not filtering inside the Component directly?

Because your next problem will be, that each time you add launches, you will have to filter them and copy again, and again.

So to not create duplicates, it's better to set the filter in the reducer and perform the filtering inside the render

So:

export default function(state = initialState, action) {
  case 'SET_LAUNCHES':
    return {...state, launches: action.launches };
  case 'SET_FILTER':
    return {...state, filter: action.filter };
  default:
    return state;
}

export function filterLaunches(launches, filter = {location: 'ES'} ) {
  const filterKeys = Object.keys(filter);
  return launches.filter((launch) => {
    return filterKeys.reduce((acc, key) => acc && launch[key] === filter[key], true)
  });
}

// Then in the component
import { filterLaunches } from '../launchesStore';

const LaunchesList = ({launches, filter}) => (
  <ul>
    {filterLaunches(launches, filter).map((launch) => (
      <li>Launch: {launch.name}</li>
    ))}
  </ul>
);

export default connect(LaunchesList);

It's good to keep the filterLaunches function next to the reducer, because this function is very related to this part of the state and if you change the format like from an array to a map-by-id you would have to change that function only in one place.

\$\endgroup\$
  • \$\begingroup\$ Thanks. I need to read up on the Array methods; no point in reinventing the wheel. Your second point had me laugh, because that was, pretty much, what I did before I put the logic into the reducer as opposed to in the component. My reason for changing my mind about this approach was that I felt it would be "dirty" to use a utility function in the component itself for the filterting, which brings me to my next question; where do you draw the line between logic that is handled through utility functions, or local React state, and what should be handled by Redux? \$\endgroup\$ – Happy Koala Mar 5 '17 at 15:03
  • \$\begingroup\$ Well, this are good questions and I guess everybody will ask those. But after some time working with Redux & React you will get a feeling for that. But I would't recommend to build with optimization in mind, instead build the app, the simple way, and optimize later. premature optimization is the root of all evil ;) \$\endgroup\$ – webdeb Mar 5 '17 at 17:03
  • \$\begingroup\$ Wax on, wax off, I guess :) Thanks for the tip, I'll keep that in mind. \$\endgroup\$ – Happy Koala Mar 5 '17 at 18:25

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.