Skip to content

Commit

Permalink
Merge pull request #10 from JakeGinnivan/feat/AllowPropsToBeInjectedD…
Browse files Browse the repository at this point in the history
…ynamically

feat: Allow component with data registrations to augment the load arg…
  • Loading branch information
JakeGinnivan authored Sep 6, 2019
2 parents e96b32e + 4a314d8 commit 4d15803
Show file tree
Hide file tree
Showing 4 changed files with 164 additions and 14 deletions.
32 changes: 32 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,35 @@ export const testComponentWithDataRegistration = createRegisterableComponentWith
},
)
```
## FAQ
### My data load function references global variables and does not update when they change
If you reference global variables in your data load function the data will not be re-fetched when that variable changes. This is because the data loader assumes if the arguments are the same, the result of the load function will be the same as the current data and do nothing.
You can use the `getRuntimeParams` function to merge additional varibles to the data loader props when it re-renders so it can fetch the updated data as expected. For example if you had state stored in redux.
```ts
import { init } from 'json-react-layouts-data-loader'
import { DataLoaderResources, DataProvider } from 'react-ssr-data-loader'

export const testComponentWithDataRegistration = createRegisterableComponentWithData(
'test-with-data',
{
getRuntimeParams: (props, services) => services.store.getState().myAdditionalState
// You provide this function to load the data
loadData: props => {
// Now the global state is visible to the data loader and will make up the cache key so changes to myAdditionalState will cause the data to be re-loaded
props.myAdditionalState
},
},
(props, data) => {
if (!data.loaded) {
return <div>Loading...</div>
}

return <TestComponentWithData data={data.result} />
},
)
```
17 changes: 13 additions & 4 deletions src/DataLoading.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
import { LayoutApi } from 'json-react-layouts'

export type LoadData<DataLoadArguments extends object, TData, Services extends object> = (
config: DataLoadArguments,
dataDefinitionArgs: DataLoadArguments,
services: Services,
context: { componentRenderPath: string; resourceType: string; paramsCacheKey: string },
) => Promise<TData>

export interface DataDefinition<DataLoadArguments extends object, TData, Services extends object> {
getCacheKey?: (config: DataLoadArguments, services: Services) => string
loadData: LoadData<DataLoadArguments, TData, Services>
export interface DataDefinition<
DataLoadArguments extends object,
TData,
Services extends object,
AdditionalParams extends object = {}
> {
/** Hook to provide additional dynamic parameters to the data loader */
getRuntimeParams?: (
dataDefinitionArgs: DataLoadArguments,
services: Services,
) => AdditionalParams
loadData: LoadData<DataLoadArguments & AdditionalParams, TData, Services>
}

export type MaybeLoaded<TData> = { loaded: false } | { loaded: true; result: TData }
Expand Down
86 changes: 85 additions & 1 deletion src/data-loading.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,90 @@ it('cap wrap data load function', async () => {
})
})

it('component can provide additional arguments dynamically', async () => {
const resources = new DataLoaderResources<{}>()
const { middleware, createRegisterableComponentWithData } = init<{}>(resources)

const lengthCalculatorWithMultiplierDataDefinition: DataDefinition<
{ dataArg: string },
number,
{},
{ multiplier: number }
> = {
// Additional params can come from anywhere, for instance redux or
// other environmental variables (window.location?)
getRuntimeParams: () => {
return {
multiplier: 2,
}
},
loadData: props =>
new Promise(resolve =>
setTimeout(() => {
resolve(props.dataArg.length * props.multiplier)
}),
),
}

const testComponentWithDataRegistration = createRegisterableComponentWithData(
'test-with-data',
lengthCalculatorWithMultiplierDataDefinition,
(props, data) => {
return (
<TestComponentWithData
length={data.loaded ? data.result : undefined}
{...props}
{...{ dataProps: { data } }}
/>
)
},
)

const layout = new LayoutRegistration()
.registerComponents(registrar =>
registrar
.registerComponent(testComponentWithDataRegistration)
.registerMiddleware(middleware),
)
.registerCompositions(registrar =>
registrar.registerComposition(testCompositionRegistration),
)

const wrapper = mount(
<DataProvider resources={resources} globalProps={{}}>
<layout.CompositionsRenderer
compositions={[
{
type: 'test-composition',
contentAreas: {
main: [
{
type: 'test-with-data',
props: { dataDefinitionArgs: { dataArg: 'Foo' } },
},
],
},
props: {},
},
]}
services={{}}
/>
</DataProvider>,
)

await new Promise(resolve => setTimeout(resolve))

const component = wrapper.update().find(TestComponentWithData)
expect(component.text()).toBe('Length: 6')
expect(component.props()).toMatchObject({
dataProps: {
data: {
dataDefinitionArgs: { dataArg: 'Foo', multiplier: 2 },
},
},
})
})

const { createRegisterableComposition } = getRegistrationCreators<{}>()

// Test component with data
Expand All @@ -153,7 +237,7 @@ const testCompositionRegistration = createRegisterableComposition<'main'>()(
contentAreas => <TestComposition main={contentAreas.main} />,
)

const lengthCalculatorDataDefinition: DataDefinition<{ dataArg: string }, number, any> = {
const lengthCalculatorDataDefinition: DataDefinition<{ dataArg: string }, number, {}> = {
loadData: props =>
new Promise(resolve =>
setTimeout(() => {
Expand Down
43 changes: 34 additions & 9 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,13 @@ export function init<Services extends object>(
): {
createRegisterableComponentWithData: <
ComponentType extends string,
ComponentProps extends {},
DataLoadArgs extends {},
ComponentData
ComponentProps extends object,
DataLoadArgs extends object,
ComponentData,
AdditionalParams extends object
>(
type: ComponentType,
dataDefinition: DataDefinition<DataLoadArgs, ComponentData, Services>,
dataDefinition: DataDefinition<DataLoadArgs, ComponentData, Services, AdditionalParams>,
render: RenderComponentWithDataProps<ComponentProps, ComponentData, DataLoadArgs, Services>,
) => ComponentRegistration<
ComponentType,
Expand Down Expand Up @@ -72,10 +73,11 @@ export function init<Services extends object>(
ComponentType extends string,
ComponentProps extends {},
DataLoadArgs extends {},
ComponentData
ComponentData,
AdditionalParams extends object
>(
type: ComponentType,
dataDefinition: DataDefinition<DataLoadArgs, ComponentData, Services>,
dataDefinition: DataDefinition<DataLoadArgs, ComponentData, Services, AdditionalParams>,
render: RenderComponentWithDataProps<
ComponentProps,
ComponentData,
Expand All @@ -90,10 +92,19 @@ export function init<Services extends object>(
// Then use the loadData function
const normalRender: RenderFunction<
ComponentProps &
ComponentState<ComponentData> & { dataDefinitionArgs: DataLoadArgs },
ComponentState<ComponentData> & {
dataDefinitionArgs: DataLoadArgs & AdditionalParams
},
Services
> = ({ data, dataDefinitionArgs, ...rest }, services) => {
return render(rest as any, { ...data, dataDefinitionArgs }, services)
return render(
rest as any,
{
...data,
dataDefinitionArgs,
},
services,
)
}

const registrationWithData: any = { type, render: normalRender, dataDefinition }
Expand All @@ -111,12 +122,26 @@ export function init<Services extends object>(
)

if (dataDefinition) {
const dataDefinitionArgs = dataDefinition.getRuntimeParams
? {
...componentProps.dataDefinitionArgs,
...dataDefinition.getRuntimeParams(
componentProps.dataDefinitionArgs,
services.services,
),
}
: componentProps.dataDefinitionArgs

if (dataDefinition.getRuntimeParams) {
componentProps = { ...componentProps, dataDefinitionArgs }
}

return (
<ComponentDataLoader
layout={services.layout}
componentRenderPath={componentProps.componentRenderPath}
dataDefinition={dataDefinition}
dataDefinitionArgs={componentProps.dataDefinitionArgs}
dataDefinitionArgs={dataDefinitionArgs}
renderData={renderProps => {
if (!renderProps.lastAction.success) {
// We have failed to load data, use error boundaries
Expand Down

0 comments on commit 4d15803

Please sign in to comment.