Portal Community

When to Define a Custom Type

Implementation Steps

Step 2 — Payload and Response Interfaces

// types/peer-review.ts
export interface PeerReviewPayload {
  revieweeName:  string;
  reviewPeriod:  string;
  criteria: {
    key:   string;
    label: string;
  }[];
}

export interface PeerReviewResponseData {
  scores: Record<string, 1 | 2 | 3 | 4 | 5>;   // criteriaKey → score
  strengths:   string;
  improvements: string;
}

export type PeerReviewOutcome = 'submitted' | 'declined';

Step 4 — React Renderer Component

// components/PeerReviewComponent.tsx
import type { InteractionRendererProps } from 'edge-interact-react';
import type { PeerReviewPayload, PeerReviewResponseData } from '../types/peer-review';

export function PeerReviewComponent({
  interaction,
  respond
}: InteractionRendererProps<PeerReviewPayload>) {
  const { criteria, revieweeName, reviewPeriod } = interaction.payload;
  const [scores, setScores] = useState<Record<string, number>>({});
  const [strengths, setStrengths] = useState('');
  const [improvements, setImprovements] = useState('');

  const handleSubmit = () => {
    const data: PeerReviewResponseData = { scores, strengths, improvements };
    respond('submitted', data);
  };

  return (
    <div className="peer-review">
      <h3>Peer Review: {revieweeName} ({reviewPeriod})</h3>
      {criteria.map(c => (
        <div key={c.key}>
          <label>{c.label}</label>
          <StarRating
            value={scores[c.key]}
            onChange={v => setScores(s => ({ ...s, [c.key]: v }))}
          />
        </div>
      ))}
      <textarea
        placeholder="Key strengths..."
        value={strengths}
        onChange={e => setStrengths(e.target.value)}
      />
      <textarea
        placeholder="Areas for improvement..."
        value={improvements}
        onChange={e => setImprovements(e.target.value)}
      />
      <button onClick={handleSubmit}>Submit Review</button>
      <button onClick={() => respond('declined', {})}>I cannot complete this review</button>
    </div>
  );
}

Step 5 — Register the Renderer

// In your app's InteractionContainer setup
import { interactionContainer } from 'edge-interact-react';
import { PeerReviewComponent } from './components/PeerReviewComponent';

interactionContainer.registerInteractionRenderer(
  'acme/peer-review',
  PeerReviewComponent
);

Step 6 — Server-Side Response Validator

// Server: PeerReviewResponseValidator.cs
public class PeerReviewResponseValidator : IInteractionResponseValidator
{
    public string TypeKey => "acme/peer-review";

    public ValidationResult Validate(InteractionResponse response)
    {
        if (response.Outcome is not ("submitted" or "declined"))
            return ValidationResult.Fail("outcome must be 'submitted' or 'declined'");

        if (response.Outcome == "submitted")
        {
            if (!response.Data.ContainsKey("scores"))
                return ValidationResult.Fail("scores is required when outcome is submitted");

            if (string.IsNullOrWhiteSpace(response.Data.GetString("strengths")))
                return ValidationResult.Fail("strengths is required when outcome is submitted");
        }

        return ValidationResult.Ok();
    }
}

// Registration in Program.cs
builder.Services.AddInteractionResponseValidator<PeerReviewResponseValidator>();

Sending a Custom Type Interaction

const response = await sendInteraction({
  type: 'acme/peer-review',
  targetUserId: 'usr_reviewer_bob',
  title: `Peer Review: Alice Johnson (Q2 2026)`,
  payload: {
    revieweeName:  'Alice Johnson',
    reviewPeriod:  'Q2 2026',
    criteria: [
      { key: 'collaboration',  label: 'Collaboration' },
      { key: 'delivery',       label: 'Delivery & Reliability' },
      { key: 'communication',  label: 'Communication' },
      { key: 'initiative',     label: 'Initiative' }
    ]
  } satisfies PeerReviewPayload,
  timeoutMs: 604_800_000
});

if (response.outcome === 'submitted') {
  await hrService.saveReview(reviewId, response.data as PeerReviewResponseData);
}

Custom Type Checklist

ItemRequired?Notes
Namespaced type keyYese.g. "acme/peer-review"
Payload TypeScript interfaceRecommendedEnforced at call site with satisfies
Response data TypeScript interfaceRecommendedUsed when casting response.data
React renderer componentYesReceives interaction and respond
Renderer registrationYesinteractionContainer.registerInteractionRenderer()
Server-side response validatorStrongly recommendedIInteractionResponseValidator
Validator registrationYes (if validator defined)AddInteractionResponseValidator<T>()
Audit and Metrics Included Automatically Custom interaction types are automatically captured by AuditInteractionHook and MetricsInteractionHook — no additional registration needed. Metrics are tagged with your custom type key, so you can filter dashboards and alerts by type.