Block and asset utilities

When you are working with a Block parameter or an Asset parameter in React or Vue, the Uniform prop will contain our common verbose value structure which looks like this:

import { BlockValue, AssetParamValue } from '@uniformdev/canvas' interface MyComponentProps { myblock: BlockValue myasset: AssetParamValue } function MyComponent({ myblock, myasset }) { console.log(myblock) // [{ fields: { title: { type: 'text', value: 'Hello' } } }, ...] console.log(myasset) // [{ fields: { url: { type: 'text', value: 'https://img...' } } }, ...] return ( <div> {myblock?.map( (block, i) => <h2 key={i}>{block.fields?.title?.value}</h2> )} {!!myasset[0] ? <img alt={myasset[0].fields?.title?.value} src={myasset[0].fields?.url?.value} /> : null } </div> ) }

Uniform components don't flatten these more complex parameters by default for:

  1. Performance: Uniform structures have the potential for unlimited nesting which isn't always used within every context. This would result in unnecessary traversal of these nested trees.
  2. Flexibility: The structure provides contextual data which might change the expected value depending on the context. Uniform also wants to prevent the loss of this data at the API level in case it's needed within UI components.
  3. The developer understands the contextual use-case best: The decision of whether to flatten, and how much to flatten, is best left to the engineer.

The Canvas package (@uniformdev/canvas) provides some helpful utility functions which make it easier to flatten and simplify the values from within verbose Uniform objects like asset, entry or block.

The utilities will also handle the possibility of empty values like null or undefined which is common in preview scenarios.

If you are working with the block parameter it's likely you will be iterating over an array of blocks. If you take the verbose example above and use the flattenValues helper, you'll get:

import { flattenValues } from '@uniformdev/canvas' function MyComponent({ myblock, myasset }) { ... return ( <div> {flattenValues(myblock)?.map( ({ title } = {}, i) => <h2 key={i}>{title}</h2> )} ... </div> ) }

This is especially helpful if you have atomic React or Vue components which expect simple, flattened props. You should be able to pass the block values directly to them.

If you are working with the asset parameter, you will have a similar need to work with arrays of assets. In this case you can follow the same process as with blocks.

It's also common to have an asset parameter which will only ever be a single asset. You can improve the verbose example above and use the flattenValues helper to make this simpler:

import { flattenValues } from '@uniformdev/canvas' function MyComponent({ myblock, myasset }) { ... const image = flattenValues(myasset, { toSingle: true }) return ( <div> ... {image ? <img alt={image.title} src={image.url} /> : null } </div> ) }

This is helpful becuase it will return a single object with with flattened values rather than an array.

Often, asset fields are nested inside a block and blocks can nest themselves. In this case, the flattenValues provides the levels option which defaults to 1. This means that for most use cases you won't need to use it.

If you have an asset field inside a block, the default setting will flatten the values on the asset as well as the block.

import { flattenValues } from '@uniformdev/canvas' // Default: const myblockWithAsset = flattenValues(myblock) // myblockWithAsset is: [{ title: "Hello", myasset: [ { title: "hello", url: "https://img..." } ], mychildblock: [ { title: "Hello" } ] }]

If you need further levels of nesting there are a couple of options:

  1. Component based nesting (recommended):

    To support infinite nesting of blocks, Uniform recommends using components. In this pattern, each component is responsible for flattening enough to feed the child level.

    import { flattenValues } from '@uniformdev/canvas' function MyParentComponent({ myblocks }) { // set levels to 0 so that the child handles it's own // flatten values return ( <div> {flattenValues(myblocks, { levels: 0 })?.map( ({ title, childblocks } = {}, i) => <> <h2 key={i}>{title}</h2> <MyChildComponent myblocks={childblocks} /> </> )} </div> ) } function MyChildComponent({ myblocks }) { return ( <div> {flattenValues(myblocks)?.map( ({ title } = {}, i) => <h2 key={i}>{title}</h2> )} </div> ) }
  2. Use levels to specify a higher number or Infinity.

    // Further Nesting: const deepNestedBlocks = flattenValues(myblock, { levels: 10 });

Other uses

The flattenValues helper will also work on components and entries, although this is usually handled for you by <Slot /> components.