Skip to content

Commit 673fa2b

Browse files
authored
Enhance README with detailed explanations of `data-hygraph-component-… (#2)
* Enhance README with detailed explanations of `data-hygraph-component-chain` and related attributes, including examples for single-level and deeply nested components. * bump version * fix description
1 parent b6dc32f commit 673fa2b

File tree

3 files changed

+308
-40
lines changed

3 files changed

+308
-40
lines changed

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.0.1] - 2025-11-24
11+
12+
### Documentation
13+
- Enhanced README with comprehensive explanations of `data-hygraph-component-chain` attribute
14+
- Added detailed examples for single-level and deeply nested components
15+
- Clarified the relationship between `data-hygraph-entry-id` and component chains
16+
- Added examples for union/modular components with multiple component types
17+
- Included GraphQL query examples showing how to extract `instanceId` values
18+
- Improved component chain documentation with helper function usage examples
19+
1020
## [1.0.0] - 2024-11-13
1121

1222
### Added

README.md

Lines changed: 297 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -108,54 +108,312 @@ Common optional attributes:
108108

109109
### Tagging Component Fields
110110

111-
Use the optional `data-hygraph-component-chain` attribute when a field lives inside a modular component, repeatable component list, or union component. The attribute accepts a JSON string array describing the path from the entry to the nested field.
111+
Use the `data-hygraph-component-chain` attribute when a field lives inside a modular component, repeatable component list, or union component. The component chain tells Studio how to navigate from the root entry to the specific nested field.
112+
113+
#### Key Concepts
114+
115+
| Attribute | Value | Purpose |
116+
|-----------|-------|---------|
117+
| `data-hygraph-entry-id` | Always the **root page/entry ID** | Identifies which Hygraph entry contains this content |
118+
| `data-hygraph-field-api-id` | The component field name in your schema | Identifies which field to open in the editor |
119+
| `data-hygraph-component-chain` | JSON array of `{fieldApiId, instanceId}` | Describes the path from root entry to the nested field |
120+
121+
**Important:** `data-hygraph-entry-id` is always the root entry ID (e.g., page or article), even for deeply nested components. The component chain handles the navigation to nested fields—you never use the component's ID as the entry ID.
122+
123+
#### What is `instanceId`?
124+
125+
The `instanceId` is the unique identifier for each component instance, returned by Hygraph in your GraphQL response:
126+
127+
```graphql
128+
query {
129+
page(where: { id: "page_123" }) {
130+
id # Root entry ID → use for data-hygraph-entry-id
131+
title
132+
contentSections { # Modular component field
133+
... on HeroSection {
134+
id # ← This is the instanceId for the component chain
135+
headline
136+
subheadline
137+
}
138+
... on FeatureGrid {
139+
id # ← instanceId
140+
features {
141+
id # ← instanceId for nested components
142+
title
143+
description
144+
}
145+
}
146+
}
147+
}
148+
}
149+
```
112150

113-
Each element in the array must contain:
114-
- `fieldApiId`: the API ID of the component field in your schema
115-
- `instanceId`: the ID returned by Hygraph for that component entry
151+
#### Single-Level Components
152+
153+
For a field inside a component list (e.g., `Page.contentSections[]`):
154+
155+
```tsx
156+
{page.contentSections.map((section) => {
157+
const chain = [
158+
createComponentChainLink('contentSections', section.id)
159+
];
160+
161+
return (
162+
<h2
163+
{...createPreviewAttributes({
164+
entryId: page.id, // Always the root page ID
165+
fieldApiId: 'headline', // Field inside the component
166+
componentChain: chain,
167+
})}
168+
>
169+
{section.headline}
170+
</h2>
171+
);
172+
})}
173+
```
116174

175+
The resulting HTML:
117176
```html
118-
<!-- Component list (e.g., Recipe.ingredients[]) -->
119-
<span
120-
data-hygraph-entry-id="cm123"
121-
data-hygraph-field-api-id="quantity"
122-
data-hygraph-component-chain='[
123-
{"fieldApiId":"ingredients","instanceId":"ingr_01"}
124-
]'
177+
<h2
178+
data-hygraph-entry-id="page_123"
179+
data-hygraph-field-api-id="headline"
180+
data-hygraph-component-chain='[{"fieldApiId":"contentSections","instanceId":"section_abc"}]'
125181
>
126-
2
127-
</span>
182+
Welcome to Our Site
183+
</h2>
184+
```
128185

129-
<!-- Nested component inside another component -->
130-
<div
131-
data-hygraph-entry-id="cm123"
132-
data-hygraph-field-api-id="notes"
133-
data-hygraph-component-chain='[
134-
{"fieldApiId":"recipeSteps","instanceId":"step_01"},
135-
{"fieldApiId":"tips","instanceId":"tip_02"}
136-
]'
137-
></div>
186+
#### Deeply Nested Components
187+
188+
For components inside other components (e.g., `Page.contentSections[].features[]`):
189+
190+
```tsx
191+
{page.contentSections.map((section, sectionIndex) => (
192+
<div key={section.id}>
193+
{section.features?.map((feature, featureIndex) => {
194+
// Build the chain: page → contentSections → features
195+
const chain = [
196+
createComponentChainLink('contentSections', section.id),
197+
createComponentChainLink('features', feature.id)
198+
];
199+
200+
return (
201+
<div key={feature.id}>
202+
<h3
203+
{...createPreviewAttributes({
204+
entryId: page.id, // Still the root page ID
205+
fieldApiId: 'title',
206+
componentChain: chain,
207+
})}
208+
>
209+
{feature.title}
210+
</h3>
211+
<p
212+
{...createPreviewAttributes({
213+
entryId: page.id,
214+
fieldApiId: 'description',
215+
componentChain: chain,
216+
})}
217+
>
218+
{feature.description}
219+
</p>
220+
</div>
221+
);
222+
})}
223+
</div>
224+
))}
225+
```
226+
227+
#### Union/Modular Components (Multiple Component Types)
228+
229+
When a field accepts different component types, handle each type in a switch statement:
230+
231+
```tsx
232+
{page.contentSections.map((section) => {
233+
const chain = [createComponentChainLink('contentSections', section.id)];
234+
235+
switch (section.__typename) {
236+
case 'HeroSection':
237+
return (
238+
<section key={section.id}>
239+
<h1
240+
{...createPreviewAttributes({
241+
entryId: page.id,
242+
fieldApiId: 'headline',
243+
componentChain: chain,
244+
})}
245+
>
246+
{section.headline}
247+
</h1>
248+
</section>
249+
);
250+
251+
case 'FeatureGrid':
252+
return (
253+
<section key={section.id}>
254+
<h2
255+
{...createPreviewAttributes({
256+
entryId: page.id,
257+
fieldApiId: 'gridTitle',
258+
componentChain: chain,
259+
})}
260+
>
261+
{section.gridTitle}
262+
</h2>
263+
{/* Render nested features with extended chain */}
264+
</section>
265+
);
266+
267+
default:
268+
return null;
269+
}
270+
})}
138271
```
139272

140-
Union components work the same way—the chain lists each component hop down to the field you want to tag. Always serialize the array with double quotes so the HTML attribute remains valid.
273+
#### Complete Example: Page with Modular Content
141274

142-
> Tip: Instead of hand-writing JSON strings, use the helpers exported from `@hygraph/preview-sdk/core`:
143-
> ```tsx
144-
> import {
145-
> createPreviewAttributes,
146-
> createComponentChainLink,
147-
> } from '@hygraph/preview-sdk/core';
275+
Here's a full example showing a page template with multiple component types and nesting:
276+
277+
```tsx
278+
import {
279+
createPreviewAttributes,
280+
createComponentChainLink,
281+
} from '@hygraph/preview-sdk/core';
282+
283+
interface Page {
284+
id: string;
285+
title: string;
286+
contentSections: Array<HeroSection | FeatureGrid | Testimonial>;
287+
}
288+
289+
export function PageTemplate({ page }: { page: Page }) {
290+
return (
291+
<main>
292+
{/* Simple field - no component chain needed */}
293+
<h1
294+
{...createPreviewAttributes({
295+
entryId: page.id,
296+
fieldApiId: 'title',
297+
})}
298+
>
299+
{page.title}
300+
</h1>
301+
302+
{/* Modular content sections */}
303+
{page.contentSections.map((section, index) => {
304+
const sectionChain = [
305+
createComponentChainLink('contentSections', section.id)
306+
];
307+
308+
switch (section.__typename) {
309+
case 'HeroSection':
310+
return (
311+
<section key={section.id} className="hero">
312+
<h2
313+
{...createPreviewAttributes({
314+
entryId: page.id,
315+
fieldApiId: 'headline',
316+
componentChain: sectionChain,
317+
})}
318+
>
319+
{section.headline}
320+
</h2>
321+
<div
322+
{...createPreviewAttributes({
323+
entryId: page.id,
324+
fieldApiId: 'content',
325+
componentChain: sectionChain,
326+
})}
327+
data-hygraph-rich-text-format="html"
328+
dangerouslySetInnerHTML={{ __html: section.content.html }}
329+
/>
330+
</section>
331+
);
332+
333+
case 'FeatureGrid':
334+
return (
335+
<section key={section.id} className="features">
336+
{section.features.map((feature, featureIndex) => {
337+
// Extend the chain for nested components
338+
const featureChain = [
339+
...sectionChain,
340+
createComponentChainLink('features', feature.id)
341+
];
342+
343+
return (
344+
<div key={feature.id} className="feature-card">
345+
<h3
346+
{...createPreviewAttributes({
347+
entryId: page.id,
348+
fieldApiId: 'title',
349+
componentChain: featureChain,
350+
})}
351+
>
352+
{feature.title}
353+
</h3>
354+
<p
355+
{...createPreviewAttributes({
356+
entryId: page.id,
357+
fieldApiId: 'description',
358+
componentChain: featureChain,
359+
})}
360+
>
361+
{feature.description}
362+
</p>
363+
</div>
364+
);
365+
})}
366+
</section>
367+
);
368+
369+
case 'Testimonial':
370+
return (
371+
<blockquote
372+
key={section.id}
373+
{...createPreviewAttributes({
374+
entryId: page.id,
375+
fieldApiId: 'quote',
376+
componentChain: sectionChain,
377+
})}
378+
>
379+
{section.quote}
380+
</blockquote>
381+
);
382+
383+
default:
384+
return null;
385+
}
386+
})}
387+
</main>
388+
);
389+
}
390+
```
391+
392+
#### Using Raw HTML Attributes (Without Helpers)
393+
394+
If you prefer not to use the helper functions, you can write the attributes directly:
395+
396+
```html
397+
<!-- Single-level component -->
398+
<span
399+
data-hygraph-entry-id="page_123"
400+
data-hygraph-field-api-id="headline"
401+
data-hygraph-component-chain='[{"fieldApiId":"contentSections","instanceId":"section_abc"}]'
148402
>
149-
> const attributes = createPreviewAttributes({
150-
> entryId: recipe.id,
151-
> fieldApiId: 'notes',
152-
> componentChain: [
153-
> createComponentChainLink('recipeSteps', step.id),
154-
> createComponentChainLink('tips', tip.id),
155-
> ],
156-
> });
157-
> ```
158-
> Apply the returned attributes to your element with the JSX spread operator.
403+
Welcome
404+
</span>
405+
406+
<!-- Deeply nested component -->
407+
<p
408+
data-hygraph-entry-id="page_123"
409+
data-hygraph-field-api-id="description"
410+
data-hygraph-component-chain='[{"fieldApiId":"contentSections","instanceId":"section_abc"},{"fieldApiId":"features","instanceId":"feature_xyz"}]'
411+
>
412+
Feature description here
413+
</p>
414+
```
415+
416+
> **Note:** Always use double quotes inside the JSON string and single quotes for the HTML attribute value to ensure valid HTML.
159417
160418
## Framework Guides
161419

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@hygraph/preview-sdk",
3-
"version": "1.0.0",
3+
"version": "1.0.1",
44
"description": "Content preview SDK for seamless real-time content editing in Hygraph CMS",
55
"publishConfig": {
66
"access": "public"

0 commit comments

Comments
 (0)