Legacy React (app/webpacker/)
Applies to: any .jsx / .js file under app/webpacker/.
NOTE: You should not be creating any new files (let alone whole features) under the legacy Webpacker frontend. Touching existing files is acceptable when fixing bugs or augmenting existing features with functionality that is deemed immediately necessary.
Before you dive deep into your work in Shakapacker, double-check that you are really sure what you’re doing!!
app/webpacker/ is on Semantic UI (SemUI for short) and has no React Compiler. This means that you do need useCallback / useMemo here to keep references stable.
- No custom CSS. No
style={{ marginBottom: ... }}, noclassNames that SemUI doesn’t define — the framework has props for spacing and layout.- If your component doesn’t have the specific layout props that you need, wrap it inside of a
<Segment basic>or use<Divider>to space it out. - Exception 1: Horizontal table overflow (
overflowX), which SemUI genuinely doesn’t support. You will need to wrap the<Table>in your own<div>with a correspondingstyletag - Exception 2: In some rare cases, the CSS classes used by SemUI can conflict with the even older parts of our frontend still running on Bootstrap 3. By now, the Shakapacker frontend is mature enough that we have caught most of these conflicts, but in the extremely unlikely event that you ever run into a bug that doesn’t come forward even after half an hour of debugging, please reach out via Slack rather than banging your head against the wall. Chances are that it’s an ultra-subtle CSS classname conflict.
- If your component doesn’t have the specific layout props that you need, wrap it inside of a
- Use Semantic UI dot-notation (
Table.Header,Table.HeaderCell) instead of manually importing all ofTableHeader,TableHeaderCellandTableFooterseparately. - Use our own hooks for common use cases:
useInputState/useInputUpdaterwrapuseStateso the setter can be passed straight to a SemUIInput’sonChange(which follows different signature conventions than a vanillaonChange). Similar hooks exist foruseCheckboxStateetc.useLoadedDatareturns responseheaders, which carry total-count information for pagination (seeIncidentsLog/index.jsx). Ideally, you should be using Tanstack Query instead ofuseLoadedDatain the first place.
- Use
fetchJsonOrErroras-is. It has error handling built into its name; don’t wrap it in your own. - Build URLs with the helpers in
lib/requests/routes.js.erb(competitionUrl(...)), not string concatenation. mutationFnmust not close over component state. Pass the values in as parameters — it keeps renders stable.useMutationhasonErroralongsideonSuccess. Use it rather than hand-rolling error handling.- Beware
onSuccessargument shadowing:onSuccess={setSuccess}passes the server response as the new state. Write() => setSuccess(true)if that’s what you mean. - Use
Messagecomponents for user-facing errors — not bareclassNames that don’t exist in SemUI. - Prefer early returns for loading states (
if (isFetching) return <Loader />) sodatais implicitly defined afterwards. This is better than a ternary for the empty-vs-populated case. - Use
<Ref>when you need to attach a ref to a SemUI component that doesn’t forward one. Don’t add a wrapper<div>to hold the ref, and don’t hand-roll a replacement for a component we already have — wrap the existing one.