From bb0ac87ce43cc8216601d67a5b2ab653039c259c Mon Sep 17 00:00:00 2001 From: probeiuscorp <70288813+probeiuscorp@users.noreply.github.com> Date: Mon, 8 Jan 2024 19:33:29 -0600 Subject: [PATCH 01/14] Fix typo in experimental_taintObjectReference (#6536) Change untained -> untainted --- .../reference/react/experimental_taintObjectReference.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/reference/react/experimental_taintObjectReference.md b/src/content/reference/react/experimental_taintObjectReference.md index 335f659c..b5b9e513 100644 --- a/src/content/reference/react/experimental_taintObjectReference.md +++ b/src/content/reference/react/experimental_taintObjectReference.md @@ -64,11 +64,11 @@ experimental_taintObjectReference( #### Caveats {/*caveats*/} -- Recreating or cloning a tainted object creates a new untained object which may contain sensitive data. For example, if you have a tainted `user` object, `const userInfo = {name: user.name, ssn: user.ssn}` or `{...user}` will create new objects which are not tainted. `taintObjectReference` only protects against simple mistakes when the object is passed through to a Client Component unchanged. +- Recreating or cloning a tainted object creates a new untainted object which may contain sensitive data. For example, if you have a tainted `user` object, `const userInfo = {name: user.name, ssn: user.ssn}` or `{...user}` will create new objects which are not tainted. `taintObjectReference` only protects against simple mistakes when the object is passed through to a Client Component unchanged. -**Do not rely on just tainting for security.** Tainting an object doesn't prevent leaking of every possible derived value. For example, the clone of a tainted object will create a new untained object. Using data from a tainted object (e.g. `{secret: taintedObj.secret}`) will create a new value or object that is not tainted. Tainting is a layer of protection; a secure app will have multiple layers of protection, well designed APIs, and isolation patterns. +**Do not rely on just tainting for security.** Tainting an object doesn't prevent leaking of every possible derived value. For example, the clone of a tainted object will create a new untainted object. Using data from a tainted object (e.g. `{secret: taintedObj.secret}`) will create a new value or object that is not tainted. Tainting is a layer of protection; a secure app will have multiple layers of protection, well designed APIs, and isolation patterns. From 083c308de8365ba1b9ef6db85d536e5cfc844ec0 Mon Sep 17 00:00:00 2001 From: Abdul Rahman Date: Tue, 9 Jan 2024 07:52:15 +0530 Subject: [PATCH 02/14] Fix(Bug) Update Feedback.tsx component visiblity issue (#6533) * Update Feedback.tsx * Update Feedback.tsx Time reduced to 1.5 seconds, also going to update css * Update index.css CSS updated for feedback component animation. * Update Feedback.tsx * Update index.css --------- Co-authored-by: Ricky --- src/components/Layout/Feedback.tsx | 7 ++++++- src/styles/index.css | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/components/Layout/Feedback.tsx b/src/components/Layout/Feedback.tsx index 8639ce12..0bdbc6b7 100644 --- a/src/components/Layout/Feedback.tsx +++ b/src/components/Layout/Feedback.tsx @@ -4,6 +4,7 @@ import {useState} from 'react'; import {useRouter} from 'next/router'; +import cn from 'classnames'; export function Feedback({onSubmit = () => {}}: {onSubmit?: () => void}) { const {asPath} = useRouter(); @@ -60,7 +61,11 @@ function sendGAEvent(isPositive: boolean) { function SendFeedback({onSubmit}: {onSubmit: () => void}) { const [isSubmitted, setIsSubmitted] = useState(false); return ( -
+

{isSubmitted ? 'Thank you for your feedback!' : 'Is this page useful?'}

diff --git a/src/styles/index.css b/src/styles/index.css index 9e1ec4aa..f22784ce 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -719,3 +719,9 @@ ol.mdx-illustration-block { width: 100%; } } + +.exit { + opacity: 0; + transition: opacity 500ms ease-out; + transition-delay: 1s; +} From 10d4a4dfb3f23dda455519fb73c24ef9a6fc6d26 Mon Sep 17 00:00:00 2001 From: Ricky Date: Mon, 8 Jan 2024 22:07:08 -0500 Subject: [PATCH 03/14] Fix forwardRef and useRef links (#6537) --- src/content/reference/react/forwardRef.md | 2 +- src/content/reference/react/useRef.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/content/reference/react/forwardRef.md b/src/content/reference/react/forwardRef.md index e1e96a20..04d69287 100644 --- a/src/content/reference/react/forwardRef.md +++ b/src/content/reference/react/forwardRef.md @@ -42,7 +42,7 @@ const MyInput = forwardRef(function MyInput(props, ref) { #### Caveats {/*caveats*/} -* In Strict Mode, React will **call your render function twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. If your render function is pure (as it should be), this should not affect the logic of your component. The result from one of the calls will be ignored. +* In Strict Mode, React will **call your render function twice** in order to [help you find accidental impurities.](/reference/react/useState#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. If your render function is pure (as it should be), this should not affect the logic of your component. The result from one of the calls will be ignored. --- diff --git a/src/content/reference/react/useRef.md b/src/content/reference/react/useRef.md index 4af7e953..fcecae71 100644 --- a/src/content/reference/react/useRef.md +++ b/src/content/reference/react/useRef.md @@ -50,7 +50,7 @@ On the next renders, `useRef` will return the same object. * You can mutate the `ref.current` property. Unlike state, it is mutable. However, if it holds an object that is used for rendering (for example, a piece of your state), then you shouldn't mutate that object. * When you change the `ref.current` property, React does not re-render your component. React is not aware of when you change it because a ref is a plain JavaScript object. * Do not write _or read_ `ref.current` during rendering, except for [initialization.](#avoiding-recreating-the-ref-contents) This makes your component's behavior unpredictable. -* In Strict Mode, React will **call your component function twice** in order to [help you find accidental impurities.](#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. Each ref object will be created twice, but one of the versions will be discarded. If your component function is pure (as it should be), this should not affect the behavior. +* In Strict Mode, React will **call your component function twice** in order to [help you find accidental impurities.](/reference/react/useState#my-initializer-or-updater-function-runs-twice) This is development-only behavior and does not affect production. Each ref object will be created twice, but one of the versions will be discarded. If your component function is pure (as it should be), this should not affect the behavior. --- From 53a1f4940ece4f5add7e6d53b10c3052d9ae5e01 Mon Sep 17 00:00:00 2001 From: Kiran Abburi Date: Tue, 9 Jan 2024 23:16:22 +0530 Subject: [PATCH 04/14] Added React Nexus 2024 conference to the conference list (#6538) --- src/content/community/conferences.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/content/community/conferences.md b/src/content/community/conferences.md index 9f020966..b3d9603a 100644 --- a/src/content/community/conferences.md +++ b/src/content/community/conferences.md @@ -20,6 +20,11 @@ June 12 - June 14, 2024. Atlanta, GA, USA [Website](https://renderatl.com) - [Discord](https://www.renderatl.com/discord) - [Twitter](https://twitter.com/renderATL) - [Instagram](https://www.instagram.com/renderatl/) - [Facebook](https://www.facebook.com/renderatl/) - [LinkedIn](https://www.linkedin.com/company/renderatl) - [Podcast](https://www.renderatl.com/culture-and-code#/) +### React Nexus 2024 {/*react-nexus-2024*/} +July 04 & 05, 2024. Bangalore, India (In-person event) + +[Website](https://reactnexus.com/) - [Twitter](https://twitter.com/ReactNexus) - [Linkedin](https://www.linkedin.com/company/react-nexus) - [YouTube](https://www.youtube.com/reactify_in) + ### React India 2024 {/*react-india-2024*/} October 17 - 19, 2024. In-person in Goa, India (hybrid event) + Oct 15 2024 - remote day From 6f8d4e60999f335ef2dd4eb2650e701deb7bb581 Mon Sep 17 00:00:00 2001 From: Ricky Date: Tue, 9 Jan 2024 12:59:49 -0500 Subject: [PATCH 05/14] Add pointer-events:none to .exit (#6541) --- src/styles/index.css | 1 + 1 file changed, 1 insertion(+) diff --git a/src/styles/index.css b/src/styles/index.css index f22784ce..cfd82dc0 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -724,4 +724,5 @@ ol.mdx-illustration-block { opacity: 0; transition: opacity 500ms ease-out; transition-delay: 1s; + pointer-events: none; } From 983bda90ccc10b52d4615bfd4769a2dfbe6802ba Mon Sep 17 00:00:00 2001 From: TymeeK <38594702+TymeeK@users.noreply.github.com> Date: Wed, 10 Jan 2024 12:53:05 -0800 Subject: [PATCH 06/14] Removed unused nextId variable (#6470) --- src/content/learn/updating-arrays-in-state.md | 1 - 1 file changed, 1 deletion(-) diff --git a/src/content/learn/updating-arrays-in-state.md b/src/content/learn/updating-arrays-in-state.md index ea861931..61e4f4e2 100644 --- a/src/content/learn/updating-arrays-in-state.md +++ b/src/content/learn/updating-arrays-in-state.md @@ -409,7 +409,6 @@ For example: ```js import { useState } from 'react'; -let nextId = 3; const initialList = [ { id: 0, title: 'Big Bellies' }, { id: 1, title: 'Lunar Landscape' }, From 9d8c5add07179839a00017f0678bffb70d3ba211 Mon Sep 17 00:00:00 2001 From: Ricky Date: Wed, 10 Jan 2024 17:46:48 -0500 Subject: [PATCH 07/14] Revert "Add useState semicolon (#5823)" (#6543) This reverts commit e18bb3b25731411acfd554a44efd0b7189f577bb. --- src/content/reference/react/useState.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/reference/react/useState.md b/src/content/reference/react/useState.md index ee86f0e8..48d96f8e 100644 --- a/src/content/reference/react/useState.md +++ b/src/content/reference/react/useState.md @@ -7,7 +7,7 @@ title: useState `useState` is a React Hook that lets you add a [state variable](/learn/state-a-components-memory) to your component. ```js -const [state, setState] = useState(initialState); +const [state, setState] = useState(initialState) ``` From eff3955836a3391d5795821d22328b86fc9f7e8e Mon Sep 17 00:00:00 2001 From: Taz Singh Date: Thu, 11 Jan 2024 19:25:58 +0000 Subject: [PATCH 08/14] Update meetups.md (#6540) --- src/content/community/meetups.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/content/community/meetups.md b/src/content/community/meetups.md index 000eeb43..a12a5349 100644 --- a/src/content/community/meetups.md +++ b/src/content/community/meetups.md @@ -72,7 +72,8 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet ## England (UK) {/*england-uk*/} * [Manchester](https://www.meetup.com/Manchester-React-User-Group/) * [React.JS Girls London](https://www.meetup.com/ReactJS-Girls-London/) -* [React London : Bring Your Own Project](https://www.meetup.com/React-London-Bring-Your-Own-Project/) +* [React Advanced London](https://guild.host/react-advanced-london) +* [React Native London](https://guild.host/RNLDN) ## France {/*france*/} * [Nantes](https://www.meetup.com/React-Nantes/) @@ -86,7 +87,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [Karlsruhe](https://www.meetup.com/react_ka/) * [Kiel](https://www.meetup.com/Kiel-React-Native-Meetup/) * [Munich](https://www.meetup.com/ReactJS-Meetup-Munich/) -* [React Berlin](https://www.meetup.com/React-Open-Source/) +* [React Berlin](https://guild.host/react-berlin) ## Greece {/*greece*/} * [Athens](https://www.meetup.com/React-To-React-Athens-MeetUp/) @@ -108,7 +109,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [Indonesia](https://www.meetup.com/reactindonesia/) ## Ireland {/*ireland*/} -* [Dublin](https://www.meetup.com/ReactJS-Dublin/) +* [Dublin](https://guild.host/reactjs-dublin) ## Israel {/*israel*/} * [Tel Aviv](https://www.meetup.com/ReactJS-Israel/) @@ -124,7 +125,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [Penang](https://www.facebook.com/groups/reactpenang/) ## Netherlands {/*netherlands*/} -* [Amsterdam](https://www.meetup.com/React-Amsterdam/) +* [Amsterdam](https://guild.host/react-amsterdam) ## New Zealand {/*new-zealand*/} * [Wellington](https://www.meetup.com/React-Wellington/) @@ -201,6 +202,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [New York, NY - React Ladies](https://www.meetup.com/React-Ladies/) * [New York, NY - React Native](https://www.meetup.com/React-Native-NYC/) * [New York, NY - useReactNYC](https://www.meetup.com/useReactNYC/) +* [New York, NY - React.NYC](https://guild.host/react-nyc) * [Omaha, NE - ReactJS/React Native](https://www.meetup.com/omaha-react-meetup-group/) * [Palo Alto, CA - React Native](https://www.meetup.com/React-Native-Silicon-Valley/) * [Philadelphia, PA - ReactJS](https://www.meetup.com/Reactadelphia/) From a3f5b133a18be27ddcc44189a0f39375744bd3df Mon Sep 17 00:00:00 2001 From: Zoey <38047633+zoephyr@users.noreply.github.com> Date: Thu, 11 Jan 2024 15:28:02 -0500 Subject: [PATCH 09/14] fix: Grammar in versioning-policy.md (#6539) Adds a missing word in the documentation describing canary versioning --- src/content/community/versioning-policy.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/community/versioning-policy.md b/src/content/community/versioning-policy.md index eb38f8c2..fad926c5 100644 --- a/src/content/community/versioning-policy.md +++ b/src/content/community/versioning-policy.md @@ -80,7 +80,7 @@ This section will be most relevant to developers who work on frameworks, librari Each of React's release channels is designed for a distinct use case: - [**Latest**](#latest-channel) is for stable, semver React releases. It's what you get when you install React from npm. This is the channel you're already using today. **User-facing applications that consume React directly use this channel.** -- [**Canary**](#canary-channel) tracks the main branch of the React source code repository. Think of these as release candidates for the next semver release. **[Frameworks or other curated setups may choose to use this channel with a pinned version of React.](/blog/2023/05/03/react-canaries) You can also Canaries for integration testing between React and third party projects.** +- [**Canary**](#canary-channel) tracks the main branch of the React source code repository. Think of these as release candidates for the next semver release. **[Frameworks or other curated setups may choose to use this channel with a pinned version of React.](/blog/2023/05/03/react-canaries) You can also use Canaries for integration testing between React and third party projects.** - [**Experimental**](#experimental-channel) includes experimental APIs and features that aren't available in the stable releases. These also track the main branch, but with additional feature flags turned on. Use this to try out upcoming features before they are released. All releases are published to npm, but only Latest uses semantic versioning. Prereleases (those in the Canary and Experimental channels) have versions generated from a hash of their contents and the commit date, e.g. `18.3.0-canary-388686f29-20230503` for Canary and `0.0.0-experimental-388686f29-20230503` for Experimental. From 097254168350cbb78d6a121653acfa278b84ab1a Mon Sep 17 00:00:00 2001 From: Ben Amor Aymen Date: Fri, 12 Jan 2024 21:22:35 +0100 Subject: [PATCH 10/14] Add React Paris 2024 conference + React Brussels videos link (#6547) Co-authored-by: Aimen Ben Amor --- src/content/community/conferences.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/content/community/conferences.md b/src/content/community/conferences.md index b3d9603a..7e3f6672 100644 --- a/src/content/community/conferences.md +++ b/src/content/community/conferences.md @@ -10,6 +10,11 @@ Do you know of a local React.js conference? Add it here! (Please keep the list c ## Upcoming Conferences {/*upcoming-conferences*/} +### React Paris 2024 {/*react-paris-2024*/} +March 22, 2024. In-person in Paris, France + Remote (hybrid) + +[Website](https://react.paris/) - [Twitter](https://twitter.com/BeJS_) - [LinkedIn](https://www.linkedin.com/events/7150816372074192900/comments/) + ### App.js Conf 2024 {/*appjs-conf-2024*/} May 22 - 24, 2024. In-person in Kraków, Poland + remote @@ -55,7 +60,7 @@ October 20 & 23, 2023. In-person in London, UK + remote first interactivity (hyb ### React Brussels 2023 {/*react-brussels-2023*/} October 13th 2023. In-person in Brussels, Belgium + Remote (hybrid) -[Website](https://www.react.brussels/) - [Twitter](https://twitter.com/BrusselsReact) +[Website](https://www.react.brussels/) - [Twitter](https://twitter.com/BrusselsReact) - [Videos](https://www.youtube.com/playlist?list=PL53Z0yyYnpWh85KeMomUoVz8_brrmh_aC) ### React India 2023 {/*react-india-2023*/} October 5 - 7, 2023. In-person in Goa, India (hybrid event) + Oct 3 2023 - remote day From 6987f0fb30899e00f5adf6945088c3fa3492e452 Mon Sep 17 00:00:00 2001 From: Prajwal Kulkarni Date: Sat, 13 Jan 2024 02:45:04 +0530 Subject: [PATCH 11/14] Fix overflowing text content in footer link (#6519) * Fix overflowing text content in footer link Add an ellipsis to overflowing text in the footer section for navigating between different references. * Add min and max width to nextlink class * Add minwidth to tailwind config * Wrap string beyond max width * Remove title attribute from span element --- src/components/DocsFooter.tsx | 16 +++++++++------- tailwind.config.js | 5 +++++ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/components/DocsFooter.tsx b/src/components/DocsFooter.tsx index 2fdbb046..5f2330e7 100644 --- a/src/components/DocsFooter.tsx +++ b/src/components/DocsFooter.tsx @@ -27,7 +27,7 @@ export const DocsPageFooter = memo( <> {prevRoute?.path || nextRoute?.path ? ( <> -
+
{prevRoute?.path ? ( - - +
+ {type} - {title} - + + {title} + +
); } diff --git a/tailwind.config.js b/tailwind.config.js index d528ff49..a9488ccc 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -57,8 +57,13 @@ module.exports = { 'meta-gradient-dark': "url('/images/meta-gradient-dark.png')", }, maxWidth: { + ...defaultTheme.maxWidth, xs: '21rem', }, + minWidth:{ + ...defaultTheme.minWidth, + 80: '20rem', + }, outline: { blue: ['1px auto ' + colors.link, '3px'], }, From 8d2664b8062a2eea1306e4b63dcecc0a17febe8c Mon Sep 17 00:00:00 2001 From: Sukka Date: Sat, 13 Jan 2024 05:18:21 +0800 Subject: [PATCH 12/14] Feat: error-decoder (#6214) * Feat: port error-decoder * Fix: do not choke on empty invariant * Refactor: read url query from `useRouter` * Fix: argsList can contains `undefined` * Fix: handle empty string arg * Feat: move error decoder to the separate page * Fix: wrap error decoder header in * Perf: cache GitHub RAW requests * Refactor: apply code review suggestions * Fix: build error * Refactor: apply code review suggestions * Discard changes to src/content/index.md * Fix: animation duration/delay * Refactor: read error page from markdown * Fix lint * Fix /error being 404 * Prevent `_default.md` being included in `[[...markdownPath]].md` * Fix custom error markdown reading * Updates --------- Co-authored-by: Ricky Hanlon --- src/components/ErrorDecoderContext.tsx | 23 ++++ src/components/MDX/ErrorDecoder.tsx | 107 +++++++++++++++++ src/components/MDX/MDXComponents.tsx | 3 + src/content/errors/377.md | 13 ++ src/content/errors/generic.md | 11 ++ src/content/errors/index.md | 10 ++ src/pages/[[...markdownPath]].js | 148 ++--------------------- src/pages/errors/[errorCode].tsx | 157 +++++++++++++++++++++++++ src/pages/errors/index.tsx | 3 + src/utils/compileMDX.ts | 153 ++++++++++++++++++++++++ tailwind.config.js | 11 ++ 11 files changed, 504 insertions(+), 135 deletions(-) create mode 100644 src/components/ErrorDecoderContext.tsx create mode 100644 src/components/MDX/ErrorDecoder.tsx create mode 100644 src/content/errors/377.md create mode 100644 src/content/errors/generic.md create mode 100644 src/content/errors/index.md create mode 100644 src/pages/errors/[errorCode].tsx create mode 100644 src/pages/errors/index.tsx create mode 100644 src/utils/compileMDX.ts diff --git a/src/components/ErrorDecoderContext.tsx b/src/components/ErrorDecoderContext.tsx new file mode 100644 index 00000000..080969ef --- /dev/null +++ b/src/components/ErrorDecoderContext.tsx @@ -0,0 +1,23 @@ +// Error Decoder requires reading pregenerated error message from getStaticProps, +// but MDX component doesn't support props. So we use React Context to populate +// the value without prop-drilling. +// TODO: Replace with React.cache + React.use when migrating to Next.js App Router + +import {createContext, useContext} from 'react'; + +const notInErrorDecoderContext = Symbol('not in error decoder context'); + +export const ErrorDecoderContext = createContext< + | {errorMessage: string | null; errorCode: string | null} + | typeof notInErrorDecoderContext +>(notInErrorDecoderContext); + +export const useErrorDecoderParams = () => { + const params = useContext(ErrorDecoderContext); + + if (params === notInErrorDecoderContext) { + throw new Error('useErrorDecoder must be used in error decoder pages only'); + } + + return params; +}; diff --git a/src/components/MDX/ErrorDecoder.tsx b/src/components/MDX/ErrorDecoder.tsx new file mode 100644 index 00000000..daa71328 --- /dev/null +++ b/src/components/MDX/ErrorDecoder.tsx @@ -0,0 +1,107 @@ +import {useEffect, useState} from 'react'; +import {useErrorDecoderParams} from '../ErrorDecoderContext'; +import cn from 'classnames'; + +function replaceArgs( + msg: string, + argList: Array, + replacer = '[missing argument]' +): string { + let argIdx = 0; + return msg.replace(/%s/g, function () { + const arg = argList[argIdx++]; + // arg can be an empty string: ?args[0]=&args[1]=count + return arg === undefined || arg === '' ? replacer : arg; + }); +} + +/** + * Sindre Sorhus + * Released under MIT license + * https://github.com/sindresorhus/linkify-urls/blob/edd75a64a9c36d7025f102f666ddbb6cf0afa7cd/index.js#L4C25-L4C137 + * + * The regex is used to extract URL from the string for linkify. + */ +const urlRegex = + /((? { + if (i % 2 === 1) { + return ( + + {message} + + ); + } + return message; + }); +} + +// `?args[]=foo&args[]=bar` +// or `// ?args[0]=foo&args[1]=bar` +function parseQueryString(search: string): Array { + const rawQueryString = search.substring(1); + if (!rawQueryString) { + return []; + } + + const args: Array = []; + + const queries = rawQueryString.split('&'); + for (let i = 0; i < queries.length; i++) { + const query = decodeURIComponent(queries[i]); + if (query.startsWith('args[')) { + args.push(query.slice(query.indexOf(']=') + 2)); + } + } + + return args; +} + +export default function ErrorDecoder() { + const {errorMessage} = useErrorDecoderParams(); + /** error messages that contain %s require reading location.search */ + const hasParams = errorMessage?.includes('%s'); + const [message, setMessage] = useState(() => + errorMessage ? urlify(errorMessage) : null + ); + + const [isReady, setIsReady] = useState(errorMessage == null || !hasParams); + + useEffect(() => { + if (errorMessage == null || !hasParams) { + return; + } + + setMessage( + urlify( + replaceArgs( + errorMessage, + parseQueryString(window.location.search), + '[missing argument]' + ) + ) + ); + setIsReady(true); + }, [hasParams, errorMessage]); + + return ( + + {message} + + ); +} diff --git a/src/components/MDX/MDXComponents.tsx b/src/components/MDX/MDXComponents.tsx index 74ab788b..3528a9a1 100644 --- a/src/components/MDX/MDXComponents.tsx +++ b/src/components/MDX/MDXComponents.tsx @@ -31,6 +31,8 @@ import {TocContext} from './TocContext'; import type {Toc, TocItem} from './TocContext'; import {TeamMember} from './TeamMember'; +import ErrorDecoder from './ErrorDecoder'; + function CodeStep({children, step}: {children: any; step: number}) { return ( + +In the minified production build of React, we avoid sending down full error messages in order to reduce the number of bytes sent over the wire. + + + +We highly recommend using the development build locally when debugging your app since it tracks additional debug info and provides helpful warnings about potential problems in your apps, but if you encounter an exception while using the production build, this page will reassemble the original error message. + +The full text of the error you just encountered is: + + + +This error occurs when you pass a BigInt value from a Server Component to a Client Component. diff --git a/src/content/errors/generic.md b/src/content/errors/generic.md new file mode 100644 index 00000000..27c3ca52 --- /dev/null +++ b/src/content/errors/generic.md @@ -0,0 +1,11 @@ + + +In the minified production build of React, we avoid sending down full error messages in order to reduce the number of bytes sent over the wire. + + + +We highly recommend using the development build locally when debugging your app since it tracks additional debug info and provides helpful warnings about potential problems in your apps, but if you encounter an exception while using the production build, this page will reassemble the original error message. + +The full text of the error you just encountered is: + + diff --git a/src/content/errors/index.md b/src/content/errors/index.md new file mode 100644 index 00000000..25746d25 --- /dev/null +++ b/src/content/errors/index.md @@ -0,0 +1,10 @@ + + +In the minified production build of React, we avoid sending down full error messages in order to reduce the number of bytes sent over the wire. + + + + +We highly recommend using the development build locally when debugging your app since it tracks additional debug info and provides helpful warnings about potential problems in your apps, but if you encounter an exception while using the production build, the error message will include just a link to the docs for the error. + +For an example, see: [https://react.dev/errors/149](/errors/421). diff --git a/src/pages/[[...markdownPath]].js b/src/pages/[[...markdownPath]].js index d1df93d3..76532361 100644 --- a/src/pages/[[...markdownPath]].js +++ b/src/pages/[[...markdownPath]].js @@ -4,14 +4,14 @@ import {Fragment, useMemo} from 'react'; import {useRouter} from 'next/router'; -import {MDXComponents} from 'components/MDX/MDXComponents'; import {Page} from 'components/Layout/Page'; import sidebarHome from '../sidebarHome.json'; import sidebarLearn from '../sidebarLearn.json'; import sidebarReference from '../sidebarReference.json'; import sidebarCommunity from '../sidebarCommunity.json'; import sidebarBlog from '../sidebarBlog.json'; - +import {MDXComponents} from 'components/MDX/MDXComponents'; +import compileMDX from 'utils/compileMDX'; export default function Layout({content, toc, meta}) { const parsedContent = useMemo( () => JSON.parse(content, reviveNodeOnClient), @@ -94,20 +94,10 @@ function reviveNodeOnClient(key, val) { } } -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -// ~~~~ IMPORTANT: BUMP THIS IF YOU CHANGE ANY CODE BELOW ~~~ -const DISK_CACHE_BREAKER = 7; -// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // Put MDX output into JSON for client. export async function getStaticProps(context) { const fs = require('fs'); - const { - prepareMDX, - PREPARE_MDX_CACHE_BREAKER, - } = require('../utils/prepareMDX'); const rootDir = process.cwd() + '/src/content/'; - const mdxComponentNames = Object.keys(MDXComponents); // Read MDX from the file. let path = (context.params.markdownPath || []).join('/') || 'index'; @@ -118,132 +108,14 @@ export async function getStaticProps(context) { mdx = fs.readFileSync(rootDir + path + '/index.md', 'utf8'); } - // See if we have a cached output first. - const {FileStore, stableHash} = require('metro-cache'); - const store = new FileStore({ - root: process.cwd() + '/node_modules/.cache/react-docs-mdx/', - }); - const hash = Buffer.from( - stableHash({ - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // ~~~~ IMPORTANT: Everything that the code below may rely on. - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - mdx, - mdxComponentNames, - DISK_CACHE_BREAKER, - PREPARE_MDX_CACHE_BREAKER, - lockfile: fs.readFileSync(process.cwd() + '/yarn.lock', 'utf8'), - }) - ); - const cached = await store.get(hash); - if (cached) { - console.log( - 'Reading compiled MDX for /' + path + ' from ./node_modules/.cache/' - ); - return cached; - } - if (process.env.NODE_ENV === 'production') { - console.log( - 'Cache miss for MDX for /' + path + ' from ./node_modules/.cache/' - ); - } - - // If we don't add these fake imports, the MDX compiler - // will insert a bunch of opaque components we can't introspect. - // This will break the prepareMDX() call below. - let mdxWithFakeImports = - mdx + - '\n\n' + - mdxComponentNames - .map((key) => 'import ' + key + ' from "' + key + '";\n') - .join('\n'); - - // Turn the MDX we just read into some JS we can execute. - const {remarkPlugins} = require('../../plugins/markdownToHtml'); - const {compile: compileMdx} = await import('@mdx-js/mdx'); - const visit = (await import('unist-util-visit')).default; - const jsxCode = await compileMdx(mdxWithFakeImports, { - remarkPlugins: [ - ...remarkPlugins, - (await import('remark-gfm')).default, - (await import('remark-frontmatter')).default, - ], - rehypePlugins: [ - // Support stuff like ```js App.js {1-5} active by passing it through. - function rehypeMetaAsAttributes() { - return (tree) => { - visit(tree, 'element', (node) => { - if (node.tagName === 'code' && node.data && node.data.meta) { - node.properties.meta = node.data.meta; - } - }); - }; - }, - ], - }); - const {transform} = require('@babel/core'); - const jsCode = await transform(jsxCode, { - plugins: ['@babel/plugin-transform-modules-commonjs'], - presets: ['@babel/preset-react'], - }).code; - - // Prepare environment for MDX. - let fakeExports = {}; - const fakeRequire = (name) => { - if (name === 'react/jsx-runtime') { - return require('react/jsx-runtime'); - } else { - // For each fake MDX import, give back the string component name. - // It will get serialized later. - return name; - } - }; - const evalJSCode = new Function('require', 'exports', jsCode); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - // THIS IS A BUILD-TIME EVAL. NEVER DO THIS WITH UNTRUSTED MDX (LIKE FROM CMS)!!! - // In this case it's okay because anyone who can edit our MDX can also edit this file. - evalJSCode(fakeRequire, fakeExports); - // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - const reactTree = fakeExports.default({}); - - // Pre-process MDX output and serialize it. - let {toc, children} = prepareMDX(reactTree.props.children); - if (path === 'index') { - toc = []; - } - - // Parse Frontmatter headers from MDX. - const fm = require('gray-matter'); - const meta = fm(mdx).data; - - const output = { + const {toc, content, meta} = await compileMDX(mdx, path, {}); + return { props: { - content: JSON.stringify(children, stringifyNodeOnServer), - toc: JSON.stringify(toc, stringifyNodeOnServer), + toc, + content, meta, }, }; - - // Serialize a server React tree node to JSON. - function stringifyNodeOnServer(key, val) { - if (val != null && val.$$typeof === Symbol.for('react.element')) { - // Remove fake MDX props. - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const {mdxType, originalType, parentName, ...cleanProps} = val.props; - return [ - '$r', - typeof val.type === 'string' ? val.type : mdxType, - val.key, - cleanProps, - ]; - } else { - return val; - } - } - - // Cache it on the disk. - await store.set(hash, output); - return output; } // Collect all MDX files for static generation. @@ -266,7 +138,12 @@ export async function getStaticPaths() { : res.slice(rootDir.length + 1); }) ); - return files.flat().filter((file) => file.endsWith('.md')); + return ( + files + .flat() + // ignores `errors/*.md`, they will be handled by `pages/errors/[errorCode].tsx` + .filter((file) => file.endsWith('.md') && !file.startsWith('errors/')) + ); } // 'foo/bar/baz.md' -> ['foo', 'bar', 'baz'] @@ -280,6 +157,7 @@ export async function getStaticPaths() { } const files = await getFiles(rootDir); + const paths = files.map((file) => ({ params: { markdownPath: getSegments(file), diff --git a/src/pages/errors/[errorCode].tsx b/src/pages/errors/[errorCode].tsx new file mode 100644 index 00000000..f1a54a3d --- /dev/null +++ b/src/pages/errors/[errorCode].tsx @@ -0,0 +1,157 @@ +import {Fragment, useMemo} from 'react'; +import {Page} from 'components/Layout/Page'; +import {MDXComponents} from 'components/MDX/MDXComponents'; +import sidebarLearn from 'sidebarLearn.json'; +import type {RouteItem} from 'components/Layout/getRouteMeta'; +import {GetStaticPaths, GetStaticProps, InferGetStaticPropsType} from 'next'; +import {ErrorDecoderContext} from 'components/ErrorDecoderContext'; +import compileMDX from 'utils/compileMDX'; + +interface ErrorDecoderProps { + errorCode: string | null; + errorMessage: string | null; + content: string; + toc: string; + meta: any; +} + +export default function ErrorDecoderPage({ + errorMessage, + errorCode, + content, +}: InferGetStaticPropsType) { + const parsedContent = useMemo( + () => JSON.parse(content, reviveNodeOnClient), + [content] + ); + + return ( + + + {parsedContent} + {/* +

+ We highly recommend using the development build locally when debugging + your app since it tracks additional debug info and provides helpful + warnings about potential problems in your apps, but if you encounter + an exception while using the production build, this page will + reassemble the original error message. +

+ +
*/} +
+
+ ); +} + +// Deserialize a client React tree from JSON. +function reviveNodeOnClient(key: unknown, val: any) { + if (Array.isArray(val) && val[0] == '$r') { + // Assume it's a React element. + let type = val[1]; + let key = val[2]; + let props = val[3]; + if (type === 'wrapper') { + type = Fragment; + props = {children: props.children}; + } + if (type in MDXComponents) { + type = MDXComponents[type as keyof typeof MDXComponents]; + } + if (!type) { + console.error('Unknown type: ' + type); + type = Fragment; + } + return { + $$typeof: Symbol.for('react.element'), + type: type, + key: key, + ref: null, + props: props, + _owner: null, + }; + } else { + return val; + } +} + +/** + * Next.js Page Router doesn't have a way to cache specific data fetching request. + * But since Next.js uses limited number of workers, keep "cachedErrorCodes" as a + * module level memory cache can reduce the number of requests down to once per worker. + * + * TODO: use `next/unstable_cache` when migrating to Next.js App Router + */ +let cachedErrorCodes: Record | null = null; + +export const getStaticProps: GetStaticProps = async ({ + params, +}) => { + const errorCodes: {[key: string]: string} = (cachedErrorCodes ||= await ( + await fetch( + 'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json' + ) + ).json()); + + const code = typeof params?.errorCode === 'string' ? params?.errorCode : null; + if (code && !errorCodes[code]) { + return { + notFound: true, + }; + } + + const fs = require('fs'); + const rootDir = process.cwd() + '/src/content/errors'; + + // Read MDX from the file. + let path = params?.errorCode || 'index'; + let mdx; + try { + mdx = fs.readFileSync(rootDir + '/' + path + '.md', 'utf8'); + } catch { + // if [errorCode].md is not found, fallback to generic.md + mdx = fs.readFileSync(rootDir + '/generic.md', 'utf8'); + } + + const {content, toc, meta} = await compileMDX(mdx, path, {code, errorCodes}); + + return { + props: { + content, + toc, + meta, + errorCode: code, + errorMessage: code ? errorCodes[code] : null, + }, + }; +}; + +export const getStaticPaths: GetStaticPaths = async () => { + /** + * Fetch error codes from GitHub + */ + const errorCodes = (cachedErrorCodes ||= await ( + await fetch( + 'https://raw.githubusercontent.com/facebook/react/main/scripts/error-codes/codes.json' + ) + ).json()); + + const paths = Object.keys(errorCodes).map((code) => ({ + params: { + errorCode: code, + }, + })); + + return { + paths, + fallback: 'blocking', + }; +}; diff --git a/src/pages/errors/index.tsx b/src/pages/errors/index.tsx new file mode 100644 index 00000000..d7742f03 --- /dev/null +++ b/src/pages/errors/index.tsx @@ -0,0 +1,3 @@ +import ErrorDecoderPage from './[errorCode]'; +export default ErrorDecoderPage; +export {getStaticProps} from './[errorCode]'; diff --git a/src/utils/compileMDX.ts b/src/utils/compileMDX.ts new file mode 100644 index 00000000..5f54d12b --- /dev/null +++ b/src/utils/compileMDX.ts @@ -0,0 +1,153 @@ +import {MDXComponents} from 'components/MDX/MDXComponents'; + +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +// ~~~~ IMPORTANT: BUMP THIS IF YOU CHANGE ANY CODE BELOW ~~~ +const DISK_CACHE_BREAKER = 8; +// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +export default async function compileMDX( + mdx: string, + path: string | string[], + params: {[key: string]: any} +): Promise<{content: string; toc: string; meta: any}> { + const fs = require('fs'); + const { + prepareMDX, + PREPARE_MDX_CACHE_BREAKER, + } = require('../utils/prepareMDX'); + const mdxComponentNames = Object.keys(MDXComponents); + + // See if we have a cached output first. + const {FileStore, stableHash} = require('metro-cache'); + const store = new FileStore({ + root: process.cwd() + '/node_modules/.cache/react-docs-mdx/', + }); + const hash = Buffer.from( + stableHash({ + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // ~~~~ IMPORTANT: Everything that the code below may rely on. + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + mdx, + ...params, + mdxComponentNames, + DISK_CACHE_BREAKER, + PREPARE_MDX_CACHE_BREAKER, + lockfile: fs.readFileSync(process.cwd() + '/yarn.lock', 'utf8'), + }) + ); + const cached = await store.get(hash); + if (cached) { + console.log( + 'Reading compiled MDX for /' + path + ' from ./node_modules/.cache/' + ); + return cached; + } + if (process.env.NODE_ENV === 'production') { + console.log( + 'Cache miss for MDX for /' + path + ' from ./node_modules/.cache/' + ); + } + + // If we don't add these fake imports, the MDX compiler + // will insert a bunch of opaque components we can't introspect. + // This will break the prepareMDX() call below. + let mdxWithFakeImports = + mdx + + '\n\n' + + mdxComponentNames + .map((key) => 'import ' + key + ' from "' + key + '";\n') + .join('\n'); + + // Turn the MDX we just read into some JS we can execute. + const {remarkPlugins} = require('../../plugins/markdownToHtml'); + const {compile: compileMdx} = await import('@mdx-js/mdx'); + const visit = (await import('unist-util-visit')).default; + const jsxCode = await compileMdx(mdxWithFakeImports, { + remarkPlugins: [ + ...remarkPlugins, + (await import('remark-gfm')).default, + (await import('remark-frontmatter')).default, + ], + rehypePlugins: [ + // Support stuff like ```js App.js {1-5} active by passing it through. + function rehypeMetaAsAttributes() { + return (tree) => { + visit(tree, 'element', (node) => { + if ( + // @ts-expect-error -- tagName is a valid property + node.tagName === 'code' && + node.data && + node.data.meta + ) { + // @ts-expect-error -- properties is a valid property + node.properties.meta = node.data.meta; + } + }); + }; + }, + ], + }); + const {transform} = require('@babel/core'); + const jsCode = await transform(jsxCode, { + plugins: ['@babel/plugin-transform-modules-commonjs'], + presets: ['@babel/preset-react'], + }).code; + + // Prepare environment for MDX. + let fakeExports = {}; + const fakeRequire = (name: string) => { + if (name === 'react/jsx-runtime') { + return require('react/jsx-runtime'); + } else { + // For each fake MDX import, give back the string component name. + // It will get serialized later. + return name; + } + }; + const evalJSCode = new Function('require', 'exports', jsCode); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + // THIS IS A BUILD-TIME EVAL. NEVER DO THIS WITH UNTRUSTED MDX (LIKE FROM CMS)!!! + // In this case it's okay because anyone who can edit our MDX can also edit this file. + evalJSCode(fakeRequire, fakeExports); + // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + + // @ts-expect-error -- default exports is existed after eval + const reactTree = fakeExports.default({}); + + // Pre-process MDX output and serialize it. + let {toc, children} = prepareMDX(reactTree.props.children); + if (path === 'index') { + toc = []; + } + + // Parse Frontmatter headers from MDX. + const fm = require('gray-matter'); + const meta = fm(mdx).data; + + const output = { + content: JSON.stringify(children, stringifyNodeOnServer), + toc: JSON.stringify(toc, stringifyNodeOnServer), + meta, + }; + + // Serialize a server React tree node to JSON. + function stringifyNodeOnServer(key: unknown, val: any) { + if (val != null && val.$$typeof === Symbol.for('react.element')) { + // Remove fake MDX props. + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const {mdxType, originalType, parentName, ...cleanProps} = val.props; + return [ + '$r', + typeof val.type === 'string' ? val.type : mdxType, + val.key, + cleanProps, + ]; + } else { + return val; + } + } + + // Cache it on the disk. + await store.set(hash, output); + return output; +} diff --git a/tailwind.config.js b/tailwind.config.js index a9488ccc..cf34559b 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -106,6 +106,7 @@ module.exports = { marquee2: 'marquee2 40s linear infinite', 'large-marquee': 'large-marquee 80s linear infinite', 'large-marquee2': 'large-marquee2 80s linear infinite', + 'fade-up': 'fade-up 1s 100ms both', }, keyframes: { shimmer: { @@ -143,6 +144,16 @@ module.exports = { '0%': {transform: 'translateX(200%)'}, '100%': {transform: 'translateX(0%)'}, }, + 'fade-up': { + '0%': { + opacity: '0', + transform: 'translateY(2rem)', + }, + '100%': { + opacity: '1', + transform: 'translateY(0)', + }, + }, }, colors, gridTemplateColumns: { From df8f301c2bc78f80244453c86b6b815a184e0c38 Mon Sep 17 00:00:00 2001 From: Bartosz Klonowski <70535775+BartoszKlonowski@users.noreply.github.com> Date: Sat, 13 Jan 2024 00:43:55 +0100 Subject: [PATCH 13/14] Use 'trim()' method in `useContext` page to prevent form from accepting whitespaces (#6549) --- src/content/reference/react/useContext.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/content/reference/react/useContext.md b/src/content/reference/react/useContext.md index bba445d0..ce06e703 100644 --- a/src/content/reference/react/useContext.md +++ b/src/content/reference/react/useContext.md @@ -457,7 +457,7 @@ function LoginForm() { const {setCurrentUser} = useContext(CurrentUserContext); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); - const canLogin = firstName !== '' && lastName !== ''; + const canLogin = firstName.trim() !== '' && lastName.trim() !== ''; return ( <>