pws-react-form-submission
v1.2.0
Published
React helper for CSRF + secure form submission
Readme
React Form Submission
Ready-to-use React utilities for CSRF + secure form submission.
Install
npm install pws-react-form-submissionUsage
import { useMemo, useState } from "react";
import {
computePayloadHash,
useFormSubmission,
type FormClientConfig,
} from "pws-react-form-submission";
type SimpleForm = {
name: string;
email: string;
amount: string;
note: string;
};
export default function App() {
const [baseUrl, setBaseUrl] = useState(
"https://pwsdevenvironment.azure-api.net"
);
const [csrfPath, setCsrfPath] = useState(
"/api/FormSubmissionService/forms/csrf/token"
);
const [submitPath, setSubmitPath] = useState(
"/api/FormSubmissionService/forms/submit"
);
const [formCode, setFormCode] = useState("CreditCard");
const [channel, setChannel] = useState("Aem_Form");
const [subscriptionKey, setSubscriptionKey] = useState(
"9a344a9ecef04eee8b59d78a579d1d50"
);
const [localError, setLocalError] = useState<string | null>(null);
const [formData, setFormData] = useState<SimpleForm>({
name: "",
email: "",
amount: "",
note: "",
});
const clientConfig: FormClientConfig = useMemo(() => {
const headers: Record<string, string> = {};
if (subscriptionKey) {
headers["Ocp-Apim-Subscription-Key"] = subscriptionKey;
}
return {
baseUrl,
csrfPath,
submitPath,
subscriptionKey,
headers,
};
}, [baseUrl, csrfPath, submitPath, subscriptionKey]);
const {
fetchCsrf,
submit,
csrfStatus,
csrfToken,
loading,
lastResponse,
error,
} = useFormSubmission(clientConfig);
async function handleSave() {
setLocalError(null);
try {
if (!subscriptionKey.trim()) {
setLocalError("Ocp-Apim-Subscription-Key is required");
return;
}
let tokenToUse = csrfToken;
if (!tokenToUse) {
const result = await fetchCsrf();
tokenToUse = result.token;
}
const payload = {
name: formData.name,
email: formData.email,
amount: formData.amount,
note: formData.note,
};
const bodyForHash = {
formCode,
channel,
payload,
};
const bodyString = JSON.stringify(bodyForHash);
const headers: Record<string, string> = {};
headers["X-Payload-Hash"] = await computePayloadHash(bodyString, "SHA-256");
await submit({
bodyString,
headers,
csrfToken: tokenToUse,
});
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
setLocalError(message);
}
}
return (
<div className="app">
<header className="hero">
<div>
<p className="eyebrow">Demo Form</p>
<h1>React Form Save (CSRF → Save)</h1>
<p className="sub">
กด Save ระบบจะเรียก CSRF ก่อน แล้วส่งข้อมูลตามรูปแบบ FormCode/Channel
</p>
</div>
<div className={`status-pill status-${csrfStatus}`}>
CSRF: {csrfStatus}
</div>
</header>
<div className="grid">
<section className="card">
<h2>Config</h2>
<label>
Base URL
<input
value={baseUrl}
onChange={(e) => setBaseUrl(e.target.value)}
placeholder="https://pwsdevenvironment.azure-api.net"
/>
</label>
<label>
CSRF Path
<input
value={csrfPath}
onChange={(e) => setCsrfPath(e.target.value)}
placeholder="/api/FormSubmissionService/forms/csrf/token"
/>
</label>
<label>
Submit Path
<input
value={submitPath}
onChange={(e) => setSubmitPath(e.target.value)}
placeholder="/api/FormSubmissionService/forms/submit"
/>
</label>
<label>
APIM Subscription Key
<input
value={subscriptionKey}
onChange={(e) => setSubscriptionKey(e.target.value)}
placeholder="9a344a9ecef04eee8b59d78a579d1d50"
/>
</label>
</section>
<section className="card">
<h2>Form</h2>
<label>
FormCode
<input
value={formCode}
onChange={(e) => setFormCode(e.target.value)}
/>
</label>
<label>
Channel
<input
value={channel}
onChange={(e) => setChannel(e.target.value)}
/>
</label>
<label>
Name
<input
value={formData.name}
onChange={(e) =>
setFormData((prev) => ({ ...prev, name: e.target.value }))
}
placeholder="John Doe"
/>
</label>
<label>
Email
<input
value={formData.email}
onChange={(e) =>
setFormData((prev) => ({ ...prev, email: e.target.value }))
}
placeholder="[email protected]"
/>
</label>
<label>
Amount
<input
value={formData.amount}
onChange={(e) =>
setFormData((prev) => ({ ...prev, amount: e.target.value }))
}
placeholder="1000"
/>
</label>
<label>
Note
<textarea
rows={4}
value={formData.note}
onChange={(e) =>
setFormData((prev) => ({ ...prev, note: e.target.value }))
}
placeholder="Additional info"
/>
</label>
<button
type="button"
className="btn primary"
onClick={handleSave}
disabled={loading}
>
{loading ? "Saving..." : "Save"}
</button>
{localError || error ? (
<div className="error">{localError ?? error?.message}</div>
) : null}
</section>
</div>
<section className="card response-card">
<h2>Response</h2>
<pre>
{lastResponse
? JSON.stringify(lastResponse, null, 2)
: "(no response yet)"}
</pre>
</section>
</div>
);
}Notes
- Provide
baseUrl,csrfPath, andsubmitPathfor your environment. - Supply
Ocp-Apim-Subscription-Keywhen required by your API gateway. computePayloadHashshould match the hashing algorithm your backend expects.
