Upgrade to Pro — share decks privately, control downloads, hide ads and more …

React Architecture at Product Hunt

React Architecture at Product Hunt

Radoslav Stankov

November 03, 2021
Tweet

More Decks by Radoslav Stankov

Other Decks in Technology

Transcript

  1. !

  2. $ Have good defaults % Have good code organization &

    Make common operations easy ' Isolate dependencies ( Extensibility and reusability
  3. $ Have good defaults % Have good code organization &

    Make common operations easy ' Isolate dependencies ( Extensibility and reusability
  4. 1 Support Components 2 components/ graphql/ hooks/ layouts/ pages/ screens/

    server/ static/ styles/ types/ utils/ config.ts paths.ts
  5. 1 Support Components Pages 2 3 components/ graphql/ hooks/ layouts/

    pages/ screens/ server/ static/ styles/ types/ utils/ config.ts paths.ts
  6. 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
  7. apollo client:codegen \ --localSchemaFile="graphql/schema.json" \ --addTypename \ --tagName=gql \ --target=typescript

    \ --includes="{components,screens,utils,hooks,layouts}/**/*.{tsx,ts}" \ --outputFlat="graphql/types.ts"
  8. // ==================================================== // GraphQL fragment: ProfileAvatarFragment // ==================================================== export interface

    ProfileAvatarFragment { __typename: "Profile"; id: string; name: string; kind: string; imageUrl: string | null; } graphql/types.ts
  9. import { format } from 'date-fns'; export function formatDateTime(date: string)

    { return format(date, 'H:mm A · MMM D, YYYY'); } utils/date.ts
  10. 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
  11. 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>

  12. import * as React from "react"; import Text from "components/Text";

    <Text>text</Text> // -> <span class="text">text</span>
  13. 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>
  14. 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
  15. import * as React from "react"; import Text from "components/Text";

    <Text>text</Text> // -> <span class="text">Text</span>
  16. 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>
  17. 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
  18. 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>
  19. <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>
  20. remoteCall Server { result: 'ok' } { 
 errors: {

    field1: 'error1',
 field2: 'error2'
 }
 }
  21. <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>
  22. <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>
  23. *

  24. *

  25. *

  26. + unified styles , common interface - custom inputs .

    understand backend GraphQL / Form
  27. const clickHandler = useOnClick({ disabled, confirm, requireLogin, mutation, input, onMutate,

    onMutateError, optimisticResponse, update, updateQueries, refetchQueries, onClick, trackingComponent, trackingData, });
  28. 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>
  29. export default { root: () => '/', static: { about()

    => '/about', // ... } profiles: { people: () => '/people', show: ({ slug }: { slug: string }) => `/@${slug}`, // ... }, // ... }; path.ts
  30. import paths from 'ph/paths'; 
 paths.root(); // => / paths.static.about();

    // => /about/ paths.profiles.people(); // => /people paths.profiles.show(profile); // => /@rstankov
  31. Loading State Not Found Error Server Error Authorization Error Authentication

    Error SEO Error State Analytics Loaded State Page Life Cycle ???
  32. Loading State Not Found Error Server Error Authorization Error Authentication

    Error SEO Error State Analytics Loaded State render Page Life Cycle ???
  33. 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> ), });
  34. 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> ), });
  35. 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> ), });
  36. 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> ), });
  37. 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> ), });
  38. 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> ), });
  39. #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 } }
  40. - GraphQL $ Components 4 isolating dependancies ( directory as

    folder . domain components + Pages , paths helper ' layouts & createPage 5 Recap