React-Admin: Using "getList", I am getting "Error: cannot read property 'map' of undefined"

Viewed 2715

With react-admin, i'm trying to get the users list from API Restful Node server,
and I have this error:

Error: cannot read property 'map' of undefined

Here's the getUsers in server user.controller.js:

const getUsers = catchAsync(async (req, res) => {
  const users = await userService.getUsers(req.query);
  const data = users.map(user => user.transform());
  const total = users.length;

  res.type('json');
  res.set('Access-Control-Expose-Headers', 'Content-Range');
  res.set('Content-Range', `users 0-2/${total}`);
  res.set('X-Total-Count', total);
  response = '{ data: ' + JSON.stringify(data) + ', total: ' + total + ' }';
  res.send(response);
});

Here the data response received:

{
 data: [
 {"id":"5e6f5e3b4cf60a67701deeae","email":"admin@test.com","firstname":"Ad","lastname":"Min","role":"admin"},
 {"id":"5e6f5e3b4cf60a67701deeaf","email":"test@test.com","firstname":"Jhon","lastname":"Doe","role":"user"}
 ],
 total: 2 
}

In react-admin, getList in dataProvider.js:

export default {
  getList: (resource, params) => {
    console.log(params);
    const { field, order } = params.sort;
    const query = {
      ...fetchUtils.flattenObject(params.filter),
      sortBy: field
    };
    const url = `${apiUrl}/${resource}?${stringify(query)}`;
    return httpClient(url).then(({ headers }, json) => ({
      data: json,
      total: parseInt(
        headers
          .get("Content-Range")
          .split("/")
          .pop(),
        10
      )
    }));
  },

Update: UserList.tsx

import React from "react";
import {
  TextField,
  Datagrid,
  DateInput,
  Filter,
  List,
  EmailField,
  SearchInput
} from "react-admin";
import { useMediaQuery, Theme } from "@material-ui/core";
import SegmentInput from "./SegmentInput";
import MobileGrid from "./MobileGrid";

const UserFilter = (props: any) => (
  <Filter {...props}>
    <SearchInput source="q" alwaysOn />
    <DateInput source="createdAt" />
    <SegmentInput />
  </Filter>
);

const UserList = (props: any) => {
  const isXsmall = useMediaQuery<Theme>(theme => theme.breakpoints.down("xs"));
  return (
    <List
      {...props}
      filters={<UserFilter />}
      sort={{ field: "createdAt", order: "desc" }}
      perPage={25}
    >
      {isXsmall ? (
        <MobileGrid />
      ) : (
        <Datagrid optimized rowClick="edit">
          <TextField source="id" />
          <EmailField source="email" />
          <TextField source="firstname" />
          <TextField source="lastname" />
          <TextField source="role" />
          <DateInput source="createdAt" />
          <DateInput source="updatedAt" />
        </Datagrid>
      )}
    </List>
  );
};

export default UserList;

Here the documentation with somes examples for getList:
https://marmelab.com/react-admin/DataProviders.html#writing-your-own-data-provider

I don't understand, i need help please, what wrong?

Thanks & Regards Ludo

1 Answers

Here's a detailed explanation of what's happening with the map() in reactjs.

And this source is on point for resolving this in nodejs

In your case, let's take a closer look here:

// user.controller.js

const getUsers = catchAsync(async (req, res) => {
  // Actually, it might be wiser to first declare users
+ let users = [];

  // You await for users (querying) to be resolved or rejected, cool
- const users = await userService.getUsers(req.query);

  // And we then we assign "users" as before
+ users = await userService.getUsers(req.query);

  // But here, map() requires users to be defined before iteration.
  // So for this to work, we need the value of users to be resolved first, right?
- const data = users.map(user => user.transform());
  
  // Could you change the above line to this, and see?
  // This doesn't because we gave map(), a synchronous parameter (function)
- const data = Promise.all(users.map(user => user.transform()));
  
  // Ok, break this up for easier understanding
  // let's declare a userList, where we give map() an "async" parameter
  // At this point, userList will be an "Array of Promises"
+ const userList = users.map(async user => user.transform());
  
  // Now we resolve all the Promises, and we should have our clean data
+ const data = await Promise.all(userList);
});

With that change, we make sure that map() runs after our users is resolved.
Does that work for you? Let me know.

Related