Skip to content

Commit

Permalink
Merge pull request #3589 from webkom/quote-final-form
Browse files Browse the repository at this point in the history
  • Loading branch information
ivarnakken committed Feb 22, 2023
2 parents 9e9657d + f384303 commit b4b43eb
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 97 deletions.
12 changes: 6 additions & 6 deletions app/components/Form/LegoFinalForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import type { FormApi } from 'final-form';
import type { FormProps } from 'react-final-form';

const focusOnError = createFocusOnErrorDecorator();
type LegoFormProps = {
type LegoFormProps<FormValues> = {
onSubmit: (
values: Record<string, any>,
form: FormApi<Record<string, any>>
values: FormValues,
form: FormApi<FormValues>
) => Promise<Record<string, any> | void | null | undefined>;

/*
Expand All @@ -23,16 +23,16 @@ type LegoFormProps = {
/* Move the screen to the first error in the list on SubmissionError */
enableFocusOnError?: boolean;
};
type Props = LegoFormProps & FormProps<Record<string, any>>;
type Props<FormValues> = LegoFormProps<FormValues> & FormProps<FormValues>;

const LegoFinalForm = ({
const LegoFinalForm = <FormValues,>({
children,
onSubmit,
enableSubmissionError = true,
enableFocusOnError = true,
decorators = [],
...rest
}: Props) => {
}: Props<FormValues>) => {
if (enableFocusOnError) {
decorators = [focusOnError, ...decorators];
}
Expand Down
19 changes: 17 additions & 2 deletions app/components/Form/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,21 @@ export const handleSubmissionErrorFinalForm = (error: any) => {
* Usage:
* withSubmissionError(onSubmit)
*/
export const withSubmissionError = (func: (arg0: any) => Promise<any>) => {
return (data: any) => func(data).catch(handleSubmissionError);
export const withSubmissionError = <Args extends unknown[], Return>(
onSubmit: (...args: Args) => Promise<Return>
) => {
return (...data: Args) => onSubmit(...data).catch(handleSubmissionError);
};

/*
* Simple utility that handles submission errors (for final-form)
*
* Usage:
* withSubmissionErrorFinalForm(onSubmit)
*/
export const withSubmissionErrorFinalForm = <Args extends unknown[], Return>(
onSubmit: (...args: Args) => Promise<Return>
) => {
return (...args: Args) =>
onSubmit(...args).catch(handleSubmissionErrorFinalForm);
};
12 changes: 6 additions & 6 deletions app/routes/quotes/QuoteDetailRoute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,18 @@ import qs from 'qs';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { fetchEmojis } from 'app/actions/EmojiActions';
import {
fetchQuote,
approve,
unapprove,
deleteQuote,
} from 'app/actions/QuoteActions';
import { addReaction, deleteReaction } from 'app/actions/ReactionActions';
import { LoginPage } from 'app/components/LoginForm';
import { selectEmojis } from 'app/reducers/emojis';
import { selectQuoteById } from 'app/reducers/quotes';
import replaceUnlessLoggedIn from 'app/utils/replaceUnlessLoggedIn';
import withPreparedDispatch from 'app/utils/withPreparedDispatch';
import {
fetchQuote,
approve,
unapprove,
deleteQuote,
} from '../../actions/QuoteActions';
import QuotePage from './components/QuotePage';

const mapStateToProps = (state, props) => {
Expand Down
8 changes: 2 additions & 6 deletions app/routes/quotes/QuoteEditorRoute.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,13 @@
import { connect } from 'react-redux';
import { compose } from 'redux';
import { formValueSelector } from 'redux-form';
import { addQuotes } from 'app/actions/QuoteActions';
import { LoginPage } from 'app/components/LoginForm';
import replaceUnlessLoggedIn from 'app/utils/replaceUnlessLoggedIn';
import { addQuotes } from '../../actions/QuoteActions';
import AddQuote from './components/AddQuote';

const mapStateToProps = (state, props) => {
const valueSelector = formValueSelector('addQuote');
const mapStateToProps = (state) => {
return {
actionGrant: state.quotes.actionGrant,
text: valueSelector(state, 'text'),
source: valueSelector(state, 'source'),
};
};

Expand Down
153 changes: 83 additions & 70 deletions app/routes/quotes/components/AddQuote.tsx
Original file line number Diff line number Diff line change
@@ -1,95 +1,108 @@
import { Field } from 'react-final-form';
import { Helmet } from 'react-helmet-async';
import { Field, reduxForm } from 'redux-form';
import { Button, TextEditor, withSubmissionError } from 'app/components/Form';
import { Button, TextInput } from 'app/components/Form';
import LegoFinalForm from 'app/components/Form/LegoFinalForm';
import { withSubmissionErrorFinalForm } from 'app/components/Form/utils';
import RandomQuote from 'app/components/RandomQuote/RandomQuote';
import { spySubmittable, spyValues } from 'app/utils/formSpyUtils';
import { createValidator, required } from 'app/utils/validation';
import { navigation } from '../utils';
import styles from './Quotes.css';

type Props = {
addQuotes: (arg0: Record<string, any>) => Promise<any>;
invalid: boolean;
pristine: boolean;
submitting: boolean;
handleSubmit: (arg0: (arg0: Record<string, any>) => Promise<any>) => void;
addQuotes: (quote: { text: string; source: string }) => Promise<unknown>;
actionGrant: Array<string>;
};

type FormValues = {
text: string;
source: string;
};

const AddQuote = ({
addQuotes,
invalid,
pristine,
submitting,
handleSubmit,
actionGrant,
text,
source,
}: Props) => {
const disabledButton = invalid || pristine || submitting;
const initialValues: FormValues = {
text: '',
source: '',
};

const validate = createValidator({
text: [required()],
source: [required()],
});

const AddQuote = ({ addQuotes, actionGrant }: Props) => {
const onSubmit = withSubmissionErrorFinalForm(addQuotes);

return (
<div className={styles.root}>
<Helmet title="Nytt sitat" />
{navigation('Legg til sitat', actionGrant)}

<div className={styles.addQuote}>
<form onSubmit={handleSubmit(withSubmissionError(addQuotes))}>
<Field
placeholder="Eks: Det er bare å gjøre det"
label="Selve sitatet"
name="text"
component={TextEditor.Field}
/>
<LegoFinalForm
onSubmit={onSubmit}
validate={validate}
initialValues={initialValues}
subscription={{}}
>
{({ handleSubmit }) => (
<>
<div className={styles.addQuote}>
<form onSubmit={handleSubmit}>
<Field
placeholder="Eks: Det er bare å gjøre det"
label="Selve sitatet"
name="text"
component={TextInput.Field}
/>

<Field
placeholder="Eks: Esso"
label="Hvor sitatet kommer fra (sleng gjerne med noe snaks!)"
name="source"
component={TextEditor.Field}
type="text"
/>
<Field
placeholder="Eks: Esso"
label="Hvor sitatet kommer fra (sleng gjerne med noe snaks!)"
name="source"
component={TextInput.Field}
type="text"
/>

<div className={styles.clear} />
<div className={styles.clear} />

<Button type="submit" disabled={disabledButton}>
Send inn sitat
</Button>
</form>
</div>
{spySubmittable((submittable) => (
<Button type="submit" disabled={!submittable}>
Send inn sitat
</Button>
))}
</form>
</div>

<h2>Forhåndsvisning</h2>
<h3 className="u-ui-heading">Overhørt</h3>
<div className={styles.innerPreview}>
<RandomQuote
fetchRandomQuote={() => Promise.resolve()}
addReaction={() => Promise.resolve()}
deleteReaction={() => Promise.resolve()}
fetchEmojis={() => Promise.resolve()}
fetchingEmojis={false}
emojis={[]}
currentQuote={{
id: 1,
text: text || 'Det er bare å gjøre det',
source: source || 'Esso',
approved: true,
contentTarget: '',
reactionsGrouped: [],
reactions: [],
}}
loggedIn={true}
useReactions={false}
/>
</div>
<h2>Forhåndsvisning</h2>
<h3 className="u-ui-heading">Overhørt</h3>
<div className={styles.innerPreview}>
{spyValues<FormValues>((values) => (
<RandomQuote
fetchRandomQuote={() => Promise.resolve()}
addReaction={() => Promise.resolve()}
deleteReaction={() => Promise.resolve()}
fetchEmojis={() => Promise.resolve()}
fetchingEmojis={false}
emojis={[]}
currentQuote={{
id: 1,
text: values.text || 'Det er bare å gjøre det',
source: values.source || 'Esso',
approved: true,
contentTarget: '',
reactionsGrouped: [],
reactions: [],
reactionCount: 0,
}}
loggedIn={true}
useReactions={false}
/>
))}
</div>
</>
)}
</LegoFinalForm>
</div>
);
};

const validate = createValidator({
text: [required()],
source: [required()],
});
export default reduxForm({
form: 'addQuote',
validate,
})(AddQuote);
export default AddQuote;
16 changes: 9 additions & 7 deletions app/utils/formSpyUtils.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import { FormSpy } from 'react-final-form';
import type { ReactNode } from 'react';
import type { FormSpyRenderProps } from 'react-final-form';

export const spyValues = (
render: (values: Record<string, any>) => React.ReactNode
export const spyValues = <FormValues,>(
render: (values: FormValues) => ReactNode
) => (
<FormSpy
subscription={{
values: true,
}}
>
{({ values }) => render(values)}
{({ values }: FormSpyRenderProps<FormValues>) => render(values)}
</FormSpy>
);
export const spyFormError = (render: (error: any) => React.ReactNode) => (

export const spyFormError = (render: (error: any) => ReactNode) => (
<FormSpy
subscription={{
error: true,
Expand All @@ -21,9 +24,8 @@ export const spyFormError = (render: (error: any) => React.ReactNode) => (
{({ error, submitError }) => render(error || submitError)}
</FormSpy>
);
export const spySubmittable = (
render: (submittable: boolean) => React.ReactNode
) => (

export const spySubmittable = (render: (submittable: boolean) => ReactNode) => (
<FormSpy
subscription={{
pristine: true,
Expand Down

0 comments on commit b4b43eb

Please sign in to comment.