<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 TodosContext = React.createContext({
  setTodos: (todos) => {},
  setPage: (page) => {},
  page: 1,
  todos: []
});

const useTodosContext = () => {
  return { page, todos } = React.useContext(TodosContext);
}

const useTodos = (defaultPage) => {
  const { page, setTodos } = useTodosContext();

  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, offset + 20));
  }, [page]);
}

const TodosContainer = () => {
  const [ state, setState ] = React.useState({
    todos: [],
    page: 1
  });
  
  const setTodos = (todos) => {
    setState({...state, todos});
  }
  
  const setPage = (page) => {
    setState({...state, page});
  }
  
  return (
    <TodosContext.Provider value={{...state, setTodos, setPage }}>
      <>
        <Todos/>
        <TodosPaginate/>
      </>
    </TodosContext.Provider>
  )
}

const Todos = () => {
  const {page} = useTodosContext();
  useTodos();
  
  return todos.length > 0 ? <TodosList/> : <Spinner/>
}

const TodosList = () => {
  const {todos} = useTodosContext();
  return (
    <ul>
      {todos.map(todo => <TodoListItem todo={todo} key={`todo-${todo.id}`}/>)}
    </ul>
  )
}

const TodoListItem = ({todo}) => {
  return <li id={`todo-item-${todo.id}`}>{todo.userId} - {todo.title} ({todo.completed ? "completed" : "in progress"})</li>
}

const TodosPaginate = () => {
  const {page, setPage} = useTodosContext();
  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(<TodosContainer/>, document.getElementById("root"));
View Compiled

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