`request` is undefined when using load hook in Smart Action form

Expected behavior

Accessing request.body.data.attributes.ids[0]; inside a load hook allows me to fetch that record’s id.


collection("Question", {
  actions: [
    {
      name: "Add Required Answer",
      type: "single",
      fields: [
        {
          field: "Required Answer",
          description:
            "You can only see answers which are answers to previous questions",
          reference: "Answer.id",
          type: "String",
          isRequired: true,
        },
      ],
      hooks: {
        load: async ({ fields, request }) => {
          console.log("fields: ", fields, "request: ", request);
          const questionId = request.body.data.attributes.ids[0];
          return fields;
        },
      },
    },
  ]
});

Actual behavior

the request object is undefined.

Failure Logs

Here is the output of my console.log("fields: ", fields, "request: ", request)

fields:  {
  'Required Answer': {
    value: null,
    field: 'Required Answer',
    description: 'You can only see answers which are answers to previous questions',
    reference: 'Answer.id',
    type: 'String',
    isRequired: true,
    position: 0,
    defaultValue: null,
    enums: null,
    widget: null
  }
} request:  undefined

Context

  • Package Version: “^7.12.0”,
  • Express Version: “^4.17.1”
  • Sequelize Version: “^6.6.2”
  • Database Dialect: postgres
  • Database Version: 11
  • Project Name: forest-api

Thanks,
Camille

Hello @cooki,

The request parameter is not available in version 7.x.x of forest-express-sequelize.
You will need to upgrade to the V8 for accessing it.

Also do not hesitate to use the getIdsFromRequest helper as described in the documentation.

I hope it helps :wink:

thanks @Guillaume_Cisco :slight_smile:

1 Like

Hey @Guillaume_Cisco I got the following error after updating to version 8.0.5 and running my app, is this something you have seen before ?

/Users/camillefeghali/dev/foxxbee/forest-api/node_modules/forest-express/dist/services/exposed/abstract-records-service.js:20
    if (!params.timezone) throw new Error('Missing timezone in parameters');
                ^

TypeError: Cannot read property 'timezone' of undefined

Thanks

This is the whole stack trace:

/Users/camillefeghali/dev/foxxbee/forest-api/node_modules/forest-express/dist/services/exposed/abstract-records-service.js:20
    if (!params.timezone) throw new Error('Missing timezone in parameters');
                ^

TypeError: Cannot read property 'timezone' of undefined
    at new AbstractRecordService (/Users/camillefeghali/dev/foxxbee/forest-api/node_modules/forest-express/dist/services/exposed/abstract-records-service.js:20:17)
    at RecordCreator._createSuperInternal (/Users/camillefeghali/dev/foxxbee/forest-api/node_modules/forest-express/dist/services/exposed/record-creator.js:15:317)
    at new RecordCreator (/Users/camillefeghali/dev/foxxbee/forest-api/node_modules/forest-express/dist/services/exposed/record-creator.js:30:19)
    at Object.<anonymous> (/Users/camillefeghali/dev/foxxbee/forest-api/controllers/answers.js:20:23)
    at Module._compile (internal/modules/cjs/loader.js:956:30)
    at Object.Module._extensions..js (internal/modules/cjs/loader.js:973:10)
    at Module.load (internal/modules/cjs/loader.js:812:32)
    at Function.Module._load (internal/modules/cjs/loader.js:724:14)
    at Module.require (internal/modules/cjs/loader.js:849:19)
    at require (internal/modules/cjs/helpers.js:74:18)
    at /Users/camillefeghali/dev/foxxbee/forest-api/node_modules/require-all/index.js:56:46
    at Array.forEach (<anonymous>)
    at requireAll (/Users/camillefeghali/dev/foxxbee/forest-api/node_modules/require-all/index.js:34:9)
    at createServer (/Users/camillefeghali/dev/foxxbee/forest-api/server.js:40:3)
    at Object.<anonymous> (/Users/camillefeghali/dev/foxxbee/forest-api/app.js:9:13)

Seems to be caused by const recordCreator = new RecordCreator(Answer); in my controllers/answers.js file,

Which is imported into server.js by

    dirname: path.join(__dirname, "controllers"),
    recursive: true,
    resolve: (Module) => app.use("/", Module),
  });

