- Published on
BigFrontEnd React Coding Questions
- Authors
- Name
- Yinhuan Yuan
Introduction
This blog post summarizes React Coding Questions on BigFrontEnd.Dev.
- 1.the React Counter app
- 2.useTimeout() Hook
- 3.useIsFirstRender() Hook
- 4.useSWR Hook
- 5.usePrevious() Hook
- 6.useHover() Hook
- 7.useToggle() Hook
- 8.useDebounce() Hook
- 9.useEffectOnce() Hook
- 10.Create a Phone Number Component
- 11.useFocus() Hook
- 12.useArray()
- 13.proxy-state valtio
- 14.useIsMounted() Hook
- 15.useClickOutside() Hook
- 16.useUpdateEffect() Hook
- 17.useWindowWidth Hook
- 18.useFetch Hook
- 19.useForm Hook
1.the React Counter app
1.https://bigfrontend.dev/react/The-React-Counter
As the first React problem, you are asked to create the famous Counter app.
counter starts from 0.
- click the '+' button to increment.
- click the '-' button to decrement.
- data-testid is used to test your code, please do not remove them.
Solution:
import React, {useState, useCallback} from 'react'
export function App() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((count) => count + 1);
}, []);
const decrement = useCallback(() => {
setCount((count) => count - 1);
}, []);
return (
<div>
<button data-testid="decrement-button" onClick={decrement}>
-
</button>
<button data-testid="increment-button" onClick={increment}>
+
</button>
<p>clicked: {count}</p>
</div>
);
}
useTimeout()
Hook
2.2.https://bigfrontend.dev/react/usetimeout
Create a hook to easily use setTimeout(callback, delay)
.
reset the timer if delay changes DO NOT reset the timer if only callback changes
Solution:
We can utilize useRef
to create a reference that stores the callback function and use useEffect
to update the ref whenever the callback changes. Additionally, another useEffect
can be used to update the setTimeout
delay in the ref when the delay
changes.
import { useEffect, useRef } from 'react'
export function useTimeout(callback: () => void, delay: number) {
// useRef for Callback Storage:
const savedCallback = useRef(callback)
// Remember the latest callback if it changes
useEffect(() => {
savedCallback.current = callback
}, [callback])
useEffect(() => {
const id = setTimeout(() => savedCallback.current(), delay)
// Cleanup timeout on component unmount or when delay changes
return () => clearTimeout(id)
}, [delay])
}
useIsFirstRender()
Hook
3.3.https://bigfrontend.dev/react/useIsFirstRender
Create a hook to tell if it is the first render.
function App() {
const isFirstRender = useIsFirstRender()
// only true for the first render
...
}
Solution:
We can use useRef
to create a value initialized as true
. After the initial render (when the component mounts), this value is then reset to false
.
import { useEffect, useRef } from 'react'
export function useIsFirstRender(): boolean {
const isFirstRender = useRef<boolean>(true)
useEffect(() => {
isFirstRender.current = false
}, [])
return isFirstRender.current
}
useSWR
Hook
4.SWR is a popular library of data fetching.
Let's try to implement the basic usage by ourselves.
import React from 'react'
function App() {
const { data, error } = useSWR('/api', fetcher)
if (error) return <div>failed</div>
if (!data) return <div>loading</div>
return <div>succeeded</div>
}
this is not to replicate the true implementation of useSWR()
The first argument key is for deduplication, we can safely ignore it for now
Solution:
We can use useState
to create two state variables, data
and error
, and utilize useEffect
to trigger the data fetching callback whenever the key changes.
import { useState, useEffect } from 'react'
export function useSWR<T = any, E = any>(
_key: string,
fetcher: () => T | Promise<T>
): {
data?: T
error?: E
} {
const promise = fetcher()
if (!(promise instanceof Promise)) {
return { data: promise }
}
const [data, setData] = useState<T>()
const [error, setError] = useState<E>()
useEffect(() => {
const fetchData = async () => {
try {
const data = await promise
setData(data)
} catch (e) {
setError(e)
}
}
fetchData()
}, [_key])
return { data, error }
}
usePrevious()
Hook
5.5.https://bigfrontend.dev/react/usePrevious
Create a hook usePrevious()
to return the previous value, with initial value of undefined
.
Solution:
We can use useRef
to create a variable that stores the previous value. Each time the value changes, it is saved in this variable.
import { useRef, useEffect } from 'react'
export function usePrevious<T>(value: T): T | undefined {
const previousValueRef = useRef<T | undefined>()
useEffect(() => {
/* This line runs after every render after the value is changed*/
previousValueRef.current = value
}, [value])
// It will return the value before each render.
return previousValueRef.current
}
useHover()
Hook
6.6.https://bigfrontend.dev/react/useHover
It is common to see conditional rendering based on hover state of some element.
We can achive it by CSS pseduo class :hover
, but for more complex cases it might be better to have state controlled by script.
Now you are asked to create a useHover()
hook.
function App() {
const [ref, isHovered] = useHover()
return <div ref={ref}>{isHovered ? 'hovered' : 'not hovered'}</div>
}
Solution:
import { useRef, useState, useCallback } from 'react'
import type { Ref } from 'react'
export function useHover<T extends HTMLElement>(): [Ref<T>, boolean] {
const elRef = useRef<T | null>(null)
const [isHovered, setIsHovered] = useState(false)
const onMouseEnter = () => {
setIsHovered(true)
}
const onMouseLeave = () => {
setIsHovered(false)
}
const setRef = useCallback((node: T) => {
if (elRef.current) {
elRef.current.removeEventListener('mouseenter', onMouseEnter)
elRef.current.removeEventListener('mouseleave', onMouseLeave)
}
if (node) {
elRef.current = node
elRef.current.addEventListener('mouseenter', onMouseEnter)
elRef.current.addEventListener('mouseleave', onMouseLeave)
}
}, [])
return [setRef, isHovered]
}
useToggle()
Hook
7.7.https://bigfrontend.dev/react/useToggle
It is quite common to see switches and checkboxes in web apps.
Implement useToggle()
to make things easier
function App() {
const [on, toggle] = useToggle()
...
}
Solution:
import { useState } from 'react'
export function useToggle(on: boolean = false): [boolean, () => void] {
const [state, setState] = useState(on)
const toggle = () => {
setState((preState) => !preState)
}
return [state, toggle]
}
useDebounce()
Hook
8.8.https://bigfrontend.dev/react/useDebounce
For a frequently changing value like text input you might want to debounce the changes.
Implement useDebounce()
to achieve this.
function App() {
const [value, setValue] = useState(...)
// this value changes frequently,
const debouncedValue = useDebounce(value, 1000)
// now it is debounced
}
The logic should be similar to 6. implement basic debounce()
Solution:
import { useState, useEffect } from 'react';
function useDebounce(value, delay) {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler);
}; // clearup function which will be called when value or delay is changed.
}, [value, delay]);
return debouncedValue;
}
export default useDebounce;
useEffectOnce()
Hook
9.9.https://bigfrontend.dev/react/useEffectOnce
Here is a simple problem, implement useEffectOnce()
as the name says itself, it runs an effect only once.
Solution:
import { useEffect, EffectCallback } from 'react'
export function useEffectOnce(effect: EffectCallback) {
useEffect(effect, [])
// Empty dependency array ensures the effect runs only once
}
10.Create a Phone Number Component
10.https://bigfrontend.dev/react/phone-number-input
Create a PhoneNumberInput
component.
- only accepts numerical digits
- format the number automatically as (123)456-7890 by
- adding the parenthesis when the 4th digit is entered
- also adding - before 7th digit You can use the default text input without any styling.
Follow-up
What if user removes some digits in the middle, does caret jumps to the end in your approach?
caret position is not covered in our tests.
Solution:
import React, { useState } from 'react'
export const PhoneNumberInput = () => {
const [phoneNumber, setPhoneNumber] = useState('')
const handleInputChange = (e) => {
// /\D/ matches any character that is not a digit (\D stands for “non-digit”).
const input = e.target.value.replace(/\D/g, '') // Remove non-numeric characters
let formattedPhoneNumber = ''
// Format the phone number
if (input.length <= 3) {
formattedPhoneNumber = input
} else if (input.length >= 4 && input.length <= 6) {
formattedPhoneNumber = `(${input.slice(0, 3)})${input.slice(3)}`
} else if (input.length >= 7 && input.length <= 10) {
formattedPhoneNumber = `(${input.slice(0, 3)})${input.slice(3, 6)}-${input.slice(6)}`
} else {
formattedPhoneNumber = `(${input.slice(0, 3)})${input.slice(3, 6)}-${input.slice(6, 10)}`
}
setPhoneNumber(formattedPhoneNumber)
}
return (
<input
type="text"
data-testid="phone-number-input"
value={phoneNumber}
onChange={handleInputChange}
/>
)
}
useFocus()
Hook
11.11.https://bigfrontend.dev/react/useFocus
CSS pseudo-class :focus-within could be used to allow conditional rendering in parent element on the focus state of descendant elements.
While it is cool, in complex web apps, it might be better to control the state in script.
Now please create useFocus()
to support this.
function App() {
const [ref, isFocused] = useFocus()
return (
<div>
<input ref={ref} />
{isFocused && <p>focused</p>}
</div>
)
}
Solution:
import React, { Ref, useState, useRef, useEffect, RefObject } from 'react'
export function useFocus<T extends HTMLElement>(): [Ref<T>, boolean] {
const [isFocused, setIsFocused] = useState<boolean>(false)
const ref = useRef<T>(null)
useEffect(() => {
const handleFocusIn = () => setIsFocused(true)
const handleFocusOut = () => setIsFocused(false)
const element = ref.current
// !!IMPORTANT!!
// initialize the focus state when currentElement changes.
setIsFocused(document.activeElement === element)
if (element) {
element.addEventListener('focus', handleFocusIn)
element.addEventListener('blur', handleFocusOut)
}
return () => {
if (element) {
element.removeEventListener('focus', handleFocusIn)
element.removeEventListener('blur', handleFocusOut)
}
}
}, [ref.current])
return [ref, isFocused]
}
12.useArray()
12.https://bigfrontend.dev/react/useArray
When array is used in React state, we need to deal with actions such as push and remove.
Implement useArray()
to make life easier.
const { value, push, removeByIndex } = useArray([1, 2, 3])
Solution:
import React, { useState, useCallback } from 'react'
type UseArrayActions<T> = {
push: (item: T) => void
removeByIndex: (index: number) => void
}
export function useArray<T>(initialValue: T[]): { value: T[] } & UseArrayActions<T> {
const [array, setArray] = useState(initialValue)
const push = useCallback((element: T) => {
setArray((prevArray) => [...prevArray, element])
}, [])
const removeByIndex = useCallback((index: number) => {
setArray((prevArray) => {
const newArray = [...prevArray]
newArray.splice(index, 1)
return newArray
})
}, [])
return {
value: array,
push,
removeByIndex,
}
}
13.proxy-state valtio
13.https://bigfrontend.dev/react/proxy-state-valtio
valtio claims to make proxy-state simple.
Let's take a look at the basic example.
import { proxy, useSnapshot } from 'valtio'
const state = proxy({ count: 0, text: 'hello' })
// This will re-render on `state.count` change
// but not on `state.text` change
function Counter() {
const snap = useSnapshot(state)
return (
<div>
{snap.count}
<button onClick={() => ++state.count}>+1</button>
</div>
)
}
// you can mutate the state from anywhere
setInterval(() => {
++state.count
}, 1000)
Now you are asked to implement proxy()
and useSnapshot()
to make above code example work.
This question is NOT to re-implement valtio
, rather it is to test your understanding of proxy-state. The test cases on BFE.dev only covers the basic usage of above two functions, not the full abilities of valtio
.
Solution:
This solution provides a function named proxy
and a hook named useSnapshot
, which together create a system for managing and reacting to changes in an object’s state using React’s state management system.
proxy
Function
1. This function creates a proxy object that intercepts get
and set
operations on the object's properties. The goal is to monitor and react to changes in the object's properties.
Breakdown:
Generics:
- The function is defined with a generic type
<T extends object>
. This means it can work with any object typeT
, ensuring type safety.
- The function is defined with a generic type
keys
Set:- A
Set
calledkeys
is used to track which properties of the object are accessed. The set only allows unique keys.
- A
setState
Reference:- A reference to React's
setState
function is stored in thesetState
variable. This is used later to trigger React re-renders.
- A reference to React's
Proxy Creation:
- A
Proxy
object is created around the initial value. This proxy interceptsget
andset
operations:get
Trap:- Whenever a property on the proxy is accessed, the
get
trap is triggered. It adds the accessed key to thekeys
set and returns the value of that property.
- Whenever a property on the proxy is accessed, the
set
Trap:- Whenever a property is set (i.e., when its value changes), the
set
trap checks:- If the
key
is"setState"
, it updates thesetState
reference (used for re-renders). - If the new value is the same as the current one, no action is taken (the change is ignored).
- If the value changes and the key has been accessed before, it clears the
keys
set and callssetState
to update the React component's state, causing a re-render.
- If the
- Whenever a property is set (i.e., when its value changes), the
- A
Return:
- The proxy object is returned, allowing the calling code to interact with the object while also tracking changes.
useSnapshot
Function
2. This hook links the proxy object to React’s state system, enabling reactivity when properties on the proxy are modified.
Breakdown:
State Initialization:
useState
is called with the proxy object. This initializes React state, andsetState
is used to update the state later.
Injecting
setState
:- The
setState
function is injected into the proxy object by setting a special"setState"
property on the proxy. This allows the proxy'sset
trap to trigger React updates.
- The
Return:
- The proxy object is returned so that the component using
useSnapshot
can interact with it, with changes automatically causing re-renders.
- The proxy object is returned so that the component using
How It Works Together
Proxy:
- The
proxy
function creates a proxied object that monitors changes to its properties. It triggers re-renders when those properties change, as long as they have been accessed before.
- The
useSnapshot:
- The
useSnapshot
hook connects this proxy to React's state system, enabling components to reactively update when the proxy's properties are modified.
- The
import React, { useState, useEffect } from 'react'
export function proxy<T extends object>(initialValue: T): T {
const keys = new Set<string>()
let setState: React.Dispatch<React.SetStateAction<T>>
const proxy = new Proxy<T>(
{ ...initialValue },
{
get(target: T, key: string) {
keys.add(key)
return Reflect.get(target, key)
},
set(target: T, key: string, value) {
if (key === 'setState') {
// This is just to update the dispatch reference in this method, won't be required
setState = value
return false
}
if (Reflect.get(target, key) === value) {
// no-change occured w.r.t prev value
return true
}
const status = Reflect.set(target, key, value)
if (status && keys.has(key)) {
keys.clear()
setState((prev) => ({
...prev,
[key]: value,
}))
}
return status
},
}
)
return proxy
}
export function useSnapshot<T extends object>(proxy: T): T {
const [_, setState] = useState(proxy)
// Pass dispatch handler to set interceptor in proxy method
Reflect.set(proxy, 'setState', setState)
return proxy
}
useIsMounted()
Hook
14.14.https://bigfrontend.dev/react/implement-useismounted
When we handle async requests in React, we need to pay attention if the component is already unmounted.
Please implement useIsMounted()
for us to easily tell if the component is still not unmounted.
Solution:
import React, { useRef, useEffect } from 'react'
export function useIsMounted(): () => boolean {
const isMountedRef = useRef(false)
useEffect(() => {
isMountedRef.current = true
return () => {
isMountedRef.current = false
}
}, [])
return () => isMountedRef.current
}
useClickOutside()
Hook
15.15.https://bigfrontend.dev/react/useclickoutside
Click above header menu on this page, you can see that the dropdown menu is dismissed after clicking outside.
Now you are asked to implement a React hook to make it eaiser to implement such behavior.
function Component() {
const ref = useClickOutside(() => {
alert('clicked outside')
})
return <div ref={ref}>..</div>
}
Solution:
import { useEffect, useRef } from 'react'
export function useClickOutside(callback: () => void) {
const ref = useRef<HTMLDivElement | null>(null)
useEffect(() => {
function handleClickOutside(event: MouseEvent) {
if (ref.current && !ref.current.contains(event.target as Node)) {
callback()
}
}
document.addEventListener('mousedown', handleClickOutside)
return () => {
document.removeEventListener('mousedown', handleClickOutside)
}
}, [callback])
return ref
}
useUpdateEffect()
Hook
16.16.https://bigfrontend.dev/react/useUpdateEffect
Implement useUpdateEffect()
that it works the same as useEffect()
except that it skips running the callback on first render.
Solution:
import React, { useEffect, useRef, EffectCallback, DependencyList} from 'react';
export function useUpdateEffect(effect: EffectCallback, deps?: DependencyList) {
const isMountedRef = useRef(false);
useEffect(() => {
if (!isMountedRef.current) {
isMountedRef.current = true;
} else {
return effect();
}
}, deps); // Only re-run effect if dependencies change
}
useWindowWidth
Hook
17.import { useState, useEffect } from 'react'
function useWindowWidth() {
const [width, setWidth] = useState(window.innerWidth)
useEffect(() => {
const handleResize = () => setWidth(window.innerWidth)
window.addEventListener('resize', handleResize)
return () => {
window.removeEventListener('resize', handleResize)
}
}, [])
return width
}
export default useWindowWidth
Reference: Mastering Custom Hooks in React: A Comprehensive Guide
useFetch
Hook
18.import { useState, useEffect } from 'react'
function useFetch(url) {
const [data, setData] = useState(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState(null)
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch(url)
if (!response.ok) throw new Error('Network response was not ok')
const result = await response.json()
setData(result)
} catch (err) {
setError(err)
} finally {
setLoading(false)
}
}
fetchData()
}, [url])
return { data, loading, error }
}
export default useFetch
Usage:
import React from 'react'
import useFetch from './useFetch'
function App() {
const { data, loading, error } = useFetch('https://api.example.com/data')
if (loading) return <div>Loading...</div>
if (error) return <div>Error: {error.message}</div>
return (
<div>
<h1>Data</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</div>
)
}
export default App
Reference: Mastering Custom Hooks in React: A Comprehensive Guide
useForm
Hook
19.import { useState } from 'react'
function useForm(initialValues, onSubmit) {
const [values, setValues] = useState(initialValues)
const handleChange = (event) => {
const { name, value } = event.target
setValues({
...values,
[name]: value,
})
}
const handleSubmit = (event) => {
event.preventDefault()
onSubmit(values)
}
return {
values,
handleChange,
handleSubmit,
}
}
export default useForm
Usage:
import React from 'react'
import useForm from './useForm'
function App() {
const initialValues = { username: '', email: '' }
const onSubmit = (values) => {
console.log('Form Submitted:', values)
}
const { values, handleChange, handleSubmit } = useForm(initialValues, onSubmit)
return (
<form onSubmit={handleSubmit}>
<div>
<label>
Username:
<input type="text" name="username" value={values.username} onChange={handleChange} />
</label>
</div>
<div>
<label>
Email:
<input type="email" name="email" value={values.email} onChange={handleChange} />
</label>
</div>
<button type="submit">Submit</button>
</form>
)
}
export default App
Reference: Mastering Custom Hooks in React: A Comprehensive Guide