<div id="root">
#root {
  display: flex;
  flex-direction: column;
}

.spinner-container {
  display: flex;
  align-items: center;
  justify-content: center;
}

.spinner {
  color: #000;
  display: inline-block;
  width: 20px;
  height: 20px;
  animation: spinner .75s linear infinite;
  border: 4px solid #000;
  border-bottom-color: transparent;
  border-radius: 100%;
  background: 0 0;
}

@keyframes spinner {
  0% {
    transform: rotate(0deg);
  }
  100% {
    transform: rotate(360deg);
  }
}
const Spinner = () => {
  return (
    <div className="spinner-container">
      <span className="spinner"/>
    </div>
  )
}

const useTodos = () => {
  const [page, setPage] = React.useState(1);
  const [todos, setTodos] = React.useState([]);

  React.useEffect(async () => {
    const response = await fetch('https://jsonplaceholder.typicode.com/todos/');
    const todos = await response.json();

    const offset = (page - 1) * 20;
    setTodos(todos.slice(offset > 0 ? offset -1 : 0, 20));
  }, [page]);
  
  return { todos, page, setPage };
}

const Todos = () => {
  const {todos, page} = useTodos();
  return todos.length > 0 ? <p>Hello, world! {todos.length} todos, page {page}!</p> : <Spinner/>
}

const TodosPaginate = () => {
  const {todos, page, setPage} = useTodos();
  return (
    <div>
      <p>Page: {page}</p>
      { page > 1 ? <button onClick={() => setPage(page-1)}>Prev ({page-1})</button> : null }
      { page < 10 ? <button onClick={() => setPage(page+1)}>Next ({page+1})</button> : null }
    </div>
  );
}

ReactDOM.render(<><Todos/><TodosPaginate/></>, document.getElementById("root"));
View Compiled
Run Pen

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js
  2. https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js