Add TypeScript Type Checks to RouteData fields

All the world's a type and we are all merely types
The Problem
RouteData is an interface defined as:
export interface RouteData { name: string; displayName?: string; fields?: { [name: string]: Field; }; databaseName?: string; deviceId?: string; itemLanguage?: string; itemVersion?: number; layoutId?: string; templateId?: string; templateName?: string; placeholders: PlaceholdersData; itemId?: string;}Whereby this:
const foo = useSitecoreContext().sitecoreContext.route?.fields;Corresponds to:
(property) RouteData.fields?: { [name: string]: Field<GenericFieldValue>;} | undefinedIn other words, RouteData.fields acts as a kind of arbitrary dictionary.
Now imagine that I have defined a custom type (the full type actually has 25+ fields):
export type Publication = { id?: string; url?: string; fields: { abstract: Field<string>; attachment: FileField; };}I'd like to be able to do this:
const foo = useSitecoreContext().sitecoreContext.route?.fields as Publication['fields'];However, I get this IDE / build error:
Conversion of type '{ [name: string]: Field<GenericFieldValue>; } | undefined' to type '{ abstract: Field<string>; attachment: FileField; }' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Type '{ [name: string]: Field<GenericFieldValue>; }' is missing the following properties from type '{ abstract: Field<string>; attachment: FileField; }': abstract, attachmentIf I do as the error suggests, the IDE and build gods are pleased:
const bar = (useSitecoreContext().sitecoreContext.route?.fields as unknown) as Publication['fields'];One Solution
Let's say you need a component that renders a header for a "blog" page. The header should display the title of the blog and the type of blog. Both fields are specified on the page item (and therefore the route). The blog type is a list field that is a reference to a custom type called BlogType. BlogType has a single field called Name. The header component should be able to render the title and the name of the blog type.
import { Text, useSitecoreContext, Field } from '@sitecore-jss/sitecore-jss-nextjs';import { Website } from 'lib/component-props/model';
type BlogHeaderRouteFields = Website.Project.Main.PageTypes.Blogs.Publication['fields'];
const BlogHeader = (): JSX.Element => { const { sitecoreContext } = useSitecoreContext();
if (!sitecoreContext || !sitecoreContext?.route?.fields) { return <></>; }
const blogHeaderRouteFields = (sitecoreContext.route.fields as unknown) as BlogHeaderRouteFields;
return ( <div> <h1 className="blog-header"> <Text field={blogHeaderRouteFields.title as Field<string>} /> </h1> {blogHeaderRouteFields.blogTypes?.value && ( <div className="blog-type"> <Text field={blogHeaderRouteFields.blogTypes.value[0]?.fields?.name as Field<string>} editable={false} /> </div> )} </div> );};
export default BlogHeader;The Better Solution
sitecoreContext has no knowledge of what RouteData it has; specifically, fields. It defaults to Record<string, string | Field | Item | Item[]>, which means we need to assert what is in it when we use it.
The better solution to this would be to have a generic type for RouteData that allows us to specify the type of fields. If generics were supported we could do this:
const { sitecoreContext } = useSitecoreContext<MyRouteFields>();This is cleaner and more less prone to errors because assertions don't need to be added whenever route fields are referenced. I am also not the first one to notice or ask for this:
https://github.com/Sitecore/jss/issues/1016
The OP describes the ask perfectly and offers a temporary solution until someone takes the precious time to submit a PR.
-MG





