Skip to content

Bad search results rendered in admin UI when using the React framework (item list rendering issue) #31136

@ender1975

Description

@ender1975
Overview of the issue

When using the React framework for JHipster admin UI, the JHipster generated components for handling entity list views show bad search results.

The user sees bad items in the search list results, but the backend returns correct data - verified with the request response in the Network tab. This issue is related to using the list item order number value for the key attribute when rendering lists in the respective UI components, instead of using unique values like an entity id.

NOTE Issue seems to be entity model independent and related purely to how React handles list rendering, so the JHipster info is attached only for posterity and completeness.

JHipster info
JHipster configuration, a .yo-rc.json file generated in the root folder
.yo-rc.json file
{
  "generator-jhipster": {
    "applicationType": "monolith",
    "authenticationType": "oauth2",
    "baseName": "backendServicesPrototype",
    "buildTool": "maven",
    "cacheProvider": "redis",
    "clientFramework": "react",
    "clientTestFrameworks": [],
    "clientTheme": "darkly",
    "clientThemeVariant": "dark",
    "creationTimestamp": 1757786915003,
    "databaseType": "sql",
    "devDatabaseType": "postgresql",
    "devServerPort": 9060,
    "enableHibernateCache": true,
    "enableTranslation": true,
    "entities": [
      "Photo",
      "Tag",
      "PhotoMetadata",
      "Contest",
      "ContestEntry",
      "Award",
      "TagCategory",
      "TagCategoryI18N",
      "UserProfile",
      "Gallery",
      "GalleryI18N",
      "UserTag",
      "UserEquipment"
    ],
    "feignClient": null,
    "jhipsterVersion": "8.11.0",
    "languages": [
      "pl",
      "en"
    ],
    "lastLiquibaseTimestamp": 1760134455000,
    "microfrontend": null,
    "microfrontends": [],
    "nativeLanguage": "pl",
    "packageName": "[***]",
    "prodDatabaseType": "postgresql",
    "reactive": false,
    "searchEngine": "elasticsearch",
    "serverPort": null,
    "serviceDiscoveryType": null,
    "skipUserManagement": true,
    "syncUserWithIdp": true,
    "testFrameworks": [
      "gatling"
    ],
    "withAdminUi": true
  }
}
Environment and Tools

