Implementing Uncontrolled Forms and Reusable Higher-Order Components in React
In React, form elements typically rely on state synchronization, but developers can opt for an alternative pattern where the DOM maintains the internal data. This approach utilizes uncontrolled inputs.
Unlike controlled elements that bind the value attribute directly to component state and trigger updates on every keystroke, uncontrolled inputs store their data with in the browser's document model. To extract this information, developers must query the node directly using a reference system. Initial value are applied via the defaultValue property rather than value.
The following implementation demonstrates how to capture credentials and file metadata without continuous state re-renders:
import React, { useRef } from 'react';
export const CredentialForm = () => {
const userRef = useRef(null);
const passRef = useRef(null);
const attachmentRef = useRef(null);
const extractFormData = () => {
const account = userRef.current?.value || '';
const secret = passRef.current?.value || '';
const files = attachmentRef.current?.files;
console.log('Account:', account);
console.log('Secret:', secret);
if (files && files.length > 0) {
Array.from(files).forEach(file => {
console.log(`Uploaded ${file.name} | ${file.size} bytes`);
});
}
};
return (
<div className="form-container">
<h2>Uncontrolled Input Pattern</h2>
<input
type="text"
ref={userRef}
defaultValue="InitialUser"
onBlur={() => console.log('Input validation check')}
/>
<input
type="password"
ref={passRef}
defaultValue="DefaultPass"
/>
<input type="file" ref={attachmentRef} multiple />
<button type="button" onClick={extractFormData}>Retrieve Data</button>
</div>
);
};
When multiple components require identical setup routines, duplicating initialization logic becomes inefficient. React addresses this through higher-order components (HOCs), which operate as pure transformations: they accept a base component as an argument and return an enhanced version with additional properties or lifecycle behavior.
Consider a scenario where several interface modules need to read a stored session identifier. Embedding the retrieval logic in each view creates redundant boilerplate. A single wrapper function can abstract this operation entirely.
First, define the enhancement utility:
import React, { useState, useEffect } from 'react';
const withSessionIdentity = (WrappedComponent) => {
return function SessionWrapper(props) {
const [currentUser, setCurrentUser] = useState('');
useEffect(() => {
const storedIdentity = sessionStorage.getItem('activeUser');
if (storedIdentity) {
setCurrentUser(storedIdentity);
}
}, []);
return <WrappedComponent currentUser={currentUser} {...props} />;
};
};
export default withSessionIdentity;
Next, apply the utility to isolated presentational modules. The original views only need to consume the injected prop:
import React from 'react';
import withSessionIdentity from './withSessionIdentity';
const GreetingView = ({ currentUser }) => (
<section>Welcome back, {currentUser || 'Guest'}</section>
);
const LogoutView = ({ currentUser }) => (
<section>Terminating session for {currentUser || 'Unknown'}</section>
);
export const EnhancedGreeting = withSessionIdentity(GreetingView);
export const EnhancedLogout = withSessionIdentity(LogoutView);
This composition strategy isolates data fetching and side effects from the rendering layer. The wrapper manages the retrieval lifecycle, computes the necessary state, and passes the result downward through props. Consequently, the underlying views remain focused purely on presentation while sharing a single source of truth for initialization logic.