adminjs icon indicating copy to clipboard operation
adminjs copied to clipboard

populating 'many to many association' records not working

Open ghost opened this issue 2 years ago • 2 comments

Hello there, hope everything is fine!

I'm developing an application including two tables in the database, respectively named Post and Tag. There's going to be a many to many association between the two resources. I'm using Sequelize as the ORM, and the code for defining the model for two entities is as follows:

const Post = sequelize.define('post', {
  title: {
    type: DataTypes.STRING,
  },
  summary: {
    type: DataTypes.STRING,
  },
  content: {
    type: DataTypes.STRING,
  }
});

const Tag = sequelize.define('tag', {
  title: {
    type: DataTypes.STRING,
  }
});

// Many-To-Many association between Post & Tag

Post.belongsToMany(Tag, { through: 'PostTags' });
Tag.belongsToMany(Post, { through: 'PostTags' });

I need to customize the edit and show component for the Tag property in the Post resource in the AdminJS panel in order to make the creation of the post resource with associated tags more user-friendly. I succeeded in creating a custom component for editing tag property using the library's own API and react-select async(multi) component. I just place the code of the edit component here for better clarification:

import React from "react";
import Select from "react-select/async";
import { FormGroup, FormMessage, Label } from "@adminjs/design-system";
import { EditPropertyProps, ApiClient, SelectRecord, RecordJSON } from "adminjs";

type SelectRecordEnhanced = SelectRecord & {
  record: RecordJSON;
};

export default function TagsSelect(props: EditPropertyProps) {
  const { onChange, property, record } = props;

  const handleChange = (selectedArr: SelectRecordEnhanced[]): void => {
    if (selectedArr) {
      onChange(property.path, JSON.stringify(selectedArr.map((selected) => selected.value)));
    } else {
      onChange(property.path, null);
    }
  };

  const loadOptions = async (inputValue: string): Promise<SelectRecordEnhanced[]> => {
    const api = new ApiClient();

    return api
      .searchRecords({
        resourceId: property.name,
        query: inputValue,
      })
      .then((optionRecords: RecordJSON[]) =>
        Promise.resolve(
          optionRecords.map((optionRecord) => ({
            label: optionRecord.title,
            value: optionRecord.id,
            record: optionRecord,
          }))
        )
      );
  };

  const error = record?.errors[property.path];

  return (
    <FormGroup>
      <Label required={property.isRequired}>{property.label}</Label>
      <Select
        cacheOptions
        defaultOptions
        isMulti
        loadOptions={loadOptions}
        onChange={handleChange}
        isClearable
      />
      <FormMessage>{error?.message}</FormMessage>
    </FormGroup>
  );
}

Therefore I can catch the tagsId in the after hook of the new action, and set the association of the tags using Sequelize mixins:

new: {
  after: async (response, request, context) => {
    let tagsId = JSON.parse(request.payload?.tags || '[]');
    let postId = response.record.params.id;
    const post = await db.Post.findByPk(postId);
    post.setTags(tagsId)
    return response;
  },
},

The logic for creating a new post and setting tags id works completely as expected! But in case I want to create the custom component for the show action of the post resource, when I try to get record.populated of the post record in the custom show component, I receive an empty object. However, I expect to get the related tags to the post as an array in the record.populated property. The property option for tag in the post resource is also as follows:

tags: {
  components: {
    edit: AdminJS.bundle("./components/TagsSelect.tsx"),
    show: AdminJS.bundle("./components/TagsList.tsx")
  },
  type: 'reference',
  resourceId: 'post'
},

How I'm supposed to make the request of API client of AdminJS to get the populated records in the corresponding resource? Setting the resource: 'Tag' property in the option above also seems to encounter error as I gave it a try. I would be grateful for any assistance and help.

ghost avatar Apr 18 '22 09:04 ghost

I'm facing the same issue. I've walked through the whole code like five times and I can't find any bugs. Have you figured it out?

periplox avatar Jun 02 '23 18:06 periplox

Did you find a solution for this?

lpbonomi avatar Oct 03 '23 12:10 lpbonomi