Context Form Example
This example follows Atomic Design:
- Atom →
Input - Molecule →
UserForm - Organism →
UserProfile - Context → Stores and updates the state
UserContext
File: context/UserContext.tsx
import {
createContext,
useContext,
useState,
ReactNode,
Dispatch,
SetStateAction,
} from 'react'
export interface User {
name: string
age: number
email: string
}
interface UserContextType {
user: User
setUser: Dispatch<SetStateAction<User>>
}
const UserContext = createContext<UserContextType | undefined>(
undefined
)
export const UserProvider = ({children}: { children: ReactNode }) => {
const [user, setUser] = useState<User>({
name: 'René',
age: 30,
email: 'rene@example.com',
})
return (
<UserContext.Provider value={{ user, setUser}}>
{children}
</UserContext.Provider>
)
}
export const useUser = () => {
const context = useContext(UserContext)
if (!context) {
throw new Error(
'useUser must be used inside UserProvider'
)
}
return context
}
Atom
File: components/atoms/Input/Input.tsx
interface InputProps {
value: string
onChange: (value: string) => void
placeholder?: string
}
const Input = ({value, onChange, placeholder}: InputProps) => (
<input value={value}
placeholder={placeholder}
onChange={(event) => onChange(event.target.value)}
/>
)
export default Input
Molecule
File: components/molecules/UserForm/UserForm.tsx
import Input from '../atoms/Input'
interface UserFormProps {
name: string
age: number
email: string
onNameChange: (name: string) => void
onAgeChange: (age: number) => void
onEmailChange: (email: string) => void
}
const UserForm = ({name, age, email, onNameChange, onAgeChange, onEmailChange }: UserFormProps) => {
return (
<div>
<div>
<label>Name</label>
<Input
value={name}
placeholder="Name"
onChange={onNameChange}
/>
</div>
<div>
<label>Age</label>
<Input
value={String(age)}
placeholder="Age"
onChange={(value) =>
onAgeChange(Number(value))
}
/>
</div>
<div>
<label>Email</label>
<Input
value={email}
placeholder="Email"
onChange={onEmailChange}
/>
</div>
</div>
)
}
export default UserForm
Organism
File: components/organisms/UserProfile/UserProfile.tsx
import UserForm from '../molecules/UserForm'
import { useUser } from '../../context/UserContext'
const UserProfile = () => {
const { user, setUser } = useUser()
const handleNameChange = (name: string) => {
setUser((current) => ({
...current,
name,
}))
}
const handleAgeChange = (age: number) => {
setUser((current) => ({
...current,
age,
}))
}
const handleEmailChange = (email: string) => {
setUser((current) => ({
...current,
email,
}))
}
return (
<div>
<h2>User Profile</h2>
<p>Name: {user.name}</p>
<p>Age: {user.age}</p>
<p>Email: {user.email}</p>
<UserForm
name={user.name}
age={user.age}
email={user.email}
onNameChange={handleNameChange}
onAgeChange={handleAgeChange}
onEmailChange={handleEmailChange}
/>
</div>
)
}
export default UserProfile
App
// App.tsx
import { UserProvider } from './context/UserContext'
import UserProfile from './components/organisms/UserProfile/UserProfile'
const App = () => (
<UserProvider>
<UserProfile />
</UserProvider>
)
export default App
Architecture
App
└── UserProvider (Context)
└── UserProfile (Organism)
└── UserForm (Molecule)
└── Input (Atom)
Responsibilities
| Layer | Responsibility |
|---|---|
| Context | Owns application state |
| Organism | Contains business logic and orchestration |
| Molecule | Groups related UI elements |
| Atom | Smallest reusable UI element |
Notice that the Atom is completely unaware of React Context, the Molecule is unaware of business rules, and all state orchestration lives in the Organism, which keeps the Atomic Design layers clean and reusable.
Assignment
info
Rewrite this code so it only passes a user object to the UserForm component, and create a function updateUser in the UserProfile
that updates the user state based on the field name and value.