The method of accessing data depends on whether you're working in a server component or client component.
If in doubt whether you need to create a server or client component, always start with a server component and add 'use client'; to convert it to a client component if/when you realise you need to add some interactivity.
Every theme page should be wrapped in ThemePage, which will expose some of the most common data as props:
// app/[domain]/page.tsx
<ThemePage pageName={PageName.Home} theme="carina">
{({ pageConfig, agency, globalConfig }) => {
if (!pageConfig) notFound();
return <Home pageConfig={pageConfig} agency={agency} globalConfig={globalConfig} />;
}}
</ThemePage>
You can therefore pass these props to children to make use of this data.
In some cases you will need to access data either not provided by ThemePage or in a disconnected part of the document. In these cases you can fetch the data yourself. Caching and request memoization should prevent the app making more requests than necessary even if you are fetching the same data in multiple different places on a page.
// The component must be async to make requests
default async function MyServerComponent() {
// fetch the current agency and return an instance of the agency model
const agency = await Agency.current()
// fetch the current agency's GlobalConfig Mongo document
const globalConfig = await GlobalConfig.findByAgency(agency);
// fetch theme settings Mongo document - pass theme as a string and type to generic
const themeSettingsConfig = await ThemeSettingsConfig.findOneBy<CarinaThemeSettings>({
agencyId: agency.id,
theme: 'carina',
});
// fetch the first 10 branches and return an array of branch model instances
const { results: branches, errors } = await Branch.findAll({ pageSize: 10 });
// fetch a property by ID
const { result, errors } = await Property.findById(id);
return null;
}
See the API reference for Models for more examples.
Most client components in the app will have access to various state via hooks:
'use client';
import useAgency from '@homeflow/v4-lib/hooks/use-agency';
import useThemeSettings from '@homeflow/v4-lib/hooks/use-theme-settings';
import { CarinaThemeSettings } from '../../theme-settings';
import useThemeSettings from '@homeflow/v4-lib/hooks/use-global-config';
default function MyClientComponent() {
// get the current agency as a serializable/simple JS object
const agency = useAgency();
// get the current agency's global config as a serializable/simple JS object
const globalConfig = useGlobalConfig();
// get the current agency's theme settings for this theme - pass theme settings type to generic
const themeSettings = useThemeSettings<CarinaThemeSettings>();
return null;
}
In many cases it's best to leverage streaming by fetching data in server components and passing that data to client components as props. However the data normally needs to be simplified (or made serializable) before it can be passed to a client component.
The most efficient way to do this is to directly call toJSON() on the object. In the case of arrays you will need to use map() and call this on each item.
// fetch a property by ID
const { result, errors } = await Property.findById(id);
// serialize and then deserialize it to convert it to a simple JS object
const serializableProperty = result.toJSON();
// then pass to client component
return <MyClientComponent property={serializableProperty} />
The structure of the serializable data for Models is defined in the relevant Serializer method. You may need to add additional fields to serializers when new accessors are added to models.
Another way of converting complex objects to serializable ones is to serialize and then deserialize them using JSON.parse(JSON.stringify(result)). Although this is more convenient and predictable (particularly with nested objects) it's slightly less performant than calling toJSON() directly.