Skip to main content

Parent Component

import { useState } from 'react'
import UserEditor from './UserEditor'

interface User {
name: string
age: number
}

const App = () => {
const [user, setUser] = useState<User>({
name: 'René',
age: 30,
})

return (
<div>
<h1>User Profile</h1>

<p>Name: {user.name}</p>
<p>Age: {user.age}</p>

<UserEditor
user={user}
setUser={setUser}
/>
</div>
)
}

export default App

Child Component

import { Dispatch, SetStateAction } from 'react'

interface User {
name: string
age: number
}

interface UserEditorProps {
user: User
setUser: Dispatch<SetStateAction<User>>
}

const UserEditor = ({
user,
setUser,
}: UserEditorProps) => {

const handleNameChange = (
event: React.ChangeEvent<HTMLInputElement>
) => {
setUser((current) => ({
...current,
name: event.target.value,
}))
}

return (
<div>
<label htmlFor="name">
Name
</label>

<input
id="name"
type="text"
value={user.name}
onChange={handleNameChange}
/>
</div>
)
}

export default UserEditor

How it works

  1. The parent owns the state:
const [user, setUser] = useState({
name: 'René',
age: 30,
})
  1. The state and setter are passed to the child:
<UserEditor
user={user}
setUser={setUser}
/>
  1. The child updates the parent's state when the user types:
setUser((current) => ({
...current,
name: event.target.value,
}))
  1. Because the input is controlled, its value always reflects the current state:
<input
value={user.name}
onChange={handleNameChange}
/>

As the user types, the name property in the parent state updates immediately, and the UI re-renders with the new value.