Beginning React - Laracast - Notes
Learned a lot about React from this series, it just went straight to the point, all the necessary skills we need. Although sometimes he doesn't give us a template to work on, I had to get it from his repository on Git Hub.
Getting the boilerlate
<https://github.com/drehimself/lc-react>
find the commit then
git reset --hard {commit number}
Introduction and Demo
npx create-react-app lc-react
- App.js can be renamed to App.jsx
- need to use className not just class
import { useState } from 'react'
import './App.css';
function App() {
const [count, setCount] = useState(0);
function decrement() {
setCount(prevCount => prevCount - 1);
}
function increment() {
setCount(prevCount => prevCount + 1);
}
const someStyle = {
background: 'blue',
color: 'white',
fontSize: '28px',
fontWeigt: 'bold',
}
return (
<div className="App">
<header className="App-header">
<div><span>{count}</span>
<button onClick={decrement}>-</button>
<button onClick={increment}>+</button>
</div>
<p style={someStyle}>{ 3 + 2 }</p>
</header>
</div>
);
}
export default App;
//Another.jsx
import React from "react";
export default function Another(props) {
return (
<div>
Another Component , { props.name }
</div>
)
}
// App.jsx
import Another from './Another';
return (
<div className="App">
...
<Another name="madindo"/>
...
</div>
);
A Better Development Experience
Make a list of todos, useState, and loop using map.
const [todos, setTodos] = useState([
{
id: 1,
title: "Finish React Series 1",
isCompleted: false
},
{
id: 2,
title: "Go groceries",
isCompleted: true
},
{
id: 3,
title: "Take over world",
isCompleted: false
},
]);
<ul className="todo-list">
{ todos.map((todo,index) => (
<li className="todo-item-container" key={todo.id}>
<div className="todo-item">
<input type="checkbox" />
<span className="todo-item-label">{todo.title}</span>
</div>
</li>
))}
</ul>
add on submit
<form action="#" onSubmit={addTodo}>
<input
type="text"
className="todo-input"
value={todoInput}
onChange={handleInput}
placeholder="What do you need to do?"
/>
</form>
// check if the input is empty
if (todoInput.trim().length === 0) {
return;
}
function addTodo() {
setTodos([...todos, {
id: idForTodo, // get the id from setIdForTodo
title: todoInput, // get input with handleInput, setTodoInput, onChange
isCompleted: false
}]);
setTodoInput(''); // set to empty after todo
// setIdForTodo(idForTodo + 1)
setIdForTodo(prevIdForTodo => prevIdForTodo + 1) // increment id put in setTodos
}
const [todoInput, setTodoInput] = useState('');
const [idForTodo, setIdForTodo] = useState(4);
// to get the value from inputtext on change
function handleInput(event) {
setTodoInput(event.target.value);
}
add todo (classed based)
<form action="#" onSubmit={this.addTodo}>
<input
type="text"
className="todo-input"
placeholder="What do you need to do?"
/>
</form>
addTodo = event => {
event.preventDefault();
this.setState(prevState => {
const newTodos = [...prevState.todos, {
id: 4,
title: 'this is class based',
isCompleted: false
}];
return { todos: newTodos }
})
}
delete todo
<button onClick={deleteTodo(todo.id)} className="x-button"></button>
function deleteTodo(id) {
setTodos([... todos].filter(todo => todo.id !== id));
}
edit todo
<span
className={`todo-item-label ${todo.isCompleted ? 'line-through' : ''}`}
onDoubleClick={() => markAsEditing(todo.id)}
>{todo.title}</span>
<div className="todo-item">
<input type="checkbox"
onChange={() => completeTodo(todo.id)}
checked={todo.isCompleted ? true : false}
/>
function completeTodo(id) {
const updatedTodos = todos.map(todo => {
if (todo.id === id) {
todo.isCompleted = !todo.isCompleted
}
return todo;
});
setTodos(updatedTodos);
}
// double click on span to input to be able to edit the title
<input type="text"
className={`todo-item-input ${todo.isCompleted ? 'line-through' : ''}`}
value={todo.title}
onBlur={(event) => updateTodo(event, todo.id)}
autoFocus
/>
{
id: 3,
title: "Take over world",
isCompleted: false,
isEditing: false // add new field isEditing
},
function markAsEditing(id) {
const editingTodos = todos.map(todo => {
if (todo.id === id) {
todo.isEditing = !todo.isEditing
}
return todo;
});
setTodos(editingTodos);
}
function updateTodo(event, id) {
const editingTodos = todos.map(todo => {
if (todo.id === id) {
if (event.target.value.trim().length === 0) {
todo.isEditing = false
return todo;
}
todo.title = event.target.value
todo.isEditing = false
}
return todo;
});
setTodos(editingTodos);
}
Hide when there are no todos
{ todos.length > 0 ? (
<>
<ul className="todo-list">
{ todos.map((todo,index) => (
<li className="todo-item-container" key={todo.id}>
<div className="todo-item">
<input type="checkbox"
onChange={() => completeTodo(todo.id)}
checked={todo.isCompleted ? true : false}
/>
{ !todo.isEditing ? (
<span
className={`todo-item-label ${todo.isCompleted ? 'line-through' : ''}`}
onDoubleClick={() => markAsEditing(todo.id)}
>
{todo.title}
</span>
) :
(
<input type="text"
className={`todo-item-input ${todo.isCompleted ? 'line-through' : ''}`}
defaultValue={todo.title}
onKeyDown={event => {
if (event.key === 'Enter') {
updateTodo(event, todo.id)
} else if (event.key === 'Escape') {
cancelEdit(event, todo.id)
}
}}
onBlur={(event) => updateTodo(event, todo.id)}
autoFocus
/>
)}
</div>
<button onClick={() => deleteTodo(todo.id)} className="x-button">
<svg
className="x-button-icon"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</li>
))}
</ul>
<div className="check-all-container">
<div>
<div className="button">Check All</div>
</div>
<span>3 items remaining</span>
</div>
<div className="other-buttons-container">
<div>
<button className="button filter-button filter-button-active">
All
</button>
<button className="button filter-button">Active</button>
<button className="button filter-button">Completed</button>
</div>
<div>
<button className="button">Clear completed</button>
</div>
</div>
</>
) : (
<div className="no-todos-container">
<p>Add some todos</p>
</div>
)
}
Extract the else into a component
<div className="no-todos-container">
<p>Add some todos</p>
</div>
to
<NoTodos />
// create file Notodos.jsx
import NoTodos from './NoTodos'
) : (
<NoTodos />
)
Extract the form and the list, addTodo functions stay in App.jsx because it’s dependent on others but in the form add a new handle submit move some lines, and then add props to the receive function from App.jsx
//App.jsx
//this stays
function addTodo(todo) {
setTodos([...todos, {
id: idForTodo,
title: todo,
isCompleted: false
}]);
setIdForTodo(prevIdForTodo => prevIdForTodo + 1)
}
//TodoForm.jsx
import React, { useState } from 'react'
export default function TodoForm(props) {
const [todoInput, setTodoInput] = useState('');
function handleInput(event) {
setTodoInput(event.target.value);
}
function handleSubmit(event) {
event.preventDefault();
if (todoInput.trim().length === 0) {
return;
}
props.addTodo(todoInput)
setTodoInput('');
}
return (
<form action="#" onSubmit={handleSubmit}>
<input
type="text"
value={todoInput}
onChange={handleInput}
className="todo-input"
placeholder="What do you need to do?"
/>
</form>
)
}
Extract the list
//App.jsx
{ todos.length > 0 ?
<TodoList
todos={todos}
completeTodo={completeTodo}
markAsEditing={markAsEditing}
updateTodo={updateTodo}
cancelEdit={cancelEdit}
deleteTodo={deleteTodo}
/>
: <NoTodos /> }
//TodoList.jsx
import React from 'react'
export default function TodoList(props) {
return (
<>
<ul className="todo-list">
{ props.todos.map((todo,index) => (
<li className="todo-item-container" key={todo.id}>
<div className="todo-item">
<input type="checkbox"
onChange={() => props.completeTodo(todo.id)}
checked={todo.isCompleted ? true : false}
/>
{ !todo.isEditing ? (
<span
className={`todo-item-label ${todo.isCompleted ? 'line-through' : ''}`}
onDoubleClick={() => props.markAsEditing(todo.id)}
>
{todo.title}
</span>
) :
(
<input type="text"
className={`todo-item-input ${todo.isCompleted ? 'line-through' : ''}`}
defaultValue={todo.title}
onKeyDown={event => {
if (event.key === 'Enter') {
props.updateTodo(event, todo.id)
} else if (event.key === 'Escape') {
props.cancelEdit(event, todo.id)
}
}}
onBlur={(event) => props.updateTodo(event, todo.id)}
autoFocus
/>
)}
</div>
<button onClick={() => props.deleteTodo(todo.id)} className="x-button">
<svg
className="x-button-icon"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</li>
))}
</ul>
<div className="check-all-container">
<div>
<div className="button">Check All</div>
</div>
<span>3 items remaining</span>
</div>
<div className="other-buttons-container">
<div>
<button className="button filter-button filter-button-active">
All
</button>
<button className="button filter-button">Active</button>
<button className="button filter-button">Completed</button>
</div>
<div>
<button className="button">Clear completed</button>
</div>
</div>
</>
)
}
Checking types of props to be sent
npm install --save prop-types
//TodoForm
\\import PropTypes from 'prop-types';
function TodoForm(props) {
}
TodoForm.propTypes = {
addTodo: PropTypes.func,
}
export default TodoForm
Remaining to a new component
//App.jsx
function remaining() {
return todos.filter(todo => !todo.isCompleted).length;
}
//pass function
<TodoList
...
remaining={remaining}
/>
//TodoList.jsx
import TodoItemRemaining from './TodoItemRemaining';
TodoList.propTypes = {
remaining: PropTypes.func.isRequired,
}
<TodoItemRemaining remaining={props.remaining}/>
//TodoRemaining
import React from 'react'
function TodoItemRemaining(props) {
return (
<span>{props.remaining()} items remaining</span>
)
}
export default TodoItemRemaining
Clear Completed to new component as well
//app.jsx
function clearCompleted() {
setTodos([... todos].filter(todo => !todo.isCompleted))
}
<TodoList
...
clearCompleted={clearCompleted}
/>
//TodoList.jsx
import TodoClearCompleted from './TodoClearCompleted';
TodoList.propTypes = {
...
clearCompleted: PropTypes.func.isRequired,
}
<TodoClearCompleted clearCompleted={props.clearCompleted} />
//TodoClearCompleted
import React from 'react'
function TodoClearCompleted(props) {
return (
<button className="button" onClick={props.clearCompleted}>Clear completed</button>
)
}
export default TodoClearCompleted
Filters
//App.jsx
function todosFiltered(filter) {
if (filter === 'all') {
return todos;
} else if (filter === 'active') {
return todos.filter(todo => !todo.isCompleted)
}else if (filter === 'completed') {
return todos.filter(todo => todo.isCompleted)
}
}
<TodoList
...
todosFiltered={todosFiltered}
/>
//TodoList.jsx
import TodoFilters from './TodoFilters';
<TodoFilters
todosFiltered={props.todosFiltered}
filter={filter}
setFilter={setFilter}
/>
//TodoFilters.jsx
import React from 'react'
import PropTypes from 'prop-types';
TodoFilters.propTypes = {
todosFiltered: PropTypes.func.isRequired,
filter: PropTypes.string.isRequired,
setFilter: PropTypes.func.isRequired,
}
function TodoFilters(props) {
return (
<div>
<button onClick={() => {
props.setFilter('all');
props.todosFiltered('all');
}} className={ `button filter-button ${props.filter === 'all' ? 'filter-button-active' : ''}`}>
All
</button>
<button onClick={() => {
props.setFilter('active');
props.todosFiltered('active');
}} className={ `button filter-button ${props.filter === 'active' ? 'filter-button-active' : ''}`}>Active</button>
<button onClick={() => {
props.setFilter('completed');
props.todosFiltered('completed');
}} className={ `button filter-button ${props.filter === 'completed' ? 'filter-button-active' : ''}`}>Completed</button>
</div>
)
}
export default TodoFilters
Other Built-in React Hooks
Adding name
const [name, setName] = useState('');
<div className="name-container">
<h2>What is your name?</h2>
<form action="#">
<input type="text" className="todo-input" placeholder="What is your name" value={name} onChange={event => setName(event.target.value)}/>
</form>
{ name && <p className="name-label">Hello, {name}</p>}
</div>
useRef
import { useRef } from 'react';
const nameInputEl = useRef(null);
<button onClick={() => nameInputEl.current.focus()}>Click Me</button>
<input type="text" ref={nameInputEl} />
Equivalent to componentDidMount
import { useEffect } from 'react';
useEffect(() => {
nameInputEl.current.focus()
}, []);
useMemo
import { useMemo } from 'react';
function remainingCaculation() {
return todos.filter(todo => !todo.isCompleted).length;
}
const remaining = useMemo(remainingCaculation, [todos])
Custom Hooks
//before
const [twoVisible, setTwoVisible] = useState(true);
<button onClick={() => setTwoVisible(prevTwoVisible => !prevTwoVisible)} className="button">Feature Two Toggle</button>
//after, naming must have use
// create new file hooks/useToggle.js
import { useState } from 'react';
function useToggle(initialState = true) {
const [visible, setVisible] = useState(initialState);
function toggle() {
setVisible(prevVisible => !prevVisible)
}
return [visible, toggle];
}
export default useToggle
set to localstorage then handling handleNameInput as a custom hook
//before
const [name, setName] = useState('');
<input
type="text"
ref={nameInputEl}
className="todo-input"
placeholder="What is your name"
value={name}
onChange={handleNameInput}
/>
function handleNameInput(event) {
setName(event.target.value);
localStorage.setItem('name', JSON.stringify(event.target.value));
}
useEffect(() => {
setName(JSON.parse(localStorage.getItem('name')) ?? '');
}, []);
//after ... custom hook make file useLocalStorage
import { useEffect, useState } from 'react';
function useLocalStorage(key, initialValue) {
const [value, setValue] = useState(() => {
const item = localStorage.getItem(key)
return item ? JSON.parse(item) : initialValue
});
useEffect(() => {
localStorage.setItem(key, JSON.stringify(value))
}, [key, value]);
return [value,setValue];
}
export default useLocalStorage
const [name, setName] = useLocalStorage('name', '');
using localstorage for the list
//before
const [todos, setTodos] = useState([]);
//after
const [todos, setTodos] = useLocalStorage('todos', []);
const [idForTodo, setIdForTodo] = useLocalStorage('idForTodo', 1);
Context for state management
//create new context folder /context/TodoContext.js
import { createContext } from "react";
export const TodosContext = createContext();
return (
<TodosContext.Provider value={{ todos, setTodos, idForTodo, setIdForTodo}}>
<div className="todo-app-container">
...
</div>
</TodosContext.Provider>
)
Removing from app.jsx
//TodoForm ... remove app.jsx
function addTodo(event) {
event.preventDefault();
if (todoInput.trim().length === 0) {
return;
}
setTodos([...todos, {
id: idForTodo,
title: todoInput,
isCompleted: false
}]);
setIdForTodo(prevIdForTodo => prevIdForTodo + 1)
setTodoInput('');
}
//TodoItemRemaining
function TodoItemRemaining() {
const { todos } = useContext(TodosContext);
function remainingCalculation() {
return todos.filter(todo => !todo.isCompleted).length;
}
const remaining = useMemo(remainingCalculation, [todos]);
return (
<span>{remaining} items remaining</span>
)
}
//TodoCompleteAll
import React, {useContext} from 'react'
import { TodosContext } from '../context/TodosContext';
function TodoCompleteAll(props) {
const { todos, setTodos } = useContext(TodosContext);
function completeAllTodos() {
const editingTodos = todos.map(todo => {
todo.isCompleted = true
return todo;
});
setTodos(editingTodos);
}
return (
<div>
<div onClick={completeAllTodos} className="button">Check All</div>
</div>
)
}
export default TodoCompleteAll
//TodoClearCompleted
import React, { useContext } from 'react'
import { TodosContext } from '../context/TodosContext';
function TodoClearCompleted() {
const { todos, setTodos} = useContext(TodosContext);
function clearCompleted() {
setTodos([...todos].filter(todo => !todo.isCompleted))
}
return (
<button className="button" onClick={clearCompleted}>Clear completed</button>
)
}
export default TodoClearCompleted
//TodoFilters
import React, { useContext } from 'react'
import { TodosContext } from '../context/TodosContext';
function TodoFilters() {
const { filter, setFilter, todosFiltered } = useContext(TodosContext);
return (
<div>
<button onClick={() => {
setFilter('all');
todosFiltered('all');
}} className={ `button filter-button ${filter === 'all' ? 'filter-button-active' : ''}`}>
All
</button>
<button onClick={() => {
setFilter('active');
todosFiltered('active');
}} className={ `button filter-button ${filter === 'active' ? 'filter-button-active' : ''}`}>Active</button>
<button onClick={() => {
setFilter('completed');
todosFiltered('completed');
}} className={ `button filter-button ${filter === 'completed' ? 'filter-button-active' : ''}`}>Completed</button>
</div>
)
}
export default TodoFilters
CSS transition with react transition
# npm
npm install react-transition-group --save
# yarn
yarn add react-transition-group
import { CSSTransition } from 'react-transition-group';
<CSSTransition
in={isFeaturesOneVisible}
timeout={300}
classNames="slide-vertical"
unmountOnExit>
<div className="check-all-container">
<TodoCompleteAll />
<TodoItemRemaining />
</div>
</CSSTransition>
import { CSSTransition, TransitionGroup } from 'react-transition-group';
<TransitionGroup component="ul" className="todo-list">
{ todosFiltered().map((todo,index) => (
<CSSTransition key={todo.id} timeout={300} classNames="slide-horizontal">
...
</CSSTransition>
))}
</TransitionGroup>
import { CSSTransition, SwitchTransition } from 'react-transition-group';
<SwitchTransition mode="out-in" >
<CSSTransition key={todos.length > 0} timeout={300} classNames="slide-vertical" unmountOnExit>
{ todos.length > 0 ?
<TodoList /> : <NoTodos /> }
</CSSTransition>
</SwitchTransition>
note: use classNames not className
React Router
//create new Root.jsx this will be on top of the main app file
import React from 'react'
import App from './App'
import About from './pages/About'
import NavigationBar from './NavigationBar'
export default function Root() {
return (
<div className="todo-app-container">
<NavigationBar />
<div className="content">
<App />
{/* <About /> */}
</div>
</div>
)
}
//navigation bar
import React from 'react'
export default function NavigationBar() {
return (
<nav>
<ul>
<li><a href="#">Home</a></li>
<li><a href="#">About</a></li>
<li><a href="#">Contact</a></li>
</ul>
</nav>
)
}
// change App to Root the one we just made
ReactDOM.render(
<React.StrictMode>
<Root />
</React.StrictMode>,
document.getElementById('root')
);
yarn add react-router-dom
// Root.jsx now add Switch, Route
export default function Root() {
return (
<Router>
<div className="todo-app-container">
<NavigationBar />
<div className="content">
<Switch>
<Route exact path="/">
<App />
</Route>
<Route path="/about">
<About />
</Route>
<Route path="/contact">
<Contact />
</Route>
</Switch>
</div>
</div>
</Router>
)
}
<Switch>
...
<Route exact path="/blog">
<Blog />
</Route>
<Route path="/blog/:id">
<BlogPost />
</Route>
</Switch>
create Blog & BlogPost, note for Link (can't add active state) NavLink can
import { Link } from 'react-router-dom'
export default function Blog() {
return (
<div className="container"><ul>
<li>
<Link to="/blog/1">Post One</Link>
</li>
<li>
<Link to="/blog/2">Post Two</Link>
</li>
</ul></div>
)
}
export default function BlogPost() {
const params = useParams();
return (
<div className="container">
this is blog post {params.id}
</div>
)
}
404
// create NoMatch.jsx
<Switch>
...
<Route path="*">
<NoMatch />
</Route>
</Switch>
another way to do it
export default function Root() {
const routes = [
{ path: '/', name: 'Home', Component: App, exact:true },
{ path: '/about', name: 'About', Component: About, exact:false },
{ path: '/contact', name: 'Contact', Component: Contact, exact:false },
{ path: '/blog', name: 'Blog', Component: Blog, exact:true },
{ path: '/blog/:id', name: 'Post', Component: BlogPost, exact:false },
{ path: '*', name: 'No Match', Component: NoMatch, exact:true },
];
return (
<Router>
<div className="todo-app-container">
<NavigationBar />
<div className="content">
<Switch>
{ routes.map(({ path, Component, exact }) => (
<Route key={path} path={path} exact={exact}>
<Component />
</Route>
))}
</Switch>
</div>
</div>
</Router>
)
}
Fetching data
import './App.css';
import { useState } from 'react';
import Reddit from './Reddit';
import Joke from './Joke';
function App() {
const [redditVisible, setRedditVisible] = useState(false);
const [jokeVisible, setJokeVisible] = useState(false);
return (
<div>
<div className='buttons'>
<button
onClick={() => setRedditVisible(prevRedditVisible => !redditVisible)}
>Toggle Redit</button>
<button
onClick={() => setJokeVisible(prevJokeVisible => !jokeVisible)}
>Toggle Joke</button>
</div>
{redditVisible && <Reddit />}
{jokeVisible && <Joke />}
</div>
);
}
export default App;
import React from 'react'
import { useEffect } from 'react';
import { useState } from 'react';
export default function Reddit() {
const [posts, setPosts] = useState([]);
const [isLoading, setIsLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState(null);
useEffect(() => {
fetch('<https://www.reddit.com/r/gaming/.json>')
.then(response => response.json())
.then(results => {
setIsLoading(false);
setPosts(results.data.children)
})
.catch(error => {
setIsLoading(false);
setErrorMessage("There was an error")
})
}, [])
return (
<div>
<h2>Reddit Api</h2>
{ isLoading && (
<div>Loading...</div>
)}
{ errorMessage && (
<div>{errorMessage}</div>
)}
{ posts && (
<ul >
{posts.map( post => (
<li key={post.data.id}>
<a href={`https://reddit.com${post.data.permalink}`}>
{post.data.title}
</a>
</li>
))}
</ul>
)}
</div>
)
}
import React from 'react'
import { useEffect } from 'react';
import { useState } from 'react';
export default function Reddit() {
const [joke, setJoke] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState(null);
useEffect(() => {
fetch('<https://official-joke-api.appspot.com/jokes/random>')
.then(response => response.json())
.then(result => {
setIsLoading(false);
setJoke(result.setup + ' ' + result.punchline)
})
.catch(error => {
setIsLoading(false);
setErrorMessage("There was an error")
})
}, [])
return (
<div>
<h2>JOKE Api</h2>
{ isLoading && (
<div>Loading...</div>
)}
{ errorMessage && (
<div>{errorMessage}</div>
)}
{ joke && (
<p>{joke}</p>
)}
</div>
)
}
Simplify
//useFetch.js
import { useEffect, useState } from "react";
function useFetch(url) {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [errorMessage, setErrorMessage] = useState(null);
useEffect(() => {
fetch(url)
.then(response => response.json())
.then(results => {
setIsLoading(false);
setData(results)
})
.catch(error => {
setIsLoading(false);
setErrorMessage("There was an error")
})
}, [url])
return { data, isLoading, errorMessage}
}
export default useFetch
import React from 'react'
import useFetch from './useFetch';
export default function Reddit() {
const { data: posts, isLoading, errorMessage } = useFetch('<https://www.reddit.com/r/gaming/.json>')
return (
<div>
<h2>Reddit Api</h2>
{ isLoading && (
<div>Loading...</div>
)}
{ errorMessage && (
<div>{errorMessage}</div>
)}
{ posts && (
<ul >
{posts.data.children.map( post => (
<li key={post.data.id}>
<a href={`https://reddit.com${post.data.permalink}`}>
{post.data.title}
</a>
</li>
))}
</ul>
)}
</div>
)
}
import React from 'react'
import useFetch from './useFetch'
export default function Reddit() {
const { data: joke, isLoading, errorMessage } = useFetch('<https://official-joke-api.appspot.com/jokes/random>')
return (
<div>
<h2>JOKE Api</h2>
{ isLoading && (
<div>Loading...</div>
)}
{ errorMessage && (
<div>{errorMessage}</div>
)}
{ joke && (
<p>{joke.setup + ' ' + joke.punchline}</p>
)}
</div>
)
}
Fetching data with react-query
npm i react-query
Installation | TanStack Query Docs
react query, devtools
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import {
QueryClient,
QueryClientProvider,
} from 'react-query';
import { ReactQueryDevtools } from 'react-query/devtools';
const root = ReactDOM.createRoot(document.getElementById('root'));
const queryClient = new QueryClient()
root.render(
<React.StrictMode>
<QueryClientProvider client={queryClient}>
<App />
<ReactQueryDevtools />
</QueryClientProvider>
</React.StrictMode>
);
reportWebVitals();
import React from 'react'
import { useQuery } from 'react-query';
export default function Reddit() {
const { data: posts, isLoading, isError, error, isSuccess } = useQuery('posts', fetchPosts, {retry: false});
function fetchPosts() {
return fetch('<https://www.reddit.com/r/gaming/.json>').then(response => response.json());
}
return (
<div>
<h2>Reddit Api</h2>
{ isLoading && (
<div>Loading...</div>
)}
{ isError && (
<div>{error.message}</div>
)}
{ isSuccess && (
<ul >
{posts.data.children.map( post => (
<li key={post.data.id}>
<a href={`https://reddit.com${post.data.permalink}`}>
{post.data.title}
</a>
</li>
))}
</ul>
)}
</div>
)
}
Joke
import React from 'react'
import { useQuery } from 'react-query';
export default function Joke() {
const { data: joke, isLoading, isError, error, isSuccess } = useQuery('joke', fetchJoke, {staleTime: 6000});
function fetchJoke() {
return fetch('<https://official-joke-api.appspot.com/jokes/random>').then(response => response.json());
}
return (
<div>
<h2>JOKE Api</h2>
{ isLoading && (
<div>Loading...</div>
)}
{ isError && (
<div>{error.message}</div>
)}
{ isSuccess && (
<p>{joke.setup + ' - ' + joke.punchline}</p>
)}
</div>
)
}