react-kt-forms
v1.0.6
Published
React KT Forms is a compact collection of React form components, validation helpers, theme utilities and a small API provider designed to be dropped into web (and web-like) React apps. It focuses on simple, composable inputs that work with a form context,
Readme
react-kt-forms
React KT Forms is a compact collection of React form components, validation helpers, theme utilities and a small API provider designed to be dropped into web (and web-like) React apps. It focuses on simple, composable inputs that work with a form context, theme-aware components, and a tiny API wrapper using axios.
This README explains how to install and use the package, shows concrete examples that match the code in this repository (see src/extampl.tsx and src/index.ts), and documents the main hooks and components you will use.
Install
Using npm:
npm install react-kt-formsOr with yarn / pnpm:
yarn add react-kt-forms
# or
pnpm add react-kt-formsImport the stylesheet
The package ships a small stylesheet required for base styles and some input behaviors. Import it once in your application's entry point (for example src/index.tsx or src/main.tsx):
import "react-kt-forms/styles.css";What this package exports
The main entry (src/index.ts) exposes the public surface you'll use:
Form— container for form inputs and provides form-specific behavior; also used as a namespace for some inputs (e.g.Form.TonalTextInput).FormProvider— wraps part of your app and provides an API instance and theme context.useApi— hook to access the axios instance fromFormProvider.rules— validation helpers (e.g.rules.required,rules.number).TonalTextInput— a commonly used text input that integrates with the form context.useColorScheme,useCurrentThemeColors— theme hooks and helpers.Button,ThemedText,ThemedView— small UI helpers that integrate with theme.
Additionally, the src/components/Form/Inputs folder contains other form inputs such as Autocomplete, Checkbox, Combobox, DatePicker, TonalTextInput, and WebOnlyFileInput.
Quick-start (recommended)
Below is a practical example demonstrating the correct way to use Form, Form.TonalTextInput, the validation rules, and FormProvider.
This example is adapted from src/extampl.tsx in this repo.
import React, { useState } from "react";
import "react-kt-forms/styles.css";
import { Form, rules, FormProvider } from "react-kt-forms";
function Main() {
const [name, setName] = useState<string | null>(null);
return (
<Form>
<div
style={{ background: "red", width: 40, height: 40 }}
className="hoverable"
/>
<Form.TonalTextInput
title="test"
value={name}
onChangeText={(t) => setName(t)}
rules={[rules.required("test"), rules.number("test", 100, 0)]}
/>
</Form>
);
}
export default function App() {
return (
<div className="app">
<FormProvider>
<Main />
</FormProvider>
</div>
);
}Key points:
- Use
Formas the immediate parent for inputs that implicitly use the form context. - Some inputs are also available via
Form.<InputName>(for convenience) — e.g.Form.TonalTextInput. - Validation rules are passed as an array to inputs that accept
rules. - Wrap your app (or the subtree that needs API access / theme management) with
FormProvidersouseApiand theme hooks work.
API integration — FormProvider and useApi
FormProvider creates and exposes an axios instance on the form context. Use useApi() to access that instance.
Important details (matching src/hooks/api.tsx):
FormProvidercreates the axios instance usingaxios.create({ baseURL: props.baseUrl || baseUrlFromEnv(), timeout: 120e3 }).baseUrlFromEnv()attempts to readprocess.env.EXPO_PUBLIC_BASE_URLand can be overridden by passingbaseUrltoFormProvider.props.headers(if given) are merged intoapi.defaults.headersvia an effect — this ensures headers are applied after the axios instance is created.
Example of using useApi:
import { useApi } from "react-kt-forms";
function Example() {
const api = useApi();
async function loadData() {
const res = await api.get("/some-endpoint");
console.log(res.data);
}
return <button onClick={loadData}>Load</button>;
}Notes:
- If you pass
baseUrlprop toFormProvider, that value will be used as the axios baseURL and will also be reapplied in a useEffect so changes propagate toapi.defaults.baseURL. - The axios instance has a 120 second timeout by default.
Theme and color scheme
Theme and color-scheme functions live under src/hooks/useColorScheme.tsx and src/hooks/formContext.tsx.
Main exports and helpers:
ColorSchemeProvider— listens to theprefers-color-schememedia query and exposescolorScheme,setPreferredColorScheme,clearPreferredColorScheme, andsystemColorSchemevia context.useColorScheme()— hook that reads the theme context. It throws if used outside a provider (so wrap components inFormProviderwhich includesColorSchemeProvider).useCurrentThemeColors()— returns the current color palette merged with any passed theme overrides (viaformContext/FormProvider'sthemeprop).setDomTheme(colorScheme)— helper that toggles.light/.darkclasses ondocument.documentElementand updates themeta[name=theme-color]tag with the theme color.
Example: toggling theme preference
import { useColorScheme } from "react-kt-forms";
function ThemeToggle() {
const { colorScheme, setPreferredColorScheme, clearPreferredColorScheme } = useColorScheme();
return (
<div>
<div>Current: {colorScheme}</div>
<button onClick={() => setPreferredColorScheme("dark")}>Dark</button>
<button onClick={() => setPreferredColorScheme("light")}>Light</button>
<button onClick={() => clearPreferredColorScheme()}>Follow system</button>
</div>
);
}The provider also saves user preference in localStorage under the color-scheme key and applies it to the DOM immediately on load (so the app matches user choice on refresh).
Form provider — API headers (auth) & theme overrides
The package's provider (exported as FormProvider in the public API — implemented as ApiProvider in the source) accepts two optional props you can use to configure the underlying axios instance and to override the theme used by components.
headers— an object merged into the axios defaults. Useful for adding Authorization tokens and custom headers used by your backend.theme— an object matching a subset of the internal theme keys (you can override as many or as few colors as you need). Components will use values from the provided theme when present.
Simple example — static headers and theme:
import React from 'react';
import { FormProvider } from 'react-native-kt-forms';
import App from './App';
const customTheme = {
// override some colors in light theme
light: {
primary: '#0066FF',
surface: '#FFFFFF',
onPrimary: '#FFFFFF',
onSurface: '#111111',
}
};
export default function Root() {
const user = useUser(); // or some state where you store your user session authentication stuff and tokens
return (
<FormProvider
headers={{ Authorization: `Bearer ${user?.token}`, 'X-App-Version': '1.2.3' }}
theme={customTheme}
>
<App />
</FormProvider>
);
}Accessing the axios instance and the active theme in a component:
import React from 'react';
import { useApi, useCurrentThemeColors } from 'react-native-kt-forms';
function Example() {
const api = useApi();
const theme = useCurrentThemeColors(); // based on the curren theme (check the theme section) it will return the `light` colors or the `dark` colors with your passed overrides
async function send() {
const res = await api.post('/endpoint', { hello: 'world' });
return res.data;
}
return (
// render UI using theme.primary, theme.surface, etc.
null
);
}Notes
headersare merged viaapi.defaults.headers = {...api.defaults.headers, ...(props.headers || {})}so you can pass any axios header value.- the updated
themeweatherdarkorlightor both may include the small set of keys used by components (for exampleprimary,surface,onPrimary,onSurface) — the Theme type in the source lists all supported keys; most are optional and you can override only the ones you need.
Utility hooks: useIsMobile
useIsMobile (in src/hooks/useIsMobile.tsx) is a tiny hook that returns whether the viewport width is below the isMobileThreshold constant. It uses window.innerWidth and listens to resize events.
Important notes:
useIsMobilereadswindow.innerWidthon first render — in server-side rendering environments you must ensure this is only used on the client or guard its usage.- The hook updates on
resizeand returns a booleantruewhen width <isMobileThreshold.
Example:
import { useIsMobile } from "react-kt-forms";
function Responsive() {
const isMobile = useIsMobile();
return <div>{isMobile ? "mobile" : "desktop"}</div>;
}Inputs overview
Inputs live under src/components/Form/Inputs. The folder contains:
Autocomplete— index atInputs/Autocomplete/index.tsx.Checkbox— index atInputs/Checkbox/index.tsx.Combobox— index atInputs/Combobox/index.tsx.DatePicker— index atInputs/DatePicker/index.tsx.TonalTextInput— index atInputs/TonalTextInput/index.tsx(also exposed asForm.TonalTextInput).WebOnlyFileInput—Inputs/WebOnlyFileInput/index.web.tsxwithstyle.css.
Each input integrates with the form context and accepts rules for validation when applicable. Check the individual components for prop details; their TypeScript signatures are the source of truth.
Example: Form with API call and theming
import { useState } from "react";
import "react-kt-forms-2/styles.css";
import { Form, rules, FormProvider, useApi } from ".";
const Main = () => {
const [name, setName] = useState(null as null | string);
const [loading, setLoading] = useState(false);
const [valid, setValid] = useState(false);
const api = useApi();
const submit = async () => {
if (loading) {
return;
}
setLoading(true);
try {
// submit the data
await api.post("/add/user", {
name,
});
// do some stuff
} catch (error) {
console.error(error);
// catch the error
} finally {
setLoading(false);
}
};
return (
<>
<Form
disabled={loading}
onValidationStateChange={(v) => {
setValid(v);
}}
>
<Form.TonalTextInput
onChangeText={(t) => {
setName(t);
}}
value={name}
title="test"
rules={[rules.required("test"), rules.number("test", 100, 0)]}
></Form.TonalTextInput>
</Form>
<button
disabled={loading || !valid}
onClick={(e) => {
e.preventDefault();
submit();
}}
>
{loading ? "submitting..." : "submit"}
</button>
</>
);
};
export function App() {
return (
<div className="app">
<FormProvider>
<Main></Main>
</FormProvider>
</div>
);
}
export default App;
Form inputs reference
Below are usage examples for each input component provided under src/components/Form/Inputs. These snippets follow the components' real props as implemented in the source.
TonalTextInput
<Form.TonalTextInput
id="name"
title="name"
label="Full name"
value={name}
onChangeText={(text) => setName(text)}
rules={[rules.required('Name')]}
variant="outlined"
placeholder="Your full name"
/>Autocomplete (data-backed)
<Autocomplete
type="data"
title="Choose city"
label="City"
data={[ 'Cairo', 'London', 'New York' ]}
text={(item) => String(item)}
selectedItem={selectedCity}
onChose={(item) => setSelectedCity(item)}
onClear={(mounted_clear) => { if (!mounted_clear) setSelectedCity(null); }}
rules={[rules.required('City')]}
/>Autocomplete (remote API)
<Autocomplete
type="unknownApi"
title="Choose user"
label="User"
url="/api/users/search"
body={{ q: 'search term' }}
apiSelector={(data) => data.users}
text={(user) => `${user.firstName} ${user.lastName}`}
selectedItem={selectedUser}
onChose={(u) => setSelectedUser(u)}
onClear={() => setSelectedUser(null)}
/>Combobox
<Combobox
title="Select option"
label="Option"
value={value}
onTextChange={(v) => setValue(v)}
data={[ 'Option A', 'Option B' ]}
type="dada"
textExtractor={(item) => String(item)}
placeholder="Start typing"
rules={[rules.required('Option')]}
/>DatePicker
<DatePicker
title="Birthdate"
label="Birthdate"
shown={showDatePicker}
onShow={() => setShowDatePicker(true)}
onConfirm={(d) => { setBirthdate(d); setShowDatePicker(false); }}
onCancel={() => setShowDatePicker(false)}
value={birthdate}
mode="date"
rules={[rules.required('Birthdate')]}
/>DateTimePicker
<DateTimePicker
title="Appointment"
shown={showDateTime}
onShow={() => setShowDateTime(true)}
onConfirm={(d) => { setAppointment(d); setShowDateTime(false); }}
onCancel={() => setShowDateTime(false)}
value={appointment}
mode="datetime"
rules={[rules.required('Appointment')]}
/>OptionsButtons
<OptionsButtons
label="Choose one"
options={[{ text: 'Yes', value: 'yes' }, { text: 'No', value: 'no' }]}
value={opt}
onValueChange={(v) => setOpt(v)}
rules={[rules.required('Choice')]}
/>Development notes
FormProvidercomposesformContextandColorSchemeProvider. If you need to pass a theme override, pass athemeprop intoFormProvideranduseCurrentThemeColors()will merge it with the default color palette.- The axios instance is created once with
useMemoand updated viauseEffectwhenprops.headersorprops.baseUrlchange. - Some utilities depend on browser globals (
window,localStorage,document). Avoid calling them during SSR.
Where to look in the repo
src/index.ts— exports and public API surface.src/extampl.tsx— concrete example usage.src/hooks/api.tsx—FormProvideranduseApiimplementation.src/hooks/useColorScheme.tsx— theme provider and helpers.src/hooks/useIsMobile.tsx— viewport helper.src/components/Formandsrc/components/Form/Inputs— core form implementation and inputs.
Contributing
Contributions are welcome. If you open a PR, include clear motivation and small, focused changes. Update README and add tests for new behavior when appropriate.
License
MIT
