Using a relationships entry title as a repeatable components entry title

Hi!

I just thought I would share an extension I’ve working on. In my project, we link many content entries together using relationships inside components. However, it’s currently not possible to make the title of a repeatable component to be the entry title of a relationship established inside of the component. Ex:

In this example, I would want the title of the Button relationship to show in the expandable repeatable component.

I was able to do this by extending this file: extensions/content-manager/admin/src/components/RepeatableComponent/DraggedItem/utils/select.js

import { useMemo } from 'react';
import { get, toString } from 'lodash';
import { useContentManagerEditViewDataManager } from 'strapi-helper-plugin';

function useSelect({ schema, componentFieldName }) {
  const {
    checkFormErrors,
    modifiedData,
    moveComponentField,
    removeRepeatableField,
    triggerFormValidation,
  } = useContentManagerEditViewDataManager();

  const mainField = useMemo(() => get(schema, ['settings', 'mainField'], 'id'), [schema]);
  const nestedObjectTitle = schema.layouts.edit?.[0]?.[0]?.metadatas?.mainField?.name;
  const nestedObjectField = schema.layouts.edit?.[0]?.[0]?.name;
  const displayValuePath = (mainField === 'id' && !!nestedObjectTitle)
    ? [...componentFieldName.split('.'), nestedObjectField, nestedObjectTitle]
    : [...componentFieldName.split('.'), mainField];
  const displayedValue = toString(
    get(modifiedData, displayValuePath, '')
  );

  return {
    displayedValue,
    mainField,
    checkFormErrors,
    moveComponentField,
    removeRepeatableField,
    schema,
    triggerFormValidation,
  };
}

export default useSelect;

With this extension, the repeatable component will attempt to use the first items relationship entry title if a title for the repeatable component has not be specified.
Here is the result:

Just wanted to share in case someone was trying to solve the same problem!

3 Likes

