adminjs
adminjs copied to clipboard
populating 'many to many association' records not working
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.
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?
Did you find a solution for this?