Catarina Nogueira
Catarina Nogueira

Reputation: 1138

Action and state shown on console but undefined on application with React hooks

I am trying to create an application that read from an API and print on the screen the list of itens but is not working.

I did a codesandbox here with a Fake API: https://codesandbox.io/s/heuristic-wright-on220 and I explain below the problem going though the code

First I have created my store on index.js

const store = createStore(reducer, applyMiddleware(...middleware));

const App = () => {
  return (
    <Router>
      <Switch>
        <Route path="/" exact component={Dashboard} />
        <Route component={NoMatchRoute} />
      </Switch>
    </Router>
  );
};

I have created my state on metrics.js

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

And finally on Dashboard.tsx I print on the screen bu updating the state by doing an api call with 'fetchMetrics':

const catalog = useSelector<RootState, MetricState>(
    (state) => state.metricsList
  );

  const dispatch = useDispatch();

  const classes = useStyles();
  useEffect(() => {
    dispatch(appReducer.actionCreators.fetchMetrics());
  }, []);

Here the definition of fetchMetrics:

   export function fetchMetrics() {
      const action = {
        type: ACTION_TYPES.FETCH_METRICS,
         payload: []
       };
      return fetchMetricsCall(action);
    }
    const fetchMetricsCall = (action) => async (dispatch) => {
      try {
        const res = await axios.get("/api/foo");
        dispatch({
          type: action.type,
          payload: res.data //contains the data to be passed to reducer
        });
      } catch (e) {
        dispatch({
          type: ACTION_TYPES.FAILURE,
          payload: console.log(e) //TODO: fix return type
        });
      }
    };
    
    export default {
      fetchMetrics
    };

On index.js I create the state and connect the provider

const store = createStore(
    rootReducer,
    applyMiddleware(...middleware)
)

const App = () => {

  return (
    <Router>
      <Switch>
        <Route path="/" exact component={Dashboard} />
        <Route component={NoMatchRoute} />
      </Switch>
    </Router>
  );
};

And this is my reducer:

const initialState: MetricState = {
  metrics: []
};

const fetchMetrics = (
  state = initialState,
  action: MetricAction
): MetricState => {
  switch (action.type) {
    case ACTION_TYPES.FETCH_METRICS:
      return {
        ...state,
        metrics: action.payload
      };
    case ACTION_TYPES.CHOOSE_METRIC:
      return {
        ...state,
        metrics: action.payload
      };
    default:
      return {
        ...state
      };
  }
};

const rootReducer = combineReducers({
  fetchMetrics
});

export default rootReducer;

I just can't make it work. I have already read lots of Stack Overflow questions and tutorials and I just cannot find my error. On the image it's possible to see that the data is fetched from the real API but do not show on the screen.

enter image description here

Upvotes: 3

Views: 67

Answers (1)

Michael Hoobler
Michael Hoobler

Reputation: 622

It doesn't look like state.metricsList actually exists inside the state, and inside the Dashboard component is (state) => state.metricsList. Your state only has fetchMetrics.metrics (combineReducer is going to add object onto the state with the key of the reducer's name)

So right now, the structure of your state looks like this:

state: { 
  fetchMetrics: { 
    metrics: [] 
  }
}

I think the main two things you need to change are:

metrics.ts

export type RootState = {
  fetchMetrics: MetricState;
};

Dashboard.tsx

const catalog = useSelector<RootState, MetricState>(
  (state) => state.fetchMetrics
);

In the code sandbox metrics is some weird HTML error, let me know if my answer doesn't work.

Upvotes: 1

Related Questions