2

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

Ludo
  • 103
  • 1
  • 15
  • Could you kindly provide the code snippet for where you are rendering this `` component at the client-side? I suspect the problem is within there. – MwamiTovi Mar 18 '20 at 03:23
  • Ok, problem might be originating from this line `const data = users.map(user => user.transform());`. At some point, `users` is undefined. See answer below. – MwamiTovi Mar 18 '20 at 03:36

1 Answers1

0

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.

Community
  • 1
  • 1
MwamiTovi
  • 2,425
  • 17
  • 25
  • Thanks you for the edit. That doesn't work with that change, i get the same error and the data response is ```{ data: {}, total: 2 }``` – Ludo Mar 18 '20 at 06:26
  • Try the latest changes. That should sort our challenge. And if possible, kindly share the kind of errors you get in case they are different from the original error. Thanks. – MwamiTovi Mar 18 '20 at 07:21
  • I tried with the latest changes, (i also added createdAt and updatedAt that was missing in the user model...), i get the same error, and data response: `{ data: [{"id":"5e71fba1c259111c00f847c0","email":"admin@test.com","firstname":"Ad","lastname":"Min","role":"admin","createdAt":"2020-03-18T10:44:49.393Z","updatedAt":"2020-03-18T10:44:49.393Z"},{"id":"5e71fba1c259111c00f847c1","email":"test@test.com","firstname":"Jhon","lastname":"Doe","role":"user","createdAt":"2020-03-18T10:44:49.411Z","updatedAt":"2020-03-18T10:44:49.411Z"}], total: 2 }` – Ludo Mar 18 '20 at 10:55
  • OK, at least there is progress. So looks like this server-side (`nodejs`) is now fine, right? Oh, i realize there is `user.transform()`. We might have to troubleshoot there too. Let me revert. – MwamiTovi Mar 18 '20 at 10:59
  • See that new update, it might be wiser to initialize "users" since we dealing with non-blocking code (asynchronous), `let users = [];` – MwamiTovi Mar 18 '20 at 17:27
  • 1
    With the new update i get the same error, and the data response seems good: `{ data: [{"id":"5e71fba1c259111c00f847c0","email":"admin@test.com","firstname":"Ad","lastname":"Min","role":"admin","createdAt":"2020-03-18T10:44:49.393Z","updatedAt":"2020-03-18T10:44:49.393Z"},{"id":"5e71fba1c259111c00f847c1","email":"test@test.com","firstname":"Jhon","lastname":"Doe","role":"user","createdAt":"2020-03-18T10:44:49.411Z","updatedAt":"2020-03-18T10:44:49.411Z"}], total: 2 }` I don't understand. – Ludo Mar 19 '20 at 15:56
  • Ok, I now suspect we have to inlclude `await` within here: `const userList = users.map(async user => await user.transform());`. – MwamiTovi Mar 19 '20 at 16:01
  • This should be a challenge between the `node` server and `dataProvider`. Could you kindly share a [code snippet in a gist](https://gist.github.com/discover) or whatever you prefer so that I could take a closer look? `map()` being called before it's ready. – MwamiTovi Mar 21 '20 at 15:57
  • Like this? https://gist.github.com/lud-o/16f0020352d711a116a2d7d3ca718762 – Ludo Mar 23 '20 at 04:32
  • Yep, that's great. Will review and revert. Please add the `utils/catchAsync` file since you use it everywhere. – MwamiTovi Mar 23 '20 at 10:38