Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
React Architecture at Product Hunt
Search
Radoslav Stankov
November 03, 2021
Technology
420
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
React Architecture at Product Hunt
Radoslav Stankov
November 03, 2021
More Decks by Radoslav Stankov
See All by Radoslav Stankov
Tips for Building Useful Agent Skills
rstankov
0
160
Building LLM Powered Features
rstankov
0
200
Tips for Tailwind CSS
rstankov
0
96
Building LLM Powered Features (lightning talk)
rstankov
0
110
All you need is CSS
rstankov
0
180
Ruby on Rails The Single Engineer Framework
rstankov
0
72
Rails: The Missing Parts
rstankov
1
290
The dream that turned into nightmare
rstankov
0
360
The dream that turned into nightmare (lightning)
rstankov
0
180
Other Decks in Technology
See All in Technology
キヨさん、67歳。AIコーディングエージェントに挑む
happysamurai294
0
120
[re:Invent2026事前勉強会(有志開催)]続きは、ラスベガスの廊下で / continue-in-the-vegas-hallway
sh_fk2
0
430
Autonomous AI Lakehouse - Live AI Hubから始めるセキュアなAIデータ活用 -
oracle4engineer
PRO
2
180
E2Eってつらくないですか?E2Eの管理コストを下げる手段とその実践
akaboshinit
6
2.8k
[FECONF-2026] 신대륙을 향한 카카오페이증권 프론트엔드 항해기 (이주승)
l1ghthouse
0
320
AIコーディングエージェント開発_全体像
happysamurai294
0
130
AWSセキュリティで活用するAI機能
cmusudakeisuke
1
380
"AWSはちょっと苦手"でも大丈夫。AWS Blocks で手軽に作る Flutter のバックエンド #FlutterKaigi
tacck
PRO
0
110
視野はAIに、視座は人に / ai-for-breadth-humans-for-perspective
nkzn
5
2.5k
Jev の中身は50行で再現できる。でも本物との差はどこにあるのか
hisuzuya
0
210
AIに任せられるFlutterコードベースを作る 〜仕様をコードで表し、ハーネスで守る〜
myohei
1
500
AI駆動開発におけるハーネスエンジニアリングの勘所
tsukuboshi
1
880
Featured
See All Featured
How to audit for AI Accessibility on your Front & Back End
davetheseo
0
620
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
330
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
12
1.9k
How to Talk to Developers About Accessibility
jct
2
600
Design in an AI World
tapps
1
370
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
74
43k
How STYLIGHT went responsive
nonsquared
100
6.3k
Designing Experiences People Love
moore
143
24k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
660
What does AI have to do with Human Rights?
axbom
PRO
1
2.5k
A brief & incomplete history of UX Design for the World Wide Web: 1989–2019
jct
2
550
Game over? The fight for quality and originality in the time of robots
wayneb77
1
320
Transcript
Product Hunt React Architecture Radoslav Stankov 11/11/2021
!
Radoslav Stankov @rstankov blog.rstankov.com twitter.com/rstankov github.com/rstankov speakerdeck.com/rstankov
None
Product Hunt React Architecture Radoslav Stankov 11/11/2021
None
https://speakerdeck.com/rstankov Thanks "
Architecture
None
None
None
None
None
None
None
None
# Frontend Tech Stack
None
None
None
None
$ Have good defaults % Have good code organization &
Make common operations easy ' Isolate dependencies ( Extensibility and reusability
$ Have good defaults % Have good code organization &
Make common operations easy ' Isolate dependencies ( Extensibility and reusability
None
None
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
2 1 3 Support Components Pages
Support Components Pages
1) Support 2) Components 3) Pages
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
1 Support components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/
styles/ types/ utils/ config.ts paths.ts
1 Support Components 2 components/ graphql/ hooks/ layouts/ pages/ screens/
server/ static/ styles/ types/ utils/ config.ts paths.ts
1 Support Components Pages 2 3 components/ graphql/ hooks/ layouts/
pages/ screens/ server/ static/ styles/ types/ utils/ config.ts paths.ts
1 Support
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
import getNextConfig from 'next/config'; const config = getNextConfig() as any;
// ... other configuration export const environment = { isTest: config.publicRuntimeConfig.NODE_ENV === 'test', isProduction: config.publicRuntimeConfig.NODE_ENV === 'production', isBrowser: !!process.browser, }; config.ts
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
apollo client:codegen \ --localSchemaFile="graphql/schema.json" \ --addTypename \ --tagName=gql \ --target=typescript
\ --includes="{components,screens,utils,hooks,layouts}/**/*.{tsx,ts}" \ --outputFlat="graphql/types.ts"
None
components/Profile/Avatar/Fragment.ts import gql from 'graphql-tag'; export default gql` fragment ProfileAvatarFragment
on Profile { id name kind imageUrl } `;
// ==================================================== // GraphQL fragment: ProfileAvatarFragment // ==================================================== export interface
ProfileAvatarFragment { __typename: "Profile"; id: string; name: string; kind: string; imageUrl: string | null; } graphql/types.ts
import { ProfileAvatarFragment } from '~/graphql/types';
None
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
None
useKey() useIsMounted() useTimeout() useHideOnTop()
useGraphQLFragment() useViewier() useIsLoggedIn()
useGraphQLFragment() useViewier() useIsLoggedIn()
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
None
import { format } from 'date-fns'; export function formatDateTime(date: string)
{ return format(date, 'H:mm A · MMM D, YYYY'); } utils/date.ts
moment date.ts Component Page
date.ts Component Page date-fns
utils/ external/ Intercom/ OneSignal/ Segment/ Sentry/
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
2 Components
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
None
) Component as directory components/ Component/ SubComponent/ Fragment.graphql Mutation.graphql icon.svg
index.js styles.css utils.js
None
import * as React from "react"; import Text from "components/Text";
<Text>{text}</Text>
import * as React from "react"; import styles from "./styles.css";
export function Text({ children }) { return ( <span className={styles.text}> {children} </span> ); } components/Text/index.tsx
CSS // style.css .text { font-size: 14px; } CSS //
component.js import styles from './styles.css'; <span className={styles.text}> </span> CSS // style.css .text_3mRwv { font-size: 14px; } CSS // result.js <span class="text_3mRwv"> </span>
import * as React from "react"; import Text from "components/Text";
<Text>text</Text> // -> <span class="text">text</span>
import * as React from "react"; import Text from "components/Text";
<Text>text</Text> // -> <span class="text">text</span> <Text as="p">text</Text> // -> <p class="text">text</p>
import * as React from "react"; import styles from "./styles.css";
export function Text({ as, ...props }) { Component = as || "span"; return <Component className={styles.text} {...props} />; } ) Pass custom component as prop
import * as React from "react"; import Text from "components/Text";
<Text>text</Text> // -> <span class="text">Text</span>
import * as React from "react"; import Text from "components/Text";
import styles from "./styles.css"; <Text className={styles.custom}>text</Text> // -> <span class="text custom">text</span>
import * as React from "react"; import styles from "./styles.css";
import classNames from "classnames"; export function Text({ as, className, ...props }) { Component = as || "span"; return <Component className={className(styles.text, className) } ) Pass extra class name
yarn install "classnames"
import * as React from "react"; import Text from "components/Text";
<Text.Button onClick={fn}>text</Text.Button> // -> <Button className="text">text</Button> <Text.Link to="/page">text</Text.Link> // -> <Link className="text" href="/page">text</Link>
None
None
<Form.Mutation mutation={MUTATION} onSubmit={onComplete}> <Form.Field name="title" /> <Form.Field name="email" control="email" />
<Form.Field name="tagline" control="textarea" /> <Form.Field name="category" control="select" options={CATEGORIES} /> <Form.Field name="photos" control={PhotosInput} /> <Form.Field name="topics" control={TopicsInput} /> <Form.Submit /> </Form.Mutation>
Input
Input Loading
Input Loading Success
Input Loading Success Errors
Submit Server Success Errors
remoteCall Server { result: 'ok' } { errors: {…} }
remoteCall Server { result: 'ok' } { errors: {
field1: 'error1', field2: 'error2' } }
{ errors: { field1: 'error1', field2: 'error2' } }
{ errors: { field1: 'error1', field2: 'error2' } }
mutation UserSettingsUpdate($input: UserSettingsUpdateInp response: userSettingsUpdate(input: $input) { node { id
...MySettingsPageViewer } errors { field message } } }
mutation UserSettingsUpdate($input: UserSettingsUpdateInp response: userSettingsUpdate(input: $input) { node { id
...MySettingsPageViewer } errors { field message } } }
<Form.Mutation mutation={MUTATION} onSubmit={onComplete}> <Form.Field name="title" /> <Form.Field name="email" control="email" />
<Form.Field name="tagline" control="textarea" /> <Form.Field name="category" control="select" options={CATEGORIES} /> <Form.Field name="photos" control={PhotosInput} /> <Form.Field name="topics" control={TopicsInput} /> <Form.Submit /> </Form.Mutation>
<Form.Mutation mutation={MUTATION} onSubmit={onComplete}> <Form.Field name="title" /> <Form.Field name="email" control="email" />
<Form.Field name="tagline" control="textarea" /> <Form.Field name="category" control="select" options={CATEGORIES} /> <Form.Field name="photos" control={PhotosInput} /> <Form.Field name="topics" control={TopicsInput} /> <Form.Submit /> </Form.Mutation>
None
Which is the form library we are using?!
None
None
*
*
*
+ unified styles , common interface - custom inputs .
understand backend GraphQL / Form
None
<Button href={paths.profile(profile)} />
<Button href={paths.profile(profile)} /> <Button onClick={onClickReturnsPromise} />
<Button href={paths.profile(profile)} /> <Button onClick={onClickReturnsPromise} /> <Button confirm="Are you sure?"
mutation={DELETE_MUTATION} input={{ id }} onMutate={redirectSomeWhere} />
<Button href={paths.profile(profile)} /> <Button onClick={onClickReturnsPromise} /> <Button confirm="Are you sure?"
mutation={DELETE_MUTATION} input={{ id }} onMutate={redirectSomeWhere} />
<Button.Primary {...props} /> <Button.Secondary {...props} /> <Button.Line {...props} /> <Button.Small
{...props} />
const clickHandler = useOnClick({ disabled, confirm, requireLogin, mutation, input, onMutate,
onMutateError, optimisticResponse, update, updateQueries, refetchQueries, onClick, trackingComponent, trackingData, });
Utility Styling Domain
None
VoteButton Button
Domain Component
PostItem Like Button
Domain Component <PostList> <PostItem> <Flex.Row> <PostThumbnail /> <Flex.Column> <Text.Title />
<Text.Subtitle /> <Flex.Row> <PostCommentButton> <Button /> </PostCommentButton> <Text.Small /> </Flex.Row> </Flex.Column> <PostVoteButton> <Button /> </PostVoteButton> </Flex.Row> </PostItem> </PostList>
Atomic Design
...Kinda 0
1 generic components - domain components
3 Pages
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
export default { root: () => '/', static: { about()
=> '/about', // ... } profiles: { people: () => '/people', show: ({ slug }: { slug: string }) => `/@${slug}`, // ... }, // ... }; path.ts
import paths from 'ph/paths'; paths.root(); // => / paths.static.about();
// => /about/ paths.profiles.people(); // => /people paths.profiles.show(profile); // => /@rstankov
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
None
None
None
None
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
2 components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/
types/ utils/ config.ts paths.ts
None
pages/profiles/[slug]/index.ts
import page from '~/screens/profiles/show'; export default page; pages/profiles/[slug]/index.ts
import page from '~/screens/profiles/show'; export default page; pages/profiles/[slug]/index.ts 3
components/ graphql/ hooks/ layouts/ pages/ screens/ server/ static/ styles/ types/
utils/ config.ts paths.ts
Component as directory screens/ profiles/ show/ SubComponent1/ SubComponent2/ Query.graphql index.js
styles.css utils.js
None
Loading State Page Life Cycle
Loading State Page Life Cycle
Loading State Error State Page Life Cycle
Loading State Not Found Error Server Error Authorization Error Authentication
Error Error State Page Life Cycle
Loading State Not Found Error Server Error Authorization Error Authentication
Error Error State Loaded State Page Life Cycle
Loading State Not Found Error Server Error Authorization Error Authentication
Error SEO Error State Analytics Loaded State Page Life Cycle ???
Loading State Not Found Error Server Error Authorization Error Authentication
Error SEO Error State Analytics Loaded State render Page Life Cycle ???
import createPage from '~/utils/createPage'; import ProfileLayout from '~/layouts/Profile'; import {
ProfileShowPage } from '~/graphql/types'; import QUERY from './Query'; export default createPage<ProfileShowPage>({ query: QUERY, queryVariables: ({ slug }) => ({ slug }), requireLogin: true, requirePermissions: ({ profile }) => profile.canManage, requireFeature: 'feature_flag', tags: ({ profile }) => profile.seoTags, title: ({ profile }) => profile.name, component: ({ data: { profile } }) => ( <ProfileLayout profile={profile}>{...}</ProfileLayout> ), });
import createPage from '~/utils/createPage'; import ProfileLayout from '~/layouts/Profile'; import {
ProfileShowPage } from '~/graphql/types'; import QUERY from './Query'; export default createPage<ProfileShowPage>({ query: QUERY, queryVariables: ({ slug }) => ({ slug }), requireLogin: true, requirePermissions: ({ profile }) => profile.canManage, requireFeature: 'feature_flag', tags: ({ profile }) => profile.seoTags, title: ({ profile }) => profile.name, component: ({ data: { profile } }) => ( <ProfileLayout profile={profile}>{...}</ProfileLayout> ), });
import createPage from '~/utils/createPage'; import ProfileLayout from '~/layouts/Profile'; import {
ProfileShowPage } from '~/graphql/types'; import QUERY from './Query'; export default createPage<ProfileShowPage>({ query: QUERY, queryVariables: ({ slug }) => ({ slug }), requireLogin: true, requirePermissions: ({ profile }) => profile.canManage, requireFeature: 'feature_flag', tags: ({ profile }) => profile.seoTags, title: ({ profile }) => profile.name, component: ({ data: { profile } }) => ( <ProfileLayout profile={profile}>{...}</ProfileLayout> ), });
import createPage from '~/utils/createPage'; import ProfileLayout from '~/layouts/Profile'; import {
ProfileShowPage } from '~/graphql/types'; import QUERY from './Query'; export default createPage<ProfileShowPage>({ query: QUERY, queryVariables: ({ slug }) => ({ slug }), requireLogin: true, requirePermissions: ({ profile }) => profile.canManage, requireFeature: 'feature_flag', tags: ({ profile }) => profile.seoTags, title: ({ profile }) => profile.name, component: ({ data: { profile } }) => ( <ProfileLayout profile={profile}>{...}</ProfileLayout> ), });
import createPage from '~/utils/createPage'; import ProfileLayout from '~/layouts/Profile'; import {
ProfileShowPage } from '~/graphql/types'; import QUERY from './Query'; export default createPage<ProfileShowPage>({ query: QUERY, queryVariables: ({ slug }) => ({ slug }), requireLogin: true, requirePermissions: ({ profile }) => profile.canManage, requireFeature: 'feature_flag', tags: ({ profile }) => profile.seoTags, title: ({ profile }) => profile.name, component: ({ data: { profile } }) => ( <ProfileLayout profile={profile}>{...}</ProfileLayout> ), });
import createPage from '~/utils/createPage'; import ProfileLayout from '~/layouts/Profile'; import {
ProfileShowPage } from '~/graphql/types'; import QUERY from './Query'; export default createPage<ProfileShowPage>({ query: QUERY, queryVariables: ({ slug }) => ({ slug }), requireLogin: true, requirePermissions: ({ profile }) => profile.canManage, requireFeature: 'feature_flag', tags: ({ profile }) => profile.seoTags, title: ({ profile }) => profile.name, component: ({ data: { profile } }) => ( <ProfileLayout profile={profile}>{...}</ProfileLayout> ), });
http://graphql.org/
None
ProfileLayout Header Fragment ProfileLayout Sidebar Fragment ProfileShowPage
Avatar Fragment PostItemFragment CommentFragment ProfileLayout SidebarInfo Fragment
Query Fragm Fragm Fragm Fragm Fragm Fragm
#import "ph/utils/seo/MetaTagsFragment.graphql" #import "ph/components/PostItemList/Framgent.graphql" #import "ph/layouts/Profile/Fragment.graphql" query ProfileShowPage( $cursor: String
$username: String! $query: String ) { user(username: $username) { id votesCount votedPosts(first: 20, after: $cursor, query: $query) { edges { node { id ...PostItemListFragment } } pageInfo { endCursor hasNextPage } } ...MetaTags ...ProfileLayoutFragment } }
None
Recap
- GraphQL $ Components 4 isolating dependancies ( directory as
folder . domain components + Pages , paths helper ' layouts & createPage 5 Recap
None
None
Thanks "
https://speakerdeck.com/rstankov Thanks "
None