Yes, absolutely @cooki,

You need to update all your overidden routes as described in the documentation for upgrading to the V8 version :slight_smile:

For example:

const { query, user } = req;

    // List helpers
    new RecordsGetter(MyModel, user, query).getAll();

@Guillaume_Cisco sweet !

1 Like

When developing a “Smart Action Form” and using a reference for a field, is there a way to filter the options ?

{
      name: "Add Required Answer",
      type: "single",
      fields: [
        {
          field: "required-answer",
          description:
            "You can only see answers which are answers to previous questions",
          reference: "Answer.id",
          type: "String",
          isRequired: true,
          widget: "belongsto select",
        },
      ],
      hooks: {
        load: async ({ fields, request }) => {
          const questionId = request.body.data.attributes.ids[0];
          console.log("questionId: ", questionId);
          return fields;
        },
      },
    },

I would like to limit the options available in the dropdown (coming from the Answer collection) with my custom logic.

Absolutely @cooki !

You can do your filtering inside the load hook function :clap:

Let me know if you succeed :wink:

Hey @Guillaume_Cisco I don’t know where to start, that’s why I’m reaching out I guess. I know how to get the data I would like to display, I just don’t know how to bind it to the fields object being returned from the load hook, should I place it in a particular key ?

Currently, if I log the fields object, I can see that it holds the following data:

fields:  [
  {
    field: 'required-answer',
    description: 'You can only see answers which are answers to previous questions',
    reference: 'Answer.id',
    type: 'String',
    isRequired: true,
    position: 0,
    defaultValue: null,
    enums: null,
    widgetEdit: { name: 'belongsto dropdown', parameters: {} },
    value: null
  }
]

No worries @cooki,

You can transform your required-answer with an Enum type, and then in your load hook, prefill its value with filtered answers.

Would that be ok for you?

Do not hesitate if you need more help :rocket:

With the following code I get the following UI, can you spot something wrong in this code snippet ?

    {
      name: "Add Required Answer",
      type: "single",
      fields: [
        {
          field: "required-answer",
          description:
            "You can only see answers which are answers to previous questions",
          type: "Enum",
          enums: [],
          isRequired: true,
          widget: "belongsto select",
        },
      ],
      hooks: {
      },
    },

Hum, I don’t think you need the widget key, furthermore, there is nothing in your load hook function.
Did you try to fill the enums or the value key?

Having the widget key messed it up, I got it to work with the following code:

    {
      name: "Add Required Answer",
      type: "single",
      fields: [
        {
          field: "required-answer",
          description:
            "You can only see answers which are answers to previous questions",
          type: "Enum",
          enums: [],
          isRequired: true,
        },
      ],
      hooks: {
        load: async ({ fields, request }) => {
          const questionId = request.body.data.attributes.ids[0];
          const requiredAnswerField = fields.find(
            (field) => field.field === "required-answer"
          );
          let answers = await Answer.findAll();
          requiredAnswerField.enums = answers;
          return fields;
        },
      },
    },

Though this gives me the following UI:

Now ideally, I would like to display a field (value), and when I hit submit, I would like send over the Id to my route.

If I do requiredAnswerField.enums = answers.map((answer) => answer.id);, then the UI shows Ids, and if I do equiredAnswerField.enums = answers.map((answer) => answer.value);, the UI would display what I want, but then would send over the value to my route and not the Id of the record.

Any Idea how I could get over this issue ?

Oh great!
It is working :star_struck:

Hum, indeed, if you need the value and the id, maybe you could also add a change hook who can fill the value of the field, with the answer selected. This way you could know the id of the selected answer.

Let me know if it helps :wink:

@Guillaume_Cisco , helps a lot, is there a way to hide a field ? have it be there but not display in the UI ?

Hum, unfortunately I don’t think you can do this :confused:

That’s ok, could you explain to me the logic of using a onChange hook to send over the ID again please ? I thought I had it understood but I cannot seem to persist data across hooks

Hello @cooki,

After some testing, I came to the conclusion it is not possible right now by using the Enum type.
By definition, your Enum values should be unique, so you should be able to retrieve them from your route with:

‌‌req['body']['data']['attributes']['values']['answers']

Then with this value, you could retrieve the id of the object pertained to this value.

Tell me if it helps :wink: