my blog https://overreacted.io

simplify styling

Changed files
+300 -222
app
public
a-complete-guide-to-useeffect
hire-me-in-japan
how-imports-work-in-rsc
impossible-components
react-as-a-ui-runtime
the-math-is-haunted
the-two-reacts
+4 -3
app/Link.tsx
··· 27 27 ...rest 28 28 }: { 29 29 className?: string; 30 - children: React.ReactNode; 30 + children?: React.ReactNode; 31 31 style?: React.CSSProperties; 32 32 href: string; 33 33 target?: string; 34 34 } & React.ComponentProps<typeof NextLink>) { 35 35 const router = useRouter(); 36 36 const [isNavigating, trackNavigation] = useTransition(); 37 - if (!target && !href.startsWith("/") && !href.startsWith("#")) { 37 + const isExternal = /^https?:\/\//.test(href); 38 + if (!target && isExternal) { 38 39 target = "_blank"; 39 40 } 40 41 return ( ··· 50 51 }); 51 52 } 52 53 }} 53 - className={[className, `scale-100 active:scale-100`].join(" ")} 54 + className={[className, "scale-100 active:scale-100"].join(" ")} 54 55 style={{ 55 56 ...style, 56 57 transform: isNavigating ? "scale(1)" : "",
+16
app/TextLink.tsx
··· 1 + import Link from "./Link"; 2 + 3 + export default function TextLink({ 4 + className, 5 + ...props 6 + }: React.ComponentProps<typeof Link>) { 7 + return ( 8 + <Link 9 + {...props} 10 + className={[ 11 + "underline decoration-[--link] decoration-1 underline-offset-4 text-[--link]", 12 + className, 13 + ].join(" ")} 14 + /> 15 + ); 16 + }
+1 -1
app/[slug]/layout.tsx
··· 4 4 return ( 5 5 <> 6 6 {children} 7 - <footer className="mt-12"> 7 + <footer className="mt-20"> 8 8 <HomeLink /> 9 9 </footer> 10 10 </>
+29 -150
app/[slug]/markdown.css
··· 1 + /* CSS vars for code block customization (used by Wrapper components) */ 1 2 .markdown { 2 - line-height: 28px; 3 - --path: none; 4 - --radius-top: 12px; 5 - --radius-bottom: 12px; 6 - --padding-top: 1rem; 7 - --padding-bottom: 1rem; 8 - } 9 - 10 - .markdown p { 11 - @apply pb-8; 12 - } 13 - 14 - .markdown a:not(.tip):not(.linked-heading) { 15 - @apply border-b-[1px] border-[--link] text-[--link]; 16 - } 17 - 18 - .markdown hr { 19 - @apply pt-8 opacity-60 dark:opacity-10; 20 - } 21 - 22 - .markdown h2 { 23 - @apply mt-2 pb-8 text-3xl font-bold; 24 - } 25 - 26 - .markdown h3 { 27 - @apply mt-2 pb-8 text-2xl font-bold; 28 - } 29 - 30 - .markdown h4 { 31 - @apply mt-2 pb-8 text-xl font-bold; 32 - } 33 - 34 - .markdown :is(h1, h2, h3, h4) a:is(:hover, :focus, :active)::before, 35 - .markdown :is(h1, h2, h3, h4):is(:target, :focus) a::before { 36 - content: "#"; 37 - position: absolute; 38 - transform: translate(-1em); 39 - opacity: 0.7; 40 - } 41 - 42 - .markdown :not(pre) > code { 43 - border-radius: 10px; 44 - background: var(--inlineCode-bg); 45 - color: var(--inlineCode-text); 46 - padding: 0.15em 0.2em 0.05em; 47 - white-space: normal; 3 + line-height: 28px; 4 + --path: none; 5 + --radius-top: 12px; 6 + --radius-bottom: 12px; 7 + --padding-top: 1rem; 8 + --padding-bottom: 1rem; 48 9 } 49 10 50 - .markdown pre { 51 - @apply -mx-4 mb-8 overflow-y-auto p-4 text-sm; 52 - clip-path: var(--path); 53 - border-top-right-radius: var(--radius-top); 54 - border-top-left-radius: var(--radius-top); 55 - border-bottom-right-radius: var(--radius-bottom); 56 - border-bottom-left-radius: var(--radius-bottom); 57 - padding-top: var(--padding-top); 58 - padding-bottom: var(--padding-bottom); 59 - } 60 - 61 - .markdown pre code { 62 - width: auto; 63 - } 64 - 65 - .markdown blockquote { 66 - @apply relative -left-2 -ml-4 mb-8 pl-4; 67 - font-style: italic; 68 - border-left: 3px solid hsla(0, 0%, 0%, 0.9); 69 - border-left-color: inherit; 70 - opacity: 0.8; 71 - } 72 - 73 - .markdown blockquote p { 74 - margin: 0; 75 - padding: 0; 76 - } 77 - 78 - .markdown p img { 79 - margin-bottom: 0; 80 - } 81 - 82 - .markdown ul:not(.unstyled) { 83 - @apply list-inside md:list-outside list-disc; 84 - margin-top: 0; 85 - padding-bottom: 0; 86 - padding-left: 0; 87 - padding-right: 0; 88 - padding-top: 0; 89 - margin-bottom: 1.75rem; 90 - list-style-image: none; 91 - } 92 - 93 - .markdown li:not(.unstyled) { 94 - margin-bottom: calc(1.75rem / 2); 95 - } 96 - 97 - .markdown img { 98 - @apply mb-8; 99 - max-width: 100%; 100 - } 101 - 102 - .markdown iframe { 103 - @apply mb-8; 104 - max-width: 100%; 105 - } 106 - 107 - .markdown ol { 108 - @apply mb-8 list-inside md:list-outside list-decimal; 109 - } 110 - 111 - .markdown input { 112 - color: #222; 113 - } 114 - 115 - .markdown table { 116 - width: 100%; 117 - border-collapse: collapse; 118 - margin-bottom: 1.5rem; 119 - } 120 - 121 - .markdown th, 122 - .markdown td { 123 - border: 1px solid #dcdcdc; 124 - padding: 8px; 125 - text-align: left; 126 - } 127 - 128 - @media (prefers-color-scheme: dark) { 129 - .markdown input { 130 - color: #111; 131 - } 132 - } 133 - 11 + /* Code line highlighting - data-attribute from rehype-pretty-code */ 134 12 .markdown pre [data-highlighted-line] { 135 - margin-left: -16px; 136 - margin-right: -16px; 137 - padding-left: 12px; 138 - border-left: 4px solid #ffa7c4; 139 - background-color: #022a4b; 140 - display: block; 141 - padding-right: 1em; 13 + margin-left: -16px; 14 + margin-right: -16px; 15 + padding-left: 12px; 16 + border-left: 4px solid #ffa7c4; 17 + background-color: #022a4b; 18 + display: block; 19 + padding-right: 1em; 142 20 } 143 21 22 + /* Tip button styles */ 144 23 .tip { 145 - @apply inline-block px-8 py-4 font-sans font-semibold text-xl rounded-full shadow-xl transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-pink-300 focus:ring-opacity-50 border-b-[1px] text-white overflow-clip transition-transform; 146 - border-color: var(--link); 24 + @apply inline-block px-8 py-4 font-sans font-semibold text-xl rounded-full shadow-xl transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-pink-300 focus:ring-opacity-50 border-b-[1px] text-white overflow-clip transition-transform; 25 + border-color: var(--link); 147 26 } 148 27 149 28 .tip.tip-sm { 150 - @apply inline-block px-6 py-2 text-lg shadow-md; 29 + @apply inline-block px-6 py-2 text-lg shadow-md; 151 30 } 152 31 153 32 .tip-bg { 154 - background-image: linear-gradient(45deg, var(--purple), var(--pink)); 155 - position: absolute; 156 - top: 0; 157 - left: 0; 158 - right: 0; 159 - bottom: 0; 160 - z-index: -1; 33 + background-image: linear-gradient(45deg, var(--purple), var(--pink)); 34 + position: absolute; 35 + top: 0; 36 + left: 0; 37 + right: 0; 38 + bottom: 0; 39 + z-index: -1; 161 40 } 162 41 163 42 @media (prefers-color-scheme: dark) { 164 - .tip-bg { 165 - filter: brightness(0.46); 166 - } 43 + .tip-bg { 44 + filter: brightness(0.46); 45 + } 167 46 }
+166
app/[slug]/markdown.tsx
··· 1 + "use client"; 2 + 3 + export function P(props: React.ComponentProps<"p">) { 4 + return <p {...props} />; 5 + } 6 + 7 + export function H2({ id, children, ...props }: React.ComponentProps<"h2">) { 8 + return ( 9 + <h2 10 + id={id} 11 + className="group relative text-3xl font-bold mt-2" 12 + {...props} 13 + > 14 + <a href={`#${id}`} className="no-underline text-inherit"> 15 + <span 16 + aria-hidden 17 + className="absolute -translate-x-[1em] opacity-0 group-hover:opacity-70 group-focus-within:opacity-70 group-[:target]:opacity-70" 18 + > 19 + # 20 + </span> 21 + {children} 22 + </a> 23 + </h2> 24 + ); 25 + } 26 + 27 + export function H3({ id, children, ...props }: React.ComponentProps<"h3">) { 28 + return ( 29 + <h3 30 + id={id} 31 + className="group relative text-2xl font-bold mt-2" 32 + {...props} 33 + > 34 + <a href={`#${id}`} className="no-underline text-inherit"> 35 + <span 36 + aria-hidden 37 + className="absolute -translate-x-[1em] opacity-0 group-hover:opacity-70 group-focus-within:opacity-70 group-[:target]:opacity-70" 38 + > 39 + # 40 + </span> 41 + {children} 42 + </a> 43 + </h3> 44 + ); 45 + } 46 + 47 + export function H4({ id, children, ...props }: React.ComponentProps<"h4">) { 48 + return ( 49 + <h4 50 + id={id} 51 + className="group relative text-xl font-bold mt-2" 52 + {...props} 53 + > 54 + <a href={`#${id}`} className="no-underline text-inherit"> 55 + <span 56 + aria-hidden 57 + className="absolute -translate-x-[1em] opacity-0 group-hover:opacity-70 group-focus-within:opacity-70 group-[:target]:opacity-70" 58 + > 59 + # 60 + </span> 61 + {children} 62 + </a> 63 + </h4> 64 + ); 65 + } 66 + 67 + export function Blockquote(props: React.ComponentProps<"blockquote">) { 68 + return ( 69 + <blockquote 70 + className="relative -left-2 -ml-4 pl-4 italic opacity-80 border-l-[3px] border-current" 71 + {...props} 72 + /> 73 + ); 74 + } 75 + 76 + export function UL(props: React.ComponentProps<"ul">) { 77 + return <ul className="list-inside md:list-outside list-disc" {...props} />; 78 + } 79 + 80 + export function OL(props: React.ComponentProps<"ol">) { 81 + return ( 82 + <ol className="list-inside md:list-outside list-decimal" {...props} /> 83 + ); 84 + } 85 + 86 + export function LI(props: React.ComponentProps<"li">) { 87 + return <li className="mb-3 last:mb-0" {...props} />; 88 + } 89 + 90 + export function Pre({ 91 + style, 92 + ...props 93 + }: React.ComponentProps<"pre">) { 94 + return ( 95 + <pre 96 + className="-mx-4 overflow-y-auto p-4 text-sm" 97 + {...props} 98 + style={{ 99 + ...style, 100 + clipPath: "var(--path, none)", 101 + borderTopLeftRadius: "var(--radius-top, 12px)", 102 + borderTopRightRadius: "var(--radius-top, 12px)", 103 + borderBottomLeftRadius: "var(--radius-bottom, 12px)", 104 + borderBottomRightRadius: "var(--radius-bottom, 12px)", 105 + paddingTop: "var(--padding-top, 1rem)", 106 + paddingBottom: "var(--padding-bottom, 1rem)", 107 + }} 108 + /> 109 + ); 110 + } 111 + 112 + export function Code({ 113 + className, 114 + ...props 115 + }: React.ComponentProps<"code"> & { "data-language"?: string }) { 116 + // Code blocks have data-language from rehype-pretty-code (defaultLang ensures all blocks have it) 117 + if ("data-language" in props) { 118 + return <code className={className} {...props} />; 119 + } 120 + // Inline code styling 121 + return ( 122 + <code 123 + className="rounded-[10px] bg-[--inlineCode-bg] text-[--inlineCode-text] px-[0.2em] py-[0.15em] whitespace-normal" 124 + {...props} 125 + /> 126 + ); 127 + } 128 + 129 + export function Table(props: React.ComponentProps<"table">) { 130 + return <table className="w-full border-collapse" {...props} />; 131 + } 132 + 133 + export function Th(props: React.ComponentProps<"th">) { 134 + return ( 135 + <th 136 + className="border border-gray-300 dark:border-gray-600 p-2 text-left" 137 + {...props} 138 + /> 139 + ); 140 + } 141 + 142 + export function Td(props: React.ComponentProps<"td">) { 143 + return ( 144 + <td 145 + className="border border-gray-300 dark:border-gray-600 p-2 text-left" 146 + {...props} 147 + /> 148 + ); 149 + } 150 + 151 + export function Hr(props: React.ComponentProps<"hr">) { 152 + return <hr className="opacity-60 dark:opacity-10 mt-4" {...props} />; 153 + } 154 + 155 + export function Img(props: React.ComponentProps<"img">) { 156 + return <img className="max-w-full" {...props} />; 157 + } 158 + 159 + export function A(props: React.ComponentProps<"a">) { 160 + return ( 161 + <a 162 + className="border-b border-[--link] text-[--link]" 163 + {...props} 164 + /> 165 + ); 166 + }
+51 -49
app/[slug]/page.tsx
··· 2 2 import { readdir, readFile } from "fs/promises"; 3 3 import matter from "gray-matter"; 4 4 import { MDXRemote } from "next-mdx-remote-client/rsc"; 5 - import Link from "../Link"; 5 + import TextLink from "../TextLink"; 6 6 import { sans } from "../fonts"; 7 7 import remarkSmartpants from "remark-smartypants"; 8 8 import rehypePrettyCode from "rehype-pretty-code"; 9 9 import rehypeSlug from "rehype-slug"; 10 - import rehypeAutolinkHeadings from "rehype-autolink-headings"; 11 10 import { remarkMdxEvalCodeBlock } from "./mdx"; 12 11 import overnight from "overnight/themes/Overnight-Slumber.json"; 13 12 import "./markdown.css"; 14 13 import remarkGfm from "remark-gfm"; 14 + import * as markdown from "./markdown"; 15 15 16 16 overnight.colors["editor.background"] = "var(--code-bg)"; 17 17 ··· 54 54 year: "numeric", 55 55 })} 56 56 </p> 57 - <div className="markdown"> 58 - <div className="mb-8 relative md:-left-6 flex flex-wrap items-baseline"> 59 - {!data.nocta && ( 60 - <a 61 - href="https://ko-fi.com/gaearon" 62 - target="_blank" 63 - className="mt-10 tip tip-sm mr-4" 64 - > 65 - <span className="tip-bg" /> 66 - Pay what you like 67 - </a> 68 - )} 69 - {data.youtube && ( 70 - <a 71 - className="leading-tight mt-4" 72 - href={data.youtube} 73 - target="_blank" 74 - > 75 - <span className="hidden min-[400px]:inline">Watch on </span> 76 - YouTube 77 - </a> 78 - )} 79 - </div> 57 + <div className="markdown flex flex-col gap-8 mt-12"> 58 + {(!data.nocta || data.youtube) && ( 59 + <div className="relative md:-left-6 flex flex-wrap items-baseline gap-4"> 60 + {!data.nocta && ( 61 + <a 62 + href="https://ko-fi.com/gaearon" 63 + target="_blank" 64 + className="tip tip-sm" 65 + > 66 + <span className="tip-bg" /> 67 + Pay what you like 68 + </a> 69 + )} 70 + {data.youtube && ( 71 + <TextLink href={data.youtube}> 72 + <span className="hidden min-[400px]:inline">Watch on </span> 73 + YouTube 74 + </TextLink> 75 + )} 76 + </div> 77 + )} 80 78 81 79 <Wrapper> 80 + <div className="flex flex-col gap-8"> 82 81 <MDXRemote 83 82 source={content} 84 83 components={{ 85 - a: Link, 84 + p: markdown.P, 85 + h2: markdown.H2, 86 + h3: markdown.H3, 87 + h4: markdown.H4, 88 + blockquote: markdown.Blockquote, 89 + ul: markdown.UL, 90 + ol: markdown.OL, 91 + li: markdown.LI, 92 + pre: markdown.Pre, 93 + code: markdown.Code, 94 + table: markdown.Table, 95 + th: markdown.Th, 96 + td: markdown.Td, 97 + hr: markdown.Hr, 98 + a: (props: React.ComponentProps<"a">) => ( 99 + <TextLink {...props} href={props.href ?? ""} /> 100 + ), 86 101 img: async ({ src, ...rest }) => { 87 102 if ( 88 103 src && ··· 121 136 finalSrc = `/${slug}/${src}`; 122 137 } 123 138 124 - return <img src={finalSrc} {...rest} />; 139 + return <markdown.Img src={finalSrc} {...rest} />; 125 140 }, 126 141 Video: ({ src, ...rest }) => { 127 142 let finalSrc = src; ··· 146 161 rehypePrettyCode, 147 162 { 148 163 theme: overnight, 164 + defaultLang: { block: "text" }, 149 165 }, 150 166 ], 151 167 [rehypeSlug], 152 - [ 153 - rehypeAutolinkHeadings, 154 - { 155 - behavior: "wrap", 156 - properties: { 157 - className: "linked-heading", 158 - target: "_self", 159 - }, 160 - }, 161 - ], 162 168 ] as any, 163 169 } as any, 164 170 }} 165 171 /> 172 + </div> 166 173 </Wrapper> 167 174 {!data.nocta && ( 168 - <div className="flex flex-wrap items-baseline"> 175 + <div className="flex flex-wrap items-baseline gap-4 relative md:-left-8"> 169 176 <a 170 177 href="https://ko-fi.com/gaearon" 171 178 target="_blank" 172 - className="tip mb-8 relative md:-left-8" 179 + className="tip" 173 180 > 174 181 <span className="tip-bg" /> 175 182 Pay what you like 176 183 </a> 177 - <a 178 - className="leading-tight ml-4 relative md:-left-8" 179 - href="/hire-me-in-japan/" 180 - > 181 - Hire me 182 - </a> 184 + <TextLink href="/hire-me-in-japan/">Hire me</TextLink> 183 185 </div> 184 186 )} 185 - <hr /> 187 + <hr className="opacity-60 dark:opacity-10" /> 186 188 <p> 187 189 {data.bluesky && ( 188 190 <> 189 - <Link href={data.bluesky}>Discuss on Bluesky</Link> 191 + <TextLink href={data.bluesky}>Discuss on Bluesky</TextLink> 190 192 &nbsp;&nbsp;&middot;&nbsp;&nbsp; 191 193 </> 192 194 )} 193 195 {data.youtube && ( 194 196 <> 195 - <Link href={data.youtube}>Watch on YouTube</Link> 197 + <TextLink href={data.youtube}>Watch on YouTube</TextLink> 196 198 &nbsp;&nbsp;&middot;&nbsp;&nbsp; 197 199 </> 198 200 )} 199 201 {/* TODO: This should say Edit when Tangled adds an editor. */} 200 - <Link href={editUrl}>Fork on Tangled</Link> 202 + <TextLink href={editUrl}>Fork on Tangled</TextLink> 201 203 </p> 202 204 </div> 203 205 </article>
+6 -6
public/a-complete-guide-to-useeffect/index.md
··· 89 89 } 90 90 ``` 91 91 92 - What does it mean? Does `count` somehow “watch” changes to our state and update automatically? That might be a useful first intuition when you learn React but it’s *not* an [accurate mental model](https://overreacted.io/react-as-a-ui-runtime/). 92 + What does it mean? Does `count` somehow “watch” changes to our state and update automatically? That might be a useful first intuition when you learn React but it’s *not* an [accurate mental model](/react-as-a-ui-runtime/). 93 93 94 94 **In this example, `count` is just a number.** It’s not a magic “data binding”, a “watcher”, a “proxy”, or anything else. It’s a good old number like this one: 95 95 ··· 140 140 141 141 The key takeaway is that the `count` constant inside any particular render doesn’t change over time. It’s our component that’s called again — and each render “sees” its own `count` value that’s isolated between renders. 142 142 143 - *(For an in-depth overview of this process, check out my post [React as a UI Runtime](https://overreacted.io/react-as-a-ui-runtime/).)* 143 + *(For an in-depth overview of this process, check out my post [React as a UI Runtime](/react-as-a-ui-runtime/).)* 144 144 145 145 ## Each Render Has Its Own Event Handlers 146 146 ··· 190 190 191 191 Go ahead and [try it yourself!](https://codesandbox.io/s/w2wxl3yo0l) 192 192 193 - If the behavior doesn’t quite make sense to you, imagine a more practical example: a chat app with the current recipient ID in the state, and a Send button. [This article](https://overreacted.io/how-are-function-components-different-from-classes/) explores the reasons in depth but the correct answer is 3. 193 + If the behavior doesn’t quite make sense to you, imagine a more practical example: a chat app with the current recipient ID in the state, and a Send button. [This article](/how-are-function-components-different-from-classes/) explores the reasons in depth but the correct answer is 3. 194 194 195 195 The alert will “capture” the state at the time I clicked the button. 196 196 ··· 394 394 395 395 **Conceptually, you can imagine effects are a *part of the render result*.** 396 396 397 - Strictly saying, they’re not (in order to [allow Hook composition](https://overreacted.io/why-do-hooks-rely-on-call-order/) without clumsy syntax or runtime overhead). But in the mental model we’re building up, effect functions *belong* to a particular render in the same way that event handlers do. 397 + Strictly saying, they’re not (in order to [allow Hook composition](/why-do-hooks-rely-on-call-order/) without clumsy syntax or runtime overhead). But in the mental model we’re building up, effect functions *belong* to a particular render in the same way that event handlers do. 398 398 399 399 --- 400 400 ··· 518 518 519 519 **It doesn’t matter whether you read from props or state “early” inside of your component.** They’re not going to change! Inside the scope of a single render, props and state stay the same. (Destructuring props makes this more obvious.) 520 520 521 - Of course, sometimes you *want* to read the latest rather than captured value inside some callback defined in an effect. The easiest way to do it is by using refs, as described in the last section of [this article](https://overreacted.io/how-are-function-components-different-from-classes/). 521 + Of course, sometimes you *want* to read the latest rather than captured value inside some callback defined in an effect. The easiest way to do it is by using refs, as described in the last section of [this article](/how-are-function-components-different-from-classes/). 522 522 523 523 Be aware that when you want to read the *future* props or state from a function in a *past* render, you’re swimming against the tide. It’s not *wrong* (and in some cases necessary) but it might look less “clean” to break out of the paradigm. This is an intentional consequence because it helps highlight which code is fragile and depends on timing. In classes, it’s less obvious when this happens. 524 524 ··· 628 628 629 629 ## Synchronization, Not Lifecycle 630 630 631 - One of my favorite things about React is that it unifies describing the initial render result and the updates. This [reduces the entropy](https://overreacted.io/the-bug-o-notation/) of your program. 631 + One of my favorite things about React is that it unifies describing the initial render result and the updates. This [reduces the entropy](/the-bug-o-notation/) of your program. 632 632 633 633 Say my component looks like this: 634 634
+1 -1
public/hire-me-in-japan/index.md
··· 61 61 - Mentoring the application team on how to root cause particularly gnarly issues. 62 62 - Pushing for a closer collaboration with the backend team so that the seams from the client/server organizational split don't "show up" as poor UX in the product. 63 63 64 - I've also helped the team explain the AT protocol to a broader community--first in a [talk](https://www.youtube.com/watch?v=F1sJW6nTP6E) last year, which then crystallized into my recent article called [Open Social](https://overreacted.io/open-social/). 64 + I've also helped the team explain the AT protocol to a broader community--first in a [talk](https://www.youtube.com/watch?v=F1sJW6nTP6E) last year, which then crystallized into my recent article called [Open Social](/open-social/). 65 65 66 66 #### 2015–2023: React at Meta/Facebook 67 67
+1 -1
public/how-imports-work-in-rsc/index.md
··· 196 196 197 197 In that sense, when you `import` some code, you bring it *into* your program. 198 198 199 - But what if we want to write *both our backend and frontend* in JavaScript? (Or, alternatively, what if we realize that adding a [JS BFF can make our app better?](https://overreacted.io/jsx-over-the-wire/#backend-for-frontend)) 199 + But what if we want to write *both our backend and frontend* in JavaScript? (Or, alternatively, what if we realize that adding a [JS BFF can make our app better?](/jsx-over-the-wire/#backend-for-frontend)) 200 200 201 201 --- 202 202
+2 -4
public/impossible-components/client.js
··· 171 171 className="block border-2 px-1 mb-4" 172 172 /> 173 173 </div> 174 - <ul className="unstyled gap-2 flex flex-col"> 174 + <ul className="gap-2 flex flex-col"> 175 175 {sortedItems.map((item) => ( 176 - <li className="unstyled" key={item.id}> 177 - {item.content} 178 - </li> 176 + <li key={item.id}>{item.content}</li> 179 177 ))} 180 178 </ul> 181 179 </>
+1 -1
public/impossible-components/index.md
··· 1143 1143 1144 1144 ### A Note on Terminology 1145 1145 1146 - As in my other [recent](https://overreacted.io/react-for-two-computers/) [articles](https://overreacted.io/jsx-over-the-wire/), I've tried to avoid using the "Server Components" and "Client Components" terminology in this post because it brings up distracting connotations and knee-jerk reactions. (In particular, people tend to assume the "client loads from the server" rather than the "server renders the client" model.) 1146 + As in my other [recent](/react-for-two-computers/) [articles](/jsx-over-the-wire/), I've tried to avoid using the "Server Components" and "Client Components" terminology in this post because it brings up distracting connotations and knee-jerk reactions. (In particular, people tend to assume the "client loads from the server" rather than the "server renders the client" model.) 1147 1147 1148 1148 The "backend components" in this post are officially called Server Components, and the "frontend components" are officially called Client Components. If I could change the official terminology, I probably still would *not.* However, I find that introducing it when you already understand the model (as I hope you do by this point) works better than starting with the terminology. This may eventually stop being a problem if the Server/Client split as modeled by React Server Components ever becomes the generally accepted model of describing distributed composable user interfaces. I think we may get there at some point within the next ten years. 1149 1149
+15 -3
public/impossible-components/server.js
··· 62 62 return ( 63 63 <section className="rounded-md bg-black/5 p-2"> 64 64 <h5 className="font-bold"> 65 - <a href={"/" + slug} target="_blank"> 65 + <a 66 + href={"/" + slug} 67 + target="_blank" 68 + className="underline decoration-[--link] decoration-1 underline-offset-4 text-[--link]" 69 + > 66 70 {data.title} 67 71 </a> 68 72 </h5> ··· 80 84 return ( 81 85 <section className="rounded-md bg-black/5 p-2"> 82 86 <h5 className="font-bold"> 83 - <a href={"/" + slug} target="_blank"> 87 + <a 88 + href={"/" + slug} 89 + target="_blank" 90 + className="underline decoration-[--link] decoration-1 underline-offset-4 text-[--link]" 91 + > 84 92 {data.title} 85 93 </a> 86 94 </h5> ··· 103 111 } 104 112 > 105 113 <h5 className="font-bold"> 106 - <a href={"/" + slug} target="_blank"> 114 + <a 115 + href={"/" + slug} 116 + target="_blank" 117 + className="underline decoration-[--link] decoration-1 underline-offset-4 text-[--link]" 118 + > 107 119 {data.title} 108 120 </a> 109 121 </h5>
+1 -1
public/react-as-a-ui-runtime/index.md
··· 1083 1083 1084 1084 **Of course, `use` is not actually a syntax.** (It wouldn’t bring much benefit and would create a lot of friction.) 1085 1085 1086 - However, React *does* expect that all calls to Hooks happen only at the top level of a component and unconditionally. These [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html) can be enforced with [a linter plugin](https://www.npmjs.com/package/eslint-plugin-react-hooks). There have been heated arguments about this design choice but in practice, I haven’t seen it confusing people. I also wrote about why commonly proposed alternatives [don’t work](https://overreacted.io/why-do-hooks-rely-on-call-order/). 1086 + However, React *does* expect that all calls to Hooks happen only at the top level of a component and unconditionally. These [Rules of Hooks](https://reactjs.org/docs/hooks-rules.html) can be enforced with [a linter plugin](https://www.npmjs.com/package/eslint-plugin-react-hooks). There have been heated arguments about this design choice but in practice, I haven’t seen it confusing people. I also wrote about why commonly proposed alternatives [don’t work](/why-do-hooks-rely-on-call-order/). 1087 1087 1088 1088 Internally, Hooks are implemented as [linked lists](https://dev.to/aspittel/thank-u-next-an-introduction-to-linked-lists-4pph). When you call `useState`, we move the pointer to the next item. When we exit the component’s [“call tree” frame](#call-tree), we save the resulting list there until the next render. 1089 1089
+1 -1
public/the-math-is-haunted/index.md
··· 30 30 31 31 To a mathematician's eye, this syntax looks like stating a theorem. We have the `theorem` keyword, the name of our theorem, a colon `:` before its statement, the statement that we'd like to prove, and `:= by` followed by the proof (`sorry` means that we haven't completed the actual proof yet but we're planning to fill it in later). 32 32 33 - But if you're a programmer, you might notice a hint of something else. That `theorem` looks suspiciously like a function. But then what is `2 = 2`? It looks like a return type of that function. But how can `2 = 2` be a *type*? Isn't `2 = 2` just a boolean? And if `2 = 2` really *is* a type, what are the *values* of that `2 = 2` type? [These are very interesting questions](https://overreacted.io/beyond-booleans/), but we'll have to forget about them for now. 33 + But if you're a programmer, you might notice a hint of something else. That `theorem` looks suspiciously like a function. But then what is `2 = 2`? It looks like a return type of that function. But how can `2 = 2` be a *type*? Isn't `2 = 2` just a boolean? And if `2 = 2` really *is* a type, what are the *values* of that `2 = 2` type? [These are very interesting questions](/beyond-booleans/), but we'll have to forget about them for now. 34 34 35 35 Instead, we'll start by inspecting the proof: 36 36
+5 -1
public/the-two-reacts/post-preview.js
··· 9 9 return ( 10 10 <section className="rounded-md bg-black/5 p-2"> 11 11 <h5 className="font-bold"> 12 - <a href={"/" + slug} target="_blank"> 12 + <a 13 + href={"/" + slug} 14 + target="_blank" 15 + className="underline decoration-[--link] decoration-1 underline-offset-4 text-[--link]" 16 + > 13 17 {data.title} 14 18 </a> 15 19 </h5>