Open document in new pane from custom component with multiple types

4 replies
Last updated: Oct 16, 2023
Hi all. I have a question about editing a document in a new pane from a custom component. I have setup the custom component in the following (simplified) desk structure:
S.list()
    .title('Content')
    .items([
      S.listItem().title('Page tree').child(
        S.component(CustomComponent).id('CustomComponent').title('Custom component'),
      ),
...
In this view I have a listening query for a set of documents and want to render those as buttons with links to open the target document in a new pane.
I have tried:

<IntentLink />
from
usePaneRouter
• The same approach as is done in
Documents Pane plugin • The same approach as is done in
Document internationalization plugin I keep getting a warning that the desk structure child was not found, but I cannot (or do not know how to) express a specific document type as a child of the structure, as it can be multiple types. This works:

S.component(CustomComponent).id('CustomComponent').title('Custom component').child(S.document().schemaType('typeA')),
but only (correctly) for a document of
typeA
and not
typeB
I want similar behaviour for panes as is done in the
documentList
.
Does anyone have experience doing this and/or can help with this? Thanks!
AI Update

I've dealt with this exact issue! The problem you're encountering is that when you define a custom component in your Structure, Sanity doesn't know what child panes to expect until a user actually clicks on something. The warning about "desk structure child was not found" happens because the Structure builder can't resolve the navigation path ahead of time.

The solution that works is actually documented in this community answer. You need to use Object.assign() to serialize a documentList and then override it with your custom component properties:

S.listItem()
  .title("Page tree")
  .child(
    Object.assign(
      S.documentList()
        .filter('_type in ["typeA", "typeB"]')
        .title("Page tree")
        .serialize(), 
      {
        __preserveInstance: true,
        key: 'pageTree',
        id: 'pageTree',
        type: 'component',
        component: CustomComponent,
      }
    ),
  )

This approach works because internally it's still a document list from the Structure builder's perspective, which means the pane router knows how to handle document navigation properly. When users click links in your custom component to open documents, the Structure tool will correctly open them in new panes.

The key insight here is that you're not trying to define .child(S.document().schemaType('typeA')) for each type. Instead, you're letting the document list handle multiple types through its filter, while your custom component just provides the visual representation.

This pattern is used by plugins like the orderable-document-list plugin, which renders custom components for list items while maintaining proper pane navigation behavior.

Once you have this structure in place, the <IntentLink /> from usePaneRouter should work as expected, because the Structure builder now understands that this is a document list that can have document children, even though it's rendering through your custom component.

Show original thread
4 replies
It’ll probably be best if you define your structure and your views separately. First, your
listItem
structure would need to look like this:
S.listItem()
   .title('Page Tree')
   .child(
      S.documentList()
        .title('Page Tree')
        .filter(`_type in [<your-types>]`)
        ),
Then, you’d define a
document node resolver to handle your views and return the component there:
return S.document().views([
      S.view.form(),
 S.view.component(CustomComponent).title('Custom Component')
    ])
Thank you for taking the time to answer this! It's unfortunately not exactly what I was looking for. In my case the 'Custom Component' is actually a component to render multiple documents of multiple schema types (a page tree to be more concrete). As I understand it the
document.views
can be a way of adding components on the document level and is rendered like a tab (like the iframe preview plugin), while I want it to replace the behaviour of the default
documentList
component. This is what led me to believe I need to add it as a component in the structure.
Do you have any tips for this?
This plugin (which has custom components for list items) is probably the closest to what you’re looking for. However, the Structure Builder API is for rendering lists of documents and a component cannot be a child of a list. If you just want a visual representation of your page tree, it’ll be easier to just create a separate plugin where you’re not bound by those rules.
Oh my, this is it! Thank you so much for pointing me to this plugin. The solution I was looking for was the following, if anyone might run into this question as well:

S.listItem()
        .title("Page tree")
        .child(
          Object.assign(S.documentList().filter('_type in ["homePage","contentPage"]').title("Page tree").serialize(), {
            __preserveInstance: true,
            key: 'pageTree',
            id: 'pageTree',
            type: 'component',
            component: PageTreeView,
          }),
        ),
Apparently it is possible to render a custom component for a
documentList
like this. Internally it will still be a document list and it is handled accordingly, resulting in the expected behaviour of the pane router 👍

Sanity – Build the way you think, not the way your CMS thinks

Sanity is the developer-first content operating system that gives you complete control. Schema-as-code, GROQ queries, and real-time APIs mean no more workarounds or waiting for deployments. Free to start, scale as you grow.

Was this answer helpful?