React is one of the most powerful, widely used frontend frameworks.
- Makes your code easily scalable
- More readable
- ~1000x More efficient
Reasons on why to learn React.js:
- Reusability of components
- Well supported (popular!)
- NOT Opinionated: it won't force you to follow a specific design pattern, project structure or logic
- Smaller learning curve
React Apps consist of reusable components (aka building blocks).
Example:
App
: main application, parent of all componentsNavbar
MainArticle
NewsletterForm
A React component is defined in a ES6 module (import
statements).
// App.js
import ExampleComponent from "./components/ExampleComponent";
React components generally have parent and/or child components.
- Keeps your code organized
- Easy to keep track of your components' relationships with one another
Example:
import React, {Component} from 'react';
class App extends Component {
constructor {
super();
}
}
{
/*JavaScript Functions Go Here!!!*/
}
render() {
return (
<div className='App'>
Hello World!
</div>
)
}
The import statement imports React & Component from the React Library, allowing us to create a class component.
import React, {Component} from 'react';
React
= default exportComponent
= not default export, requiring{ }
By declaring the class component App
. By extending the React class Component, we have Reactified
our App component:
- Gives
App
React component methods & properties - Should ALWAYS be declared with PascalCase
- Just like classes & factory functions
- Used by React core team @ Facebook & most other devs
constructor() {
super()
}
Constructors are not required for class components. However, they become important when inheritance & state are involved.
- Pass
props
as an argument to constructor super()
* required by any React constructor
{
/* Comments in React are weird! */
}
React comments are written with /* xxx */
within curly braces {}
.
Parts of React allow // Comments
.
render() {
return (
<div className="App">
Hello World!
</div>
)
}
render()
is a function that returns JSX
. JSX is an HTML-like syntax that is transpiled into JavaScript.
React allows you combine JavaScript & JSX.
JSX requires you use alternatives to protected JavaScript words for HTML attributes:
onChange
instead ofonchange
htmlFor
instead offor
className
instead ofclass
render()
= Most used React "lifecycle" function.
- EVERY class component requires a render function.
render()
returns one top-level JSX Element.
// BAD - ERROR !!
render() {
return (
<h1>hii</h1>
<h2>heyyy</h2>
)
}
// GOOD!
render() {
return (
<div>
<h1>hii</h1>
<h2>heyyy</h2>
</div>
)
}
In order to reuse the App
component, we have to export it:
export default App
If multiple components are in one file, you can:
- add
export
before each declaration of the component - export them all within curly braces `export { ComponentA, ComponentB, ComponentC }
The more modern approach is defining components as functions (like a factory function).
import React from "react";
// function declaration
function App() {
return <div className="App">Hello World!</div>;
}
// function expression
const App = () => {
return <div className="App">Hello World!</div>;
};
// function expression with implicit return
const App = () => <div className="App">Hello World!</div>;
export default App;
Functional components...
- Don't have to import & extend "Component" from React.
- Don't need a constructor.
- Don't need the
render()
function- Instead, we use
return
statement at the end of the function body
- Instead, we use
Facebook developers came up with create-react-app
, which sets up a complete React app for you. It does all the setup & configuration for you.
npx create-react-app my-app
index.js
is the "entry point" of your application.
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
const name = "Joe";
const element = <h1>Hello, {name}</h1>;
In JSX, you can put any valid JavaScript Expression inside curly braces {}
.
2+2
user.firstName
formatName(user)
Embed results of
formatName(user)
into<h1>
function formatName(user) {
return user.firstName + " " + user.lastName;
}
const user = {
firstName: "Harper",
lastName: "Perez",
};
const element = <h1>Hello, {formatName(user)}!</h1>;
To automatic semi-colon insertion, wrap JSX in parentheses ( )
.
JSX expressions are compiled into regular JavaScript function calls and evaluate to JavaScript objects.
You can use JSX inside of:
if
statementsfor
loops- assign it to variables
- accept it as arguments
- return it from functions
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
const element = <img src={user.avatarURL}></img>;
If a tag is empty, close it with />
like XML:
const element = <img src={user.avatarURL} />;
JSX tags can have children:
const element = (
<div>
<h1>Hello!</h1>
<h2>Good to see you here.</h2>
</div>
);
Everything is converted to a string before being rendered and helps prevent XSS cross-site-scripting attacks:
const title = response.potentiallyMaliciousInput;
// This is safe:
const element = <h1>{title}</h1>;
Babel compiles JSX down to React.createElement()
calls.
const element = <h1 className="greeting">Hello, world!</h1>;
// is exactly the same as:
const element = React.createElement(
"h1",
{ className: "greeting" },
"Hello, world!"
);
React.createElement()
performs a few checks & creates an object like this:
// Note: this structure is simplified
// * called React elements
const element = {
type: "h1",
props: {
className: "greeting",
children: "Hello, world!",
},
};
HTML file contains: <div id="root"></div>
Everything inside the "root" DOM node will be managed by React.
- Apps usually have ONE root DOM node.
- You have have as many isolated root DOM nodes as you'd like.
To render a React element:
- pass the DOM elemet to
ReactDOM.createRoot()
- pass React element to
root.render()
const root = ReactDOM.createRoot(document.getElementById("root"));
const element = <h1>Hello world!</h1>;
root.render(element);
React elements are immutable.
- Once created, you can't change its children or attributes.
- Like a single frame in a movie
- Represents UI at a certain point in time
To update the UI, create a new element & pass it to root.render()
const root = ReactDOM.createRoot(document.getElementById("root"));
function tick() {
const element = (
<div>
<h1>Hello, world!</h1>
<h2>It is {new Date().toLocaleTimeString()}.</h2>
</div>
);
root.render(element);
}
setInterval(tick, 1000);
In practice, MOST Apps only call root.render()
ONCE
React DOM compares the element & its children to the previous one and only applies DOM updates necessary to bring the DOM to the desired state.
In the above example:
- We created an entire element, describing the whole UI tree, on every tick
- However, only the text node whose contents have changed gets updated by ReactDOM
Components are like JavaScript functions:
- Accept inputs: called Props
- Return: React elements, describing what should appear on the screen
Function Components: simplest way
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
ES6 Class Components:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
React elements can represent:
- DOM tags:
<div \>
- User-defined components:
<Welcome name="Sara" />
When React sees an element representing a user-defined component, it passes JSX attributes & children to this component as a single object.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
const root = ReactDOM.createRoot(document.getElementById("root"));
const element = <Welcome name="Sara" />;
root.render(element);
Summary of what occurs above:
root.render()
is called with the<Welcome name="Sara" >
element- React calls
Welcome
component with{name: "Sara"}
as the props Welcome
component returns<h1>Hello, Sara</h1>
- ReactDOM efficiently updates the DOM to match
<h1>Hello, Sara</h1>
Components can refer to other components in their output.
Any level of detail can be used as a component:
- button
- form
- dialog
- screen
In React apps, all those are commonly expressed as components
Component that renders
Welcome
many times
function App() {
return (
<div>
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
</div>
);
}
Don't be afraid to split components up into smaller components.
function Comment(props) {
return (
<div className="Comment">
<div className="UserInfo">
<img
className="Avatar"
src={props.author.avatarUrl}
alt={props.author.name}
/>
<div className="UserInfo-name">{props.author.name}</div>
</div>
<div className="Comment-text">{props.text}</div>
<div className="Comment-date">{formatDate(props.date)}</div>
</div>
);
}
The above describes a comment on a social media site and accepts the following props
:
author
- objecttext
- stringdate
- date
This component can be tricky to change:
- Lots of nesting
- Hard to reuse individual parts of it
Avatar
doesn't need to know it's being rendered inside a Comment
:
function Avatar(props) {
return (
<img className="Avatar" src={props.user.avatarUrl} alt={props.user.name} />
);
}
We should name props from the component's own point of view... NOT the context in which it's being used.
Next, extract UserInfo
into its own component & reference Avatar
in it:
function UserInfo(props) {
return (
<div className="UserInfo">
<Avatar user={props.user} />
<div className="UserInfo-name">{props.user.name}</div>
</div>
);
}
Final result:
function Comment(props) {
return (
<div className="Comment">
<UserInfo user={props.author} />
<div className="Comment-text">{props.text}</div>
<div className="Comment-date">{formatDate(props.date)}</div>
</div>
);
}
Rule of thumb:
- Part of the UI that's used several times:
Button
,Panel
,Avatar
- Complex enough on its own:
App
,FeedStory
,Comment
Should all be extracted into it's own component!
Components must NEVER modify it's own props
// PURE FUNCTION
function sum(a, b) {
return a + b;
}
All React components must act like pure functions with respect to their props.
Props are used to share values or functionality between components.
Props are short for: Object Property
Example:
//
// MyComponent.js
//
import React, { Component } from "react";
class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>{this.props.title}</h1>
</div>
);
}
}
export default MyComponent;
//
// App.js
//
import React, { Component } from 'react';
import MyComponent from './MyComponent';
class App extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<MyComponent title="React" />
</div>
);
}
}
export default App;
In the above code:
MyComponent
is imported intoApp
MyComponent
is rendered as a child component ofApp
- In
App
, we pass the propertytitle="React"
To access the title
prop within MyComponent
, we use the syntax:
this.props.title
In MyComponent
, you MUST:
prop
object must be passed to the constructorsuper()
must be called within the constructor
With functional components, props
work the same way.
//
// MyComponent.js
//
import React, { Component } from 'react';
class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
return (
<div>
<h1>{this.props.title}</h1>
<button onClick={this.props.onButtonClicked}>Click Me!</button>
</div>
);
}
}
export default MyComponent;
//
// App.js
//
import React, { Component } from 'react';
import MyComponent from './MyComponent';
class App extends Component {
constructor(props) {
super(props);
this.onClickBtn = this.onClickBtn.bind(this);
}
onClickBtn() {
console.log('Button has been clicked!');
}
render() {
return (
<div>
<MyComponent title="React" onButtonClicked={this.onClickBtn} />
</div>
);
}
}
export default App;
{this.props.onButtonClicked}
is assigned to onClick
event of the component:
MyComponent
expects a prop to be passed to it:onButtonClicked
- Value of propr should be a function
- Attach the function to the click event of our button
In React, we assign event listeners directly in the JSX - not addEventListener()
.
We can rename props between parent/child components:
App
:- Actual function name:
onButtonClick()
- MyComponent prop:
onButtonClicked={this.onClickBtn}
- Actual function name:
MyComponent
:onClick={this.props.onButtonClicked}
With class components, you have to bind methods to this
.
- Recommended way: via the constructor & below the
super()
call. - WHY? Functions that are passed to another component need to stay in the same context in which it was declared.
// MyComponent.js
import React, { Component } from "react";
class MyComponent extends Component {
constructor(props) {
super(props);
}
render() {
const { title, onButtonClicked } = this.props;
return (
<div>
<h1>{title}</h1>
<button onClick={onButtonClicked}>Click Me!</button>
</div>
);
}
}
export default MyComponent;
Passing properties & functions via this.props.SOMETHING
is a pain.
Destructuring allows us to use their base names:
const { title, onButtonClicked } = this.props;
State is what we use to handle values that can change over time.
Example: An app w/ a counter & button to increment it:
import React, { Component } from "react";
class App extends Component {
constructor() {
super();
this.state = {
count: 0,
};
this.countUp = this.countUp.bind(this);
}
countUp() {
this.setState({
count: this.state.count + 1,
});
}
render() {
return (
<div>
<button onClick={this.countUp}>Click Me!</button>
<p>{this.state.count}</p>
</div>
);
}
}
State is ALWAYS declared in the constructor of a class component.
In the above code, we declared our state as an object with a property count
set to an initial value of 0
.
setState()
method is called inside the countUp
method, which sets the state to a new value.
- State should be treated as immutable.
- NEVER directly change the state (without using
setState
)- NEVER do:
this.state.count = 3
orthis.state.count++
- NEVER do:
- ALWAYS use
setState()
method
Passing State as a Prop is one of the greatest & most powerful fetures of React:
- Ability to pass one component's state down to multiple children
- When that piece of state is changed, each child component that uses it will be automatically re-rendered w/ the new value
Example: Forum
, passing username to multiple components of the app via the parent component
NavBar
&Forum
will auto update on changes tousername
Footer
will NOT update
// in the render method of App.js
return (
<div>
<NavBar username={this.state.username} />
<Forum username={this.state.username} />
<Footer />
</div>
);
Functional components differ in that:
props
are passed as an argument to the functionprops
are accessed viaprops.someFunction
(nothis
keyword)
Example:
// MyComponent.js
import React from 'react';
function MyComponent(props) {
return <div>{props.title}</div>;
}
export default MyComponent;
// App.js
import React from 'react';
import MyComponent from './MyComponent';
function App() {
return (
<div>
<MyComponent title="Hello World" />
</div>
);
}
export default App;
Destructuring props:
function MyComponent({ title }) {
/...
}
// OR
const {title} = props;
React Hooks allow us to set and access state in functional components.
To pass an extra parameter to an event handler, you can do the following:
<button onClick={(e) => this.deleteRow(id, e)}>Delete Row</button>
// or
<button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>
function WarningBanner(props) {
if (!props.warn) {
return null;
}
return <div>Not rendered if props.warn exists</div>;
}
Event handlers in React are passed instances of SyntheticEvent
, a wrapper around the standard browser event.
It contains the same interface as the browser's native event:
stopPropogation()
preventDefault()
To access the underlying browser event, use the nativeEvent
attribute.
Synthetic events are different than the native events. Event names:
// Keyboard
- event: onKeyDown onKeyPress onKeyUp
- props: boolean altKey
number charCode
boolean ctrlKey
boolean getModifierState(key)
string key
number keyCode
string locale
number location
boolean metaKey
boolean repeat
boolean shiftKey
number which
// Mouse Events
- event: onClick onContextMenu onDoubleClick
onDrag onDragEnd onDragEnter onDragExit
onDragLeave onDragOver onDragStart onDrop
onMouseDown onMouseEnter onMouseLeave
onMouseMove onMouseOut onMouseOver onMouseUp
- props: boolean altKey
number button
number buttons
number clientX
number clientY
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
number pageX
number pageY
DOMEventTarget relatedTarget
number screenX
number screenY
boolean shiftKey
// Form
- event: onChange onInput onInvalid onReset onSubmit
// Animation
- event: onAnimationStart onAnimationEnd onAnimationIteration
- props: string animationName
string pseudoElement
float elapsedTime
// Transition
- event: onTransitionEnd
- props: string propertyName
string pseudoElement
float elapsedTime
// Select
- event: onSelect
// UI
- event: onScroll
- props: number detail
DOMAbstractView view
// Wheel
- event: onWheel
- props: number deltaMode
number deltaX
number deltaY
number deltaZ
// Clipboard
- event: onCopy onCut onPaste
- props: DOMDataTransfer clipboardData
// Other
- event: onToggle
// Image
- event: onLoad onError
// Composition
- event: onCompositionEnd onCompositionStart onCompositionUpdate
- props: string data
// Focus
- event: onFocus onBlur
- props: DOMEventTarget relatedTarget
// Generic
- event: onError onLoad
// Pointer
- event: onPointerDown onPointerMove onPointerUp onPointerCancel
onGotPointerCapture onLostPointerCapture onPointerEnter
onPointerLeave onPointerOver onPointerOut
- props: number pointerId
number width
number height
number pressure
number tangentialPressure
number tiltX
number tiltY
number twist
string pointerType
boolean isPrimary
// Touch
- event: onTouchCancel onTouchEnd onTouchMove onTouchStart
- props: boolean altKey
DOMTouchList changedTouches
boolean ctrlKey
boolean getModifierState(key)
boolean metaKey
boolean shiftKey
DOMTouchList targetTouches
DOMTouchList touches
// Media
- event: onAbort onCanPlay onCanPlayThrough onDurationChange onEmptied onEncrypted
onEnded onError onLoadedData onLoadedMetadata onLoadStart onPause onPlay
onPlaying onProgress onRateChange onSeeked onSeeking onStalled onSuspend
onTimeUpdate onVolumeChange onWaiting
Lifecycle methods are special methods used to operate on components throughout their duration in the DOM.
For example, when the component:
- Mounts
- Renders
- Updates
- Unmounts
render
is the most important lifecycle method.
Lifecycle methods can ONLY be used in class components
.
React uses a virtual DOM. Its stages include:
componentDidMount
Creation of the componentrender
render of the componentcomponentDidUpdate
update of the component - optionalcomponentWillUnmount
death of the component
componentDidMount
runs when the component is mounted (when it's inserted into the DOM tree).
Common tasks completed in componentDidMount
method:
- Connect to web APIs & JS frameworks
- Set Timers:
setTimeout
andsetInterval
- Add event listeners
render
is a required method for all class components.
It contains all logic your component should display on the screen. It might also contain a null
value if you don't want to show anything.
componentDidUpdate
is:
- NOT called on initial render
- IS called any other time that the component updates/changes
- Prone to infinite loops
- if used to update state in a way that would cause a re-render
- should contain conditional statements to prevent that
- ie: compare new props with previous props to make sure some value has changed
This method is a great place:
- To work & operate on the DOM when the component has updated.
- Send network requests when conditions are met
- ie: User changed accounts > fetch data for new account during this lifecycle method
componentWillUnmount
is called when the component is removed from the DOM tree.
It is often used for cleanup tasks, cleaning up componentDidMount
's additions, such as:
- Remove event listeners
- Cancel network requests
- Other cleanup routines
Lifecycle methods such as componentDidMount
, componentDidUpdate
, and render
can ONLY be called in class components.
Before Hooks, functional components were considered 'dumb' or 'stateless'. Hooks have changed that completely.
Hooks allow functional components to also have a lifecycle as well as a state.
The useState
hook lets you use state in functional components.
- Import
useState
hook from React. - Declare state:
const [count, setCount] = useState(0)
count
andsetCount
can be called anything- Naming convention:
[ something, setSomething]
useState(0)
initializes state with a value of0
import React, { useState } from "react";
const App = () => {
const [count, setCount] = useState(0);
const incrementCount = () => {
setCount(count + 1);
};
return (
<div>
<div>{count}</div>
<button onClick={incrementCount}>Increment</button>
</div>
);
};
export default App;
Note:
- Setting State is an ASYNCHRONOUS TASK.
- Setting State causes a RE-RENDER
useEffect
is a better alternative to:
componentDidMount
componentDidUpdate
componentWillUnmount
.
React defers running useEffect
until AFTER the browser has painted. Doing extra work here is less of a problem.
The syntax for useEffect
is:
useEffect(() => {}, []);
useEffect(
() => {
// code to be executed
},
[
// dependency: state, prop or context
]
);
useEffect
is triggered based on changes in the dependencies listed.
- ESLint will warn you if it expects a dependency
- However, they are NOT required
import React, { useState, useEffect } from "react";
const App = () => {
const [color, setColor] = useState("black");
useEffect(() => {
const changeColorOnClick = () => {
if (color === "black") {
setColor("red");
} else {
setColor("black");
}
};
document.addEventListener("click", changeColorOnClick);
return () => {
document.removeEventListener("click", changeColorOnClick);
};
}, [color]);
return (
<div>
<div
id="myDiv"
style={{
color: "white",
width: "100px",
height: "100px",
position: "absolute",
left: "50%",
top: "50%",
backgroundColor: color,
}}
>
This div can change color. Click on me!
</div>
</div>
);
};
export default App;
There are three different options for the dependency array:
- Leave it empty
- equal to
componentDidMount
- runs one time when component mounts (inserted in DOM tree)
- equal to
useEffect(() => {
// Do something ONCE
// [] will always equal [] so this will not trigger again
}, []);
-
Add a dependency to the array
- Similar to
componentDidUpdate
- Only difference: only runs when certain condition has changed
- Will re-run anytime the dependency (color) changes
- Similar to
useEffect(() => {
// Do something WHEN color changes
}, [color]);
-
Leave out the dependency array
- equal to
componentDidMount
ANDcomponentDidUpdate
combined - Runs anytime the component is updated AND right after the initial render
- equal to
useEffect(() => {
// Do something on MOUNT AND UPDATE
});
The return
statement in our useEffect function is equal to componentWillUnmount
method.
return () => {
document.removeEventListener("click", changeColorOnClick);
};
Only Call Hooks At the TOP Level.
- Don't call Hooks inside loops, conditions, or nested functions.
- Add conditional logic INSIDE useEffect Call
- React relies on the ORDER in which Hooks are called
Only Call Hooks from React Functions.
- Don't call Hooks from regular JavaScript functions
Once an application has multiple pages, you need to setup a reliable routing system to handle the component or page that should be rendered.
Client-Side Routing is:
- internal routing inside the JS file
- rendered to client (front-end)
- helps build single-page applications (SPAs) without refreshing
- ie: user clicks navbar > URL changes & view of page is modified within the client
Benefits:
- Faster user experiences
- Browser doesn't need to request entirely new document
- Or need to re-evaluate CSS & JavaScript assets for next page
- Enables more dynamic UX with animation
React Router is the standard routing library for React apps.
Install: npm i react-router-dom
Example config
// RouteSwitch.js
import { BrowserRouter, Routes, Route } from "react-router-dom";
import App from "./App";
import Profile from "./Profile";
const RouteSwitch = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<App />} />
<Route path="/profile" element={<Profile />} />
<Route path="/*" element={<Error />} />
</Routes>
</BrowserRouter>
);
};
export default RouteSwitch;
// App.js
import RouteSwitch from "./RouteSwitch";
// ...
root.render(
<React.StrictMode>
<RouteSwitch />
</React.StrictMode>
);
- Route: routes with a path (URL path) & component to render
- BrowserRouter: router
- Uses history API:
pushState
,replaceState
,popstate
event - Keeps UI in sync with URL
- Assume
BrowserRouter
is at the root of all your projects
- Uses history API:
- Routes: renders 1st child Route that matches the location
- First Route path that matches the URL exactly will be rendered
- All others are ignored (by default)
- Old versions required
exact
keyword to achieve this
In order to do testing inside React, we need to import some packages inside of our testing file:
import React from "react";
import { ... } from "@testing-library/react";
import "@testing-library/jest-dom"; // optional
import userEvent from "@testing-library/user-event";
import TestComponent from "path-to-test-component";
@testing-library/react
: useful functions -render()
@testing-library/jest-dom
: custom matchers, assertive functions -toBeInTheDocument
(complete list)- Element attributes
- Text Content
- Classes, etc.
@testing-library/user-event
:userEvent
API- Simulates user interactions with a web page
- No need to import
jest
: automatically detected in.test.jsx?
files
Note: All of these packages are included in create-react-app
AND the scripts are preconfigured in package.json
// App.js
import React from "react";
const App = () => <h1>Our First Test</h1>;
export default App;
// App.test.js
import React from "react";
import { render } from "@testing-library/react";
import App from "./App";
describe("App component", () => {
it("renders correct heading", () => {
const { getByRole } = render(<App />);
expect(getByRole("heading").textContent).toMatch(/our first test/i);
});
});
The byRole
methods (with name
option) are the preferred method for querying.
- Ensure our UI is accessible to everyone
- Regardless of what they use to navigate the page: mouse, assistive tech
To improve the above example, we should use:
getByRole("heading", { name: "Our First Test" });
Priority of queries: Accessible to Everyone (including assistive technology)
- *
getRoleBy
: every element in accessibility tree.name
option: filter returned elements by their accessible name- should be top preference for just about everything
getByLabelText
: form field label textgetByText
: text content (non-interactive), ie: divs, spans, paragraphsgetDisplayValue
: form filled in valuesgetByPlaceholderText
TextMatch: string, regex, or function of signature
Live user feedback & interaction is #1.
Confidence in our components can be built through tests.
// Changes heading of App:
import React, { useState } from "react";
const App = () => {
const [heading, setHeading] = useState("Magnificent Monkeys");
const clickHandler = () => {
setHeading("Radical Rhinos");
};
return (
<>
<button type="button" onClick={clickHandler}>
Click Me
</button>
<h1>{heading}</h1>
</>
);
};
export default App;
To test if the button works as intended, the screen
object has all the methods needed for querying.
With screen
, we don't have to worry about keeping render
's destructuring up-to-date.
- Better to use
screen
to access queries - Rather than to destructure
render
// App.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import App from "./App";
describe("App component", () => {
it("renders magnificent monkeys", () => {
// since screen does not have the container property, we'll destructure render to obtain container for this test
const { container } = render(<App />);
expect(container).toMatchSnapshot();
});
it("renders radical rhinos after button click", () => {
render(<App />);
const button = screen.getByRole("button", { name: "Click Me" });
userEvent.click(button);
expect(screen.getByRole("heading").textContent).toMatch(/radical rhinos/i);
});
});
First test: utilizes snapshots to check whether all nodes render as we expect them to.
Second test: simulate click event & check if heading changed
toMatch
is one of many assertions we can make.
Note: React Testing Library unmounts the rendered components.
- We must render for each test
beforeEach
Jest function is great for lots of tests
Snapshot testing: Comparing our rendered component with an associated snapshot file.
- Example: "magnificent monkeys renders" test generated this snapshot:
// App.test.js.snap
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`magnificent monkeys render 1`] = `
<div>
<button
type="button"
>
Click Me
</button>
<h1>
Magnificent Monkeys
</h1>
</div>
`;
Snapshots are HTML representations of our components.
- Compared against
App
in future snapshot assertions - If
App
changes, the test fails.
Snapshots are fast & easy to write.
toMatchSnapshot
: assertion saving us from writing multiple lines of code- Don't have to test the existence of button/heading
- Prevent unexpected changes to creep into our code
import renderer from "react-test-renderer";
import Link from "../Link";
it("renders correctly", () => {
const tree = renderer
.create(<Link page="http://www.facebook.com">Facebook</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});
- Renders UI component
- Creates a snapshot
- Compares snapshot to reference file stored alongside the test
To update a snapshot, run the command:
# update all
jest --updateSnapshot
# update specific tests
jest --updateSnapshot --testNamePattern <NAME PATTERN>
To interactively update a snapshot after it fails in the command line, follow the prompt: Press i to update failing snapshots interactively.
Tips for snapshot testing:
- Treat snapshots as code: commit & review them
- Tests should be deterministic: same output everytime
- ie:
Date.now()
should be mocked toDate.now = jest.fn(() => 1482363367071)
- ie:
- Use descriptive snapshot names:
<UserName /> should render null
<UserName /> should render Alan Turing
Things to consider (Cons):
- What exactly are we testing? What's being validated? Is it even correct?
- Snapshots may cause false positives
- We can't be sure of the validity of our snapshot test
- Can make us overconfident in our code
- Snapshots may cause false negatives
- Fixing puncutation: FAIL
- Replacing HTML tag: FAIL
- We may lose confidence in our test suite altogether
Benefits of Snapshot Testing:
- Snapshot tests can be written faster
- Snapshot tests check if components behave correctly
- Snapshots allow conditional rendering tests
Disadvantages of Snapshot Testing:
- Problems w/ larger snapshots
- Solution: npm's
no-large-snapshots
- Solution: npm's
- Issues with translations: multi-language apps
Given this HTML:
<div>Hello World</div>
The following WILL FIND the <div>
:
// Matching a string:
getByText(container, "Hello World"); // full string match
getByText(container, "llo Worl", { exact: false }); // substring match
getByText(container, "hello world", { exact: false }); // ignore case
// Matching a regex:
getByText(container, /World/); // substring match
getByText(container, /world/i); // substring match, ignore case
getByText(container, /^hello world$/i); // full string match, ignore case
getByText(container, /Hello W?oRlD/i); // advanced regex
// Matching with a custom function:
getByText(container, (content, element) => content.startsWith("Hello"));
ByTestId
can be used to target data-testid
attribute:
// App.js
<div data-testid="custom-element" />;
// App.Test.js
import { render, screen } from "@testing-library/react";
render(<MyComponent />);
const element = screen.getByTestId("custom-element");
getByTestId,
queryByTestId,
getAllByTestId,
queryAllByTestId,
findByTestId,
findAllByTestId;
fireEvent
: dispatches DOM events
userEvent
: simulates full interactions
Recommended:
- invoke
userEvent.setup()
before rendering the component - DON'T render or use
userEvent
OUTSIDE of the test itself- ie: no
before
/after
hook
- ie: no
// inlining
test("trigger some awesome feature when clicking the button", async () => {
const user = userEvent.setup();
render(<MyComponent />);
await user.click(screen.getByRole("button", { name: /click me!/i }));
// ...assertions...
});
// OR
// setup function
function setup(jsx) {
return {
user: userEvent.setup(),
...render(jsx),
};
}
test("render with a setup function", async () => {
const { user } = setup(<MyComponent />);
// ...
});
What is Mocking?
Mocking function calls allows you to 'fake' API calls and return a static set of data, overriding the real function in your code.
Every single user interaction involves callbacks.
Sometimes they're passed in as props to alter state of the parent component.
// FavoriteInput.js
import React from "react";
const FavoriteInput = ({ onChange: onInputChange, id }) => {
const inputHandler = (event) => onInputChange(event.target.value);
return (
<label htmlFor={id}>
What is your favorite wild animal?
<input id={id} onChange={inputHandler} />
</label>
);
};
export default FavoriteInput;
FavoriteInput
: single component, couple of props passed inonChange
prop: no idea what it does or how it affects the application- All we know: must be called when users type in the input box
Testing the component:
// FavoriteInput.test.js
import React from "react";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";
import userEvent from "@testing-library/user-event";
import FavoriteInput from "./FavoriteInput";
describe("Favorite Input", () => {
it("calls onChange correct number of times", () => {
const onChangeMock = jest.fn();
render(<FavoriteInput onChange={onChangeMock} />);
const input = screen.getByRole("textbox");
userEvent.type(input, "Lion");
expect(onChangeMock).toHaveBeenCalledTimes(4);
});
it("calls onChange with correct argument(s) on each input", () => {
const onChangeMock = jest.fn();
render(<FavoriteInput onChange={onChangeMock} />);
const input = screen.getByRole("textbox");
userEvent.type(input, "Ox");
expect(onChangeMock).toHaveBeenNthCalledWith(1, "O");
expect(onChangeMock).toHaveBeenNthCalledWith(2, "Ox");
});
it("input has correct values", () => {
const onChangeMock = jest.fn();
render(<FavoriteInput onChange={onChangeMock} />);
const input = screen.getByRole("textbox");
userEvent.type(input, "Whale");
expect(input).toHaveValue("Whale");
});
});
Three tests & we're done.
onChange
handler is mocked using Jest's jest.fn()
function.
- Number of times
onChange
is called - Check the arguments as they're passed to
onChange
- Alternative/redudant test: check input value contains userEvent type
Mocks could be setup in a beforeEach
block:
- Fine in most cases
- However, having all setup inside the test makes it easier to understand
- Don't have to look around the file for context
- Makes reviewing changes in the future substantially easier
- Decreases chances of leakage creating problems throughout the test suite
Unless our test file is getting really long and the test prep is dozens of lines in length, test setup should be done within the same block.
In React, component trees can get large, causing tests to become convoluted.
For components higher up in the tree, mocking child components can be necessary.
- Doesn't happen often
- Beneficial to know this concept
Wrap test interactions with act(() => ...)
. React will take care of the rest.
act()
guarentees 2 things:
- Any state updates will be executed
- Any enqueued effects will be executed
act()
helps with:
- events
- timers
- promises
Event Test
// App
function App() {
let [counter, setCounter] = useState(0);
// use setState(xxx => xxx + 1), not setState(state+1)
return <button onClick={() => setCounter((x) => x + 1)}>{counter}</button>;
}
// App.test
it("should increment a counter", () => {
const el = document.createElement("div");
document.body.appendChild(el);
// we attach the element to document.body to ensure events work
ReactDOM.render(<App />, el);
const button = el.childNodes[0];
act(() => {
for (let i = 0; i < 3; i++) {
button.dispatchEvent(new MouseEvent("click", { bubbles: true }));
}
});
expect(button.innerHTML).toBe(3); // this fails, it's actually "1"!
});
Timer Test
// App
function App() {
const [ctr, setCtr] = useState(0);
useEffect(() => {
setTimeout(() => setCtr(1), 1000);
}, []);
return ctr;
}
// App.test * fake timers
it("should tick to a new value", () => {
jest.useFakeTimers();
const el = document.createElement("div");
act(() => {
ReactDOM.render(<App />, el);
});
expect(el.innerHTML).toBe("0");
act(() => {
jest.runAllTimers();
});
expect(el.innerHTML).toBe("1");
});
// App.test * "real" timers with async version of act
it("should tick to a new value", async () => {
// a helper to use promises with timeouts
function sleep(period) {
return new Promise((resolve) => setTimeout(resolve, period));
}
const el = document.createElement("div");
act(() => {
ReactDOM.render(<App />, el);
});
expect(el.innerHTML).toBe("0");
await act(async () => {
await sleep(1100); // wait *just* a little longer than the timeout in the component
});
expect(el.innerHTML).toBe("1");
});
Promises Test
// App
function App() {
let [data, setData] = useState(null);
useEffect(() => {
fetch("/some/url").then(setData);
}, []);
return data;
}
// App.test
it("should display fetched data", () => {
// a rather simple mock, you might use something more advanced for your needs
let resolve;
function fetch() {
return new Promise((_resolve) => {
resolve = _resolve;
});
}
const el = document.createElement("div");
act(() => {
ReactDOM.render(<App />, el);
});
expect(el.innerHTML).toBe("");
await act(async () => {
resolve(42);
});
expect(el.innerHTML).toBe("42");
});
Async / Await Tests
// App
function App() {
let [data, setData] = useState(null);
async function somethingAsync() {
// this time we use the await syntax
let response = await fetch("/some/url");
setData(response);
}
useEffect(() => {
somethingAsync();
}, []);
return data;
}
// App.test
it("should display fetched data", async () => {
// a rather simple mock, you might use something more advanced for your needs
let resolve;
function fetch() {
return new Promise((_resolve) => {
resolve = _resolve;
});
}
const el = document.createElement("div");
act(() => {
ReactDOM.render(<App />, el);
});
expect(el.innerHTML).toBe("");
await act(async () => {
resolve(42);
});
expect(el.innerHTML).toBe("42");
});
Mocked child components should:
- accurately capture the functionality of the original component
- needs to present the same API as the original
- same props
- same callbacks
JavaScript does not allow you to declare types for variables and properties. However, many programmers agree that the pattern of declaring types is preferable because:
- Allows you to catch errors
- ie: Passing string to variable that should be a number
React provides type declaration using PropTypes
.
TypeScript is another option to use with React.
React's Documentation on PropTypes
# Installing PropTypes:
npm install --save prop-types
// class components
import PropTypes from "prop-types";
class Greeting extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
Greeting.propTypes = {
name: PropTypes.string,
};
// functional components
import PropTypes from "prop-types";
function HelloWorldComponent({ name }) {
return <div>Hello, {name}</div>;
}
// name must be a string
HelloWorldComponent.propTypes = {
name: PropTypes.string,
};
// sets the default values for props:
HelloWorldComponent.defaultProps = {
name: "Stranger",
};
// require a single child element:
HelloWorldComponent.propTypes = {
children: PropTypes.element.isRequired,
};
export default HelloWorldComponent;
The style-components package provides the ability to write CSS directly to React components for easy reuse & consistency.
With styled components, you get:
- Automatic critical CSS: only loads css styles that are necessary for what's rendered. Users load the minimal amount of code necessary.
- No Class Name Bugs: generates unique class names for styles automatically.
- Easier Deletion of CSS: every bit of styling is tied to specific components.
- Simple Dynamic Styling: adapting styling of a component based on props or a global theme is simple & intuitive
- Painless Maintenance: never have to hunt across files to find out what style is affecting a component
- Automatic Vendor Prefixing: write css in the current standard & styled-components handles the rest
npm install --save styled-components
// package.json
{
"resolutions": {
"styled-components": "^5"
}
}
// Create a Title component that'll render an <h1> tag with some styles
const Title = styled.h1`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
// Create a Wrapper component that'll render a <section> tag with some styles
const Wrapper = styled.section`
padding: 4em;
background: papayawhip;
`;
// Use Title and Wrapper like any other React component – except they're styled!
render(
<Wrapper>
<Title>Hello World!</Title>
</Wrapper>
);
Conditional styling based on props:
const Button = styled.button`
/* Adapt the colors based on primary prop */
background: ${(props) => (props.primary ? "palevioletred" : "white")};
color: ${(props) => (props.primary ? "white" : "palevioletred")};
`;
Extending Styles:
// The Button from the last section without the interpolations
const Button = styled.button`
color: palevioletred;
font-size: 1em;
margin: 1em;
padding: 0.25em 1em;
border: 2px solid palevioletred;
border-radius: 3px;
`;
// A new component based on Button, but with some override styles
const TomatoButton = styled(Button)`
color: tomato;
border-color: tomato;
`;
To change the tag or component that a styled component renders, you can use the as=""
prop.
Example: Mix of <button>
and <a>
tags that you want to style the same way:
render(
<div>
<Button>Normal Button</Button>
<Button as="a" href="#">
Link with Button styles
</Button>
<TomatoButton as="a" href="#">
Link with Tomato Button styles
</TomatoButton>
</div>
);
as
works with components too:
const ReversedButton = (props) => (
<Button {...props} children={props.children.split("").reverse()} />
);
render(
<div>
<Button>Normal Button</Button>
<Button as={ReversedButton}>Custom Button with Normal Button styles</Button>
</div>
);
Styling any component:
// This could be react-router-dom's Link for example
const Link = ({ className, children }) => (
<a className={className}>{children}</a>
);
const StyledLink = styled(Link)`
color: palevioletred;
font-weight: bold;
`;
Passed props:
// Create an Input component that'll render an <input> tag with some styles
const Input = styled.input`
padding: 0.5em;
margin: 0.5em;
color: ${(props) => props.inputColor || "palevioletred"};
background: papayawhip;
border: none;
border-radius: 3px;
`;
// Render a styled text input with the standard input color, and one with a custom input color
render(
<div>
<Input defaultValue="@probablyup" type="text" />
<Input defaultValue="@geelen" type="text" inputColor="rebeccapurple" />
</div>
);
Redux is the most popular state management system out there.
Combined with React, they make up a very powerful duo.
Redux's purpose is to:
- Store your applicatoin's state in a single place (called a store)
- Then, you dispatch actions to the store
- Reducers handle the state changes
Primary benefit of a state management library is:
- Prevent having to pass props through multiple levels of a component tree
- Recommended for larger applications
# installation
# tool kit (for use with a module bundler / node app)
npm install @reduxjs/toolkit
# core (for use with a module bundler/node app)
npm install redux
# create a react redux app:
# Redux + Plain JS template
npx create-react-app my-app --template redux
# Redux + TypeScript template
npx create-react-app my-app --template redux-typescript
Redux example:
import { createStore } from "redux";
/**
* This is a reducer - a function that takes a current state value and an
* action object describing "what happened", and returns a new state value.
* A reducer's function signature is: (state, action) => newState
*
* The Redux state should contain only plain JS objects, arrays, and primitives.
* The root state value is usually an object. It's important that you should
* not mutate the state object, but return a new object if the state changes.
*
* You can use any conditional logic you want in a reducer. In this example,
* we use a switch statement, but it's not required.
*/
function counterReducer(state = { value: 0 }, action) {
switch (action.type) {
case "counter/incremented":
return { value: state.value + 1 };
case "counter/decremented":
return { value: state.value - 1 };
default:
return state;
}
}
// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
let store = createStore(counterReducer);
// You can use subscribe() to update the UI in response to state changes.
// Normally you'd use a view binding library (e.g. React Redux) rather than subscribe() directly.
// There may be additional use cases where it's helpful to subscribe as well.
store.subscribe(() => console.log(store.getState()));
// The only way to mutate the internal state is to dispatch an action.
// The actions can be serialized, logged or stored and later replayed.
store.dispatch({ type: "counter/incremented" });
// {value: 1}
store.dispatch({ type: "counter/incremented" });
// {value: 2}
store.dispatch({ type: "counter/decremented" });
// {value: 1}
Redux Tookit Example:
import { createSlice, configureStore } from "@reduxjs/toolkit";
const counterSlice = createSlice({
name: "counter",
initialState: {
value: 0,
},
reducers: {
incremented: (state) => {
// Redux Toolkit allows us to write "mutating" logic in reducers. It
// doesn't actually mutate the state because it uses the Immer library,
// which detects changes to a "draft state" and produces a brand new
// immutable state based off those changes
state.value += 1;
},
decremented: (state) => {
state.value -= 1;
},
},
});
export const { incremented, decremented } = counterSlice.actions;
const store = configureStore({
reducer: counterSlice.reducer,
});
// Can still subscribe to the store
store.subscribe(() => console.log(store.getState()));
// Still pass action objects to `dispatch`, but they're created for us
store.dispatch(incremented());
// {value: 1}
store.dispatch(incremented());
// {value: 2}
store.dispatch(decremented());
// {value: 1}
As applications grow in size, we end up passing props through a lot of components or we might have a lot of components that require the same props.
This is known as PROP DRILLING.
To avoid this, we can use the Context API
:
- Lets parent component provide data to ALL child components in its tree
- WITHOUT passing props
Example: Dark theme with a lot of components. context
lets all child components have access to the theme data.
// App.js
import Heading from "./Heading.js";
import Section from "./Section.js";
export default function ProfilePage() {
return (
<Section>
<Heading>My Profile</Heading>
<Post title="Hello traveller!" body="Read about my adventures." />
<AllPosts />
</Section>
);
}
function AllPosts() {
return (
<Section>
<Heading>Posts</Heading>
<RecentPosts />
</Section>
);
}
function RecentPosts() {
return (
<Section>
<Heading>Recent Posts</Heading>
<Post title="Flavors of Lisbon" body="...those pastéis de nata!" />
<Post title="Buenos Aires in the rhythm of tango" body="I loved it!" />
</Section>
);
}
function Post({ title, body }) {
return (
<Section isFancy={true}>
<Heading>{title}</Heading>
<p>
<i>{body}</i>
</p>
</Section>
);
}
//
// Section.js
//
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Section({ children, isFancy }) {
const level = useContext(LevelContext);
return (
<section className={
'section ' +
(isFancy ? 'fancy' : '')
}>
<LevelContext.Provider value={level + 1}>
{children}
</LevelContext.Provider>
</section>
);
}
//
// Heading.js
//
import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';
export default function Heading({ children }) {
const level = useContext(LevelContext);
switch (level) {
case 0:
throw Error('Heading must be inside a Section!');
case 1:
return <h1>{children}</h1>;
case 2:
return <h2>{children}</h2>;
case 3:
return <h3>{children}</h3>;
case 4:
return <h4>{children}</h4>;
case 5:
return <h5>{children}</h5>;
case 6:
return <h6>{children}</h6>;
default:
throw Error('Unknown level: ' + level);
}
}
//
// LevelContext.js
//
import { createContext } from 'react';
export const LevelContext = createContext(0);
Reducers let you consolidate a component's state update logic.
As components grow, so does the amount of state logic sprinkled throughout it. To reduce complexity and keep all your logic in one easy-to-access place, you can:
- Move state logic into a single function
- Outside your component
- Called a reducer
To migrate FROM useState
TO useReducer
:
- Move from setting state to dispatching actions
- Write a reducer function
- Use the reducer from your component
Context lets you pass info deep down to other components. You can combine reducers & context together to manage state of a complex screen.
A higher-order component (HOC) is a technique for reusing component logic.
HOC component are functions that:
- Take 1+ components
- Return a new, upgraded component
- Don't modify or mutate components: Create new ones
- Used to compose components for code reuse
- Pure function: no side effects
Real world HOC examples:
- react-redux:
connect(mapStateToProps, mapDispatchToProps)(UserPage)
- react-router:
withRouter(UserPage)
- material-ui:
withStyles(styles)(UserPage)
Structure of a HOC:
- Component
- Takes another component as an argument
- Returns a new component
- The returned component can render the original component that was passed to it
import React from "react";
// Take in a component as argument WrappedComponent
const higherOrderComponent = (WrappedComponent) => {
// And return another component
class HOC extends React.Component {
render() {
return <WrappedComponent />;
}
}
return HOC;
};
Use cases:
- Loader while component waits for data
WithLoading
example below
import React from "react";
// List component
const List = (props) => {
const { repos } = props;
if (!repos) return null;
if (!repos.length) return <p>No repos, sorry</p>;
return (
<ul>
{repos.map((repo) => {
return <li key={repo.id}>{repo.full_name}</li>;
})}
</ul>
);
};
// withLoading HOC
function WithLoading(Component) {
return function WithLoadingComponent({ isLoading, ...props }) {
if (!isLoading) return <Component {...props} />;
return <p>Hold on, fetching data might take some time.</p>;
};
}
// Supercharged List
const ListWithLoading = WithLoading(List);
// App
class App extends React.Component {
constructor() {
this.state = {
loading: false,
repos: null,
};
}
componentDidMount() {
this.setState({ loading: true });
fetch(`https://api.github.com/users/hacktivist123/repos`)
.then((json) => json.json())
.then((repos) => {
this.setState({ loading: false, repos: repos });
});
}
render() {
return (
// Supercharged List: Built w/ HOC withLoading
<ListWithLoading
isLoading={this.state.loading}
repos={this.state.repos}
/>
);
}
}
export default App;
Example: Render a component when a user has been authenticated (ie: a protected component)
withAuth
is a HOC that takes a component and returns a new component named AuthenticatedComponent
, that checks whether the user is authenticated.
If not authenticated, the loginErrorMessage
component is returned.
// withAuth
import React from "react";
function withAuth(Component) {
return class AuthenticatedComponent extends React.Component {
isAuthenticated() {
// has to be set from your app's logic
return this.props.isAuthenticated;
}
render() {
const loginErrorMessage = (
<div>
Please <a href="/login">login</a> in order to view this part of the
application.
</div>
);
return (
<div>
{this.isAuthenticated === true ? (
<Component {...this.props} />
) : (
loginErrorMessage
)}
</div>
);
}
};
}
// MyProtectedComponent
class MyProectedComponent extends React.Component {
render() {
return <div>This is only viewable by authenticated users.</div>;
}
}
// Add authentication to MyPrivateComponent:
const ProtectedComponent = withAuth(MyPrivateComponent);
<ProtectedComponent isAuthenticated=false />
A popular case for HOCs is creating a wrapper HOC to provide components with a reusable prop, that's needed by many components.
// simple component
const HelloComponent = ({ name, ...otherProps }) => (
<div {...otherProps}>Hello {name}!</div>
);
// HOC: withNameChange, sets a name prop on base component to "New Name"
const withNameChange = (BaseComponent) => (props) =>
<BaseComponent {...props} name="New Name" />;
// Using HOC to create a new, pure component
const EnhancedHello = withNameChange(HelloComponent);
<EnhancedHello />; // <div>Hello New Name</div>
<EnhancedHello name="bmilcs" />; // <div>Hello bmilcs</div>
import React from "react";
const Hello = ({ name }) => <h1>Hello {name}!</h1>;
function withName(WrappedComponent) {
return class extends React.Component {
render() {
return <WrappedComponent name="Smashing Magazine" {...this.props} />;
}
};
}
const HelloWithName = withName(Hello);
const App = () => (
<div>
<HelloWithName />
</div>
);
useTransition
: returns a stateful value for pending state of the transition & a function to start it
const [isPending, startTransition] = useTransition();
useId
: generate unique IDs, stable across server/client, avoiding hydration mismatches
Custom Hooks let you extract commonly used logic from the UI into JS functions.
- Prevent code duplication
- Make logic reuseable within multiple components
Rules for custom hooks:
- Must being with
use
(ie:useToggle
) - Must be called within a React component
- Must be called in the highest level of a component
- No nesting: functions, etc.
Example: Return a response from API URL (Free Code Camp Example)
//useFetch.js
import { useState, useEffect } from "react";
export function useFetch(url) {
//values
const [data, setData] = useState(null);
const [error, setError] = useState("");
useEffect(() => {
fetch(url)
.then((res) => {
if (!res.ok) {
throw Error("something wrong, çould not connect to resource");
}
setData(res.json());
})
.then(() => {
setError("");
})
.catch((error) => {
console.warn(`sorry an error occurred, due to ${error.message} `);
setData(null);
setError(error.message);
});
}, [url]);
return [data, error];
}
// useToggle.js
import { useContext, useEffect, useState } from "react";
export const useToggle = (initialVal = false) => {
const [state, setState] = useState(initialVal);
const toggle = () => {
setState((prev) => !prev);
};
// differs from a normal react component (logic, not UI/dom)
return [state, toggle];
};
// App.js
import { useToggle } from "./useToggle";
function App() {
const [isVisible, toggle] = useToggle();
return (
<div className="App">
<button onClick={toggle}>{isVisible ? "Hide" : "Show"}</button>
{isVisible && <h1>Secret Text</h1>}
</div>
);
}