Skip to content

Commit

Permalink
Using our own createContext() + QoL changes (#1687)
Browse files Browse the repository at this point in the history
Co-authored-by: Ole Martin Handeland <git@olemartin.org>
  • Loading branch information
olemartinorg and Ole Martin Handeland authored Dec 6, 2023
1 parent 75a6559 commit bf1edfa
Show file tree
Hide file tree
Showing 12 changed files with 95 additions and 79 deletions.
16 changes: 12 additions & 4 deletions src/components/form/Panel.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { screen } from '@testing-library/react';

import { getInitialStateMock } from 'src/__mocks__/initialStateMock';
import { getVariant, Panel, PanelVariant } from 'src/components/form/Panel';
import { FormComponentContext } from 'src/layout';
import { FormComponentContextProvider } from 'src/layout/FormComponentContext';
import { renderWithInstanceAndLayout } from 'src/test/renderWithProviders';
import type { IPanelProps } from 'src/components/form/Panel';
import type { IFormComponentContext } from 'src/layout';
import type { IFormComponentContext } from 'src/layout/FormComponentContext';
import type { IRuntimeState } from 'src/types';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';

describe('Panel', () => {
it('should show icon when showIcon is true', async () => {
Expand Down Expand Up @@ -74,9 +75,16 @@ const render = async (

await renderWithInstanceAndLayout({
renderer: () => (
<FormComponentContext.Provider value={suppliedContext}>
<FormComponentContextProvider
value={{
baseComponentId: undefined,
node: undefined as unknown as LayoutNode,
id: 'some-id',
...suppliedContext,
}}
>
<Panel {...allProps} />
</FormComponentContext.Provider>
</FormComponentContextProvider>
),
reduxState: {
...getInitialStateMock(),
Expand Down
6 changes: 3 additions & 3 deletions src/components/form/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import React, { useContext } from 'react';
import React from 'react';

import { Panel as PanelDesignSystem, PanelVariant } from '@altinn/altinn-design-system';

import { ConditionalWrapper } from 'src/components/ConditionalWrapper';
import { FullWidthWrapper } from 'src/components/form/FullWidthWrapper';
import { FormComponentContext } from 'src/layout';
import { useFormComponentCtx } from 'src/layout/FormComponentContext';
import { assertUnreachable } from 'src/types';
import type { IPanelBase } from 'src/layout/common.generated';

Expand Down Expand Up @@ -41,7 +41,7 @@ export interface IPanelProps {
}

export const Panel = ({ children, variant, showIcon, title, showPointer }: IPanelProps) => {
const { grid, baseComponentId } = useContext(FormComponentContext);
const { grid, baseComponentId } = useFormComponentCtx() || {};
const shouldHaveFullWidth = !grid && !baseComponentId;

return (
Expand Down
16 changes: 12 additions & 4 deletions src/components/form/SoftValidations.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import { screen } from '@testing-library/react';

import { getInitialStateMock } from 'src/__mocks__/initialStateMock';
import { SoftValidations } from 'src/components/form/SoftValidations';
import { FormComponentContext } from 'src/layout';
import { FormComponentContextProvider } from 'src/layout/FormComponentContext';
import { renderWithInstanceAndLayout } from 'src/test/renderWithProviders';
import type { ISoftValidationProps, SoftValidationVariant } from 'src/components/form/SoftValidations';
import type { IFormComponentContext } from 'src/layout';
import type { IFormComponentContext } from 'src/layout/FormComponentContext';
import type { IRuntimeState } from 'src/types';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';

const render = async (
props: Partial<ISoftValidationProps> = {},
Expand All @@ -23,9 +24,16 @@ const render = async (

await renderWithInstanceAndLayout({
renderer: () => (
<FormComponentContext.Provider value={suppliedContext}>
<FormComponentContextProvider
value={{
baseComponentId: undefined,
id: 'some-id',
node: undefined as unknown as LayoutNode,
...suppliedContext,
}}
>
<SoftValidations {...allProps} />
</FormComponentContext.Provider>
</FormComponentContextProvider>
),
reduxState: {
...getInitialStateMock(),
Expand Down
1 change: 1 addition & 0 deletions src/core/contexts/context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export function createContext<T>({ name, required, ...rest }: CreateContextProps
innerValue: defaultValue,
provided: false,
});
Context.displayName = name;

const useHasProvider = () => Boolean(React.useContext(Context).provided);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
import { createContext, useContext } from 'react';

import { createContext } from 'src/core/contexts/context';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';

export type NodeInspectorContextValue =
| {
selectedNodeId: string | undefined;
selectNode: (id: string) => void;
node: LayoutNode | undefined;
}
| undefined;

const NodeInspectorContext = createContext<NodeInspectorContextValue>(undefined);
interface NodeInspectorContextValue {
selectedNodeId: string | undefined;
selectNode: (id: string) => void;
node: LayoutNode | undefined;
}

export const useNodeInspectorContext = () => {
const context = useContext(NodeInspectorContext);
if (!context) {
throw new Error('useNodeInspectorContext must be used within a NodeInspectorContextProvider');
}
return context;
};
const { Provider, useCtx } = createContext<NodeInspectorContextValue>({
name: 'NodeInspectorContext',
required: true,
});

export const NodeInspectorContextProvider = NodeInspectorContext.Provider;
export const useNodeInspectorContext = () => useCtx();
export const NodeInspectorContextProvider = Provider;
15 changes: 10 additions & 5 deletions src/features/devtools/layoutValidation/useLayoutValidation.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import React, { createContext, useContext, useMemo } from 'react';
import React, { useMemo } from 'react';
import type { PropsWithChildren } from 'react';

import { createContext } from 'src/core/contexts/context';
import { dotNotationToPointer } from 'src/features/datamodel/notations';
import { lookupBindingInSchema } from 'src/features/datamodel/SimpleSchemaTraversal';
import { useCurrentDataModelSchema, useCurrentDataModelType } from 'src/features/datamodel/useBindingSchema';
Expand Down Expand Up @@ -125,9 +126,13 @@ function useDataModelBindingsValidation(props: LayoutValidationProps) {
}, [layoutSetId, schema, layoutLoaded, dataType, nodes, logErrors]);
}

const Context = createContext<LayoutValidationErrors | undefined>(undefined);
const { Provider, useCtx } = createContext<LayoutValidationErrors | undefined>({
name: 'LayoutValidation',
required: false,
default: undefined,
});

export const useLayoutValidation = () => useContext(Context);
export const useLayoutValidation = () => useCtx();
export const useLayoutValidationForPage = () => {
const ctx = useLayoutValidation();
const layoutSetId = useCurrentLayoutSetId() || 'default';
Expand All @@ -145,9 +150,9 @@ export function LayoutValidationProvider({ children }: PropsWithChildren) {
const dataModelBindingsValidations = useDataModelBindingsValidation({ logErrors: true });

if (!layoutSchemaValidations) {
return <Context.Provider value={undefined}>{children}</Context.Provider>;
return <Provider value={undefined}>{children}</Provider>;
}

const value = mergeValidationErrors(dataModelBindingsValidations, layoutSchemaValidations);
return <Context.Provider value={value}>{children}</Context.Provider>;
return <Provider value={value}>{children}</Provider>;
}
6 changes: 3 additions & 3 deletions src/features/language/useLanguage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Children, isValidElement, useContext, useMemo } from 'react';
import { Children, isValidElement, useMemo } from 'react';
import type { JSX, ReactNode } from 'react';

import { useLaxInstanceData } from 'src/features/instance/InstanceContext';
Expand All @@ -8,7 +8,7 @@ import { useTextResources } from 'src/features/language/textResources/TextResour
import { useAppSelector } from 'src/hooks/useAppSelector';
import { getLanguageFromCode } from 'src/language/languages';
import { getParsedLanguageFromText } from 'src/language/sharedLanguage';
import { FormComponentContext } from 'src/layout';
import { useFormComponentCtx } from 'src/layout/FormComponentContext';
import { getKeyWithoutIndexIndicators } from 'src/utils/databindings';
import { transposeDataBinding } from 'src/utils/databindings/DataBinding';
import { buildInstanceDataSources } from 'src/utils/instanceDataSources';
Expand Down Expand Up @@ -74,7 +74,7 @@ export type ValidLanguageKey = ObjectToDotNotation<FixedLanguageList>;
export function useLanguage(node?: LayoutNode) {
const textResources = useTextResources();
const selectedAppLanguage = useCurrentLanguage();
const componentCtx = useContext(FormComponentContext);
const componentCtx = useFormComponentCtx();
const nearestNode = node || componentCtx?.node;
const formData = useAppSelector((state) => state.formData.formData);
const applicationSettings = useAppSelector((state) => state.applicationSettings.applicationSettings);
Expand Down
19 changes: 19 additions & 0 deletions src/layout/FormComponentContext.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { createContext } from 'src/core/contexts/context';
import type { IGrid } from 'src/layout/common.generated';
import type { LayoutNode } from 'src/utils/layout/LayoutNode';

export interface IFormComponentContext {
id: string;
baseComponentId: string | undefined;
node: LayoutNode;
grid?: IGrid;
}

const { Provider, useCtx } = createContext<IFormComponentContext | undefined>({
name: 'FormComponent',
required: false,
default: undefined,
});

export const useFormComponentCtx = () => useCtx();
export const FormComponentContextProvider = Provider;
14 changes: 8 additions & 6 deletions src/layout/GenericComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@ import { useLanguage } from 'src/features/language/useLanguage';
import { useAppDispatch } from 'src/hooks/useAppDispatch';
import { useAppSelector } from 'src/hooks/useAppSelector';
import { Triggers } from 'src/layout/common.generated';
import { FormComponentContext, shouldComponentRenderLabel } from 'src/layout/index';
import { FormComponentContextProvider } from 'src/layout/FormComponentContext';
import { shouldComponentRenderLabel } from 'src/layout/index';
import { SummaryComponent } from 'src/layout/Summary/SummaryComponent';
import { makeGetFocus } from 'src/selectors/getLayoutData';
import { gridBreakpoints, pageBreakStyles } from 'src/utils/formComponentUtils';
import { renderValidationMessagesForComponent } from 'src/utils/render';
import type { ISingleFieldValidation } from 'src/features/formData/formDataTypes';
import type { IGridStyling } from 'src/layout/common.generated';
import type { IComponentProps, IFormComponentContext, PropsFromGenericComponent } from 'src/layout/index';
import type { IFormComponentContext } from 'src/layout/FormComponentContext';
import type { IComponentProps, PropsFromGenericComponent } from 'src/layout/index';
import type { CompInternal, CompTypes, ITextResourceBindings } from 'src/layout/layout';
import type { LayoutComponent } from 'src/layout/LayoutComponent';
import type { IComponentFormData } from 'src/utils/formComponentUtils';
Expand Down Expand Up @@ -298,14 +300,14 @@ export function GenericComponent<Type extends CompTypes = CompTypes>({

if (layoutComponent.directRender(componentProps) || overrideDisplay?.directRender) {
return (
<FormComponentContext.Provider value={formComponentContext}>
<FormComponentContextProvider value={formComponentContext}>
<RenderComponent {...componentProps} />
</FormComponentContext.Provider>
</FormComponentContextProvider>
);
}

return (
<FormComponentContext.Provider value={formComponentContext}>
<FormComponentContextProvider value={formComponentContext}>
<Grid
data-componentbaseid={item.baseComponentId || item.id}
data-componentid={item.id}
Expand Down Expand Up @@ -341,7 +343,7 @@ export function GenericComponent<Type extends CompTypes = CompTypes>({
{showValidationMessages && renderValidationMessagesForComponent(filterValidationErrors(), id)}
</Grid>
</Grid>
</FormComponentContext.Provider>
</FormComponentContextProvider>
);
}

Expand Down
31 changes: 13 additions & 18 deletions src/layout/Group/RepeatingGroupsFocusContext.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import React, { createContext, useContext, useMemo, useRef } from 'react';
import React, { useMemo, useRef } from 'react';

import { createContext } from 'src/core/contexts/context';

type FocusableHTMLElement = HTMLElement &
HTMLButtonElement &
Expand All @@ -15,19 +17,16 @@ interface Context {
triggerFocus: FocusTrigger;
}

const RepeatingGroupsFocusContext = createContext<Context | null>(null);

export function useRepeatingGroupsFocusContext(): Context {
const context = useContext(RepeatingGroupsFocusContext);
if (!context) {
return {
refSetter: () => undefined,
triggerFocus: () => undefined,
};
}
const { Provider, useCtx } = createContext<Context>({
name: 'RepeatingGroupsFocus',
required: false,
default: {
refSetter: () => undefined,
triggerFocus: () => undefined,
},
});

return context;
}
export const useRepeatingGroupsFocusContext = () => useCtx();

export function RepeatingGroupsFocusProvider(props: { children: React.ReactNode }) {
const elementRefs = useMemo(() => new Map<string, HTMLElement | null>(), []);
Expand Down Expand Up @@ -67,11 +66,7 @@ export function RepeatingGroupsFocusProvider(props: { children: React.ReactNode
}
};

return (
<RepeatingGroupsFocusContext.Provider value={{ refSetter, triggerFocus }}>
{props.children}
</RepeatingGroupsFocusContext.Provider>
);
return <Provider value={{ refSetter, triggerFocus }}>{props.children}</Provider>;
}

function isFocusable(element: FocusableHTMLElement) {
Expand Down
17 changes: 1 addition & 16 deletions src/layout/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createContext, useMemo } from 'react';
import { useMemo } from 'react';

import { useAttachments } from 'src/features/attachments/AttachmentsContext';
import { useCurrentLanguage } from 'src/features/language/LanguageProvider';
Expand All @@ -9,7 +9,6 @@ import { ComponentConfigs } from 'src/layout/components.generated';
import type { IAttachments } from 'src/features/attachments';
import type { IFormData } from 'src/features/formData';
import type { AllOptionsMap } from 'src/features/options/useAllOptions';
import type { IGrid } from 'src/layout/common.generated';
import type { IGenericComponentProps } from 'src/layout/GenericComponent';
import type { CompInternal, CompRendersLabel, CompTypes } from 'src/layout/layout';
import type { AnyComponent, LayoutComponent } from 'src/layout/LayoutComponent';
Expand Down Expand Up @@ -66,20 +65,6 @@ export interface PropsFromGenericComponent<T extends CompTypes = CompTypes> exte
overrideDisplay?: IGenericComponentProps<T>['overrideDisplay'];
}

export interface IFormComponentContext {
grid?: IGrid;
id?: string;
baseComponentId?: string;
node?: LayoutNode;
}

export const FormComponentContext = createContext<IFormComponentContext>({
grid: undefined,
id: undefined,
baseComponentId: undefined,
node: undefined,
});

export function getLayoutComponentObject<T extends keyof CompClassMap>(type: T): CompClassMap[T] {
if (type && type in ComponentConfigs) {
return ComponentConfigs[type as keyof typeof ComponentConfigs].def as any;
Expand Down
2 changes: 1 addition & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"skipLibCheck": true,
"resolveJsonModule": true,
"strictNullChecks": true,
"noUnusedLocals": true,
"noUnusedLocals": false,
"esModuleInterop": true,
"strictBindCallApply": true,
"allowUnusedLabels": false,
Expand Down

0 comments on commit bf1edfa

Please sign in to comment.