Client Components
Up until very recently, client components were the only type of components that most developers knew about and used. All popular react tooling, like vite and create-react-app, only worked with client componetns, so we would build Single Page Applications (SPAs) using only client components. That means that all courses and videos that taught react, only taught client components.
Server Components are becoming more popular since Next.js made it so easy to work with them. The default is to use server components, and we now have to opt in to using client components by adding "use client"
to the top of every file that we want to use client components in.
Here are a series of videos that go over some of the core concepts of React using vite. So all examples are for a SPA using only client components by default. You won't see "use client"
anywhere because all the components are client components, also all code is in JavaScript instead of Typescript. All the concepts in these videos are still accurate and relevant, especially if you want to setup an SPA, but I have included code examples below each video to demonstrate how these things would work in a modern Next.js app.
To follow along with these videos, you could setup a vite app or just use a next.js app and add "use client"
to the top of each file.
VIDEO
" use client "
import Form from " ./form "
export default function Page () {
function handleClick ( e : React . MouseEvent < HTMLButtonElement , MouseEvent > ) {
console . log ( " Button clicked " )
}
function handleSubmit () {
console . log ( " Form submitted " )
}
return (
< div className = " App " >
< button onClick = { handleClick } > Do Something </ button >
< Form onSubmit = { handleSubmit } />
</ div >
)
}
" use client "
type FormProps = {
onSubmit : () => void
}
export default function Form ( { onSubmit } : FormProps ) {
function handleSubmit ( e : React . FormEvent < HTMLFormElement > ) {
e . preventDefault ()
onSubmit ()
}
return (
< form onSubmit = { handleSubmit } >
< input type = " text " onChange = { ( e ) => console . log (e . target . value ) } />
< button type = " submit " > Submit </ button >
</ form >
)
}
" use client "
import Form from " ./form "
export default function Page () {
function handleClick ( e : React . MouseEvent < HTMLButtonElement , MouseEvent > ) {
console . log ( " Button clicked " )
}
function handleSubmit () {
console . log ( " Form submitted " )
}
return (
<div className = " App " >
<button onClick = { handleClick } > Do Something </button>
< Form onSubmit = { handleSubmit } />
</div>
)
}
" use client "
type FormProps = {
onSubmit : () => void
}
export default function Form ( { onSubmit } : FormProps) {
function handleSubmit ( e : React . FormEvent < HTMLFormElement > ) {
e . preventDefault ()
onSubmit ()
}
return (
<form onSubmit = { handleSubmit } >
<input type = " text " onChange = { ( e ) => console . log (e . target . value ) } />
<button type = " submit " > Submit </button>
</form>
)
}
" use client "
import Form from " ./form "
export default function Page () {
function handleClick ( e : React . MouseEvent < HTMLButtonElement , MouseEvent > ) {
console . log ( " Button clicked " )
}
function handleSubmit () {
console . log ( " Form submitted " )
}
return (
< div className = " App " >
< button onClick = { handleClick } > Do Something </ button >
< Form onSubmit = { handleSubmit } />
</ div >
)
}
" use client "
type FormProps = {
onSubmit : () => void
}
export default function Form ( { onSubmit } : FormProps ) {
function handleSubmit ( e : React . FormEvent < HTMLFormElement > ) {
e . preventDefault ()
onSubmit ()
}
return (
< form onSubmit = { handleSubmit } >
< input type = " text " onChange = { ( e ) => console . log (e . target . value ) } />
< button type = " submit " > Submit </ button >
</ form >
)
}
" use client "
import Form from " ./form "
export default function Page () {
function handleClick ( e : React . MouseEvent < HTMLButtonElement , MouseEvent > ) {
console . log ( " Button clicked " )
}
function handleSubmit () {
console . log ( " Form submitted " )
}
return (
<div className = " App " >
<button onClick = { handleClick } > Do Something </button>
< Form onSubmit = { handleSubmit } />
</div>
)
}
" use client "
type FormProps = {
onSubmit : () => void
}
export default function Form ( { onSubmit } : FormProps) {
function handleSubmit ( e : React . FormEvent < HTMLFormElement > ) {
e . preventDefault ()
onSubmit ()
}
return (
<form onSubmit = { handleSubmit } >
<input type = " text " onChange = { ( e ) => console . log (e . target . value ) } />
<button type = " submit " > Submit </button>
</form>
)
}
VIDEO
import Jokes from " ./jokes "
export default function Page () {
const jokes = [
{
id : 1 ,
text : " I'm afraid for the calendar. Its days are numbered. " ,
} ,
{
id : 2 ,
text : " I used to be addicted to soap, but I'm clean now. " ,
} ,
]
return (
< div >
< h1 > Dad Jokes </ h1 >
< Jokes jokes = { jokes } />
</ div >
)
}
" use client "
import { useState } from " react "
import Joke from " ./joke "
import JokeForm from " ./joke-form "
type Props = {
jokes : {
id : number
text : string
}[]
}
export default function Jokes ( { jokes } : Props ) {
const [ favorite , setFavorite ] = useState ( 1 )
const handleFavorite = ( id : number ) => {
setFavorite ( id )
}
const handleNewJoke = ( text : string ) => {
console . log ( " new joke " , text )
}
return (
< div className = "" >
< h1 > Dad Jokes </ h1 >
{ jokes . map ( ( joke ) => (
< Joke
onFavorite = { handleFavorite }
favorite = { favorite === joke . id }
key = { joke . id }
id = { joke . id }
text = { joke . text }
/>
)) }
< JokeForm onNewJoke = { handleNewJoke } />
</ div >
)
}
" use client "
import { useState } from " react "
type Props = {
id : number
text : string
favorite : boolean
onFavorite : ( id : number ) => void
}
export default function Joke ( { id , text , favorite , onFavorite } : Props ) {
const [ likes , setLike ] = useState ( 0 )
const [ dislikes , setDislike ] = useState ( 0 )
const handleLike = () => {
const newLikes = likes + 1
setLike ( newLikes )
console . log (` like id: ${ id } , totalLikes ${ newLikes } `)
}
const handleDislike = () => {
setDislike ( dislikes + 1 )
console . log (` dislike id: ${ id } , totalLikes ${ likes } `)
}
const handleFavorite = () => {
onFavorite ( id )
}
return (
< div >
< p > { text } </ p >
< p > Likes: { likes - dislikes } </ p >
< p > Favorite: { favorite ? " YES " : " NO " } </ p >
< button onClick = { handleLike } > 👍 </ button >
< button onClick = { handleDislike } > 👎 </ button >
< button onClick = { handleFavorite } > Favorite </ button >
</ div >
)
}
" use client "
import { useState } from " react "
type Props = {
onNewJoke : ( text : string ) => void
}
export default function JokeForm ( { onNewJoke } : Props ) {
const [ text , setText ] = useState ( "" )
const handleSubmit = ( event : React . FormEvent < HTMLFormElement > ) => {
event . preventDefault ()
onNewJoke ( text )
setText ( "" )
}
return (
< form onSubmit = { handleSubmit } >
< input
type = " text "
placeholder = " Enter a joke "
value = { text }
onChange = { ( e ) => setText (e . target . value ) }
/>
< button type = " submit " > Submit </ button >
</ form >
)
}
import Jokes from " ./jokes "
export default function Page () {
const jokes = [
{
id: 1 ,
text: " I'm afraid for the calendar. Its days are numbered. " ,
} ,
{
id: 2 ,
text: " I used to be addicted to soap, but I'm clean now. " ,
} ,
]
return (
<div>
<h1> Dad Jokes </h1>
< Jokes jokes = { jokes } />
</div>
)
}
" use client "
import { useState } from " react "
import Joke from " ./joke "
import JokeForm from " ./joke-form "
type Props = {
jokes : {
id : number
text : string
}[]
}
export default function Jokes ( { jokes } : Props) {
const [ favorite , setFavorite ] = useState ( 1 )
const handleFavorite = ( id : number ) => {
setFavorite (id)
}
const handleNewJoke = ( text : string ) => {
console . log ( " new joke " , text)
}
return (
<div className = "" >
<h1> Dad Jokes </h1>
{ jokes . map ( ( joke ) => (
< Joke
onFavorite = { handleFavorite }
favorite = { favorite === joke . id }
key = { joke . id }
id = { joke . id }
text = { joke . text }
/>
)) }
< JokeForm onNewJoke = { handleNewJoke } />
</div>
)
}
" use client "
import { useState } from " react "
type Props = {
id : number
text : string
favorite : boolean
onFavorite : ( id : number ) => void
}
export default function Joke ( { id , text , favorite , onFavorite } : Props) {
const [ likes , setLike ] = useState ( 0 )
const [ dislikes , setDislike ] = useState ( 0 )
const handleLike = () => {
const newLikes = likes + 1
setLike (newLikes)
console . log (` like id: ${ id } , totalLikes ${ newLikes } `)
}
const handleDislike = () => {
setDislike (dislikes + 1 )
console . log (` dislike id: ${ id } , totalLikes ${ likes } `)
}
const handleFavorite = () => {
onFavorite (id)
}
return (
<div>
<p> { text } </p>
<p> Likes: { likes - dislikes } </p>
<p> Favorite: { favorite ? " YES " : " NO " } </p>
<button onClick = { handleLike } > 👍 </button>
<button onClick = { handleDislike } > 👎 </button>
<button onClick = { handleFavorite } > Favorite </button>
</div>
)
}
" use client "
import { useState } from " react "
type Props = {
onNewJoke : ( text : string ) => void
}
export default function JokeForm ( { onNewJoke } : Props) {
const [ text , setText ] = useState ( "" )
const handleSubmit = ( event : React . FormEvent < HTMLFormElement > ) => {
event . preventDefault ()
onNewJoke (text)
setText ( "" )
}
return (
<form onSubmit = { handleSubmit } >
<input
type = " text "
placeholder = " Enter a joke "
value = { text }
onChange = { ( e ) => setText (e . target . value ) }
/>
<button type = " submit " > Submit </button>
</form>
)
}
import Jokes from " ./jokes "
export default function Page () {
const jokes = [
{
id : 1 ,
text : " I'm afraid for the calendar. Its days are numbered. " ,
} ,
{
id : 2 ,
text : " I used to be addicted to soap, but I'm clean now. " ,
} ,
]
return (
< div >
< h1 > Dad Jokes </ h1 >
< Jokes jokes = { jokes } />
</ div >
)
}
" use client "
import { useState } from " react "
import Joke from " ./joke "
import JokeForm from " ./joke-form "
type Props = {
jokes : {
id : number
text : string
}[]
}
export default function Jokes ( { jokes } : Props ) {
const [ favorite , setFavorite ] = useState ( 1 )
const handleFavorite = ( id : number ) => {
setFavorite ( id )
}
const handleNewJoke = ( text : string ) => {
console . log ( " new joke " , text )
}
return (
< div className = "" >
< h1 > Dad Jokes </ h1 >
{ jokes . map ( ( joke ) => (
< Joke
onFavorite = { handleFavorite }
favorite = { favorite === joke . id }
key = { joke . id }
id = { joke . id }
text = { joke . text }
/>
)) }
< JokeForm onNewJoke = { handleNewJoke } />
</ div >
)
}
" use client "
import { useState } from " react "
type Props = {
id : number
text : string
favorite : boolean
onFavorite : ( id : number ) => void
}
export default function Joke ( { id , text , favorite , onFavorite } : Props ) {
const [ likes , setLike ] = useState ( 0 )
const [ dislikes , setDislike ] = useState ( 0 )
const handleLike = () => {
const newLikes = likes + 1
setLike ( newLikes )
console . log (` like id: ${ id } , totalLikes ${ newLikes } `)
}
const handleDislike = () => {
setDislike ( dislikes + 1 )
console . log (` dislike id: ${ id } , totalLikes ${ likes } `)
}
const handleFavorite = () => {
onFavorite ( id )
}
return (
< div >
< p > { text } </ p >
< p > Likes: { likes - dislikes } </ p >
< p > Favorite: { favorite ? " YES " : " NO " } </ p >
< button onClick = { handleLike } > 👍 </ button >
< button onClick = { handleDislike } > 👎 </ button >
< button onClick = { handleFavorite } > Favorite </ button >
</ div >
)
}
" use client "
import { useState } from " react "
type Props = {
onNewJoke : ( text : string ) => void
}
export default function JokeForm ( { onNewJoke } : Props ) {
const [ text , setText ] = useState ( "" )
const handleSubmit = ( event : React . FormEvent < HTMLFormElement > ) => {
event . preventDefault ()
onNewJoke ( text )
setText ( "" )
}
return (
< form onSubmit = { handleSubmit } >
< input
type = " text "
placeholder = " Enter a joke "
value = { text }
onChange = { ( e ) => setText (e . target . value ) }
/>
< button type = " submit " > Submit </ button >
</ form >
)
}
import Jokes from " ./jokes "
export default function Page () {
const jokes = [
{
id: 1 ,
text: " I'm afraid for the calendar. Its days are numbered. " ,
} ,
{
id: 2 ,
text: " I used to be addicted to soap, but I'm clean now. " ,
} ,
]
return (
<div>
<h1> Dad Jokes </h1>
< Jokes jokes = { jokes } />
</div>
)
}
" use client "
import { useState } from " react "
import Joke from " ./joke "
import JokeForm from " ./joke-form "
type Props = {
jokes : {
id : number
text : string
}[]
}
export default function Jokes ( { jokes } : Props) {
const [ favorite , setFavorite ] = useState ( 1 )
const handleFavorite = ( id : number ) => {
setFavorite (id)
}
const handleNewJoke = ( text : string ) => {
console . log ( " new joke " , text)
}
return (
<div className = "" >
<h1> Dad Jokes </h1>
{ jokes . map ( ( joke ) => (
< Joke
onFavorite = { handleFavorite }
favorite = { favorite === joke . id }
key = { joke . id }
id = { joke . id }
text = { joke . text }
/>
)) }
< JokeForm onNewJoke = { handleNewJoke } />
</div>
)
}
" use client "
import { useState } from " react "
type Props = {
id : number
text : string
favorite : boolean
onFavorite : ( id : number ) => void
}
export default function Joke ( { id , text , favorite , onFavorite } : Props) {
const [ likes , setLike ] = useState ( 0 )
const [ dislikes , setDislike ] = useState ( 0 )
const handleLike = () => {
const newLikes = likes + 1
setLike (newLikes)
console . log (` like id: ${ id } , totalLikes ${ newLikes } `)
}
const handleDislike = () => {
setDislike (dislikes + 1 )
console . log (` dislike id: ${ id } , totalLikes ${ likes } `)
}
const handleFavorite = () => {
onFavorite (id)
}
return (
<div>
<p> { text } </p>
<p> Likes: { likes - dislikes } </p>
<p> Favorite: { favorite ? " YES " : " NO " } </p>
<button onClick = { handleLike } > 👍 </button>
<button onClick = { handleDislike } > 👎 </button>
<button onClick = { handleFavorite } > Favorite </button>
</div>
)
}
" use client "
import { useState } from " react "
type Props = {
onNewJoke : ( text : string ) => void
}
export default function JokeForm ( { onNewJoke } : Props) {
const [ text , setText ] = useState ( "" )
const handleSubmit = ( event : React . FormEvent < HTMLFormElement > ) => {
event . preventDefault ()
onNewJoke (text)
setText ( "" )
}
return (
<form onSubmit = { handleSubmit } >
<input
type = " text "
placeholder = " Enter a joke "
value = { text }
onChange = { ( e ) => setText (e . target . value ) }
/>
<button type = " submit " > Submit </button>
</form>
)
}
VIDEO
import Jokes from " ./jokes "
export default function Page () {
const jokes = [
{
id : " 1 " ,
text : " I'm afraid for the calendar. Its days are numbered. " ,
likes : 0
} ,
{
id : " 2 " ,
text : " I used to be addicted to soap, but I'm clean now. " ,
likes : 0
}
]
return (
< div >
< h1 > Dad Jokes </ h1 >
< Jokes initialJokes = { jokes } />
</ div >
)
}
" use client "
import Joke from " ./joke "
import JokeForm from " ./joke-form "
import { useState } from " react "
type Props = {
initialJokes : {
text : string
id : string
likes : number
}[]
}
export default function Jokes ( { initialJokes } : Props ) {
const [ jokes , setJokes ] = useState ( initialJokes )
const handleAddJoke = ( text : string ) => {
const joke = {
text ,
id : self . crypto . randomUUID () ,
likes : 0 ,
}
setJokes ([ joke , ... jokes ])
console . log ( " New Joke: " , text )
}
const handleDeleteJoke = ( id : string ) => {
setJokes ( jokes . filter ( ( joke ) => joke . id !== id ))
console . log ( " delete joke " , id )
}
const handleLike = ( id : string ) => {
setJokes (
jokes . map ( ( joke ) => {
if ( joke . id === id ) {
return { ... joke , likes : joke . likes + 1 }
} else {
return joke
}
} )
)
}
const handleDislike = ( id : string ) => {
setJokes (
jokes . map ( ( joke ) => {
if ( joke . id === id ) {
return { ... joke , likes : joke . likes - 1 }
} else {
return joke
}
} )
)
}
const handleSort = () => {
setJokes ([ ... jokes ] . sort ( ( a , b ) => b . likes - a . likes ))
}
return (
< div className = "" >
< h1 > Dad Jokes </ h1 >
< button onClick = { handleSort } > Sort </ button >
< JokeForm onAddJoke = { handleAddJoke } />
{ jokes . map ( ( joke ) => (
< Joke
onDelete = { handleDeleteJoke }
key = { joke . id }
onLike = { handleLike }
onDislike = { handleDislike }
{ ... joke }
/>
)) }
</ div >
)
}
" use client "
type Props = {
id : string
text : string
likes : number
onDelete : ( id : string ) => void
onLike : ( id : string ) => void
onDislike : ( id : string ) => void
}
export default function Joke ( { id , text , onDelete , likes , onLike , onDislike } : Props ) {
const handleLike = () => {
onLike ( id )
}
const handleDislike = () => {
onDislike ( id )
}
return (
< div >
< p > { text } </ p >
< p > Likes: { likes } </ p >
< button onClick = { handleLike } > 👍 </ button >
< button onClick = { handleDislike } > 👎 </ button >
< button onClick = { () => onDelete (id) } > delete </ button >
</ div >
)
}
" use client "
import { useState } from " react "
type Props = {
onAddJoke : ( text : string ) => void
}
export default function JokeForm ( { onAddJoke } : Props ) {
const [ text , setText ] = useState ( "" )
const handleSubmit = ( e : React . FormEvent < HTMLFormElement > ) => {
e . preventDefault ()
onAddJoke ( text )
setText ( "" )
}
return (
< form onSubmit = { handleSubmit } >
< label htmlFor = " text " > New Joke </ label >
< input type = " text " id = " text " value = { text } onChange = { ( e ) => setText (e . target . value ) } />
< button type = " submit " > Add Joke </ button >
</ form >
)
}
import Jokes from " ./jokes "
export default function Page () {
const jokes = [
{
id: " 1 " ,
text: " I'm afraid for the calendar. Its days are numbered. " ,
likes: 0
} ,
{
id: " 2 " ,
text: " I used to be addicted to soap, but I'm clean now. " ,
likes: 0
}
]
return (
<div>
<h1> Dad Jokes </h1>
< Jokes initialJokes = { jokes } />
</div>
)
}
" use client "
import Joke from " ./joke "
import JokeForm from " ./joke-form "
import { useState } from " react "
type Props = {
initialJokes : {
text : string
id : string
likes : number
}[]
}
export default function Jokes ( { initialJokes } : Props) {
const [ jokes , setJokes ] = useState (initialJokes)
const handleAddJoke = ( text : string ) => {
const joke = {
text ,
id: self . crypto . randomUUID () ,
likes: 0 ,
}
setJokes ([joke , ... jokes])
console . log ( " New Joke: " , text)
}
const handleDeleteJoke = ( id : string ) => {
setJokes (jokes . filter ( ( joke ) => joke . id !== id))
console . log ( " delete joke " , id)
}
const handleLike = ( id : string ) => {
setJokes (
jokes . map ( ( joke ) => {
if (joke . id === id) {
return { ... joke , likes: joke . likes + 1 }
} else {
return joke
}
} )
)
}
const handleDislike = ( id : string ) => {
setJokes (
jokes . map ( ( joke ) => {
if (joke . id === id) {
return { ... joke , likes: joke . likes - 1 }
} else {
return joke
}
} )
)
}
const handleSort = () => {
setJokes ([ ... jokes] . sort ( ( a , b ) => b . likes - a . likes ))
}
return (
<div className = "" >
<h1> Dad Jokes </h1>
<button onClick = { handleSort } > Sort </button>
< JokeForm onAddJoke = { handleAddJoke } />
{ jokes . map ( ( joke ) => (
< Joke
onDelete = { handleDeleteJoke }
key = { joke . id }
onLike = { handleLike }
onDislike = { handleDislike }
{ ... joke }
/>
)) }
</div>
)
}
" use client "
type Props = {
id : string
text : string
likes : number
onDelete : ( id : string ) => void
onLike : ( id : string ) => void
onDislike : ( id : string ) => void
}
export default function Joke ( { id , text , onDelete , likes , onLike , onDislike } : Props) {
const handleLike = () => {
onLike (id)
}
const handleDislike = () => {
onDislike (id)
}
return (
<div>
<p> { text } </p>
<p> Likes: { likes } </p>
<button onClick = { handleLike } > 👍 </button>
<button onClick = { handleDislike } > 👎 </button>
<button onClick = { () => onDelete (id) } > delete </button>
</div>
)
}
" use client "
import { useState } from " react "
type Props = {
onAddJoke : ( text : string ) => void
}
export default function JokeForm ( { onAddJoke } : Props) {
const [ text , setText ] = useState ( "" )
const handleSubmit = ( e : React . FormEvent < HTMLFormElement > ) => {
e . preventDefault ()
onAddJoke (text)
setText ( "" )
}
return (
<form onSubmit = { handleSubmit } >
<label htmlFor = " text " > New Joke </label>
<input type = " text " id = " text " value = { text } onChange = { ( e ) => setText (e . target . value ) } />
<button type = " submit " > Add Joke </button>
</form>
)
}
import Jokes from " ./jokes "
export default function Page () {
const jokes = [
{
id : " 1 " ,
text : " I'm afraid for the calendar. Its days are numbered. " ,
likes : 0
} ,
{
id : " 2 " ,
text : " I used to be addicted to soap, but I'm clean now. " ,
likes : 0
}
]
return (
< div >
< h1 > Dad Jokes </ h1 >
< Jokes initialJokes = { jokes } />
</ div >
)
}
" use client "
import Joke from " ./joke "
import JokeForm from " ./joke-form "
import { useState } from " react "
type Props = {
initialJokes : {
text : string
id : string
likes : number
}[]
}
export default function Jokes ( { initialJokes } : Props ) {
const [ jokes , setJokes ] = useState ( initialJokes )
const handleAddJoke = ( text : string ) => {
const joke = {
text ,
id : self . crypto . randomUUID () ,
likes : 0 ,
}
setJokes ([ joke , ... jokes ])
console . log ( " New Joke: " , text )
}
const handleDeleteJoke = ( id : string ) => {
setJokes ( jokes . filter ( ( joke ) => joke . id !== id ))
console . log ( " delete joke " , id )
}
const handleLike = ( id : string ) => {
setJokes (
jokes . map ( ( joke ) => {
if ( joke . id === id ) {
return { ... joke , likes : joke . likes + 1 }
} else {
return joke
}
} )
)
}
const handleDislike = ( id : string ) => {
setJokes (
jokes . map ( ( joke ) => {
if ( joke . id === id ) {
return { ... joke , likes : joke . likes - 1 }
} else {
return joke
}
} )
)
}
const handleSort = () => {
setJokes ([ ... jokes ] . sort ( ( a , b ) => b . likes - a . likes ))
}
return (
< div className = "" >
< h1 > Dad Jokes </ h1 >
< button onClick = { handleSort } > Sort </ button >
< JokeForm onAddJoke = { handleAddJoke } />
{ jokes . map ( ( joke ) => (
< Joke
onDelete = { handleDeleteJoke }
key = { joke . id }
onLike = { handleLike }
onDislike = { handleDislike }
{ ... joke }
/>
)) }
</ div >
)
}
" use client "
type Props = {
id : string
text : string
likes : number
onDelete : ( id : string ) => void
onLike : ( id : string ) => void
onDislike : ( id : string ) => void
}
export default function Joke ( { id , text , onDelete , likes , onLike , onDislike } : Props ) {
const handleLike = () => {
onLike ( id )
}
const handleDislike = () => {
onDislike ( id )
}
return (
< div >
< p > { text } </ p >
< p > Likes: { likes } </ p >
< button onClick = { handleLike } > 👍 </ button >
< button onClick = { handleDislike } > 👎 </ button >
< button onClick = { () => onDelete (id) } > delete </ button >
</ div >
)
}
" use client "
import { useState } from " react "
type Props = {
onAddJoke : ( text : string ) => void
}
export default function JokeForm ( { onAddJoke } : Props ) {
const [ text , setText ] = useState ( "" )
const handleSubmit = ( e : React . FormEvent < HTMLFormElement > ) => {
e . preventDefault ()
onAddJoke ( text )
setText ( "" )
}
return (
< form onSubmit = { handleSubmit } >
< label htmlFor = " text " > New Joke </ label >
< input type = " text " id = " text " value = { text } onChange = { ( e ) => setText (e . target . value ) } />
< button type = " submit " > Add Joke </ button >
</ form >
)
}
import Jokes from " ./jokes "
export default function Page () {
const jokes = [
{
id: " 1 " ,
text: " I'm afraid for the calendar. Its days are numbered. " ,
likes: 0
} ,
{
id: " 2 " ,
text: " I used to be addicted to soap, but I'm clean now. " ,
likes: 0
}
]
return (
<div>
<h1> Dad Jokes </h1>
< Jokes initialJokes = { jokes } />
</div>
)
}
" use client "
import Joke from " ./joke "
import JokeForm from " ./joke-form "
import { useState } from " react "
type Props = {
initialJokes : {
text : string
id : string
likes : number
}[]
}
export default function Jokes ( { initialJokes } : Props) {
const [ jokes , setJokes ] = useState (initialJokes)
const handleAddJoke = ( text : string ) => {
const joke = {
text ,
id: self . crypto . randomUUID () ,
likes: 0 ,
}
setJokes ([joke , ... jokes])
console . log ( " New Joke: " , text)
}
const handleDeleteJoke = ( id : string ) => {
setJokes (jokes . filter ( ( joke ) => joke . id !== id))
console . log ( " delete joke " , id)
}
const handleLike = ( id : string ) => {
setJokes (
jokes . map ( ( joke ) => {
if (joke . id === id) {
return { ... joke , likes: joke . likes + 1 }
} else {
return joke
}
} )
)
}
const handleDislike = ( id : string ) => {
setJokes (
jokes . map ( ( joke ) => {
if (joke . id === id) {
return { ... joke , likes: joke . likes - 1 }
} else {
return joke
}
} )
)
}
const handleSort = () => {
setJokes ([ ... jokes] . sort ( ( a , b ) => b . likes - a . likes ))
}
return (
<div className = "" >
<h1> Dad Jokes </h1>
<button onClick = { handleSort } > Sort </button>
< JokeForm onAddJoke = { handleAddJoke } />
{ jokes . map ( ( joke ) => (
< Joke
onDelete = { handleDeleteJoke }
key = { joke . id }
onLike = { handleLike }
onDislike = { handleDislike }
{ ... joke }
/>
)) }
</div>
)
}
" use client "
type Props = {
id : string
text : string
likes : number
onDelete : ( id : string ) => void
onLike : ( id : string ) => void
onDislike : ( id : string ) => void
}
export default function Joke ( { id , text , onDelete , likes , onLike , onDislike } : Props) {
const handleLike = () => {
onLike (id)
}
const handleDislike = () => {
onDislike (id)
}
return (
<div>
<p> { text } </p>
<p> Likes: { likes } </p>
<button onClick = { handleLike } > 👍 </button>
<button onClick = { handleDislike } > 👎 </button>
<button onClick = { () => onDelete (id) } > delete </button>
</div>
)
}
" use client "
import { useState } from " react "
type Props = {
onAddJoke : ( text : string ) => void
}
export default function JokeForm ( { onAddJoke } : Props) {
const [ text , setText ] = useState ( "" )
const handleSubmit = ( e : React . FormEvent < HTMLFormElement > ) => {
e . preventDefault ()
onAddJoke (text)
setText ( "" )
}
return (
<form onSubmit = { handleSubmit } >
<label htmlFor = " text " > New Joke </label>
<input type = " text " id = " text " value = { text } onChange = { ( e ) => setText (e . target . value ) } />
<button type = " submit " > Add Joke </button>
</form>
)
}
VIDEO
In React, components are functions that take input data and return output markup. It's important to keep the component function focused on rendering and not interacting with external systems like network requests or updating the window object. Any interaction with external systems is considered a side effect.
if ( images . length === 0 ) {
return ' No memes '
}
if ( images . length === 0 ) {
return ' No memes '
}
if ( images . length === 0 ) {
return ' No memes '
}
if ( images . length === 0 ) {
return ' No memes '
}
This code inside of a components is absolutely fine because the logic here is only accessing state that exists within the component, and only returning markup that will be rendered to the dom. There is no interaction with an external system.
But what about this code:
// Bad because it's in the top level of the component and is executed as part of the component rendering
document . title = ` You have ${ images . length } memes `
// Bad because it's in the top level of the component and is executed as part of the component rendering
document . title = ` You have ${ images . length } memes `
// Bad because it's in the top level of the component and is executed as part of the component rendering
document . title = ` You have ${ images . length } memes `
// Bad because it's in the top level of the component and is executed as part of the component rendering
document . title = ` You have ${ images . length } memes `
This code interacts with an external system, the document, and is considered a side effect. To avoid running side effect code in the component function, we can create a function that causes side effects and call it later, such as when a user clicks a button.
// Good because it's inside a function that isn't executed as part of the component rendering
const updateDocumentTitle = () => {
document . title = ` You have ${ images . length } memes `
}
return (
// ...
< button onClick = { updateDocumentTitle } >
// Good because it's inside a function that isn't executed as part of the component rendering
const updateDocumentTitle = () => {
document . title = ` You have ${ images . length } memes `
}
return (
// ...
<button onClick = { updateDocumentTitle } >
// Good because it's inside a function that isn't executed as part of the component rendering
const updateDocumentTitle = () => {
document . title = ` You have ${ images . length } memes `
}
return (
// ...
< button onClick = { updateDocumentTitle } >
// Good because it's inside a function that isn't executed as part of the component rendering
const updateDocumentTitle = () => {
document . title = ` You have ${ images . length } memes `
}
return (
// ...
<button onClick = { updateDocumentTitle } >
Event listeners , like onClick
or onSubmit
, are a great place to run side effects because they are triggered by user interaction and not part of the component rendering. And this is usually where you want to start with side effects. Add an event listener.
But if I refresh the page, we can see that the window's title isn't right, it should say 0 memes, or if I had some initial data in the images array, then it should say however many images i have. Basically, I don't want this side effect to be triggered by an user event. I want this side effect to be triggered by a state update.
useEffect for Side Effects
I want this side effect to be in sync with the state of my component. And this is where the useEffect
hook comes in.
We use useEffect
by importing it, then calling it inside of the component function. And we pass it a function and run any side effect code. And now everything just works, like magic. React will run the useEffect function after the component is rendered.
useEffect ( () => {
document . title = ` You have ${ images . length } memes `
})
useEffect ( () => {
document . title = ` You have ${ images . length } memes `
})
useEffect ( () => {
document . title = ` You have ${ images . length } memes `
})
useEffect ( () => {
document . title = ` You have ${ images . length } memes `
})
useEffect
will let you run some code after rendering so that you can synchronize your component with some system outside of React.
So that's the basic idea of useEffect
. It's a hook that lets you run some code after the component is rendered to synchronize your component and it's state with some system outside of React.
Rendering the UI is driven by props and state, and is guaranteed to be consistent with them, but side effects are not. useEffect
is for side effects, not for rendering the UI. It's important to keep that in mind.
If you want to learn more about using React for client side SPAs, then check out my full playlist on React: https://www.sammeechward.com/playlists/all-you-need-to-know-about-react-js