Skip to content

wip: pairing with Narin on page->comp context bridge #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 87 additions & 22 deletions components/todo.component.tsx
Original file line number Diff line number Diff line change
@@ -1,36 +1,101 @@
import { useSSE } from "use-sse";

import { createContext, useState, useEffect, useMemo, useContext } from 'react'
interface Todo {
userId: number;
id: number;
title: string;
completed: boolean;
body: string;
}

export const TodoComponent = () => {
const [todos, error] = useSSE((): Promise<Todo[]> => {
return fetch("https://jsonplaceholder.typicode.com/todos").then(
// get a Post By ID: https://jsonplaceholder.typicode.com/posts/1

// When the Page loads, we want to set the product ID in context, so that
// it is avaialble to children.

// page -> component bridge
// this is for client

// RULE OF THREE
// 1: Data fetch
export function dataFetcher(postId: number) {
return fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`).then(
(response: Response) => response.json()
);
}, []);
}

if (error) return <div>{error.message}</div>;
// 2: Dumb Component - takes the data, does not request it
export const TodoMarkup = ({ todo, error }: any) => {

if (!todos?.length) return <span>Loading...</span>;
if (!todo || !Object.keys(todo)?.length) return <span>Loading...</span>;

return (
<section>
<h1>Todo List</h1>
<ul>
{todos.map((todo: Todo) => {
return (
<li key={todo.id}>
<h2>{todo.title}</h2>
<p>{todo.completed ? "Completed" : "Not Completed"}</p>
if (error) return <span>Error: {error.message}</span>;

if (todo?.id) {
const {id, title, body} = todo;
return (
<section>
<h1>Todo List</h1>
<ul>
<li key={id}>
<h2>{title}</h2>
<p>{body ? body : "Not body"}</p>
</li>
);
})}
</ul>
</section>
);
</ul>
</section>
);
}

return null;
};


export const useTodos = (postId: number, dataFetcher: any): {todo: Todo, error: Error} => {
const [todo, error] = useSSE((): Promise<Todo[]> => {
return dataFetcher(postId)
}, [postId]);

return {
todo,
error
};
};

// this is a helper component that will get the context from the Provider. It's like <Context.Consumer>
// this will help any child of the <TodosProvider /> get access to the todos data from the context API and not from props.
export function useTodosContext() {
const todosContext = useContext(TodosContext);
if (!todosContext) {
// if the call to get the contet from the provider doesn't work its because there is not context prodiver to consume
// https://reactjs.org/docs/hooks-reference.html#usecontext
throw new Error(`useTodosContext must be used within a Todos Provider!`);
}
return todosContext;
}

// Todos Context
export const TodosContext = createContext<{
todo: Todo,
error: Error | undefined
}>({
todo: {
userId: 0,
id: 0,
title: 'default',
body: 'some defualt text'
},
error: undefined
});

export const TodosComponent = ({ todo: todoProp, error: errorProp }: {todo: Todo | undefined, error: Error | undefined}) => {
const { todo: todoInternal, error: errorInternal } = useTodosContext();

if (!todoProp) {
if (!todoInternal || !Object.keys(todoInternal)?.length) return <span>Loading...</span>;
if (errorInternal) return <span>Error: {errorInternal.message}</span>;
if (todoInternal) {
<TodoMarkup todo={todoInternal} error={errorInternal} />
}
}

return <TodoMarkup todo={todoProp} error={errorProp} />;
};
69 changes: 44 additions & 25 deletions pages/index.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,52 @@
import Head from "next/head";
import Image from "next/image";
import { TodoComponent } from "../components/todo.component";
import { useMemo } from "react";
import { TodosComponent, useTodos, TodosContext, dataFetcher } from "../components/todo.component";
import styles from "../styles/Home.module.css";

function TodosProvider({ children, postId }: any) {
const { todo, error } = useTodos(postId, dataFetcher);
const value = {todo, error};
return <TodosContext.Provider value={value}>{children}</TodosContext.Provider>;
}

export default function Home() {
// PDP page knows what the product id is based on the url
// We pass the product id to the Page -> Component Content bridge

return (
<div>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>

<main>
<TodoComponent />
</main>

<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{" "}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
<TodosProvider postId={1}>
<div>
<Head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</Head>
<main>
{ /* no props here! */ }
<TodosComponent
error={undefined}
todo={undefined}
/>
</main>
<footer className={styles.footer}>
<a
href="https://vercel.com?utm_source=create-next-app&utm_medium=default-template&utm_campaign=create-next-app"
target="_blank"
rel="noopener noreferrer"
>
Powered by{" "}
<span className={styles.logo}>
<Image src="/vercel.svg" alt="Vercel Logo" width={72} height={16} />
</span>
</a>
</footer>
</div>
</TodosProvider>
);
}





Loading