Hey, I created this file exactly where you mentioned but it didn’t work. It’s still giving me only one option “id”. So, I thought maybe it isn’t extending for some reason so I went to the node_modules and edited this file there. It still didn’t work =(

Any hints?

@starrett67 Thank you, this saved me a ton of searching!

@dydx-git Do you have strapi running with npm run develop -- --watch-admin so that it builds the admin interace when a file is changed? Also make sure to configure the view to make it show the id as entry title. I added the supplied code to extensions and it worked out of the box like a charm!

@starrett67 This doesn’t work with v4, right?

Its not working in v4, im also interested how to make it in v4.

I’m also interested in this functionality for v4. The above code works when the relevant file is updated in node_modules perfectly @starrett67 so thanks for providing this.

Does anyone how to do this for v4?

Hi, same, would be really interested as well to get this working on v4

Following my previous message, I figured out how to make this code work with v4, by inserting the logic exposed above (@starrett67 thanks again) inside the corresponding v4 file, directly in the @strapi node module (as copying files & hierarchy in the extension folder doesn’t work anymore for a basic override).

So: in node_modules/@strapi/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js here is the code I inserted :

import { useMemo } from "react";
import { get, toString } from "lodash";
import { useCMEditViewDataManager } from "@strapi/helper-plugin";

function useSelect({ schema, componentFieldName }) {
  const {
    checkFormErrors,
    modifiedData,
    moveComponentField,
    removeRepeatableField,
    triggerFormValidation,
  } = useCMEditViewDataManager();

  const mainField = useMemo(
    () => get(schema, ["settings", "mainField"], "id"),
    [schema]
  );

  const nestedObjectTitle = schema.layouts.edit?.[0]?.[0]?.metadatas?.mainField?.name;  
  const nestedObjectField = schema.layouts.edit?.[0]?.[0]?.name;
  
      const displayValuePath = (mainField === 'id' && !!nestedObjectTitle)
    ? [...componentFieldName.split('.'), nestedObjectField, nestedObjectTitle]
    : [...componentFieldName.split('.'), mainField];

  const displayedValue = toString(
    get(modifiedData, displayValuePath, '')
  );

  return {
    displayedValue,
    mainField,
    checkFormErrors,
    moveComponentField,
    removeRepeatableField,
    schema,
    triggerFormValidation,
  };
}

export default useSelect;

Then we have to rebuild using npm run build --clean (to clean the cache).

As modyfying the node_module is a very ugly process, some recommendations I found browsing the official Discord are to use the path-package, so that basically we don’t loose the file as soon as we reinstall the packages, but it is suggest to break.

Some folks say it will be fixed in future version, which I highly expect :slightly_smiling_face:.

1 Like

Worked like a charm! Now only if I can get this working with list view…

This did not work for me but I am using yarn. I’ve been seeing conversations about this since 2020 so I doubt this will happen anytime soon.

If someone figures this out for v4 (without having to use the node_module edit method) I would love to know.

1 Like

This is working in v4.4.5 by editing the below file inside the node_modules

node_modules/@strapi/admin/admin/src/content-manager/components/RepeatableComponent/DraggedItem/utils/select.js
import { useMemo } from 'react';
import { get, toString } from 'lodash';
import { useCMEditViewDataManager } from '@strapi/helper-plugin';

function useSelect({ schema, componentFieldName }) {
  const {
    checkFormErrors,
    modifiedData,
    moveComponentField,
    removeRepeatableField,
    triggerFormValidation,
  } = useCMEditViewDataManager();

  const mainField = useMemo(() => get(schema, ['settings', 'mainField'], 'id'), [schema]);
  const nestedObjectTitle = schema.layouts.edit?.[0]?.[0]?.metadatas?.mainField?.name;  
  const nestedObjectField = schema.layouts.edit?.[0]?.[0]?.name;

  const displayValuePath = (mainField === 'id' && !!nestedObjectTitle)
  ? [...componentFieldName.split('.'), nestedObjectField, nestedObjectTitle]
  : [...componentFieldName.split('.'), mainField];

  const displayedValue = toString(
    get(modifiedData, displayValuePath, '')
  );

  return {
    displayedValue,
    mainField,
    checkFormErrors,
    moveComponentField,
    removeRepeatableField,
    schema,
    triggerFormValidation,
  };
}

export default useSelect;
3 Likes

This is a nice workaround but it raises two questions:

  • Is path-package the only way to deploy this in production?
  • Are there any plan to include this to the official package?
1 Like

I’m using Strapi 4.5.4 and it doesn’t work. Any workaround?

Using @mcnaveen solution but adding a "0" between nestedObjectField and nestedObjectTitle seems to kind of work for me on 4.5.6. Seems like the nested object is an array nowadays :smile:

However, it looks like I have to open the accordion before the title gets loaded :thinking:

import { useMemo } from 'react';
import { get, toString } from 'lodash';
import { useCMEditViewDataManager } from '@strapi/helper-plugin';

function useSelect({ schema, componentFieldName }) {
  const {
    checkFormErrors,
    modifiedData,
    moveComponentField,
    removeRepeatableField,
    triggerFormValidation,
  } = useCMEditViewDataManager();

  const mainField = useMemo(() => get(schema, ['settings', 'mainField'], 'id'), [schema]);
  const nestedObjectTitle = schema.layouts.edit?.[0]?.[0]?.metadatas?.mainField?.name;
  const nestedObjectField = schema.layouts.edit?.[0]?.[0]?.name;
  const displayValuePath = (mainField === 'id' && !!nestedObjectTitle)
  ? [...componentFieldName.split('.'), nestedObjectField, "0", nestedObjectTitle]
  : [...componentFieldName.split('.'), mainField];

  const displayedValue = toString(
    get(modifiedData, displayValuePath, '')
  );

  return {
    displayedValue,
    mainField,
    checkFormErrors,
    moveComponentField,
    removeRepeatableField,
    schema,
    triggerFormValidation,
  };
}

export default useSelect;

Here is a solution for this in strapi 4.7.0 that will also truncate long values to 50 characters with an ellipsis.

node_modules/@strapi/admin/admin/src/content-manager/components/RepeatableComponent/components/Component.js

old:

const DraggedItem = ({
  componentFieldName,
  componentUid,
  fields,
  index,
  isOpen,
  isReadOnly,
  mainField,
  moveComponentField,
  onClickToggle,
  toggleCollapses,
  onGrabItem,
  onDropItem,
  onCancel,
}) => {
  const { modifiedData, removeRepeatableField, triggerFormValidation } = useCMEditViewDataManager();

  const displayedValue = toString(
    get(modifiedData, [...componentFieldName.split('.'), mainField], '')
  );

...

new:

const DraggedItem = ({
  componentFieldName,
  componentUid,
  fields,
  index,
  isOpen,
  isReadOnly,
  mainField,
  moveComponentField,
  onClickToggle,
  toggleCollapses,
  onGrabItem,
  onDropItem,
  onCancel,
}) => {
  const { modifiedData, removeRepeatableField, triggerFormValidation } = useCMEditViewDataManager();
  const fieldValue = get(modifiedData, `${componentFieldName}[${get(fields, '[0][0].name')}]`);
  const displayedValue = 
    toString(
      fieldValue
        ? (
            fieldValue.length > 50
            ? `${fieldValue.slice(0, 50)}…`
            : fieldValue
          )
        : get(modifiedData, [...componentFieldName.split('.'), mainField], '')
    );

I can’t get it to work :frowning: