Skip to content

Commit

Permalink
Merge pull request #239 from SFTtech/milo/remove-formik
Browse files Browse the repository at this point in the history
remove formik in favor of react-hook-form
  • Loading branch information
mikonse authored Sep 22, 2024
2 parents 9dc5c75 + deba396 commit 95e3f88
Show file tree
Hide file tree
Showing 78 changed files with 1,226 additions and 1,303 deletions.
4 changes: 2 additions & 2 deletions frontend/apps/mobile/src/components/DateTimeInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { DateTimePickerAndroid, DateTimePickerEvent } from "@react-native-commun
import React, { useEffect, useState } from "react";
import { HelperText, TextInput } from "react-native-paper";

interface Props
export interface DateTimeInputProps
extends Omit<React.ComponentProps<typeof TextInput>, "onChange" | "value" | "disabled" | "editable" | "mode"> {
value: Date | null;
onChange: (newValue: Date) => void;
Expand All @@ -12,7 +12,7 @@ interface Props
editable?: boolean;
}

export const DateTimeInput: React.FC<Props> = ({
export const DateTimeInput: React.FC<DateTimeInputProps> = ({
value,
onChange,
mode = "date",
Expand Down
27 changes: 27 additions & 0 deletions frontend/apps/mobile/src/components/FormCheckbox.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import * as React from "react";
import { Control, Controller } from "react-hook-form";
import { Checkbox, HelperText, CheckboxItemProps } from "react-native-paper";

export type FormCheckboxProps = Omit<CheckboxItemProps, "onPress" | "status"> & {
name: string;
control: Control<any, any>;
};

export const FormCheckbox: React.FC<FormCheckboxProps> = ({ name, control, ...props }) => {
return (
<Controller
name={name}
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<>
<Checkbox.Item
status={value ? "checked" : "unchecked"}
onPress={() => onChange(!value)}
{...props}
/>
{error && <HelperText type="error">{error.message}</HelperText>}
</>
)}
/>
);
};
30 changes: 30 additions & 0 deletions frontend/apps/mobile/src/components/FormDateTimeInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import * as React from "react";
import { Control, Controller } from "react-hook-form";
import { HelperText } from "react-native-paper";
import DateTimeInput, { DateTimeInputProps } from "./DateTimeInput";
import { fromISOStringNullable, toISODateStringNullable } from "@abrechnung/utils";

export type FormDateTimeInputProps = Omit<DateTimeInputProps, "onChange" | "value" | "error"> & {
name: string;
control: Control<any, any>;
};

export const FormDateTimeInput: React.FC<FormDateTimeInputProps> = ({ name, control, ...props }) => {
return (
<Controller
control={control}
name={name}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<>
<DateTimeInput
value={fromISOStringNullable(value)}
onChange={(val) => onChange(toISODateStringNullable(val))}
error={!!error}
{...props}
/>
{error && <HelperText type="error">{error.message}</HelperText>}
</>
)}
/>
);
};
24 changes: 24 additions & 0 deletions frontend/apps/mobile/src/components/FormNumericInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from "react";
import { Control, Controller } from "react-hook-form";
import { HelperText } from "react-native-paper";
import { NumericInput, NumericInputProps } from "./NumericInput";

export type FormNumericInput = Omit<NumericInputProps, "onChange" | "value" | "error"> & {
name: string;
control: Control<any, any>;
};

export const FormNumericInput: React.FC<FormNumericInput> = ({ name, control, ...props }) => {
return (
<Controller
name={name}
control={control}
render={({ field: { onChange, onBlur, value }, fieldState: { error } }) => (
<>
<NumericInput error={!!error} onChange={onChange} onBlur={onBlur} value={value} {...props} />
{error && <HelperText type="error">{error.message}</HelperText>}
</>
)}
/>
);
};
24 changes: 24 additions & 0 deletions frontend/apps/mobile/src/components/FormTagSelect.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from "react";
import { Control, Controller } from "react-hook-form";
import { HelperText } from "react-native-paper";
import { TagSelect, TagSelectProps } from "./tag-select";

export type FormTagSelect = Omit<TagSelectProps, "value" | "onChange"> & {
name: string;
control: Control<any, any>;
};

export const FormTagSelect: React.FC<FormTagSelect> = ({ name, control, ...props }) => {
return (
<Controller
control={control}
name={name}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<>
<TagSelect value={value} onChange={onChange} {...props} />
{error && <HelperText type="error">{error.message}</HelperText>}
</>
)}
/>
);
};
23 changes: 23 additions & 0 deletions frontend/apps/mobile/src/components/FormTextInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import * as React from "react";
import { Control, Controller } from "react-hook-form";
import { HelperText, TextInput, TextInputProps } from "react-native-paper";

export type FormTextInputProps = Omit<TextInputProps, "onChange" | "value" | "error"> & {
name: string;
control: Control<any, any>;
};

export const FormTextInput: React.FC<FormTextInputProps> = ({ name, control, ...props }) => {
return (
<Controller
name={name}
control={control}
render={({ field: { onChange, onBlur, value }, fieldState: { error } }) => (
<>
<TextInput error={!!error} onChange={onChange} onBlur={onBlur} value={value} {...props} />
{error && <HelperText type="error">{error.message}</HelperText>}
</>
)}
/>
);
};
24 changes: 24 additions & 0 deletions frontend/apps/mobile/src/components/FormTransactionShareInput.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from "react";
import { Control, Controller } from "react-hook-form";
import { HelperText } from "react-native-paper";
import { TransactionShareInput, TransactionShareInputProps } from "./transaction-shares/TransactionShareInput";

export type FormTransactionShareInputProps = Omit<TransactionShareInputProps, "value" | "onChange"> & {
name: string;
control: Control<any, any>;
};

export const FormTransactionShareInput: React.FC<FormTransactionShareInputProps> = ({ name, control, ...props }) => {
return (
<Controller
control={control}
name={name}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<>
<TransactionShareInput value={value} onChange={onChange} error={!!error} {...props} />
{error && <HelperText type="error">{error.message}</HelperText>}
</>
)}
/>
);
};
4 changes: 2 additions & 2 deletions frontend/apps/mobile/src/components/NumericInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ import React from "react";
import { TextInput } from "react-native-paper";
import { parseAbrechnungFloat } from "@abrechnung/utils";

