Strapi V4 populate Media and Dynamiczones from Components

Found the issue in the strapi-plugin-populate-deep plugin:
In the plugin’s code in /server/helpers/index.js, there is this:

 const getFullPopulateObject = (modelUid, maxDepth = 20) => {
   if (maxDepth <= 1) {
     return true;
   }
   if (modelUid === "admin::user") {
     return undefined;
   }
...
}

Basically, it rejects and sets to undefined anything coming from the “admin::user” module!!

Went to node_modules/strapi-plugin-populate-deep/server/helpers/index.js and commented out these 3 lines :smiley:

Not only have I my image components populated, but my creator attributes as well!!

1 Like

You could report such bug on the repository and they can fix it as soon as possible.

I proposed a PR to the author where if you use “populate=deepua” instead of “populate=deep”, it keeps the user admin module and allows tapping into its data.
I’ve been using it for the last couple of days successfully!

Brother, you are amazing!

thank you for this. i don’t love that we have to edit things in node_modules, but it is what it is for this solution. Hopefully the strapi team builds something natively that makes accessing image data in components less hacky.

1 Like

Grate worked for me!

worked for me thanks!

It would be amazing!

Could this topic be related too?

Hello everone,

Since Strapi 4.5.0, I cannot access the Contents on the Admin page.

I get an error on the method "reduce’, as if “components” is not defined.

Anyone got an issue as well ?

Did you rebuild the admin after migrating to 4.5.0?

I tried this after reading your message.

But now I get another error:

main.9b85996d.js:890 Error: react-error-boundary requires either a fallback, fallbackRender, or FallbackComponent prop

It is related to some of the packages or any modification in the admin, You have to check which one is conflicting with your configuration.

1 Like

Thanks LuisAlaguna for your help and attention.

I found on github that other people were having the same issues on the last version of Strapi. So I downgraded to 4.2.1 and it’s working fine :+1:

I just had to say that this plugin " strapi-plugin-populate-deep" has been a major relief. The population system by strapi default is extremely confusing and frustrating especially when you get to dynamic components with media items. This capability needs to get merged back into strapi.

I went from this.

'populate[authors][populate]': '*',
			'populate[seo][populate]': '*',
			'populate[thumbnail][populate]': '*',
			'populate[alsoCheckout][populate]': '*',
			'populate[alsoCheckout][on][__articles.podcast]': '*',

which the thumbnail was still not loading to just this…

'populate': 'deep'

and it just works!

But the populate-deep plugin gives a huge performance penalty and also is prone to overfetching. So be aware of it before you decide to use it :slight_smile:

2 Likes

Hello, I am having a relation (faq entries) inside relation (faq) inside dynamic zone of a blog. Unable to populate faq entries using this plugin and populate=deep,10 parameter strapi-plugin-populate-deep - npm. Please suggest if any one has faced similar issue.

I tend to agree.
I finally decided to go with ‘qs’ to build my query, as done in the API examples, and now I have the exact granularity that I need for each type of data fetching. It took some type to build each query but now, performance is back and I only fetch exactly what I need, attribute per attribute.

1 Like

This is my version for helpers/populate.js and fits my needs … hope it helps

const { createCoreController } = require('@strapi/strapi').factories;

const DEFAULT_POPULATE = { populate: '*' };

const getPathForComponent = (componentDir, componentName) => {
  if (componentName.startsWith('api::')) {
    const parts = componentName.split('::')[1].split('.');
    return `../api/${parts[0]}/content-types/${parts[1]}/schema.json`;
  }
  return `../components/${componentDir}/${componentName}.json`;
};

const getComponentAttributes = (componentDir, componentName) => {
  try {
    return Object.entries(require(getPathForComponent(componentDir, componentName)).attributes);
  } catch (error) {
    console.error(`Error loading attributes for component: ${componentName}`, error);
    return [];
  }
};

const populateComponent = (component) => {
  const componentAttributes = getComponentAttributes(...component.split('.'));
  const attrPopulates = componentAttributes.reduce((acc, [attributeName, attribute]) => {
    return { ...acc, [attributeName]: populateAttribute(attribute) };
  }, {});
  return { ...DEFAULT_POPULATE, ...attrPopulates };
};

const populateDynamicZone = ({ components }) => {
  if (!components) return DEFAULT_POPULATE;

  return {
    populate: components.reduce((acc, component) => {
      const componentAttributes = Object.entries(require(getPathForComponent(...component.split('.'))).attributes)
        .filter(([, attr]) => ['component', 'relation', 'media', 'dynamiczone'].includes(attr.type));

      const attrPopulates = componentAttributes.reduce((innerAcc, [attributeName]) => {
        return { ...innerAcc, [attributeName]: DEFAULT_POPULATE };
      }, {});

      return { ...acc, [component.split('.').pop()]: DEFAULT_POPULATE, ...attrPopulates };
    }, {})
  };
};

const populateAttribute = (attribute) => {
  switch (attribute.type) {
    case 'component':
      return populateComponent(attribute.component);
    case 'relation':
      const componentAttributes = getComponentAttributes("", attribute.target);
      const attrPopulates = componentAttributes.reduce((acc, [attributeName, attr]) => {
        return { ...acc, [attributeName]: populateAttribute(attr) };
      }, {});
      return { ...DEFAULT_POPULATE, ...attrPopulates };
    case 'dynamiczone':
      return populateDynamicZone(attribute);
    default:
      return DEFAULT_POPULATE;
  }
};

const getPopulateFromSchema = (schema) => {
  return Object.keys(schema.attributes).reduce((acc, attributeName) => {
    return { ...acc, [attributeName]: populateAttribute(schema.attributes[attributeName]) };
  }, {});
};

function createPopulatedController(uid, schema) {
  return createCoreController(uid, () => {
    return {
      async find(ctx) {
        ctx.query = { ...ctx.query, populate: getPopulateFromSchema(schema) };
        return await super.find(ctx);
      },
      async findOne(ctx) {
        ctx.query = { ...ctx.query, populate: getPopulateFromSchema(schema) };
        return await super.findOne(ctx);
      },
    };
  });
}

module.exports = createPopulatedController;

Hello,
i have created one collection in that i have created 2 dynamic zone components in onc component i have fields like name dexription phone

in second component i have field of rich text

but iam not able see in content manager any of this field in display view i am just able to see the id , createdAt, updatedAt

so is tehre any solution that i can see that field in display view