Catarina Nogueira
Catarina Nogueira

Reputation: 1138

Filter item on item list that is on state using useState and useEffect

I have a list of items on my application and I am trying to create a details page to each one on click. Moreover I am not managing how to do it with useState and useEffect with typescript, I could manage just using componentDidMount and is not my goal here.

On my state, called MetricState, I have "metrics" and I have seen in some places that I should add a "selectedMetric" to my state too. I think this is a weird solution since I just want to be able to call a dispatch with the action of "select metric with id x on the metrics list that is on state".

Here is the codesandbox, when you click on the "details" button you will see that is not printing the name of the selected metric coming from the state: https://codesandbox.io/s/react-listing-forked-6sd99

This is my State:

export type MetricState = {
  metrics: IMetric[];
};

My actionCreators.js file that the dispatch calls

export function fetchMetric(catalog, metricId) {
  const action = {
    type: ACTION_TYPES.CHOOSE_METRIC,
    payload: []
  };
  return fetchMetricCall(action, catalog, metricId);
}

const fetchMetricCall = (action, catalog, metricId) => async (dispatch) => {
  dispatch({
    type: action.type,
    payload: { metrics: [catalog.metrics.filter((x) => x.name === metricId)] } //contains the data to be passed to reducer
  });
};

and finally the MetricsDeatail.tsx page where I try to filter the selected item with the id coming from route parametes:

const metricId = props.match.params.metricId;
  const catalog = useSelector<RootState, MetricState>(
    (state) => state.fetchMetrics
  );

  const selectedMetric: IMetric = {
    name: "",
    owner: { team: "" }
  };
  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(appReducer.actionCreators.fetchMetric(catalog, metricId));
  }, []);

Upvotes: 0

Views: 221

Answers (1)

Linda Paiste
Linda Paiste

Reputation: 42198

On my state, called MetricState, I have "metrics" and I have seen in some places that I should add a "selectedMetric" to my state too. I think this is a weird solution since I just want to be able to call a dispatch with the action of "select metric with id x on the metrics list that is on state".

I agree with you. The metricId comes from the URL through props so it does not also need to be in the state. You want to have a "single source of truth" for each piece of data.

In order to use this design pattern, your Redux store needs to be able to:

  • Fetch and store a single Metric based on its id.
  • Select a select Metric from the store by its id.

I generally find that to be easier with a keyed object state (Record<string, IMetric>), but an array works too.

Your component should look something like this:

const MetricDetailsPage: React.FC<MatchProps> = (props) => {
  const metricId = props.match.params.metricId;

  // just select the one metric that we want
  // it might be undefined initially
  const selectedMetric = useSelector<RootState, IMetric | undefined>(
    (state) => state.fetchMetrics.metrics.find(metric => metric.name === metricId )
  );

  const dispatch = useDispatch();

  useEffect(() => {
    dispatch(appReducer.actionCreators.fetchMetric(metricId));
  }, []);

  return (
    <div>
      <Link to={`/`}>Go back to main page</Link>
      Here should print the name of the selected metric:{" "}
      <strong>{selectedMetric?.name}</strong>
    </div>
  );
};

export default MetricDetailsPage;

But you should re-work your action creators so that you aren't selecting the whole catalog in the component and then passing it as an argument to the action creator.

Upvotes: 1

Related Questions