type Props = Omit<React.ComponentProps<typeof TextInput>, "onChange" | "value"> & {
export type NumericInputProps = Omit<React.ComponentProps<typeof TextInput>, "onChange" | "value"> & {
value: number;
onChange: (newValue: number) => void;
};

export const NumericInput: React.FC<Props> = ({ value, onChange, ...props }) => {
export const NumericInput: React.FC<NumericInputProps> = ({ value, onChange, ...props }) => {
const [internalValue, setInternalValue] = React.useState("");
const { editable } = props;

Expand Down
6 changes: 6 additions & 0 deletions frontend/apps/mobile/src/components/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,9 @@ export * from "./NumericInput";
export * from "./DateTimeInput";
export * from "./CurrencySelect";
export * from "./tag-select";
export * from "./FormTextInput";
export * from "./FormCheckbox";
export * from "./FormTransactionShareInput";
export * from "./FormTagSelect";
export * from "./FormNumericInput";
export * from "./FormDateTimeInput";
4 changes: 2 additions & 2 deletions frontend/apps/mobile/src/components/tag-select/TagSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import { TouchableHighlight, View } from "react-native";
import { Portal, Text, useTheme } from "react-native-paper";
import { TagSelectDialog } from "./TagSelectDialog";

interface Props {
export interface TagSelectProps {
groupId: number;
label: string;
value: string[];
disabled: boolean;
onChange: (newValue: string[]) => void;
}

export const TagSelect: React.FC<Props> = ({ groupId, label, value, onChange, disabled }) => {
export const TagSelect: React.FC<TagSelectProps> = ({ groupId, label, value, onChange, disabled }) => {
const theme = useTheme();
const [showDialog, setShowDialog] = useState(false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { TransactionShare } from "@abrechnung/types";
import { useAppSelector } from "../../store";
import { selectGroupAccounts } from "@abrechnung/redux";

interface Props {
export interface TransactionShareInputProps {
groupId: number;
title: string;
multiSelect: boolean;
Expand All @@ -19,7 +19,7 @@ interface Props {
excludedAccounts?: number[];
}

export const TransactionShareInput: React.FC<Props> = ({
export const TransactionShareInput: React.FC<TransactionShareInputProps> = ({
groupId,
title,
multiSelect,
Expand Down Expand Up @@ -103,5 +103,3 @@ export const TransactionShareInput: React.FC<Props> = ({
</>
);
};

export default TransactionShareInput;
101 changes: 40 additions & 61 deletions frontend/apps/mobile/src/screens/AddGroup.tsx
Original file line number Diff line number Diff line change
@@ -1,48 +1,57 @@
import { components } from "@abrechnung/api";
import { createGroup } from "@abrechnung/redux";
import { toFormikValidationSchema } from "@abrechnung/utils";
import { useFormik } from "formik";
import React from "react";
import { StyleSheet, View } from "react-native";
import { Button, Checkbox, HelperText, ProgressBar, TextInput, useTheme } from "react-native-paper";
import { Button, HelperText, useTheme } from "react-native-paper";
import { CurrencySelect } from "../components/CurrencySelect";
import { useApi } from "../core/ApiProvider";
import { GroupStackScreenProps } from "../navigation/types";
import { useAppDispatch } from "../store";
import { StackNavigationOptions } from "@react-navigation/stack";
import { z } from "zod";
import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { FormCheckbox, FormTextInput } from "../components";
import { notify } from "../notifications";

type FormSchema = z.infer<typeof components.schemas.GroupPayload>;

export const AddGroup: React.FC<GroupStackScreenProps<"AddGroup">> = ({ navigation }) => {
const theme = useTheme();
const dispatch = useAppDispatch();
const { api } = useApi();

const formik = useFormik({
initialValues: {
const {
control,
handleSubmit,
reset: resetForm,
} = useForm<FormSchema>({
resolver: zodResolver(components.schemas.GroupPayload),
defaultValues: {
name: "",
description: "",
currency_symbol: "€",
terms: "",
add_user_account_on_join: false,
},
validationSchema: toFormikValidationSchema(components.schemas.GroupPayload),
onSubmit: (values, { setSubmitting }) => {
setSubmitting(true);
});
const onSubmit = React.useCallback(
(values: FormSchema) => {
dispatch(createGroup({ api, group: values }))
.unwrap()
.then(() => {
setSubmitting(false);
navigation.goBack();
})
.catch(() => {
setSubmitting(false);
notify({ text: "An error occured during group creation" });
});
},
});
[dispatch, navigation, api]
);

const cancel = React.useCallback(() => {
formik.resetForm();
resetForm();
navigation.goBack();
}, [formik, navigation]);
}, [resetForm, navigation]);

React.useLayoutEffect(() => {
navigation.setOptions({
Expand All @@ -53,63 +62,33 @@ export const AddGroup: React.FC<GroupStackScreenProps<"AddGroup">> = ({ navigati
<Button onPress={cancel} textColor={theme.colors.error}>
Cancel
</Button>
<Button onPress={() => formik.handleSubmit()}>Save</Button>
<Button onPress={() => handleSubmit(onSubmit)()}>Save</Button>
</>
);
},
} as any);
}, [theme, navigation, formik, cancel]);
}, [theme, navigation, handleSubmit, onSubmit, cancel]);

return (
<View style={styles.container}>
{formik.isSubmitting ? <ProgressBar indeterminate /> : null}
<TextInput
label="Name"
value={formik.values.name}
style={styles.input}
onChangeText={(val) => formik.setFieldValue("name", val)}
error={formik.touched.name && !!formik.errors.name}
/>
{formik.touched.name && !!formik.errors.name ? (
<HelperText type="error">{formik.errors.name}</HelperText>
) : null}
<TextInput
label="Description"
value={formik.values.description}
style={styles.input}
onChangeText={(val) => formik.setFieldValue("description", val)}
error={formik.touched.description && !!formik.errors.description}
/>
{formik.touched.description && !!formik.errors.description ? (
<HelperText type="error">{formik.errors.description}</HelperText>
) : null}
<TextInput
label="Terms"
value={formik.values.terms}
style={styles.input}
multiline={true}
onChangeText={(val) => formik.setFieldValue("terms", val)}
error={formik.touched.terms && !!formik.errors.terms}
/>
{formik.touched.terms && !!formik.errors.terms ? (
<HelperText type="error">{formik.errors.terms}</HelperText>
) : null}
<CurrencySelect
label="Currency"
value={formik.values.currency_symbol}
onChange={(val) => formik.setFieldValue("currency_symbol", val)}
// error={formik.touched.description && !!formik.errors.currency_symbol}
<FormTextInput label="Name" name="name" control={control} style={styles.input} />
<FormTextInput label="Description" name="description" control={control} style={styles.input} />
<FormTextInput label="Terms" name="terms" control={control} style={styles.input} multiline={true} />
<Controller
name="currency_symbol"
control={control}
render={({ field: { onChange, value }, fieldState: { error } }) => (
<>
<CurrencySelect label="Currency" value={value} onChange={onChange} />
{error && <HelperText type="error">{error.message}</HelperText>}
</>
)}
/>
{formik.touched.currency_symbol && !!formik.errors.description ? (
<HelperText type="error">{formik.errors.currency_symbol}</HelperText>
) : null}
<Checkbox.Item
<FormCheckbox
label="Add user accounts on join"
status={formik.values.add_user_account_on_join ? "checked" : "unchecked"}
name="add_user_account_on_join"
control={control}
style={styles.input}
onPress={() =>
formik.setFieldValue("add_user_account_on_join", !formik.values.add_user_account_on_join)
}
/>
</View>
);
Expand Down
Loading

0 comments on commit 95e3f88

Please sign in to comment.