Skip to content

InseeFr/Lunatic

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Lunatic logo

Lunatic

Lunatic CI npm version Coverage Quality Gate Status License: MIT

Lunatic is a front-end library in the form of a React hook and component libraries for generating a questionnaire from the Lunatic-Model data format.

Table of Contents

Usage

To get started, you need to install Lunatic:

yarn add @inseefr/lunatic

The useLunatic Hook

Next, wherever you want to display the form, you'll need to use the useLunatic hook.

import { useLunatic } from '@inseefr/lunatic';

const obj = useLunatic(source, data, options);

This hook takes three parameters:

  • The source, which is a JSON representation of the Lunatic-Model.
  • The data, which contains the initial questionnaire data (can be an empty object).
  • An options object to configure the behavior.
    • features (default ['VTL', 'MD']), allows you to define supported functionalities.
    • preferences (default ['COLLECTED']).
    • onChange (default () => {}), allows you to add logic to apply when an answer is changed (must be memoized as it's used as a dependency in an internal useCallback).
    • management (default false): Not yet implemented, will allow managing multiple states of the same variable (used by recovery positions).
    • initialPage (default '1'), allows you to define the starting page.
    • lastReachedPage (default undefined), allows you to define the furthest reached page.
    • autoSuggesterLoading (default false).
    • suggesters.
    • suggesterFetcher (default fetch), method used to retrieve suggester data.
    • activeControls (default false), activates data controls.

And it returns an object that allows you to control the questionnaire:

  • getComponents(), returns the components to display for the current page.
  • goPreviousPage(), allows you to go to the previous page.
  • goNextPage(), allows you to go to the next page.
  • goToPage({ page: string }), allows you to go to an arbitrary page.
  • getErrors(), returns the errors.
  • getModalErrors(), returns the errors in modals.
  • getCurrentErrors(), returns the errors for the current page.
  • pageTag, a string containing the page number (e.g., 8.1).
  • isFirstPage.
  • isLastPage.
  • pager, an object containing information related to the page.
  • waiting, indicates waiting for information from a suggester.
  • getData(), returns the collected data in the questionnaire.
  • loopVariables, is an array containing the list of variables used for the current loop.

For more information on the types of this return, you can refer to the available types in the source code. You can also find an example of using the hook in the Storybook section.

Components

To display the questionnaire, start by retrieving the list of components to display using the getComponents() method returned by the hook.

Lunatic offers a library of pre-designed components to cover the different types of fields available in questionnaires.

import * as lunatic from '@inseefr/lunatic';

function App({ source, data }) {
	const { getComponents, getCurrentErrors, getModalErrors } =
		lunatic.useLunatic(source, data, {});
	const components = getComponents();
	const currentErrors = getCurrentErrors();
	const modalErrors = getModalErrors();

	return (
		<div className="container">
			<LunaticComponents components={components} />
			<lunatic.Modal errors={modalErrors} goNext={goNextPageAction} />
		</div>
	);
}

All the components offered by Lunatic are available in the src/components folder.

To activate the autofocus, you need to pass a key in the autoFocusKey property of LunaticComponents. As soon as this value changes, the first field is focused (a good solution is to pass the pageTag provided by useLunatic).

Customization

By default, the components offered by Lunatic are quite simple with a minimal style. You can customize the fields with your own CSS, but for more complex cases, you can also replace the basic components using the custom property that you can pass when calling useLunatic.

const custom = {
	Input: MyCustomInput,
	InputNumber: MyCustomInputNumber,
};

function App({ source, data }) {
	const {} = useLunatic(source, data, { custom });

	// ...
}

Internal Working

This section covers the internal working of the useLunatic() hook. The goal is to help understand how it operates.

General Functioning

The hook is based on an internal state that is updated through a reducer system. The actions affecting this state are limited:

  • An action 'use-lunatic/on-init' allows initialization of the state from the data received as a parameter of the hook.
  • The actions 'use-lunatic/go-previous', 'use-lunatic/go-next', and 'use-lunatic/go-to-page' are called during navigation using the methods returned by the hook.
  • The action use-lunatic/handle-change is the most important action and is called whenever data is changed in the questionnaire.

All the reducers corresponding to these actions are available here. Generally, they are broken down into several methods depending on the part of the state they modify.

Pages and Pager

At initialization, the questionnaire scenario is modeled as an object which is stored in the state (in the pages property). This object is indexed by page number and contains the list of components to display for each page. Combined with the pager which contains the state of navigation, this property allows resolving the components to display.

VTL Execution

Another important point of Lunatic is the execution of VTL expressions which allow making certain properties dynamic (labels, errors, etc.). This filling is done when the state changes.

To facilitate expression execution, an executeExpression() method is exposed in the Lunatic state. This method is accompanied by an updateBindings() method which allows updating internal values. This expression execution system uses a memoization system to not re-execute the same expression multiple times. When the use-lunatic/handle-change action is executed, the values ("bindings") are updated to memorize the values associated with the different VTL variables. Similarly, the values of calculated variables on which the modified variable depends are forgotten to refresh the value during the next execution.

Convention and Best Practices

Stable Branchs

  • Stable branches follow the glob pattern '2.*' or '3.*', like 2.7 or 3.0.
  • We can maintain if needs, the old stable branches
  • 3.0 branch is currently the most advanced branch

Commits and feature branches

  • Avoid "default" exports as it impairs readability during import.

  • Comments in the code should be in English.

  • Files containing JSX should use the .jsx (or .tsx) extension.

  • Commits follow the specification Conventional Commits.

  • Pull Requests should be prefixed in the same convention as commits:

    • test(XXX?): XXX for adding tests.
    • feat(XXX?): XXX for adding new features.
    • fix(XXX?): XXX for bug fixes.
    • docs(XXX?): XXX for adding documentation.
    • refactor(XXX?): XXX for refactoring that doesn't change functionality.
    • build(XXX?): XXX for changes related to the build process, compilation scripts, etc.
    • style(XXX?): XXX for style modifications.
    • ci(XXX?): XXX for CI modification.
    • perf(XXX?): XXX for performance improvement.
    • revert(XXX?): XXX to revert a previous PR.
    • chore(XXX?): XXX for maintenance tasks or tasks that don't fall into other categories.
  • Branches should be prefixed (following the same prefixes as Conventional Commits):

    • test/XXX: for adding tests.
    • feat/XXX: for adding new features.
    • fix/XXX: for bug fixes.
    • docs/XXX: for adding documentation.
    • refactor/XXX: for refactoring that doesn't change functionality.
    • build/XXX: for changes related to the build process, compilation scripts, etc.
    • style/XXX: for changes related to code style.
    • ci/XXX: for changes related to continuous integration (CI).
    • perf/XXX: for performance improvements.
    • revert/XXX: to revert a previous PR.
    • chore/XXX: for maintenance tasks or tasks that don't fall into other categories.

Contributing

Testing your changes in an external app

You have made some changes to the code and you want to test them in your app before submitting a pull request?

Assuming you/my-app have @inseefr/lunatic as a dependency.

cd ~/github
git clone https://github.com/you/my-app
cd my-app
yarn

cd ~/github
git clone https://github.com/InseeFr/Lunatic
cd Lunatic
yarn
yarn build
yarn link-in-app my-app
npx tsc -w

# Open another terminal

cd ~/github/my-app
rm -rf node_modules/.cache
yarn start # Or whatever my-app is using for starting the project

You don't have to use ~/github as reference path. Just make sure my-app and @inseefr/lunatic are in the same directory.

Note for the maintainer: You might run into issues if you do not list all your singleton dependencies in src/link-in-app.js -> singletonDependencies. A singleton dependency is a dependency that can only be present once in an App. Singleton dependencies are usually listed as peerDependencies example react, @emotion/*.

Releasing

For releasing a new version on GitHub and NPM you don't need to create a tag.
Just update the package.json version number and push.

For publishing a release candidate update your package.json with 1.3.4-rc.0 (.1, .2, ...).
It also work if you do it from a branch that have an open PR on main.

Make sure your have defined the NPM_TOKEN repository secret or NPM publishing will fail.

Build

We build this library in ESM and CJS. ESM is the standard when you develop a front-end app. But if you need test which use @inseefr/lunatic, you need lunatic library as CJS lib (to allow to run in node environnement). So we have two build: one for ESM and one for CJS. You have nothing to change in your code base, it's simply working.