Portal Community

Complete Minimal Player

// src/players/MinimalPlayer.tsx
import React, { useMemo } from 'react';
import { FormStateProvider }  from '@atlas-forms/state-react';
import { ControlRenderer,
         useAtlasForm }       from '@atlas-forms/player-components-react';
import { FormEngine }         from '@atlas-forms/form-engine-js';
import { parseSchema }        from '@atlas-forms/schema-js';
import type { FormSchema,
              FormMode }      from '@atlas-forms/types-js';

interface MinimalPlayerProps {
  schema:          FormSchema | object;
  initialValues?:  Record<string, any>;
  mode?:           FormMode;
  onSubmit:        (values: Record<string, any>) => Promise<void>;
}

// Inner component — must be inside FormStateProvider
const MinimalPlayerInner: React.FC<{ onSubmit: (v: any) => Promise<void> }> = ({ onSubmit }) => {
  const { values, errors, isValid, isSubmitting, submitForm, engine } = useAtlasForm();
  const schema  = engine.getSchema();
  const controls = schema.controls ?? [];

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    await submitForm();
    if (isValid) {
      await onSubmit(values);
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      {controls.map(control => (
        <ControlRenderer key={control.id} control={control} />
      ))}

      {Object.keys(errors).length > 0 && (
        <div className="error-summary">
          {Object.values(errors).map((msg, i) => (
            <p key={i} className="error">{msg}</p>
          ))}
        </div>
      )}

      <button type="submit" disabled={isSubmitting}>
        {isSubmitting ? 'Submitting...' : 'Submit'}
      </button>
    </form>
  );
};

// Outer component — creates FormEngine and wraps in FormStateProvider
export const MinimalPlayer: React.FC<MinimalPlayerProps> = ({
  schema: rawSchema,
  initialValues = {},
  mode = 'edit',
  onSubmit
}) => {
  const schema = useMemo(() =>
    rawSchema instanceof Object && 'controls' in rawSchema
      ? rawSchema as FormSchema
      : parseSchema(rawSchema as object),
    [rawSchema]
  );

  const engine = useMemo(() =>
    new FormEngine(schema, initialValues),
    [schema, initialValues]
  );

  return (
    <FormStateProvider engine={engine} mode={mode}>
      <MinimalPlayerInner onSubmit={onSubmit} />
    </FormStateProvider>
  );
};

Usage

import { MinimalPlayer } from './players/MinimalPlayer';
import schema from './my-form.schema.json';

<MinimalPlayer
  schema={schema}
  initialValues={{ country: 'UK' }}
  onSubmit={async (values) => {
    console.log('Submitted:', values);
  }}
/>

What This Player Does Not Have

The minimal player omits several features of FormRenderer — add them incrementally as your custom player needs grow:

FeatureHow to Add
Draft auto-saveSubscribe to engine change event; save via StorageManager
Mode-based visibilityUse useFormVisibility() to filter controls before rendering
Section groupingGroup controls by control.sectionId before rendering
ThemingWrap in ThemeProvider above FormStateProvider
Action bar actionsRender form schema.actions below the field list
useMemo on FormEngine Always create the FormEngine instance inside a useMemo — do not create it directly in the render function. A new engine on every render would reset all form state on each parent re-render.