openjdk version "21.0.3" 2024-04-16 LTS
OpenJDK Runtime Environment Temurin-21.0.3+9 (build 21.0.3+9-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.3+9 (build 21.0.3+9-LTS, mixed mode)

git version 2.51.1

node: v22.15.1
npm: 10.9.2

Docker version 28.5.1, build e180ab8ab8

JDL for the Entity configuration(s) entityName.json files generated in the .jhipster directory
JDL entity definitions
/**
 * Entity Photo - Photo
 */
@ChangelogDate("20250913181332")
entity Photo {
  url String required
  thumbnailUrl String required
  contentType String required
  createdAt Instant required
  updatedAt Instant
  width Integer required
  height Integer required
  license License required
  isActive Boolean required
  fileSize Long required
}
/**
 * Entity Tag - Tag.\n\nTags have name and category. Combination of name and category is unique.
 */
@AddLiquibaseChangeSet("classpath:config/liquibase/changelog/20251005_add_unique_tag.xml")
@ChangelogDate("20250913181333")
entity Tag {
  name String required minlength(2) maxlength(50)
}
/**
 * Entity PhotoMetadata - Metadata of the photo.\n\nThis entity stores the metadata of the photo that was added to the service.\nMetadata for the purpose of a Contest is stored in a separate instance of\nPhotoMetadata as it can be customized for the purpose of the Contest.
 */
@ChangelogDate("20251009212632")
entity PhotoMetadata {
  name String required minlength(2) maxlength(200)
  description String maxlength(500)
  timeOfDay TimeOfDay
  season Season
  district District
  createdAt Instant required
  updatedAt Instant
}
/**
 * Entity Contest - Represents a monthly photo contest (or some other photo contest).
 */
@ChangelogDate("20251009212633")
entity Contest {
  name String required minlength(2) maxlength(50)
  description String maxlength(500)
  startDate Instant required
  endDate Instant required
  status ContestStatus required
}
/**
 * Entity ContestEntry - Contest entry.\n\nThis entity stores information on the photo used in the contest.
 */
@ChangelogDate("20251009212634")
entity ContestEntry {
  votes Integer required
  createdAt Instant required
  updatedAt Instant
  isActive Boolean required
}
/**
 * Entity Award - Award.\n\nThis entity stores information about the award given to the photo used in the contest.
 */
@ChangelogDate("20251009212635")
entity Award {
  type AwardType required
  description String
}
/**
 * Entity TagCategory - Tag category.\n\nThis entity stores information about the tag category. Initially it contains six categories.
 */
@ChangelogDate("20251009212636")
entity TagCategory {
  code String required minlength(2) maxlength(50)
}
/**
 * Entity TagCategoryI18N - Tag category translation.\n
 */
@ChangelogDate("20251009212637")
entity TagCategoryI18N (tag_category_i_18_n) {
  name String required minlength(2) maxlength(50)
  locale String required
}
/**
 * Entity UserProfile - User profile.\n\nUser profile contains information extending standard user information stored in the built-in User entity.
 */
@ChangelogDate("20251009212638")
entity UserProfile {
  nickName String minlength(2) maxlength(50)
  type AccountType required
  isActive Boolean required
  description TextBlob
  homepage String
}
/**
 * Entity Gallery - Gallery.\n\nGallery is a collection of photos or sub galleries.\nIf a gallery has no parent, it can contain only sub galleries.\nOnly sub gallery can contain photos.
 */
@ChangelogDate("20251009212639")
entity Gallery {
  isActive Boolean required
  order Integer required
  createdAt Instant required
  updatedAt Instant
}
/**
 * Entity GalleryI18N - Gallery translation.\n
 */
@ChangelogDate("20251009212640")
entity GalleryI18N (gallery_i_18_n) {
  locale String required
  name String required minlength(2) maxlength(50)
  description String maxlength(500)
  createdAt Instant required
  updatedAt Instant
}
/**
 * Entity UserTag - User tag.\n\nUser tag is a tag assigned to the user. It does not have any category like regular Tags.
 */
@ChangelogDate("20251010221414")
entity UserTag {
  name String required minlength(2) maxlength(50)
}
/**
 * Entity UserEquipment - User equipment.\n\nUser equipment contains information about the equipment used by the user.\nEach user can have multiple user equipment.
 */
@ChangelogDate("20251010221415")
entity UserEquipment {
  name String required minlength(2) maxlength(200)
}

/**
 * Enum License - License of the photo
 */
enum License {
  CREATIVE_COMMONS,
  PUBLIC_DOMAIN,
  ALL_RIGHTS_RESERVED
}
/**
 * Enum TimeOfDay - Time of day when the photo was taken
 */
enum TimeOfDay {
  DAY,
  NIGHT,
  NOT_SPECIFIED
}
/**
 * Enum Season - Season when the photo was taken
 */
enum Season {
  SPRING,
  SUMMER,
  AUTUMN,
  WINTER,
  NOT_SPECIFIED
}
/**
 * Enum District - District of Poznań where the photo was taken
 */
enum District {
  JEZYCE (Jeżyce),
  WILDA (Wilda),
  DEBIEC (Dębiec),
  WINOGRADY (Winogrady),
  NARAMOWICE (Naramowice),
  STAROLEKA (Starołęka),
  STRZESZYN (Strzeszyn),
  LAZARZ (Łazarz),
  RATAJE (Rataje),
  GRUNWALD (Grunwald),
  STARE_MIASTO (Stare Miasto),
  NOWE_MIASTO (Nowe Miasto),
  PIATKOWO (Piątkowo),
  KIEKRZ (Kiekrz),
  LAWICA (Ławica),
  GORCZYN (Górczyn),
  SOLACZ (Sołacz)
}
/**
 * Enum ContestStatus - Status of a photo contest
 */
enum ContestStatus {
  PENDING,
  ACTIVE,
  ENDED
}
/**
 * Enum AwardType - Type of the award\n\nAward type can be winner or honorable mention. All honorable mentions have the same priority.
 */
enum AwardType {
  WINNER,
  HONORABLE_MENTION
}
/**
 * Enum AccountType - Type of the account\n\nAccount type can be private or media.
 */
enum AccountType {
  PRIVATE,
  MEDIA
}

relationship OneToOne {
  Photo{baseMetadata} to PhotoMetadata
  Contest{leadingPhoto} to Photo
  ContestEntry{contestMetadata} to PhotoMetadata
  UserProfile{user(login) required} to User with builtInEntity
  Gallery{cover required} to Photo
}
relationship OneToMany {
  TagCategory{translations} to TagCategoryI18N{tagCategory(code)}
  Gallery{translations} to GalleryI18N{gallery}
}
relationship ManyToOne {
  Photo{owner(login) required} to User with builtInEntity
  Tag{category(code) required} to TagCategory
  ContestEntry{photo} to Photo
  ContestEntry{contest(name)} to Contest
  Award{contestEntry} to ContestEntry{award(type)}
  Gallery{parent} to Gallery
}
relationship ManyToMany {
  PhotoMetadata{tags(name)} to Tag
  UserProfile{tags(name)} to UserTag
  UserProfile{equipment(name)} to UserEquipment
  Gallery{photos} to Photo{galleries}
}

dto Photo, Tag, PhotoMetadata, Contest, ContestEntry, Award, TagCategory, TagCategoryI18N, UserProfile, Gallery, GalleryI18N, UserTag, UserEquipment with mapstruct
paginate Photo, Tag, PhotoMetadata, Contest, ContestEntry, Award, TagCategory, TagCategoryI18N, UserProfile, Gallery, GalleryI18N, UserTag, UserEquipment with pagination
service Photo, Tag, PhotoMetadata, Contest, ContestEntry, Award, TagCategory, TagCategoryI18N, UserProfile, Gallery, GalleryI18N, UserTag, UserEquipment with serviceClass
filter Photo, Tag, PhotoMetadata, Contest, ContestEntry, Award, TagCategory, TagCategoryI18N, UserProfile, Gallery, GalleryI18N, UserTag, UserEquipment

  • [ x] jhipster info output is mandatory for bug reports. This will allow us to use automated tests and generate the broken sample using jhipster from-issue command.
Motivation for or Use Case

Visually breaks the search functionality in the admin UI when searching by a phrase and using React in the admin UI and Elasticsearch under the hood. User sees bad search results (even though the backend returns the correct data).

Reproduce the error
  • Create a few entries for your entity (again, the exact model doesn't matter) - e.g. 10 entries for the Photo - first 3 with CREATIVE_COMMONS license, the rest with PUBLIC_DOMAIN
  • The default entity list view should show the first 3 rows of Photos as having the CREATIVE_COMMONS license, the remaining 7 having PUBLIC_DOMAIN (so far so good)
  • Now, search for PUBLIC_DOMAIN using the phrase search input box at the top
  • The first 3 Photos will have the CREATIVE_COMMONS as the license displayed (and should have PUBLIC_DOMAIN (Note: Other properties may also be mis-rendered)
Related issues

Didn't find any related issues. Sorry if I missed them.

Suggest a Fix

The fix needs to modify the generator to (for example) use the entity id as the key attribute value, instead of using the number of the rendered row.

Sticking with the Photo example - instead of:

<tbody>
  {photoList.map((photo, i) => (
  <tr key={`entity-${i}`} data-cy="entityTable">
  //[...]

use:

<tbody>
  {photoList.map((photo, i) => (
  <tr key={`entity-${photo.id}`} data-cy="entityTable">
  //[...]
JHipster Version(s)

8.11

Browsers and Operating System
  • macOS Sequoia 15.7.1 (24G231)
  • tested with Arc (1.117.0), Safari (26.0.1) & Firefox (144.0)
  • [x ] Tickets opened without reproduction steps or that doesn't follows the template recommendation will be closed.
  • [x ] Checking this box is mandatory (this is just to show you read everything)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions