[Blocks Fixture] Misc updates (#18811)
* [Blocks Fixture] Update navigation buttons immediately * Add more profile links * Minor refactor * Add subroutes to Profile
This commit is contained in:
parent
fe7163e73d
commit
4d124a4f67
|
@ -23,37 +23,55 @@
|
|||
{
|
||||
"id": 1,
|
||||
"body": "Hey there",
|
||||
"postId": 1
|
||||
"postId": 1,
|
||||
"userId": 1
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"body": "Welcome to the chat",
|
||||
"postId": 1
|
||||
"postId": 1,
|
||||
"userId": 2
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"body": "What editor/font are you using?",
|
||||
"postId": 2
|
||||
"postId": 2,
|
||||
"userId": 2
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"body": "It's always been hard",
|
||||
"postId": 3
|
||||
"postId": 3,
|
||||
"userId": 1
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"body": "It's still easy",
|
||||
"postId": 3
|
||||
"postId": 3,
|
||||
"userId": 2
|
||||
}
|
||||
],
|
||||
"users": [{
|
||||
"id": 1,
|
||||
"name": "Sebastian"
|
||||
"name": "Sebastian",
|
||||
"bioId": 10
|
||||
}, {
|
||||
"id": 2,
|
||||
"name": "Sophie"
|
||||
"name": "Sophie",
|
||||
"bioId": 20
|
||||
}, {
|
||||
"id": 3,
|
||||
"name": "Dan"
|
||||
"name": "Dan",
|
||||
"bioId": 30
|
||||
}],
|
||||
"bios": [{
|
||||
"id": 10,
|
||||
"text": "I like European movies"
|
||||
}, {
|
||||
"id": 20,
|
||||
"text": "I like math puzzles"
|
||||
}, {
|
||||
"id": 30,
|
||||
"text": "I like reading twitter"
|
||||
}]
|
||||
}
|
||||
|
|
|
@ -24,17 +24,24 @@ const initialState = {
|
|||
// TODO: use this for invalidation.
|
||||
cache: createCache(),
|
||||
url: initialUrl,
|
||||
pendingUrl: initialUrl,
|
||||
RootBlock: loadApp(initialUrl),
|
||||
};
|
||||
|
||||
function reducer(state, action) {
|
||||
switch (action.type) {
|
||||
case 'navigate':
|
||||
case 'startNavigation':
|
||||
return {
|
||||
...state,
|
||||
pendingUrl: action.url,
|
||||
};
|
||||
case 'completeNavigation':
|
||||
// TODO: cancel previous fetch?
|
||||
return {
|
||||
cache: state.cache,
|
||||
...state,
|
||||
url: action.url,
|
||||
RootBlock: loadApp(action.url),
|
||||
pendingUrl: action.url,
|
||||
RootBlock: action.RootBlock,
|
||||
};
|
||||
default:
|
||||
throw new Error();
|
||||
|
@ -44,7 +51,7 @@ function reducer(state, action) {
|
|||
function Router() {
|
||||
const [state, dispatch] = useReducer(reducer, initialState);
|
||||
const [startTransition, isPending] = useTransition({
|
||||
timeoutMs: 3000,
|
||||
timeoutMs: 1500,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -56,12 +63,16 @@ function Router() {
|
|||
startTransition(() => {
|
||||
// TODO: Here, There, and Everywhere.
|
||||
// TODO: Instant Transitions, somehow.
|
||||
// TODO: Buttons should update immediately.
|
||||
dispatch({
|
||||
type: 'navigate',
|
||||
type: 'completeNavigation',
|
||||
RootBlock: loadApp(url),
|
||||
url,
|
||||
});
|
||||
});
|
||||
dispatch({
|
||||
type: 'startNavigation',
|
||||
url,
|
||||
});
|
||||
},
|
||||
[startTransition]
|
||||
);
|
||||
|
@ -76,10 +87,11 @@ function Router() {
|
|||
|
||||
const routeContext = useMemo(
|
||||
() => ({
|
||||
pendingUrl: state.pendingUrl,
|
||||
url: state.url,
|
||||
navigate,
|
||||
}),
|
||||
[state.url, navigate]
|
||||
[state.url, state.pendingUrl, navigate]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {useRouter} from './RouterContext';
|
||||
|
||||
export default function Link({to, children, ...rest}) {
|
||||
const {navigate} = useRouter();
|
||||
return (
|
||||
<a
|
||||
href={to}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
window.history.pushState(null, null, to);
|
||||
navigate(to);
|
||||
}}
|
||||
{...rest}>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
|
@ -6,57 +6,25 @@
|
|||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {useRouter} from './RouterContext';
|
||||
import {TabBar, TabLink} from './TabNav';
|
||||
|
||||
function TabBar({children}) {
|
||||
return (
|
||||
<div style={{border: '1px solid #aaa', padding: 20, width: 500}}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// TODO: Error Boundaries.
|
||||
|
||||
function TabLink({to, children}) {
|
||||
const {url: activeUrl, navigate} = useRouter();
|
||||
const active = activeUrl === to;
|
||||
if (active) {
|
||||
return (
|
||||
<b
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
width: 50,
|
||||
marginRight: 20,
|
||||
}}>
|
||||
{children}
|
||||
</b>
|
||||
);
|
||||
}
|
||||
function MainTabNav() {
|
||||
return (
|
||||
<a
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
width: 50,
|
||||
marginRight: 20,
|
||||
}}
|
||||
href={to}
|
||||
onClick={e => {
|
||||
e.preventDefault();
|
||||
window.history.pushState(null, null, to);
|
||||
navigate(to);
|
||||
}}>
|
||||
{children}
|
||||
</a>
|
||||
<TabBar>
|
||||
<TabLink to="/">Home</TabLink>
|
||||
<TabLink to="/profile/3" partial={true}>
|
||||
Profile
|
||||
</TabLink>
|
||||
</TabBar>
|
||||
);
|
||||
}
|
||||
|
||||
export default function Shell({children}) {
|
||||
return (
|
||||
<>
|
||||
<TabBar>
|
||||
<TabLink to="/">Home</TabLink>
|
||||
<TabLink to="/profile">Profile</TabLink>
|
||||
</TabBar>
|
||||
<br />
|
||||
<MainTabNav />
|
||||
{children}
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import Link from './Link';
|
||||
import {useRouter} from './RouterContext';
|
||||
|
||||
export function TabBar({children}) {
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
border: '1px solid #aaa',
|
||||
padding: 20,
|
||||
marginBottom: 20,
|
||||
width: 500,
|
||||
}}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function TabLink({to, partial, children}) {
|
||||
const {pendingUrl: activeUrl} = useRouter();
|
||||
const active = partial ? activeUrl.startsWith(to) : activeUrl === to;
|
||||
if (active) {
|
||||
return (
|
||||
<b
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
marginRight: 20,
|
||||
}}>
|
||||
{children}
|
||||
</b>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<Link
|
||||
style={{
|
||||
display: 'inline-block',
|
||||
marginRight: 20,
|
||||
}}
|
||||
to={to}>
|
||||
{children}
|
||||
</Link>
|
||||
);
|
||||
}
|
|
@ -16,15 +16,13 @@ import loadProfilePage from './ProfilePage.block';
|
|||
|
||||
function load(url) {
|
||||
let Page;
|
||||
switch (url) {
|
||||
case '/':
|
||||
Page = loadFeedPage();
|
||||
break;
|
||||
case '/profile':
|
||||
Page = loadProfilePage(3);
|
||||
break;
|
||||
default:
|
||||
throw Error('Not found');
|
||||
const segments = url.split('/').filter(Boolean);
|
||||
if (segments.length === 0) {
|
||||
Page = loadFeedPage();
|
||||
} else if (segments[0] === 'profile') {
|
||||
Page = loadProfilePage(Number(segments[1]), segments[2]);
|
||||
} else {
|
||||
throw Error('Not found');
|
||||
}
|
||||
return {Page};
|
||||
}
|
||||
|
@ -33,6 +31,8 @@ function load(url) {
|
|||
|
||||
import Shell from '../client/Shell';
|
||||
|
||||
// TODO: some notion of key.
|
||||
|
||||
function App(props, data) {
|
||||
return (
|
||||
<Shell>
|
||||
|
|
|
@ -15,19 +15,25 @@ import {fetch} from 'react-data/fetch';
|
|||
|
||||
function load(postId) {
|
||||
return {
|
||||
comments: fetch(`/comments?postId=${postId}`).json(),
|
||||
comments: fetch(`/comments?postId=${postId}&_expand=user`).json(),
|
||||
};
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
import Link from '../client/Link';
|
||||
|
||||
function Comments(props, data) {
|
||||
return (
|
||||
<>
|
||||
<h5>Comments</h5>
|
||||
<ul>
|
||||
{data.comments.slice(0, 5).map(item => (
|
||||
<li key={item.id}>{item.body}</li>
|
||||
{data.comments.slice(0, 5).map(comment => (
|
||||
<li key={comment.id}>
|
||||
{comment.body}
|
||||
{' • '}
|
||||
<Link to={`/profile/${comment.user.id}`}>{comment.user.name}</Link>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</>
|
||||
|
|
|
@ -15,7 +15,7 @@ import {fetch} from 'react-data/fetch';
|
|||
import PostList from './PostList';
|
||||
|
||||
function load(params) {
|
||||
const allPosts = fetch('/posts').json();
|
||||
const allPosts = fetch('/posts?_expand=user').json();
|
||||
return {
|
||||
posts: <PostList posts={allPosts} />,
|
||||
};
|
||||
|
|
|
@ -21,6 +21,8 @@ function load(postId) {
|
|||
|
||||
// Client
|
||||
|
||||
import Link from '../client/Link';
|
||||
|
||||
function Post(props, data) {
|
||||
return (
|
||||
<div
|
||||
|
@ -31,7 +33,13 @@ function Post(props, data) {
|
|||
padding: 20,
|
||||
maxWidth: 500,
|
||||
}}>
|
||||
<h4 style={{marginTop: 0}}>{props.post.title}</h4>
|
||||
<h4 style={{marginTop: 0}}>
|
||||
{props.post.title}
|
||||
{' by '}
|
||||
<Link to={`/profile/${props.post.user.id}`}>
|
||||
{props.post.user.name}
|
||||
</Link>
|
||||
</h4>
|
||||
<p>{props.post.body}</p>
|
||||
<Suspense
|
||||
fallback={<h5>Loading comments...</h5>}
|
||||
|
|
|
@ -17,7 +17,7 @@ export default function PostList({posts}) {
|
|||
return (
|
||||
<SuspenseList revealOrder="forwards" tail="collapsed">
|
||||
{posts.map(post => {
|
||||
preload(`/comments?postId=${post.id}`);
|
||||
preload(`/comments?postId=${post.id}&_expand=user`);
|
||||
const Post = loadPost(post.id);
|
||||
return (
|
||||
<Suspense key={post.id} fallback={<PostGlimmer />}>
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
/* eslint-disable import/first */
|
||||
|
||||
import * as React from 'react';
|
||||
import {block} from 'react';
|
||||
|
||||
// Server
|
||||
|
||||
import {fetch} from 'react-data/fetch';
|
||||
|
||||
function load(user) {
|
||||
return {
|
||||
user,
|
||||
bio: fetch(`/bios/${user.bioId}`).json(),
|
||||
};
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
function ProfileBio(props, data) {
|
||||
return (
|
||||
<>
|
||||
<h3>{data.user.name}'s Bio</h3>
|
||||
<p>{data.bio.text}</p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default block(ProfileBio, load);
|
|
@ -12,23 +12,47 @@ import {block, Suspense} from 'react';
|
|||
// Server
|
||||
|
||||
import {fetch} from 'react-data/fetch';
|
||||
import loadProfileBio from './ProfileBio.block';
|
||||
import loadProfileTimeline from './ProfileTimeline.block';
|
||||
|
||||
function load(userId) {
|
||||
function load(userId, tab) {
|
||||
const user = fetch(`/users/${userId}`).json();
|
||||
let Tab;
|
||||
switch (tab) {
|
||||
case 'bio':
|
||||
Tab = loadProfileBio(user);
|
||||
break;
|
||||
default:
|
||||
Tab = loadProfileTimeline(userId);
|
||||
break;
|
||||
}
|
||||
return {
|
||||
user: fetch(`/users/${userId}`).json(),
|
||||
ProfileTimeline: loadProfileTimeline(userId),
|
||||
Tab,
|
||||
user,
|
||||
};
|
||||
}
|
||||
|
||||
// Client
|
||||
|
||||
import {TabBar, TabLink} from '../client/TabNav';
|
||||
|
||||
function ProfileTabNav({userId}) {
|
||||
// TODO: Don't hardcode ID.
|
||||
return (
|
||||
<TabBar>
|
||||
<TabLink to={`/profile/${userId}`}>Timeline</TabLink>
|
||||
<TabLink to={`/profile/${userId}/bio`}>Bio</TabLink>
|
||||
</TabBar>
|
||||
);
|
||||
}
|
||||
|
||||
function ProfilePage(props, data) {
|
||||
return (
|
||||
<>
|
||||
<h2>{data.user.name}</h2>
|
||||
<Suspense fallback={<h3>Loading Timeline...</h3>}>
|
||||
<data.ProfileTimeline />
|
||||
<ProfileTabNav userId={data.user.id} />
|
||||
<Suspense fallback={<h3>Loading...</h3>}>
|
||||
<data.Tab />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
|
|
|
@ -15,7 +15,7 @@ import {fetch} from 'react-data/fetch';
|
|||
import PostList from './PostList';
|
||||
|
||||
function load(userId) {
|
||||
const postsByUser = fetch(`/posts?userId=${userId}`).json();
|
||||
const postsByUser = fetch(`/posts?userId=${userId}&_expand=user`).json();
|
||||
return {
|
||||
posts: <PostList posts={postsByUser} />,
|
||||
};
|
||||
|
@ -24,12 +24,7 @@ function load(userId) {
|
|||
// Client
|
||||
|
||||
function ProfileTimeline(props, data) {
|
||||
return (
|
||||
<>
|
||||
<h3>Timeline</h3>
|
||||
{data.posts}
|
||||
</>
|
||||
);
|
||||
return <>{data.posts}</>;
|
||||
}
|
||||
|
||||
export default block(ProfileTimeline, load);
|
||||
|
|
Loading…
Reference in New Issue