Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@
- [@bedrockstreaming/form-builder](libs/form-builder/README.md) :construction_worker:
- [@bedrockstreaming/form-validation-rule-list](libs/form-validation-rule-list/README.md) 🧑‍⚖️
- [@bedrockstreaming/form-redux](libs/form-redux/README.md) :globe_with_meridians:
- [@bedrockstreaming/form-context](libs/form-context/README.md) :globe_with_meridians:

## Why

The idea of this library came from the variety of requests our customers had in terms of forms. Thus, we wanted to be able to generate any form by simply passing some config and a dictionary of inputs to go with.
As we were eager to keep some control over the process, but not willing to control the form state ourselves, we went with [react-hook-form](https://react-hook-form.com/) which has great capabilities. Unfortunately we were missing some features that we had to implement ourselves.

- Complex validation with multiple visuals feedback
- Complex validation with multiple visuals feedback (at the same time)
- Steps handling

We believe that anyone using react could use our libraries to create and manage forms the way we do. We are still exposing - what we think are - the relevant parts of `react-hook-form` API so we think of the FormBuilder as an opinionated solution to industrialize forms across your application.
Expand Down
8 changes: 5 additions & 3 deletions apps/demo/src/app/app.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Switch, Route } from 'react-router-dom';

import { Layout } from './components/app/layout.component';
import MUIForm from './examples/with-material-ui/form.component';
import { FormContainer } from './examples/with-material-ui/login/form.container';
import MUIRegisterForm from './examples/with-material-ui/register/form.component';
import { Generator as SchemaBuilder } from '@bedrockstreaming/form-editor';
import StyledForm from './examples/with-styled-components/form.component';
import { dictionary } from './examples/with-material-ui/dictionary';
Expand All @@ -13,7 +14,7 @@ export function App() {
<main>
<Switch>
<Route exact path="/">
<MUIForm />
<MUIRegisterForm />
</Route>
<Route exact path="/schema-builder">
<SchemaBuilder
Expand All @@ -25,7 +26,8 @@ export function App() {
<StyledForm />
</Route>
<Route exact path="/examples/material-ui">
<MUIForm />
<MUIRegisterForm />
<FormContainer />
</Route>
</Switch>
</main>
Expand Down
131 changes: 131 additions & 0 deletions apps/demo/src/app/examples/with-material-ui/login/form.component.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { useEffect } from 'react';
import _ from 'lodash';

import { FieldValues } from 'react-hook-form';
import { FormBuilder } from '@bedrockstreaming/form-builder';
import {
useFormsDispatch,
useFormsState,
getCurrentStepIndex,
isLastStep as isLastStepSelector,
initForm,
setNextStep,
updateFormData,
getFormData,
setPreviousStep
} from '@bedrockstreaming/form-context';
import {
Divider,
Paper,
Typography,
Stepper,
Step,
StepLabel,
Box
} from '@mui/material';
import { makeStyles } from '@mui/styles';

import { config } from '../../../login.config';
import { dictionary } from '../dictionary';
import { useSubmit } from '../../../hooks/useLoginSubmit.hook';
import { extraValidation } from '../../../extraValidation';

const formId = 'login';
const defaultValues = {
email: '',
password: ''
};

const {
schemas: { login: schema }
} = config;

const useStyles = makeStyles({
root: {
margin: '0 auto',

'& .validation-rule-ul': {
display: 'flex',
padding: 0,
listStyle: 'none'
},

'& .validation-rule-ul li': {
margin: '4px',
fontSize: 'smaller'
},

'& .complete-li': {
color: '#4ed569'
},
'& .incomplete-li,.idle-li': {
color: '#da3b2b'
},
'& .step-fields-actions': {
width: '100%',
display: 'flex',
justifyContent: 'center'
}
}
});

const Form = () => {
const classes = useStyles();
const dispatch = useFormsDispatch();
const state = useFormsState();
const currentStepIndex = getCurrentStepIndex(formId)(state);
const isLastStep = isLastStepSelector(formId)(state);
const previousValues = getFormData(formId)(state);

useEffect(() => {
dispatch(initForm(formId, schema));
}, [dispatch]);

const handleSubmit = useSubmit(formId);

const handleNextStep = (fieldsValues: FieldValues) => {
dispatch(updateFormData(formId, fieldsValues));
dispatch(setNextStep(formId));
};

const handlePreviousStep = () => {
dispatch(setPreviousStep(formId));
};

return (
<Paper className={classes.root} sx={{ p: 2, m: 2 }}>
<Typography component="h1" variant="h6">
{formId} with React Context API
</Typography>
<Divider />
<Box sx={{ m: 2 }}>
<Box sx={{ p: 2 }}>
<Stepper activeStep={currentStepIndex}>
{Object.keys(schema.steps).map((label, index) => {
return (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
);
})}
</Stepper>
</Box>
<FormBuilder
dictionary={dictionary}
schema={schema}
defaultValues={
_.isEmpty(previousValues) ? defaultValues : previousValues
}
onNextStep={handleNextStep}
onPreviousStep={handlePreviousStep}
onSubmit={handleSubmit}
currentStepIndex={currentStepIndex}
isLastStep={isLastStep}
extraValidation={extraValidation}
/>
</Box>
</Paper>
);
};

export default Form;
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { FormsProvider } from '@bedrockstreaming/form-context';
import Form from '../login/form.component';

export const FormContainer = () => {
return (
<FormsProvider>
<Form />
</FormsProvider>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ import {
} from '@mui/material';
import { makeStyles } from '@mui/styles';

import { config } from '../../config';
import { dictionary } from './dictionary';
import { useSubmit } from '../../hooks/useSubmit.hook';
import { extraValidation } from '../../extraValidation';
import { config } from '../../../register.config';
import { dictionary } from '../dictionary';
import { useSubmit } from '../../../hooks/useRegisterSubmit.hook';
import { extraValidation } from '../../../extraValidation';

const formId = 'register';
const defaultValues = {
Expand Down Expand Up @@ -104,9 +104,9 @@ const Form = () => {
);

return (
<Paper className={classes.root} sx={{ p: 2 }}>
<Paper className={classes.root} sx={{ p: 2, m: 2 }}>
<Typography component="h1" variant="h6">
{formId}
{formId} with React Redux
</Typography>
<Divider />
<Box sx={{ m: 2 }}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,9 @@ import {
} from '@bedrockstreaming/form-redux';
import _ from 'lodash';

import { config } from '../../config';
import { config } from '../../register.config';
import { dictionary } from './dictionary';
import { useSubmit } from '../../hooks/useSubmit.hook';
import { useSubmit } from '../../hooks/useRegisterSubmit.hook';
import { extraValidation } from '../../extraValidation';

const formId = 'register';
Expand Down
25 changes: 25 additions & 0 deletions apps/demo/src/app/hooks/useLoginSubmit.hook.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { useCallback } from 'react';
import { updateFormData, useFormsDispatch } from '@bedrockstreaming/form-context';

const transformFields = (x) => x;
const formSubmit = (processedFields) => ({
type: 'some_scope/SUBMIT',
payload: processedFields
});

export const useSubmit = (formId) => {
const dispatch = useFormsDispatch();

const callback = useCallback(
async (fieldsValues) => {
dispatch(updateFormData(formId, fieldsValues));

const processedFields = transformFields(fieldsValues);

return console.log(formSubmit(processedFields));
},
[dispatch, formId]
);

return callback;
};
85 changes: 85 additions & 0 deletions apps/demo/src/app/login.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
export const config = {
formIds: {
login: 'login'
},
schemas: {
login: {
fields: {
email: {
id: 'email',
meta: {
errorMessage: 'Invalid Email',
label: 'Email',
name: 'email'
},
title: 'Email',
type: 'text',
validation: {
checkPattern: {
key: 'checkPattern',
message: 'Email format',
value:
'^(([^<>()[\\]\\\\.,;:\\s@"]+(\\.[^<>()[\\]\\\\.,;:\\s@"]+)*)|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$'
},
required: {
key: 'required',
message: 'Required field',
value: true
}
}
},
password: {
id: 'password',
meta: {
errorMessage: 'Invalid Password',
label: 'Password',
name: 'password'
},
title: 'password',
type: 'password',
validation: {
checkForLower: {
key: 'checkForLower',
message: 'Lowercase expected'
},
checkForNumber: {
key: 'checkForNumber',
message: 'Number expected'
},
checkForUpper: {
key: 'checkForUpper',
message: 'Uppercase expected'
},
checkMinLength: {
key: 'checkMinLength',
message: 'Minimum chars expected',
value: 8
},
required: {
key: 'required',
message: 'Required field',
value: true
}
}
}
},
steps: {
'login-step-0': {
fieldsById: ['email'],
id: 'login-step-0',
submit: {
label: 'Next'
}
},
'login-step-1': {
fieldsById: ['password'],
id: 'login-step-1',
submit: {
label: 'Next'
}
}
},
stepsById: ['login-step-0', 'login-step-1']
}
}
};
File renamed without changes.
Loading