diff --git a/.gitignore b/.gitignore index d8bec488b..7bf71dbc5 100644 --- a/.gitignore +++ b/.gitignore @@ -36,3 +36,6 @@ yarn-error.log* # external fonts public/fonts/**/Optimistic_*.woff2 + +# rss +public/rss.xml diff --git a/colors.js b/colors.js index acf8214ee..872f33cac 100644 --- a/colors.js +++ b/colors.js @@ -11,7 +11,7 @@ module.exports = { tertiary: '#5E687E', // gray-50 'tertiary-dark': '#99A1B3', // gray-30 link: '#087EA4', // blue-50 - 'link-dark': '#149ECA', // blue-40 + 'link-dark': '#58C4DC', // blue-40 syntax: '#EBECF0', // gray-10 wash: '#FFFFFF', 'wash-dark': '#23272F', // gray-90 @@ -23,6 +23,8 @@ module.exports = { 'border-dark': '#343A46', // gray-80 'secondary-button': '#EBECF0', // gray-10 'secondary-button-dark': '#404756', // gray-70 + brand: '#087EA4', // blue-40 + 'brand-dark': '#58C4DC', // blue-40 // Gray 'gray-95': '#16181D', diff --git a/package.json b/package.json index d75201780..1f63eb121 100644 --- a/package.json +++ b/package.json @@ -15,17 +15,19 @@ "prettier:diff": "yarn nit:source", "lint-heading-ids": "node scripts/headingIdLinter.js", "fix-headings": "node scripts/headingIdLinter.js --fix", - "ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids", + "ci-check": "npm-run-all prettier:diff --parallel lint tsc lint-heading-ids rss", "tsc": "tsc --noEmit", "start": "next start", "postinstall": "patch-package && (is-ci || husky install .husky)", - "check-all": "npm-run-all prettier lint:fix tsc" + "check-all": "npm-run-all prettier lint:fix tsc rss", + "rss": "node scripts/generateRss.js" }, "dependencies": { "@codesandbox/sandpack-react": "2.13.5", "@docsearch/css": "3.0.0-alpha.41", "@docsearch/react": "3.0.0-alpha.41", "@headlessui/react": "^1.7.0", + "@radix-ui/react-context-menu": "^2.1.5", "body-scroll-lock": "^3.1.3", "classnames": "^2.2.6", "date-fns": "^2.16.1", @@ -97,7 +99,7 @@ "webpack-bundle-analyzer": "^4.5.0" }, "engines": { - "node": "^16.8.0 || ^18.0.0 || ^19.0.0 || ^20.0.0 || ^21.0.0" + "node": ">=16.8.0" }, "nextBundleAnalysis": { "budget": null, diff --git a/public/android-chrome-192x192.png b/public/android-chrome-192x192.png new file mode 100644 index 000000000..5de701e13 Binary files /dev/null and b/public/android-chrome-192x192.png differ diff --git a/public/android-chrome-384x384.png b/public/android-chrome-384x384.png new file mode 100644 index 000000000..f42a6776e Binary files /dev/null and b/public/android-chrome-384x384.png differ diff --git a/public/android-chrome-512x512.png b/public/android-chrome-512x512.png new file mode 100644 index 000000000..2fdbf6902 Binary files /dev/null and b/public/android-chrome-512x512.png differ diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 000000000..baf1332a3 Binary files /dev/null and b/public/apple-touch-icon.png differ diff --git a/public/browserconfig.xml b/public/browserconfig.xml new file mode 100644 index 000000000..f9c2e67fe --- /dev/null +++ b/public/browserconfig.xml @@ -0,0 +1,9 @@ +<?xml version="1.0" encoding="utf-8"?> +<browserconfig> + <msapplication> + <tile> + <square150x150logo src="/mstile-150x150.png"/> + <TileColor>#2b5797</TileColor> + </tile> + </msapplication> +</browserconfig> diff --git a/public/favicon-16x16.png b/public/favicon-16x16.png new file mode 100644 index 000000000..d24cb4f76 Binary files /dev/null and b/public/favicon-16x16.png differ diff --git a/public/favicon-32x32.png b/public/favicon-32x32.png new file mode 100644 index 000000000..953ae4cc3 Binary files /dev/null and b/public/favicon-32x32.png differ diff --git a/public/favicon.ico b/public/favicon.ico index 38fd8641c..519b939a0 100644 Binary files a/public/favicon.ico and b/public/favicon.ico differ diff --git a/public/favicon_old.ico b/public/favicon_old.ico new file mode 100644 index 000000000..20b59d440 Binary files /dev/null and b/public/favicon_old.ico differ diff --git a/public/images/brand/logo_dark.svg b/public/images/brand/logo_dark.svg new file mode 100644 index 000000000..265777fa3 --- /dev/null +++ b/public/images/brand/logo_dark.svg @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="569px" height="512px" viewBox="0 0 569 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <title>React-Logo-Filled (1)</title> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="Artboard-Copy-7" transform="translate(-227, -256)" fill="#58C4DC" fill-rule="nonzero"> + <g id="React-Logo-Filled-(1)" transform="translate(227, 256)"> + <path d="M285.5,201 C255.400481,201 231,225.400481 231,255.5 C231,285.599519 255.400481,310 285.5,310 C315.599519,310 340,285.599519 340,255.5 C340,225.400481 315.599519,201 285.5,201" id="Path"></path> + <path d="M568.959856,255.99437 C568.959856,213.207656 529.337802,175.68144 466.251623,150.985214 C467.094645,145.423543 467.85738,139.922107 468.399323,134.521063 C474.621631,73.0415145 459.808523,28.6686204 426.709856,9.5541429 C389.677085,-11.8291748 337.36955,3.69129898 284.479928,46.0162134 C231.590306,3.69129898 179.282771,-11.8291748 142.25,9.5541429 C109.151333,28.6686204 94.3382249,73.0415145 100.560533,134.521063 C101.102476,139.922107 101.845139,145.443621 102.708233,151.02537 C97.4493791,153.033193 92.2908847,155.161486 87.3331099,157.39017 C31.0111824,182.708821 0,217.765415 0,255.99437 C0,298.781084 39.6220545,336.307301 102.708233,361.003527 C101.845139,366.565197 101.102476,372.066633 100.560533,377.467678 C94.3382249,438.947226 109.151333,483.32012 142.25,502.434597 C153.629683,508.887578 166.52439,512.186771 179.603923,511.991836 C210.956328,511.991836 247.567589,495.487529 284.479928,465.972527 C321.372196,495.487529 358.003528,511.991836 389.396077,511.991836 C402.475265,512.183856 415.36922,508.884856 426.75,502.434597 C459.848667,483.32012 474.661775,438.947226 468.439467,377.467678 C467.897524,372.066633 467.134789,366.565197 466.291767,361.003527 C529.377946,336.347457 569,298.761006 569,255.99437 M389.155214,27.1025182 C397.565154,26.899606 405.877839,28.9368502 413.241569,33.0055186 C436.223966,46.2772304 446.540955,82.2775015 441.522965,131.770345 C441.181741,135.143488 440.780302,138.556788 440.298575,141.990165 C414.066922,134.08804 387.205771,128.452154 360.010724,125.144528 C343.525021,103.224055 325.192524,82.7564475 305.214266,63.9661533 C336.586743,39.7116483 366.032313,27.1025182 389.135142,27.1025182 M378.356498,310.205598 C368.204912,327.830733 357.150626,344.919965 345.237759,361.405091 C325.045049,363.479997 304.758818,364.51205 284.459856,364.497299 C264.167589,364.51136 243.888075,363.479308 223.702025,361.405091 C211.820914,344.919381 200.80007,327.83006 190.683646,310.205598 C180.532593,292.629285 171.306974,274.534187 163.044553,255.99437 C171.306974,237.454554 180.532593,219.359455 190.683646,201.783142 C200.784121,184.229367 211.770999,167.201087 223.601665,150.764353 C243.824636,148.63809 264.145559,147.579168 284.479928,147.591877 C304.772146,147.579725 325.051559,148.611772 345.237759,150.68404 C357.109048,167.14607 368.136094,184.201112 378.27621,201.783142 C388.419418,219.363718 397.644825,237.458403 405.915303,255.99437 C397.644825,274.530337 388.419418,292.625022 378.27621,310.205598 M419.724813,290.127366 C426.09516,307.503536 431.324985,325.277083 435.380944,343.334682 C417.779633,348.823635 399.836793,353.149774 381.668372,356.285142 C388.573127,345.871232 395.263781,335.035679 401.740334,323.778483 C408.143291,312.655143 414.144807,301.431411 419.805101,290.207679 M246.363271,390.377981 C258.848032,391.140954 271.593728,391.582675 284.5,391.582675 C297.406272,391.582675 310.232256,391.140954 322.737089,390.377981 C310.880643,404.583418 298.10766,417.997563 284.5,430.534446 C270.921643,417.999548 258.18192,404.585125 246.363271,390.377981 Z M187.311556,356.244986 C169.137286,353.123646 151.187726,348.810918 133.578912,343.334682 C137.618549,325.305649 142.828222,307.559058 149.174827,290.207679 C154.754833,301.431411 160.736278,312.655143 167.239594,323.778483 C173.74291,334.901824 180.467017,345.864539 187.311556,356.285142 M149.174827,221.760984 C142.850954,204.473938 137.654787,186.794745 133.619056,168.834762 C151.18418,163.352378 169.085653,159.013101 187.211197,155.844146 C180.346585,166.224592 173.622478,176.986525 167.139234,188.210257 C160.65599,199.433989 154.734761,210.517173 149.074467,221.760984 M322.616657,121.590681 C310.131896,120.827708 297.3862,120.385987 284.379568,120.385987 C271.479987,120.385987 258.767744,120.787552 246.242839,121.590681 C258.061488,107.383537 270.801211,93.9691137 284.379568,81.4342157 C297.99241,93.9658277 310.765727,107.380324 322.616657,121.590681 Z M401.70019,188.210257 C395.196875,176.939676 388.472767,166.09743 381.527868,155.68352 C399.744224,158.819049 417.734224,163.151949 435.380944,168.654058 C431.331963,186.680673 426.122466,204.426664 419.785029,221.781062 C414.205023,210.55733 408.203506,199.333598 401.720262,188.230335 M127.517179,131.790423 C122.438973,82.3176579 132.816178,46.2973086 155.778503,33.0255968 C163.144699,28.9632474 171.455651,26.9264282 179.864858,27.1225964 C202.967687,27.1225964 232.413257,39.7317265 263.785734,63.9862316 C243.794133,82.7898734 225.448298,103.270812 208.949132,125.204763 C181.761691,128.528025 154.90355,134.14313 128.661281,141.990165 C128.199626,138.556788 127.778115,135.163566 127.456963,131.790423 M98.4529773,182.106474 C101.54406,180.767925 104.695358,179.429376 107.906872,178.090828 C114.220532,204.735668 122.781793,230.7969 133.498624,255.99437 C122.761529,281.241316 114.193296,307.357063 107.8868,334.058539 C56.7434387,313.076786 27.0971497,284.003505 27.0971497,255.99437 C27.0971497,229.450947 53.1907013,202.526037 98.4529773,182.106474 Z M155.778503,478.963143 C132.816178,465.691432 122.438973,429.671082 127.517179,380.198317 C127.838331,376.825174 128.259842,373.431953 128.721497,369.978497 C154.953686,377.878517 181.814655,383.514365 209.009348,386.824134 C225.500295,408.752719 243.832321,429.233234 263.805806,448.042665 C220.069,481.834331 180.105722,492.97775 155.838719,478.963143 M441.502893,380.198317 C446.520883,429.691161 436.203894,465.691432 413.221497,478.963143 C388.974566,493.017906 348.991216,481.834331 305.274481,448.042665 C325.241364,429.232737 343.566681,408.752215 360.050868,386.824134 C387.245915,383.516508 414.107066,377.880622 440.338719,369.978497 C440.820446,373.431953 441.221885,376.825174 441.563109,380.198317 M461.193488,334.018382 C454.869166,307.332523 446.294494,281.231049 435.561592,255.99437 C446.289797,230.744081 454.857778,204.629101 461.173416,177.930202 C512.216417,198.911955 541.942994,227.985236 541.942994,255.99437 C541.942994,284.003505 512.296705,313.076786 461.153344,334.058539" id="Shape"></path> + </g> + </g> + </g> +</svg> \ No newline at end of file diff --git a/public/images/brand/logo_light.svg b/public/images/brand/logo_light.svg new file mode 100644 index 000000000..bbe5a8994 --- /dev/null +++ b/public/images/brand/logo_light.svg @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="569px" height="512px" viewBox="0 0 569 512" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <title>React-Logo-Filled (1)</title> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="React-Logo-Filled-(1)" fill="#087EA4" fill-rule="nonzero"> + <path d="M285.5,201 C255.400481,201 231,225.400481 231,255.5 C231,285.599519 255.400481,310 285.5,310 C315.599519,310 340,285.599519 340,255.5 C340,225.400481 315.599519,201 285.5,201" id="Path"></path> + <path d="M568.959856,255.99437 C568.959856,213.207656 529.337802,175.68144 466.251623,150.985214 C467.094645,145.423543 467.85738,139.922107 468.399323,134.521063 C474.621631,73.0415145 459.808523,28.6686204 426.709856,9.5541429 C389.677085,-11.8291748 337.36955,3.69129898 284.479928,46.0162134 C231.590306,3.69129898 179.282771,-11.8291748 142.25,9.5541429 C109.151333,28.6686204 94.3382249,73.0415145 100.560533,134.521063 C101.102476,139.922107 101.845139,145.443621 102.708233,151.02537 C97.4493791,153.033193 92.2908847,155.161486 87.3331099,157.39017 C31.0111824,182.708821 0,217.765415 0,255.99437 C0,298.781084 39.6220545,336.307301 102.708233,361.003527 C101.845139,366.565197 101.102476,372.066633 100.560533,377.467678 C94.3382249,438.947226 109.151333,483.32012 142.25,502.434597 C153.629683,508.887578 166.52439,512.186771 179.603923,511.991836 C210.956328,511.991836 247.567589,495.487529 284.479928,465.972527 C321.372196,495.487529 358.003528,511.991836 389.396077,511.991836 C402.475265,512.183856 415.36922,508.884856 426.75,502.434597 C459.848667,483.32012 474.661775,438.947226 468.439467,377.467678 C467.897524,372.066633 467.134789,366.565197 466.291767,361.003527 C529.377946,336.347457 569,298.761006 569,255.99437 M389.155214,27.1025182 C397.565154,26.899606 405.877839,28.9368502 413.241569,33.0055186 C436.223966,46.2772304 446.540955,82.2775015 441.522965,131.770345 C441.181741,135.143488 440.780302,138.556788 440.298575,141.990165 C414.066922,134.08804 387.205771,128.452154 360.010724,125.144528 C343.525021,103.224055 325.192524,82.7564475 305.214266,63.9661533 C336.586743,39.7116483 366.032313,27.1025182 389.135142,27.1025182 M378.356498,310.205598 C368.204912,327.830733 357.150626,344.919965 345.237759,361.405091 C325.045049,363.479997 304.758818,364.51205 284.459856,364.497299 C264.167589,364.51136 243.888075,363.479308 223.702025,361.405091 C211.820914,344.919381 200.80007,327.83006 190.683646,310.205598 C180.532593,292.629285 171.306974,274.534187 163.044553,255.99437 C171.306974,237.454554 180.532593,219.359455 190.683646,201.783142 C200.784121,184.229367 211.770999,167.201087 223.601665,150.764353 C243.824636,148.63809 264.145559,147.579168 284.479928,147.591877 C304.772146,147.579725 325.051559,148.611772 345.237759,150.68404 C357.109048,167.14607 368.136094,184.201112 378.27621,201.783142 C388.419418,219.363718 397.644825,237.458403 405.915303,255.99437 C397.644825,274.530337 388.419418,292.625022 378.27621,310.205598 M419.724813,290.127366 C426.09516,307.503536 431.324985,325.277083 435.380944,343.334682 C417.779633,348.823635 399.836793,353.149774 381.668372,356.285142 C388.573127,345.871232 395.263781,335.035679 401.740334,323.778483 C408.143291,312.655143 414.144807,301.431411 419.805101,290.207679 M246.363271,390.377981 C258.848032,391.140954 271.593728,391.582675 284.5,391.582675 C297.406272,391.582675 310.232256,391.140954 322.737089,390.377981 C310.880643,404.583418 298.10766,417.997563 284.5,430.534446 C270.921643,417.999548 258.18192,404.585125 246.363271,390.377981 Z M187.311556,356.244986 C169.137286,353.123646 151.187726,348.810918 133.578912,343.334682 C137.618549,325.305649 142.828222,307.559058 149.174827,290.207679 C154.754833,301.431411 160.736278,312.655143 167.239594,323.778483 C173.74291,334.901824 180.467017,345.864539 187.311556,356.285142 M149.174827,221.760984 C142.850954,204.473938 137.654787,186.794745 133.619056,168.834762 C151.18418,163.352378 169.085653,159.013101 187.211197,155.844146 C180.346585,166.224592 173.622478,176.986525 167.139234,188.210257 C160.65599,199.433989 154.734761,210.517173 149.074467,221.760984 M322.616657,121.590681 C310.131896,120.827708 297.3862,120.385987 284.379568,120.385987 C271.479987,120.385987 258.767744,120.787552 246.242839,121.590681 C258.061488,107.383537 270.801211,93.9691137 284.379568,81.4342157 C297.99241,93.9658277 310.765727,107.380324 322.616657,121.590681 Z M401.70019,188.210257 C395.196875,176.939676 388.472767,166.09743 381.527868,155.68352 C399.744224,158.819049 417.734224,163.151949 435.380944,168.654058 C431.331963,186.680673 426.122466,204.426664 419.785029,221.781062 C414.205023,210.55733 408.203506,199.333598 401.720262,188.230335 M127.517179,131.790423 C122.438973,82.3176579 132.816178,46.2973086 155.778503,33.0255968 C163.144699,28.9632474 171.455651,26.9264282 179.864858,27.1225964 C202.967687,27.1225964 232.413257,39.7317265 263.785734,63.9862316 C243.794133,82.7898734 225.448298,103.270812 208.949132,125.204763 C181.761691,128.528025 154.90355,134.14313 128.661281,141.990165 C128.199626,138.556788 127.778115,135.163566 127.456963,131.790423 M98.4529773,182.106474 C101.54406,180.767925 104.695358,179.429376 107.906872,178.090828 C114.220532,204.735668 122.781793,230.7969 133.498624,255.99437 C122.761529,281.241316 114.193296,307.357063 107.8868,334.058539 C56.7434387,313.076786 27.0971497,284.003505 27.0971497,255.99437 C27.0971497,229.450947 53.1907013,202.526037 98.4529773,182.106474 Z M155.778503,478.963143 C132.816178,465.691432 122.438973,429.671082 127.517179,380.198317 C127.838331,376.825174 128.259842,373.431953 128.721497,369.978497 C154.953686,377.878517 181.814655,383.514365 209.009348,386.824134 C225.500295,408.752719 243.832321,429.233234 263.805806,448.042665 C220.069,481.834331 180.105722,492.97775 155.838719,478.963143 M441.502893,380.198317 C446.520883,429.691161 436.203894,465.691432 413.221497,478.963143 C388.974566,493.017906 348.991216,481.834331 305.274481,448.042665 C325.241364,429.232737 343.566681,408.752215 360.050868,386.824134 C387.245915,383.516508 414.107066,377.880622 440.338719,369.978497 C440.820446,373.431953 441.221885,376.825174 441.563109,380.198317 M461.193488,334.018382 C454.869166,307.332523 446.294494,281.231049 435.561592,255.99437 C446.289797,230.744081 454.857778,204.629101 461.173416,177.930202 C512.216417,198.911955 541.942994,227.985236 541.942994,255.99437 C541.942994,284.003505 512.296705,313.076786 461.153344,334.058539" id="Shape"></path> + </g> + </g> +</svg> \ No newline at end of file diff --git a/public/images/brand/wordmark_dark.svg b/public/images/brand/wordmark_dark.svg new file mode 100644 index 000000000..ec028ae21 --- /dev/null +++ b/public/images/brand/wordmark_dark.svg @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="600px" height="180.766722px" viewBox="0 0 600 180.766722" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <title>Group</title> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="Group" transform="translate(0, 0)" fill-rule="nonzero"> + <path d="M238.674643,136.444487 L238.674643,40.7880921 L279.602667,40.7880921 C291.581922,40.7880921 300.554839,43.4984068 306.523063,48.9190363 C312.489641,54.2938957 315.47293,61.5593239 315.47293,70.7149916 C315.47293,77.6386258 313.765607,83.5146514 310.349313,88.3430683 C306.933019,93.1713207 301.784705,96.6104997 294.907664,98.660276 C299.05167,104.536302 303.106769,110.640025 307.06967,116.971612 C311.032571,123.303033 314.835771,129.793992 318.479269,136.444487 L301.740252,136.444487 C298.41451,129.976249 294.907664,123.667548 291.218067,117.518219 C287.572922,111.368889 283.996927,105.743282 280.49008,100.641727 C280.216777,100.641727 279.920424,100.641727 279.602667,100.641727 L253.023077,100.641727 L253.023077,136.444487 L238.674643,136.444487 Z M278.23615,54.1118031 L253.023077,54.1118031 L253.023077,87.5914837 L278.23615,87.5914837 C285.9792,87.5914837 291.672475,86.2249661 295.317619,83.4919309 C298.961117,80.7132903 300.78369,76.4998063 300.78369,70.8516434 C300.78369,65.2033159 298.961117,61.0127168 295.317619,58.2796817 C291.672475,55.5010411 285.9792,54.1118031 278.23615,54.1118031 Z M360.98949,138.084308 C354.430206,138.084308 348.667783,136.581139 343.70222,133.5748 C338.782757,130.568461 334.933458,126.377698 332.15597,121.002838 C329.422934,115.627814 328.056417,109.410159 328.056417,102.349873 C328.056417,95.2894229 329.422934,89.071768 332.15597,83.6969086 C334.933458,78.2762791 338.782757,74.0629597 343.70222,71.056621 C348.667783,68.0046769 354.430206,66.4787871 360.98949,66.4787871 L362.49266,66.4787871 C368.459238,66.4787871 373.698104,67.9590714 378.207612,70.9199693 C382.761573,73.835097 386.314519,78.0713015 388.866449,83.6285827 C391.416733,89.1400938 392.692698,95.7905891 392.692698,103.579739 L392.692698,107.747618 L342.199051,107.747618 C343.018961,113.35034 345.114837,117.768637 348.485031,121.002838 C351.901325,124.19127 356.343331,125.78565 361.809401,125.78565 L363.175919,125.78565 C366.455561,125.78565 369.598551,125.125112 372.60489,123.804199 C375.611228,122.437682 378.138463,120.615604 380.188239,118.338129 L388.593145,127.220493 C385.494608,130.545576 381.668359,133.187565 377.114398,135.146295 C372.60489,137.104861 367.72988,138.084308 362.49266,138.084308 L360.98949,138.084308 Z M360.579535,78.7774453 C355.704525,78.7774453 351.628022,80.3943814 348.34838,83.6285827 C345.114837,86.817014 343.065061,91.1898702 342.199051,96.7471514 L378.958373,96.7471514 C378.548418,91.6910364 376.885547,87.4319469 373.971407,83.9702121 C371.10172,80.5083127 367.09272,78.7774453 361.946053,78.7774453 L360.579535,78.7774453 Z M427.265593,137.811005 C420.067503,137.811005 414.464781,135.966206 410.457427,132.276608 C406.494526,128.541405 404.512252,123.644663 404.512252,117.586544 C404.512252,111.573867 406.74478,106.540473 411.208188,102.486525 C415.717696,98.4324133 422.892737,96.405522 432.731663,96.405522 L451.316302,96.405522 L451.316302,96.2005444 C451.316302,84.7217968 446.031337,78.9824229 435.464698,78.9824229 C431.045743,78.9824229 427.310046,79.7794484 424.259254,81.3738287 C421.252916,82.9680443 418.814587,85.2000779 416.947562,88.0697648 L407.58774,80.4855923 C410.50188,75.9760843 414.328129,72.5141849 419.066487,70.1000587 C423.849299,67.6857679 429.26927,66.4787871 435.328047,66.4787871 L436.831216,66.4787871 C445.211426,66.4787871 451.953462,68.8929133 457.055676,73.7213303 C462.156244,78.5495826 464.708174,85.7694054 464.708174,95.3806339 L464.708174,136.444487 L451.589606,136.444487 L451.589606,125.307369 C448.947123,129.315711 445.553879,132.41326 441.408227,134.599688 C437.262574,136.740511 432.548912,137.811005 427.265593,137.811005 Z M430.271932,125.648998 C435.555251,125.648998 439.951157,124.464573 443.458003,122.096052 C446.964849,119.681762 449.584282,116.425005 451.316302,112.325452 L451.316302,107.132685 L433.27827,107.132685 C427.902752,107.132685 424.053453,107.998036 421.730373,109.729068 C419.453393,111.41433 418.31408,113.71469 418.31408,116.629982 C418.31408,122.642659 422.30003,125.648998 430.271932,125.648998 Z M513.468156,138.084308 C506.908871,138.084308 501.101995,136.649465 496.04588,133.779778 C490.989765,130.910091 487.026864,126.810538 484.157177,121.48112 C481.331943,116.106096 479.920972,109.729068 479.920972,102.349873 C479.920972,94.9250731 481.331943,88.548046 484.157177,83.2186274 C486.980764,77.8436034 490.897566,73.7213303 495.909228,70.8516434 C500.965343,67.936351 506.818319,66.4787871 513.468156,66.4787871 L514.971325,66.4787871 C520.256291,66.4787871 525.243257,67.8453047 529.935516,70.5783399 C534.672228,73.2657695 538.498477,76.9780874 541.414263,81.7154581 L530.822929,89.094653 C528.865351,85.9971034 526.519222,83.5829772 523.786187,81.8521099 C521.053151,80.075637 517.954614,79.1874006 514.493867,79.1874006 L513.12735,79.1874006 C507.205225,79.1874006 502.536015,81.2826178 499.119721,85.4733814 C495.749527,89.618375 494.063606,95.221097 494.063606,102.281548 C494.063606,109.296228 495.749527,114.89895 499.119721,119.089714 C502.536015,123.280313 507.205225,125.375695 513.12735,125.375695 L514.493867,125.375695 C517.954614,125.375695 521.143704,124.601225 524.05949,123.052615 C526.97363,121.50384 529.479461,119.431343 531.575337,116.83496 L541.414263,125.170717 C538.361825,129.179059 534.512526,132.344934 529.866367,134.668014 C525.266306,136.945489 520.300744,138.084308 514.971325,138.084308 L513.468156,138.084308 Z M587.154735,137.332723 C579.046182,137.332723 572.875449,135.282947 568.639245,131.183394 C564.40304,127.083842 562.284115,120.774976 562.284115,112.257126 L562.284115,80.0073111 L548.892243,80.0073111 L548.892243,68.1186082 L562.284115,68.1186082 L562.284115,47.7573317 L575.949291,47.7573317 L575.949291,68.1186082 L598.975935,68.1186082 L598.975935,80.0073111 L575.949291,80.0073111 L575.949291,110.412327 C575.949291,115.787187 577.065554,119.522389 579.298082,121.617771 C581.528963,123.712988 584.831655,124.760762 589.204511,124.760762 C591.300387,124.760762 593.167412,124.669551 594.807233,124.487458 C596.493154,124.305201 598.223527,124.077503 600,123.804199 L600,135.761228 C598.177428,136.216624 596.127651,136.581139 593.850671,136.854442 C591.57369,137.173187 589.341163,137.332723 587.154735,137.332723 Z" id="Shape" fill="#F6F7F9"></path> + <g transform="translate(0, 0.0009)" fill="#58C4DC"> + <path d="M100.331696,71.2413046 C96.5513816,71.2413046 92.8560217,72.3638247 89.7128667,74.46678 C86.569547,76.5698999 84.1198584,79.5591159 82.6731608,83.0564131 C81.2264632,86.5537102 80.8479543,90.4021859 81.5853799,94.1148331 C82.3229701,97.8276449 84.143402,101.238012 86.8163433,103.91474 C89.4894493,106.591468 92.8952062,108.414205 96.6029141,109.152783 C100.310622,109.891361 104.153665,109.512193 107.646187,108.063685 C111.13871,106.615011 114.12381,104.161865 116.224131,101.014265 C118.324287,97.8668293 119.445326,94.1663656 119.445326,90.3809473 C119.445326,85.3047461 117.431606,80.4364861 113.847049,76.8471548 C110.262657,73.2576589 105.400982,71.2413046 100.331696,71.2413046 Z" id="Path"></path> + <path d="M200.664051,90.380618 C200.664051,75.2745071 186.68935,62.025543 164.440304,53.3063376 C164.73781,51.342833 165.006174,49.400567 165.197157,47.4935341 C167.391817,25.7878018 162.167933,10.1216657 150.494416,3.37315978 C137.433471,-4.17637401 118.985484,1.3032314 100.332025,16.246381 C81.6785665,1.3032314 63.2304146,-4.17637401 50.1694701,3.37315978 C38.4961172,10.1216657 33.2717394,25.7878018 35.4662349,47.4935341 C35.6573828,49.400567 35.9193261,51.3499125 36.2237467,53.3206614 C34.3690695,54.0294395 32.5496254,54.7808595 30.8011415,55.5676772 C10.9371951,64.5066779 0,76.8835404 0,90.380618 C0,105.486729 13.9741404,118.735693 36.2237467,127.454898 C35.9193261,129.418403 35.6573828,131.360834 35.4662349,133.267702 C33.2717394,154.973434 38.4961172,170.639323 50.1694701,177.387945 C54.1830804,179.666571 58.7307851,180.830581 63.3436874,180.763078 C74.4012842,180.763078 87.3135582,174.934799 100.332025,164.51469 C113.343413,174.934799 126.262767,180.763078 137.334358,180.763078 C141.94726,180.830581 146.4948,179.664925 150.508575,177.387945 C162.182093,170.639323 167.406635,154.973434 165.211975,133.267702 C165.020992,131.360834 164.750981,129.418403 164.454463,127.454898 C186.704168,118.749852 200.678868,105.479649 200.678868,90.380618 L200.664051,90.380618 Z M137.249568,9.56873644 C140.21557,9.49710128 143.147326,10.216367 145.744368,11.6529062 C153.849957,16.3385798 157.488681,29.048675 155.718794,46.5224834 C155.598441,47.7133294 155.457015,48.9184991 155.287106,50.1305837 C146.035453,47.3407475 136.562028,45.3509004 126.970721,44.1831041 C121.156436,36.4440047 114.690832,29.2177609 107.644705,22.5837297 C118.709382,14.0204391 129.094421,9.56873644 137.242488,9.56873644 L137.249568,9.56873644 Z M133.440935,109.520261 C129.860659,115.74302 125.961968,121.776442 121.760503,127.596654 C114.638806,128.32914 107.484016,128.69349 100.324946,128.688278 C93.1681804,128.693326 86.0158604,128.328976 78.8964685,127.596654 C74.7061987,121.776277 70.8193617,115.74269 67.2514337,109.520261 C63.6713223,103.314789 60.4175287,96.9262372 57.5035535,90.380618 C60.4175287,83.8349988 63.6713223,77.4464468 67.2514337,71.2409753 C70.813764,65.0434065 74.6885822,59.0315523 78.8610707,53.2284626 C85.9934693,52.4777011 93.1604423,52.1038022 100.332025,52.10837 C107.488791,52.1041314 114.641111,52.4684812 121.760503,53.2001444 C125.947315,59.0121247 129.836292,65.033528 133.412617,71.2409753 C136.989929,77.4479286 140.243723,83.8363159 143.160497,90.380618 C140.243723,96.9249201 136.989929,103.313307 133.412617,109.520261 L133.440935,109.520261 Z M148.030898,102.431492 C150.277749,108.566333 152.122218,114.841447 153.552616,121.216663 C147.345005,123.154649 141.016711,124.68202 134.609061,125.789064 C137.044261,122.112309 139.403891,118.286718 141.688116,114.312292 C143.946327,110.385118 146.062948,106.422546 148.059216,102.45981 L148.030898,102.431492 Z M100.339105,152.003152 C95.5502017,147.577611 91.0571578,142.841557 86.8887852,137.825779 C91.2919354,138.095131 95.7872843,138.251046 100.339105,138.251046 C104.890926,138.251046 109.414428,138.095131 113.824822,137.825779 C109.643114,142.841063 105.13838,147.576952 100.339105,152.003152 Z M66.0620695,125.77474 C59.6522789,124.6728 53.3216804,123.150203 47.1114343,121.216663 C48.53607,114.85149 50.3734599,108.585925 52.6119145,102.45981 C54.5798644,106.422546 56.6894054,110.385118 58.9830146,114.312292 C61.2766238,118.239466 63.648108,122.110004 66.0620695,125.789064 L66.0620695,125.77474 Z M52.6119145,78.2943463 C50.3815273,72.190952 48.548912,65.9492594 47.1255934,59.6082886 C53.3205279,57.6727729 59.6341684,56.1406268 66.0266718,55.0218934 C63.6056307,58.6867948 61.2341465,62.4863722 58.9476168,66.4489439 C56.6610872,70.4115156 54.5727849,74.3245304 52.5765168,78.2943463 L52.6119145,78.2943463 Z M100.296628,28.7510046 C105.097714,33.1752288 109.602612,37.911447 113.782345,42.9283775 C109.37903,42.6590254 104.883846,42.5031107 100.296628,42.5031107 C95.7471119,42.5031107 91.2636172,42.6448663 86.8463079,42.9283775 C91.0146805,37.9125995 95.5077244,33.1765459 100.296628,28.7510046 Z M141.673957,66.4489439 C139.380347,62.4697435 137.008863,58.6418479 134.559504,54.9650924 C140.984112,56.0721363 147.32887,57.6019774 153.552616,59.5445727 C152.124688,65.9089225 150.287298,72.1743232 148.052136,78.3014258 C146.084186,74.3386895 143.967566,70.3761178 141.681036,66.4560234 L141.673957,66.4489439 Z M44.9734105,46.5295629 C43.1824493,29.0628341 46.8424114,16.3456593 54.9409214,11.6599858 C57.5387866,10.2256856 60.4700491,9.50656812 63.4357215,9.57583245 C71.5837884,9.57583245 81.968828,14.0275186 93.0335044,22.5908093 C85.9827676,29.2296151 79.5123892,36.4604687 73.6933293,44.2045074 C64.1048212,45.3777368 54.6322202,47.3601751 45.3769448,50.1305837 C45.2141152,48.9184991 45.0654447,47.7204089 44.9521719,46.5295629 L44.9734105,46.5295629 Z M9.55678182,90.380618 C9.55678182,81.009271 18.7596521,71.5032479 34.7228823,64.2939621 C35.8131328,63.8214434 36.924622,63.34876 38.0571852,62.8762413 C40.2839503,72.2833154 43.3034602,81.4844239 47.0831161,90.380618 C43.296216,99.294264 40.2744011,108.514636 38.0501056,117.941796 C20.0125676,110.533953 9.55678182,100.269431 9.55678182,90.380618 Z M54.9409214,169.10158 C46.8424114,164.415906 43.1824493,151.698402 44.9734105,134.231673 C45.0866833,133.040827 45.2353538,131.842737 45.3981835,130.623573 C54.6500014,133.41275 64.1234256,135.402433 73.714568,136.571052 C79.5306643,144.313115 85.9961035,151.543804 93.0405839,158.184586 C77.6152348,170.115766 63.5206761,174.049032 54.9621601,169.10158 L54.9409214,169.10158 Z M155.711714,134.231673 C157.481601,151.705481 153.842878,164.415906 145.737288,169.10158 C137.185852,174.063849 123.084214,170.115766 107.665944,158.184586 C114.707955,151.54364 121.171089,144.312786 126.98488,136.571052 C136.576187,135.403256 146.049612,133.413409 155.301265,130.623573 C155.47101,131.842737 155.6126,133.040827 155.732953,134.231673 L155.711714,134.231673 Z M162.656422,117.927637 C160.425871,108.50591 157.401751,99.2906419 153.616332,90.380618 C157.400104,81.4658196 160.421919,72.2457773 162.649343,62.8194403 C180.651977,70.2272827 191.136295,80.4918053 191.136295,90.380618 C191.136295,100.269431 180.679966,110.533953 162.642263,117.941796" id="Shape"></path> + </g> + <path d="M100.334001,110.494483 C110.855528,110.494483 119.384902,101.680938 119.384902,90.808727 C119.384902,79.9365156 110.855528,71.1229712 100.334001,71.1229712 C89.8126389,71.1229712 81.2832643,79.9365156 81.2832643,90.808727 C81.2832643,101.680938 89.8126389,110.494483 100.334001,110.494483 Z" id="Path" fill="#58C4DC"></path> + <path d="M200.66899,90.1760129 C200.66899,75.1235749 186.692643,61.9078682 164.452323,53.2367379 C168.047087,29.6442236 163.568532,10.9419817 150.501166,3.41577915 C138.831435,-3.31130369 122.670472,-0.0193332173 104.992509,12.6952896 C103.427764,13.8164926 101.875038,15.0093143 100.322147,16.2497171 C98.7812749,15.0093143 97.2285487,13.8164926 95.6758225,12.6952896 C77.9978597,-0.0193332173 61.836896,-3.32323026 50.1670005,3.41577915 C37.1116538,10.9419817 32.6324397,29.6322048 36.2397168,53.2008462 C13.9751118,61.8839953 0,75.1115561 0,90.1760129 C0,105.228451 13.9751118,118.443993 36.2158439,127.115288 C32.6205856,150.707638 37.0997997,169.410324 50.1670005,176.936049 C54.1206816,179.214676 58.5998957,180.347404 63.4732596,180.347404 C72.9692396,180.347404 83.9939083,176.053575 95.6758225,167.656901 C97.2404028,166.535698 98.7932936,165.342053 100.34602,164.102309 C101.886892,165.342053 103.439618,166.535698 104.992509,167.656901 C116.674258,176.0651 127.699092,180.347404 137.194907,180.347404 C142.068271,180.347404 146.547485,179.214676 150.501166,176.936049 C162.171062,170.208831 167.390171,154.584102 165.205389,132.93583 C165.014406,131.02748 164.762506,129.095257 164.452323,127.127142 C186.705815,118.455847 200.66899,105.240305 200.66899,90.1760129 Z M110.582554,20.4362001 C120.520429,13.2916181 129.765497,9.57032735 137.194907,9.57032735 C140.408034,9.57032735 143.274758,10.2621145 145.723294,11.6695782 C154.275718,16.6074813 157.930741,30.7654265 155.314766,50.0639475 C146.71476,47.4280504 137.194907,45.4123546 126.958538,44.1240414 C120.830941,36.0731125 114.333068,28.8093307 107.679939,22.6069873 C108.647532,21.8675861 109.61496,21.1280202 110.582554,20.4362001 Z M54.9448728,11.6695782 C57.3935735,10.2621145 60.2601323,9.57032735 63.4732596,9.57032735 C70.9028346,9.57032735 80.1479022,13.279764 90.0857778,20.4362001 C91.0532064,21.1280202 92.020635,21.8555673 92.9882282,22.6069873 C86.3350986,28.8093307 79.849244,36.0610938 73.7216475,44.1121872 C63.4971325,45.4003359 53.9654255,47.4280504 45.36542,50.0520934 C42.7494443,30.7654265 46.3926131,16.6074813 54.9448728,11.6695782 Z M9.55562934,90.1760129 C9.55562934,80.3118964 20.0189886,70.0663074 38.0672283,62.6831611 C40.0858876,71.4260747 43.0958483,80.6698252 47.0734023,90.15214 C43.0719754,99.646309 40.0620147,108.902078 38.0433554,117.656846 C20.0189886,110.2737 9.55562934,100.039965 9.55562934,90.1760129 Z M90.0857778,159.915497 C75.8478171,170.149561 63.0432182,173.345236 54.9448728,168.682612 C46.3926131,163.745039 42.7375902,149.586599 45.3534012,130.287914 C53.9534067,132.923976 63.4732596,134.939671 73.7097934,136.22782 C79.8372253,144.278749 86.3350986,151.542695 92.9882282,157.744874 C92.020635,158.496294 91.0532064,159.223841 90.0857778,159.915497 Z M100.334166,151.530676 C88.3896496,140.605121 76.910902,126.005939 67.2237741,109.259842 C64.631836,104.787049 59.3914881,93.5442318 57.2653184,89.1908032 C55.5691902,93.2460675 53.8938068,96.9662882 52.5798096,100.926061 C53.6549132,102.918048 57.7995774,112.026958 58.9462997,114.018946 C61.2157067,117.942992 63.5927887,121.759856 66.0413248,125.457356 C59.1731745,124.252681 52.8187032,122.702095 47.0375106,120.877218 C50.3222567,106.063344 56.8798946,88.9831913 67.2237741,71.0920188 C69.7679664,66.6908443 75.2956123,56.8637719 78.0668441,52.7846346 C73.6951404,53.3452361 70.1843427,53.1596861 66.0754054,54.006433 C64.8809374,55.938656 60.093022,64.3410927 58.9462997,66.3330801 C56.6528551,70.2928529 54.550723,74.2527904 52.5798096,78.1888549 C50.1908734,71.6407661 48.363362,65.3669687 47.0615482,59.4509354 C61.5502566,54.8826507 79.6462421,52.0081892 100.334166,52.0081892 C105.506023,52.0081892 116.307439,51.9034777 121.133057,52.2493878 C118.4695,48.7545603 116.185111,45.5911544 113.402025,42.4662744 C111.144472,42.3946558 102.639464,42.4662744 100.334166,42.4662744 C95.7713141,42.4662744 91.3041188,42.6212013 86.9203962,42.8836385 C91.2682271,37.7190253 95.7713141,32.9958137 100.334166,28.8211849 C112.266663,39.7467398 123.757429,54.3459221 133.444393,71.0920188 C135.988585,75.4933578 141.485772,85.7389468 143.647669,90.1760129 C145.355816,86.1205839 146.971599,82.4220959 148.297451,78.4382855 C147.222347,76.4344439 142.868589,68.3010301 141.721867,66.3210614 C139.45246,62.3970156 137.075543,58.5801509 134.626842,54.8946695 C141.494992,56.0991806 147.849464,57.6497665 153.630656,59.4748083 C150.34591,74.288682 143.788272,91.36867 133.444393,109.259842 C130.9002,113.661017 123.659468,123.739825 120.888401,127.818962 C125.260105,127.258361 129.994512,126.42034 134.103449,125.573593 C135.297917,123.641205 140.575309,116.010769 141.721867,114.018946 C144.003293,110.082881 146.10559,106.146817 148.052465,102.222606 C150.369783,108.591895 152.209313,114.853839 153.547019,120.912945 C139.070329,125.481229 120.998216,128.343837 100.334166,128.343837 C95.1621436,128.343837 83.1371182,127.664365 78.3115002,127.318455 C80.9752216,130.813117 83.8440853,134.451676 86.6270066,137.576721 C88.8845594,137.648175 98.028867,137.885751 100.334166,137.885751 C104.897017,137.885751 109.364213,137.73066 113.74777,137.468223 C109.388086,142.632836 104.897017,147.356048 100.334166,151.530676 Z M145.723294,168.682612 C137.624949,173.358407 124.808495,170.161086 110.582554,159.915497 C109.61496,159.223841 108.647532,158.496294 107.679939,157.744874 C114.333068,151.542695 120.818923,144.290767 126.946519,136.239674 C137.15918,134.951525 146.667014,132.93583 155.255166,130.311787 C155.422276,131.516462 155.589551,132.721138 155.708915,133.901941 C157.464808,151.339858 153.821804,164.006817 145.723294,168.682612 Z M162.624811,117.6687 C160.570425,108.794733 157.524573,99.5509821 153.594764,90.21174 C157.596191,80.717571 160.606317,71.4618017 162.624811,62.707034 C180.648684,70.0901803 191.113245,80.3118964 191.113245,90.1760129 C191.113245,100.039965 180.661856,110.2737 162.624811,117.6687 Z" id="Shape" fill="#58C4DC"></path> + </g> + </g> +</svg> \ No newline at end of file diff --git a/public/images/brand/wordmark_light.svg b/public/images/brand/wordmark_light.svg new file mode 100644 index 000000000..2de8f3cc7 --- /dev/null +++ b/public/images/brand/wordmark_light.svg @@ -0,0 +1,11 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg width="600px" height="180.322004px" viewBox="0 0 600 180.322004" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> + <title>Group</title> + <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"> + <g id="Group" fill-rule="nonzero"> + <path d="M238.282648,135.760088 L238.282648,40.7871199 L279.255074,40.7871199 C291.247326,40.7871199 300.229977,43.4780705 306.204676,48.8599717 C312.177727,54.1964299 315.164252,61.4099495 315.164252,70.5002036 C315.164252,77.3743712 313.455076,83.2084149 310.035076,88.0023348 C306.615076,92.7960912 301.461177,96.2106986 294.576675,98.2458301 C298.725177,104.079874 302.784676,110.139989 306.751876,116.426338 C310.719076,122.712525 314.526402,129.157108 318.173853,135.760088 L301.416676,135.760088 C298.087326,129.338063 294.576675,123.074435 290.883075,116.96904 C287.233976,110.863646 283.654101,105.278231 280.14345,100.213124 C279.86985,100.213124 279.573175,100.213124 279.255074,100.213124 L252.646648,100.213124 L252.646648,135.760088 L238.282648,135.760088 Z M277.887074,54.0156383 L252.646648,54.0156383 L252.646648,87.2561199 L277.887074,87.2561199 C285.638525,87.2561199 291.337976,85.8993655 294.987075,83.1858568 C298.634526,80.4270685 300.459076,76.2436881 300.459076,70.6358791 C300.459076,65.0279066 298.634526,60.8672477 294.987075,58.153739 C291.337976,55.3949507 285.638525,54.0156383 277.887074,54.0156383 Z M360.730192,137.388193 C354.163792,137.388193 348.395117,135.895763 343.424167,132.910904 C338.499367,129.926044 334.645892,125.765222 331.865391,120.428764 C329.129391,115.092142 327.76139,108.91891 327.76139,101.909067 C327.76139,94.8990604 329.129391,88.7258281 331.865391,83.38937 C334.645892,78.0074687 338.499367,73.8242518 343.424167,70.8393922 C348.395117,67.809253 354.163792,66.2942651 360.730192,66.2942651 L362.234993,66.2942651 C368.208044,66.2942651 373.452593,67.7639734 377.966994,70.7037168 C382.525895,73.5980171 386.082695,77.8039556 388.637394,83.3215323 C391.190445,88.7936658 392.467794,95.396646 392.467794,103.130146 L392.467794,107.268247 L341.919367,107.268247 C342.740167,112.830939 344.838317,117.21767 348.212168,120.428764 C351.632168,123.594415 356.078992,125.177404 361.550993,125.177404 L362.918993,125.177404 C366.202193,125.177404 369.348593,124.521585 372.358193,123.21011 C375.367793,121.853356 377.897769,120.044296 379.94977,117.783093 L388.363794,126.601996 C385.261895,129.903323 381.431495,132.526436 376.872593,134.471171 C372.358193,136.415744 367.477894,137.388193 362.234993,137.388193 L360.730192,137.388193 Z M360.319792,78.5050543 C355.439493,78.5050543 351.358568,80.110438 348.075368,83.3215323 C344.838317,86.4871834 342.786317,90.8287974 341.919367,96.346374 L378.718569,96.346374 C378.308169,91.3263829 376.643495,87.0977229 373.726193,83.6607209 C370.853393,80.2235553 366.840043,78.5050543 361.687793,78.5050543 L360.319792,78.5050543 Z M427.078197,137.116842 C419.872297,137.116842 414.263497,135.285224 410.251796,131.621987 C406.284595,127.913471 404.300171,123.051713 404.300171,117.036878 C404.300171,111.067159 406.535121,106.069726 411.003371,102.044742 C415.517772,98.0195954 422.700596,96.0071855 432.550197,96.0071855 L451.154998,96.0071855 L451.154998,95.8036723 C451.154998,84.4069357 445.864299,78.7085675 435.286197,78.7085675 C430.862447,78.7085675 427.122698,79.4998985 424.068596,81.0828876 C421.058996,82.6657132 418.618023,84.8817998 416.748972,87.7309839 L407.378995,80.2009973 C410.296297,75.7237079 414.126697,72.2865424 418.870196,69.8896642 C423.658196,67.4926225 429.084047,66.2942651 435.149397,66.2942651 L436.654197,66.2942651 C445.043499,66.2942651 451.792849,68.6911433 456.900598,73.4850632 C462.0067,78.2788196 464.561399,85.4470596 464.561399,94.9896197 L464.561399,135.760088 L451.428598,135.760088 L451.428598,124.70254 C448.783249,128.682244 445.386323,131.757663 441.236173,133.92847 C437.086024,136.053997 432.367247,137.116842 427.078197,137.116842 Z M430.087797,125.041729 C435.376848,125.041729 439.777523,123.865766 443.288173,121.514167 C446.798824,119.117126 449.421099,115.883637 451.154998,111.813374 L451.154998,106.657707 L433.097397,106.657707 C427.716047,106.657707 423.862572,107.516876 421.536972,109.23554 C419.257521,110.908762 418.116972,113.192686 418.116972,116.08715 C418.116972,122.056869 422.107247,125.041729 430.087797,125.041729 Z M513.374279,137.388193 C506.807879,137.388193 500.994702,135.963601 495.933102,133.114417 C490.871502,130.265233 486.904301,126.19497 484.031501,120.903628 C481.203202,115.567006 479.790701,109.23554 479.790701,101.909067 C479.790701,94.5373138 481.203202,88.2058479 484.031501,82.914506 C486.858152,77.5778844 490.779203,73.4850632 495.796302,70.6358791 C500.857902,67.7414153 506.717228,66.2942651 513.374279,66.2942651 L514.879079,66.2942651 C520.169778,66.2942651 525.162154,67.6510195 529.859504,70.3645282 C534.601355,73.0327573 538.431755,76.7185521 541.350705,81.4220762 L530.74788,88.7485497 C528.788179,85.6731308 526.439504,83.2762526 523.703504,81.5577516 C520.967504,79.7939709 517.865605,78.9120806 514.401103,78.9120806 L513.033103,78.9120806 C507.104553,78.9120806 502.430278,80.9923283 499.010278,85.1531506 C495.636427,89.2685299 493.948678,94.8312227 493.948678,101.841229 C493.948678,108.805793 495.636427,114.368485 499.010278,118.529308 C502.430278,122.689967 507.104553,124.770378 513.033103,124.770378 L514.401103,124.770378 C517.865605,124.770378 521.058154,124.001441 523.977104,122.463895 C526.894405,120.926186 529.402955,118.868496 531.501104,116.290663 L541.350705,124.566865 C538.294955,128.546568 534.44148,131.689825 529.79028,133.996307 C525.185229,136.25751 520.214279,137.388193 514.879079,137.388193 L513.374279,137.388193 Z M587.140799,136.641978 C579.023449,136.641978 572.846022,134.606847 568.605222,130.536584 C564.364422,126.466321 562.243198,120.202529 562.243198,111.745536 L562.243198,79.7261332 L548.836797,79.7261332 L548.836797,67.9223704 L562.243198,67.9223704 L562.243198,47.7065671 L575.923199,47.7065671 L575.923199,67.9223704 L598.974824,67.9223704 L598.974824,79.7261332 L575.923199,79.7261332 L575.923199,109.913918 C575.923199,115.250376 577.040673,118.958892 579.275623,121.039303 C581.508924,123.119551 584.815199,124.159838 589.192799,124.159838 C591.290949,124.159838 593.16,124.069279 594.8016,123.888487 C596.489349,123.707532 598.2216,123.481461 600,123.21011 L600,135.081711 C598.17545,135.533853 596.12345,135.895763 593.844,136.167114 C591.564549,136.483581 589.329599,136.641978 587.140799,136.641978 Z" id="Shape" fill="#23272F"></path> + <path d="M99.8210256,110.554562 C110.491336,110.554562 119.141324,101.6644 119.141324,90.6976744 C119.141324,79.7309491 110.491336,70.8407871 99.8210256,70.8407871 C89.1508821,70.8407871 80.5008945,79.7309491 80.5008945,90.6976744 C80.5008945,101.6644 89.1508821,110.554562 99.8210256,110.554562 Z" id="Path" fill="#087EA4"></path> + <path d="M200.715564,90.1633095 C200.715564,75.1129909 186.735973,61.8991451 164.490491,53.2292358 C168.08609,29.6400435 163.606495,10.9404515 150.536096,3.41529223 C138.863657,-3.3108368 122.698942,-0.0193199813 105.016877,12.6934961 C103.451769,13.8145412 101.898682,15.0071949 100.345431,16.247423 C98.8042012,15.0071949 97.2511146,13.8145412 95.6980281,12.6934961 C78.0159623,-0.0193199813 61.8512478,-3.32276333 50.1786439,3.41529223 C37.1202671,10.9404515 32.6400134,29.6280264 36.2481278,53.1933492 C13.9783553,61.8752756 0,75.1009739 0,90.1633095 C0,105.213628 13.9783553,118.427309 36.2242493,127.097383 C32.6281565,150.686411 37.1084102,169.386464 50.1786439,176.911129 C54.1332426,179.189435 58.6134963,180.322004 63.4879912,180.322004 C72.9861752,180.322004 84.0134026,176.028779 95.6980281,167.633288 C97.2629715,166.512242 98.8162227,165.318766 100.369309,164.079196 C101.910539,165.318766 103.463625,166.512242 105.016877,167.633288 C116.701338,176.040303 127.72873,180.322004 137.226749,180.322004 C142.101244,180.322004 146.581498,179.189435 150.536096,176.911129 C162.2087,170.184859 167.429021,154.56233 165.243732,132.917105 C165.052705,131.009024 164.800746,129.077073 164.490491,127.109236 C186.749147,118.439162 200.715564,105.22548 200.715564,90.1633095 Z M110.608219,20.4333166 C120.548401,13.2897406 129.795614,9.56897382 137.226749,9.56897382 C140.440622,9.56897382 143.308011,10.26068 145.757115,11.6679291 C154.311524,16.6051368 157.967396,30.7610886 155.350813,50.0568922 C146.748811,47.4213662 137.226749,45.4059543 126.988004,44.1178224 C120.858985,36.0680272 114.359604,28.8052682 107.70493,22.6037981 C108.672748,21.864501 109.640401,21.1250392 110.608219,20.4333166 Z M54.9576251,11.6679291 C57.4068941,10.26068 60.2741181,9.56897382 63.4879912,9.56897382 C70.9192905,9.56897382 80.1665039,13.2778882 90.1066859,20.4333166 C91.0743391,21.1250392 92.0419922,21.8524839 93.00981,22.6037981 C86.3551363,28.8052682 79.8677764,36.0561747 73.7387577,44.1059699 C63.5118697,45.3939372 53.9779504,47.4213662 45.3759489,50.0450397 C42.7593661,30.7610886 46.4033805,16.6051368 54.9576251,11.6679291 Z M9.55784712,90.1633095 C9.55784712,80.3005819 20.0236348,70.0564355 38.0760634,62.6743289 C40.0951911,71.4160114 43.1058505,80.6584603 47.0843276,90.13944 C43.0819721,99.6322721 40.0713127,108.886738 38.0521849,117.640273 C20.0236348,110.258166 9.55784712,100.025872 9.55784712,90.1633095 Z M90.1066859,159.892973 C75.8654208,170.125596 63.05785,173.320822 54.9576251,168.658854 C46.4033805,163.721976 42.7475092,149.56553 45.3639273,130.269562 C53.9659288,132.905253 63.4879912,134.920665 73.7269008,136.208632 C79.8557548,144.258427 86.3551363,151.521351 93.00981,157.722656 C92.0419922,158.47397 91.0743391,159.201415 90.1066859,159.892973 Z M100.357452,151.509334 C88.4101641,140.585317 76.9287523,125.988191 67.2393762,109.244452 C64.6468365,104.772289 59.4052723,93.5310541 57.2786092,89.1782384 C55.5820874,93.2329318 53.9063151,96.9526286 52.5920129,100.911844 C53.667366,102.903551 57.8129922,112.011178 58.9599806,114.002885 C61.2299144,117.926378 63.6075481,121.742705 66.0566524,125.439685 C59.1869081,124.235179 52.830962,122.684811 47.0484276,120.860191 C50.333936,106.048403 56.893096,88.9706558 67.2393762,71.0820025 C69.784159,66.6814478 75.3130878,56.8557591 78.0849628,52.7771962 C73.7122444,53.3377187 70.2006319,53.1521948 66.090741,53.9988226 C64.8959957,55.9307734 60.1069691,64.332027 58.9599806,66.323734 C56.6660038,70.2829492 54.5633838,74.242329 52.5920129,78.1778393 C50.2025223,71.6306726 48.3745867,65.3577585 47.0724708,59.4425583 C61.5645419,54.8749168 79.6647273,52.0008601 100.357452,52.0008601 C105.53051,52.0008601 116.334433,51.8961634 121.161171,52.2420247 C118.496996,48.7476893 116.212077,45.5847288 113.428345,42.4602889 C111.170268,42.3886803 102.663286,42.4602889 100.357452,42.4602889 C95.7935418,42.4602889 91.3253097,42.615194 86.9405697,42.8775942 C91.2894097,37.7137082 95.7935418,32.9911617 100.357452,28.8171207 C112.292719,39.7411372 123.786152,54.3382638 133.475364,71.0820025 C136.020147,75.4827218 141.51861,85.7268682 143.681008,90.1633095 C145.389387,86.1084515 147.00571,82.4104842 148.331869,78.4272348 C147.256516,76.42384 142.901748,68.2914068 141.754759,66.3117169 C139.484826,62.3882237 137.107357,58.5718964 134.658088,54.8869339 C141.527832,56.0914401 147.883778,57.641643 153.666312,59.4664278 C150.380804,74.2782157 143.821644,91.3557986 133.475364,109.244452 C130.930581,113.645007 123.688169,123.722395 120.916458,127.800958 C125.289177,127.240436 130.024683,126.402533 134.134573,125.555905 C135.329319,123.623954 140.607936,115.994427 141.754759,114.002885 C144.036715,110.067375 146.139499,106.131864 148.086827,102.208207 C150.404683,108.576599 152.24464,114.83766 153.582656,120.895913 C139.102441,125.463555 121.026299,128.325759 100.357452,128.325759 C95.18423,128.325759 83.1564137,127.646383 78.3296756,127.300521 C80.9940152,130.794692 83.8635448,134.432738 86.647112,137.557343 C88.9051888,137.628787 98.0516187,137.86633 100.357452,137.86633 C104.921198,137.86633 109.389595,137.71126 113.77417,137.44886 C109.413474,142.612746 104.921198,147.335293 100.357452,151.509334 Z M145.757115,168.658854 C137.65689,173.333991 124.837462,170.13712 110.608219,159.892973 C109.640401,159.201415 108.672748,158.47397 107.70493,157.722656 C114.359604,151.521351 120.846964,144.270444 126.975982,136.220484 C137.191014,134.932517 146.701054,132.917105 155.291199,130.293432 C155.458348,131.497938 155.625662,132.702444 155.745054,133.883081 C157.501354,151.318542 153.857505,163.983718 145.757115,168.658854 Z M162.662555,117.652126 C160.607692,108.779407 157.561133,99.5369585 153.630412,90.1990315 C157.632768,80.7061994 160.643592,71.4517334 162.662555,62.6981984 C180.690611,70.0803051 191.157601,80.3005819 191.157601,90.1633095 C191.157601,100.025872 180.703786,110.258166 162.662555,117.652126 Z" id="Shape" fill="#087EA4"></path> + </g> + </g> +</svg> \ No newline at end of file diff --git a/public/images/team/lauren.jpg b/public/images/team/lauren.jpg index 1485cf8ff..cb08b9725 100644 Binary files a/public/images/team/lauren.jpg and b/public/images/team/lauren.jpg differ diff --git a/public/images/team/lesiutin.jpg b/public/images/team/lesiutin.jpg new file mode 100644 index 000000000..edfc942e0 Binary files /dev/null and b/public/images/team/lesiutin.jpg differ diff --git a/public/images/uwu.png b/public/images/uwu.png new file mode 100644 index 000000000..a09d245ea Binary files /dev/null and b/public/images/uwu.png differ diff --git a/public/mstile-150x150.png b/public/mstile-150x150.png new file mode 100644 index 000000000..d36e7ee9e Binary files /dev/null and b/public/mstile-150x150.png differ diff --git a/public/safari-pinned-tab.svg b/public/safari-pinned-tab.svg new file mode 100644 index 000000000..7e4874b2f --- /dev/null +++ b/public/safari-pinned-tab.svg @@ -0,0 +1,60 @@ +<?xml version="1.0" standalone="no"?> +<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN" + "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" + width="397.000000pt" height="397.000000pt" viewBox="0 0 397.000000 397.000000" + preserveAspectRatio="xMidYMid meet"> +<metadata> +Created by potrace 1.14, written by Peter Selinger 2001-2017 +</metadata> +<g transform="translate(0.000000,397.000000) scale(0.100000,-0.100000)" +fill="#000000" stroke="none"> +<path d="M1151 3759 c-30 -5 -88 -25 -129 -45 -185 -89 -296 -291 -321 -581 +-10 -112 -3 -294 14 -386 6 -28 4 -37 -6 -37 -26 0 -264 -121 -349 -178 -266 +-177 -394 -414 -346 -640 49 -233 242 -423 600 -592 101 -48 107 -53 102 -76 +-17 -65 -25 -276 -15 -387 30 -351 184 -567 444 -622 89 -19 122 -19 231 0 +158 28 355 120 525 246 43 33 81 59 84 59 3 0 41 -26 84 -59 170 -126 367 +-218 525 -246 109 -19 142 -19 231 0 262 55 413 269 445 628 9 102 0 319 -16 +381 -5 23 1 28 102 76 359 169 551 359 600 592 48 228 -80 460 -355 643 -93 +62 -312 175 -340 175 -10 0 -12 9 -7 34 15 81 23 275 15 379 -28 373 -196 597 +-479 638 -73 10 -99 10 -184 -5 -170 -28 -372 -122 -549 -255 -37 -28 -70 -51 +-72 -51 -2 0 -35 23 -72 51 -169 127 -379 226 -537 254 -97 16 -143 17 -225 4z +m238 -195 c91 -22 253 -100 361 -174 l90 -62 -77 -82 c-96 -99 -203 -222 -263 +-298 -30 -39 -52 -58 -66 -58 -43 0 -262 -42 -398 -76 -76 -19 -141 -33 -143 +-30 -2 2 -7 75 -10 162 -6 182 9 298 54 411 36 90 126 184 199 206 65 21 172 +21 253 1z m1447 -1 c73 -24 162 -118 197 -206 45 -111 60 -229 54 -406 -3 -84 +-9 -157 -14 -162 -4 -4 -68 8 -141 26 -109 28 -276 60 -398 77 -13 2 -52 40 +-99 98 -42 52 -128 149 -190 216 l-114 121 47 35 c119 88 275 168 392 199 84 +23 202 24 266 2z m-737 -473 c60 -61 119 -123 129 -138 l20 -27 -263 0 -263 0 +18 25 c28 39 231 250 241 250 5 0 58 -50 118 -110z m284 -366 c33 -8 79 -69 +203 -271 90 -146 244 -441 244 -468 0 -22 -128 -274 -207 -407 -105 -178 -203 +-318 -232 -331 -37 -16 -776 -16 -813 0 -47 21 -237 322 -368 583 -38 77 -70 +147 -70 155 0 22 129 275 207 407 79 135 198 314 214 324 26 16 190 22 484 19 +171 -2 323 -7 338 -11z m-1083 -48 c0 -2 -30 -53 -67 -113 -38 -60 -95 -159 +-128 -221 -33 -61 -63 -112 -66 -112 -10 0 -116 358 -107 365 6 4 136 39 228 +61 91 21 140 28 140 20z m1599 -42 c74 -19 136 -36 139 -38 8 -9 -97 -366 +-108 -366 -3 0 -28 41 -54 90 -26 50 -83 149 -127 221 -43 72 -79 133 -79 136 +0 9 93 -8 229 -43z m-2120 -196 c21 -84 90 -291 136 -409 l18 -45 -41 -105 +c-46 -120 -104 -299 -122 -382 -15 -67 -10 -67 -135 -5 -190 95 -342 222 -406 +340 -32 58 -34 69 -34 153 0 78 4 97 27 140 68 131 201 244 402 345 66 33 122 +60 125 60 4 0 17 -42 30 -92z m2556 40 c191 -95 341 -221 405 -339 33 -60 35 +-70 35 -154 0 -84 -2 -94 -35 -154 -64 -118 -214 -244 -405 -339 -125 -62 +-120 -62 -135 5 -18 83 -76 262 -122 382 l-41 105 18 45 c46 118 115 325 136 +408 27 107 16 104 144 41z m-2241 -829 c26 -50 84 -149 127 -221 44 -72 79 +-132 79 -135 0 -9 -93 8 -229 43 -74 19 -136 36 -138 38 -9 9 95 366 106 366 +4 0 28 -41 55 -91z m1851 57 c14 -34 95 -311 95 -326 0 -14 -354 -99 -365 -88 +-3 2 31 64 75 138 44 73 101 173 127 221 26 49 48 89 50 89 2 0 10 -15 18 -34z +m-1885 -556 c115 -29 333 -70 374 -70 14 0 36 -19 66 -57 60 -77 167 -200 263 +-299 l78 -82 -63 -44 c-130 -93 -299 -175 -406 -197 -75 -16 -169 -14 -232 5 +-77 23 -166 114 -203 207 -44 111 -60 229 -54 404 3 84 8 157 12 163 4 7 17 9 +29 6 11 -4 73 -20 136 -36z m2024 -37 c17 -201 -1 -375 -51 -501 -37 -92 -126 +-183 -203 -206 -67 -20 -166 -20 -250 0 -87 20 -245 97 -359 173 l-92 62 83 +87 c101 105 209 229 263 300 26 35 46 52 61 52 45 0 264 42 389 74 72 19 135 +34 141 35 7 1 14 -29 18 -76z m-867 -107 c-17 -21 -74 -83 -128 -139 l-98 +-100 -39 31 c-33 26 -183 185 -219 232 -10 13 19 15 252 15 l263 0 -31 -39z"/> +<path d="M1912 2360 c-62 -13 -157 -67 -198 -113 -20 -22 -50 -69 -67 -106 +-27 -58 -31 -77 -31 -151 0 -74 4 -93 31 -151 43 -92 91 -142 180 -186 70 -34 +82 -37 162 -37 75 0 94 4 152 31 92 43 142 91 186 180 34 70 37 82 37 163 0 +81 -3 93 -37 163 -43 87 -94 137 -181 178 -68 32 -164 44 -234 29z"/> +</g> +</svg> diff --git a/public/site.webmanifest b/public/site.webmanifest new file mode 100644 index 000000000..337446d52 --- /dev/null +++ b/public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "React", + "short_name": "React", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-384x384.png", + "sizes": "384x384", + "type": "image/png" + } + ], + "theme_color": "#23272f", + "background_color": "#23272f", + "display": "standalone" +} diff --git a/scripts/generateRss.js b/scripts/generateRss.js new file mode 100644 index 000000000..e0f3d5561 --- /dev/null +++ b/scripts/generateRss.js @@ -0,0 +1,6 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ +const {generateRssFeed} = require('../src/utils/rss'); + +generateRssFeed(); diff --git a/src/components/ButtonLink.tsx b/src/components/ButtonLink.tsx index 15ab83f2b..23c971756 100644 --- a/src/components/ButtonLink.tsx +++ b/src/components/ButtonLink.tsx @@ -26,7 +26,8 @@ function ButtonLink({ className, 'active:scale-[.98] transition-transform inline-flex font-bold items-center outline-none focus:outline-none focus-visible:outline focus-visible:outline-link focus:outline-offset-2 focus-visible:dark:focus:outline-link-dark leading-snug', { - 'bg-link text-white hover:bg-opacity-80': type === 'primary', + 'bg-link text-white dark:bg-brand-dark dark:text-secondary hover:bg-opacity-80': + type === 'primary', 'text-primary dark:text-primary-dark shadow-secondary-button-stroke dark:shadow-secondary-button-stroke-dark hover:bg-gray-40/5 active:bg-gray-40/10 hover:dark:bg-gray-60/5 active:dark:bg-gray-60/10': type === 'secondary', 'text-lg py-3 rounded-full px-4 sm:px-6': size === 'lg', diff --git a/src/components/Icon/IconCanary.tsx b/src/components/Icon/IconCanary.tsx index a7782b141..7f584fed7 100644 --- a/src/components/Icon/IconCanary.tsx +++ b/src/components/Icon/IconCanary.tsx @@ -4,29 +4,35 @@ import {memo} from 'react'; -export const IconCanary = memo<JSX.IntrinsicElements['svg'] & {title?: string}>( - function IconCanary({className, title}) { - return ( - <svg - className={className} - width="20px" - height="20px" - viewBox="0 0 20 20" - version="1.1" - xmlns="http://www.w3.org/2000/svg"> - {title && <title>{title}</title>} - <g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd"> - <g - id="noun-labs-1201738-(2)" - transform="translate(2, 0)" - fill="currentColor" - fillRule="nonzero"> - <path - d="M10.2865804,5.55665262 L10.2865804,2.22331605 L10.8591544,2.22331605 C11.0103911,2.22244799 11.1551447,2.16342155 11.2617505,2.05914367 C11.3684534,1.95486857 11.4282767,1.81370176 11.4282767,1.66667106 L11.4282767,0.556642208 C11.4282767,0.40907262 11.3678934,0.26747526 11.2605218,0.16308627 C11.1531503,0.0587028348 11.0074938,0 10.8556998,0 L5.14338868,0 C4.9915947,0 4.84594391,0.0587028348 4.73856664,0.16308627 C4.63119507,0.267469704 4.57081178,0.40907262 4.57081178,0.556642208 L4.57081178,1.66667106 C4.57081178,1.81434899 4.63119507,1.95594912 4.73856664,2.06033811 C4.8459382,2.16472155 4.9915947,2.22331605 5.14338868,2.22331605 L5.71596273,2.22331605 L5.71596273,5.55665262 C5.71596273,8.38665538 2.97295619,9.88999017 0.651686904,15.5566623 C-0.0957823782,17.360053 -2.00560068,20 7.99951567,20 C18.004632,20 16.0948137,17.3600252 15.3507732,15.5566623 C13.0124432,9.88999017 10.2865804,8.38665538 10.2865804,5.55665262 Z M9.89570197,10.709991 C10.0921412,10.709991 10.2805515,10.7858383 10.4193876,10.9209301 C10.5583466,11.0559135 10.6363652,11.2390693 10.6363652,11.4300417 C10.6363652,11.6210141 10.5583466,11.8040698 10.4193876,11.9391533 C10.2805401,12.0741367 10.0921412,12.1499813 9.89570197,12.1499813 C9.6992627,12.1499813 9.51096673,12.074134 9.37201631,11.9391533 C9.23316875,11.8040615 9.15515307,11.6210141 9.15515307,11.4300417 C9.15515307,11.2390693 9.2331716,11.0559024 9.37201631,10.9209301 C9.57264221,10.7258996 9.61239426,10.709991 9.89570197,10.709991 Z M8.98919546,9.04212824 C9.09790709,9.14792278 9.15884755,9.29158681 9.1585213,9.44110085 C9.15829001,9.59073155 9.09678989,9.73407335 8.98763252,9.83954568 C8.87847514,9.945018 8.73069852,10.0039347 8.57678157,10.0033977 C8.42286747,10.0027392 8.27565088,9.94273467 8.16727355,9.83639845 C8.05900765,9.73006224 7.99873866,9.58628988 7.99963013,9.43664806 C8.00052304,9.28788403 8.0620221,9.14542556 8.17051087,9.04048101 C8.27911107,8.93555591 8.42599335,8.87663641 8.57913312,8.87663641 C8.73291864,8.87665585 8.88047525,8.93622535 8.98919546,9.04212824 Z M7.99965585,17.9999981 C4.91377349,17.9999981 3.29882839,17.7332867 2.51364277,17.4999976 C2.37780966,17.4476975 2.26954376,17.3439641 2.21396931,17.2125528 C2.15838628,17.0811499 2.16006066,16.9334692 2.21876871,16.8033858 C2.6144474,15.5921346 3.14916224,14.4280501 3.81316983,13.3333824 C5.980145,9.82337899 8.22941036,13.8867718 10.0980836,13.8867718 C11.9666996,13.8867718 11.4695868,12.1534924 12.1827971,13.3333824 C12.8511505,14.4269112 13.3916656,15.5896902 13.794259,16.8000524 C13.8533022,16.9322137 13.8537479,17.0822749 13.7952635,17.2147751 C13.7368889,17.3472613 13.6248314,17.4504531 13.4856467,17.5000531 C12.6833967,17.7332867 11.0855382,17.9999981 7.99965585,17.9999981 Z" - id="Shape"></path> - </g> - </g> - </svg> - ); +export const IconCanary = memo< + JSX.IntrinsicElements['svg'] & {title?: string; size?: 's' | 'md'} +>(function IconCanary( + {className, title, size} = { + className: undefined, + title: undefined, + size: 'md', } -); +) { + return ( + <svg + className={className} + width={size === 's' ? '12px' : '20px'} + height={size === 's' ? '12px' : '20px'} + viewBox="0 0 20 20" + version="1.1" + xmlns="http://www.w3.org/2000/svg"> + {title && <title>{title}</title>} + <g stroke="none" strokeWidth="1" fill="none" fillRule="evenodd"> + <g + id="noun-labs-1201738-(2)" + transform="translate(2, 0)" + fill="currentColor" + fillRule="nonzero"> + <path + d="M10.2865804,5.55665262 L10.2865804,2.22331605 L10.8591544,2.22331605 C11.0103911,2.22244799 11.1551447,2.16342155 11.2617505,2.05914367 C11.3684534,1.95486857 11.4282767,1.81370176 11.4282767,1.66667106 L11.4282767,0.556642208 C11.4282767,0.40907262 11.3678934,0.26747526 11.2605218,0.16308627 C11.1531503,0.0587028348 11.0074938,0 10.8556998,0 L5.14338868,0 C4.9915947,0 4.84594391,0.0587028348 4.73856664,0.16308627 C4.63119507,0.267469704 4.57081178,0.40907262 4.57081178,0.556642208 L4.57081178,1.66667106 C4.57081178,1.81434899 4.63119507,1.95594912 4.73856664,2.06033811 C4.8459382,2.16472155 4.9915947,2.22331605 5.14338868,2.22331605 L5.71596273,2.22331605 L5.71596273,5.55665262 C5.71596273,8.38665538 2.97295619,9.88999017 0.651686904,15.5566623 C-0.0957823782,17.360053 -2.00560068,20 7.99951567,20 C18.004632,20 16.0948137,17.3600252 15.3507732,15.5566623 C13.0124432,9.88999017 10.2865804,8.38665538 10.2865804,5.55665262 Z M9.89570197,10.709991 C10.0921412,10.709991 10.2805515,10.7858383 10.4193876,10.9209301 C10.5583466,11.0559135 10.6363652,11.2390693 10.6363652,11.4300417 C10.6363652,11.6210141 10.5583466,11.8040698 10.4193876,11.9391533 C10.2805401,12.0741367 10.0921412,12.1499813 9.89570197,12.1499813 C9.6992627,12.1499813 9.51096673,12.074134 9.37201631,11.9391533 C9.23316875,11.8040615 9.15515307,11.6210141 9.15515307,11.4300417 C9.15515307,11.2390693 9.2331716,11.0559024 9.37201631,10.9209301 C9.57264221,10.7258996 9.61239426,10.709991 9.89570197,10.709991 Z M8.98919546,9.04212824 C9.09790709,9.14792278 9.15884755,9.29158681 9.1585213,9.44110085 C9.15829001,9.59073155 9.09678989,9.73407335 8.98763252,9.83954568 C8.87847514,9.945018 8.73069852,10.0039347 8.57678157,10.0033977 C8.42286747,10.0027392 8.27565088,9.94273467 8.16727355,9.83639845 C8.05900765,9.73006224 7.99873866,9.58628988 7.99963013,9.43664806 C8.00052304,9.28788403 8.0620221,9.14542556 8.17051087,9.04048101 C8.27911107,8.93555591 8.42599335,8.87663641 8.57913312,8.87663641 C8.73291864,8.87665585 8.88047525,8.93622535 8.98919546,9.04212824 Z M7.99965585,17.9999981 C4.91377349,17.9999981 3.29882839,17.7332867 2.51364277,17.4999976 C2.37780966,17.4476975 2.26954376,17.3439641 2.21396931,17.2125528 C2.15838628,17.0811499 2.16006066,16.9334692 2.21876871,16.8033858 C2.6144474,15.5921346 3.14916224,14.4280501 3.81316983,13.3333824 C5.980145,9.82337899 8.22941036,13.8867718 10.0980836,13.8867718 C11.9666996,13.8867718 11.4695868,12.1534924 12.1827971,13.3333824 C12.8511505,14.4269112 13.3916656,15.5896902 13.794259,16.8000524 C13.8533022,16.9322137 13.8537479,17.0822749 13.7952635,17.2147751 C13.7368889,17.3472613 13.6248314,17.4504531 13.4856467,17.5000531 C12.6833967,17.7332867 11.0855382,17.9999981 7.99965585,17.9999981 Z" + id="Shape"></path> + </g> + </g> + </svg> + ); +}); diff --git a/src/components/Layout/Footer.tsx b/src/components/Layout/Footer.tsx index 26bdf6711..5bcf9df98 100644 --- a/src/components/Layout/Footer.tsx +++ b/src/components/Layout/Footer.tsx @@ -285,6 +285,30 @@ export function Footer() { dir="ltr"> ©{new Date().getFullYear()} </div> + <div + className="uwu-visible text-xs cursor-pointer hover:text-link hover:dark:text-link-dark hover:underline" + onClick={() => { + // @ts-ignore + window.__setUwu(false); + }}> + no uwu plz + </div> + <div + className="uwu-hidden text-xs cursor-pointer hover:text-link hover:dark:text-link-dark hover:underline" + onClick={() => { + // @ts-ignore + window.__setUwu(true); + }}> + uwu? + </div> + <div className="uwu-visible text-xs"> + Logo by + <ExternalLink + className="ms-1" + href="https://twitter.com/sawaratsuki1004"> + @sawaratsuki1004 + </ExternalLink> + </div> </div> <div className="flex flex-col"> <FooterLink href="/learn" isHeader={true}> diff --git a/src/components/Layout/HomeContent.js b/src/components/Layout/HomeContent.js index e1fab6d71..72ab36884 100644 --- a/src/components/Layout/HomeContent.js +++ b/src/components/Layout/HomeContent.js @@ -26,6 +26,8 @@ import Link from 'components/MDX/Link'; import CodeBlock from 'components/MDX/CodeBlock'; import {ExternalLink} from 'components/ExternalLink'; import sidebarBlog from '../../sidebarBlog.json'; +import * as React from 'react'; +import Image from 'next/image'; function Section({children, background = null}) { return ( @@ -115,12 +117,22 @@ export function HomeContent() { <> <div className="ps-0"> <div className="mx-5 mt-12 lg:mt-24 mb-20 lg:mb-32 flex flex-col justify-center"> + <div className="uwu-visible flex justify-center"> + <Image + alt="logo by @sawaratsuki1004" + title="logo by @sawaratsuki1004" + loading="eager" + width={313} + height={160} + src="/images/uwu.png" + /> + </div> <Logo className={cn( - 'mt-4 mb-3 text-link dark:text-link-dark w-24 lg:w-28 self-center text-sm me-0 flex origin-center transition-all ease-in-out' + 'uwu-hidden mt-4 mb-3 text-brand dark:text-brand-dark w-24 lg:w-28 self-center text-sm me-0 flex origin-center transition-all ease-in-out' )} /> - <h1 className="text-5xl font-display lg:text-6xl self-center flex font-semibold leading-snug text-primary dark:text-primary-dark"> + <h1 className="uwu-hidden text-5xl font-display lg:text-6xl self-center flex font-semibold leading-snug text-primary dark:text-primary-dark"> React </h1> <p className="text-4xl font-display max-w-lg md:max-w-full py-1 text-center text-secondary dark:text-primary-dark leading-snug self-center"> @@ -489,7 +501,15 @@ export function HomeContent() { </div> <div className="mt-20 px-5 lg:px-0 mb-6 max-w-4xl text-center text-opacity-80"> - <Logo className="text-link dark:text-link-dark w-24 lg:w-28 mb-10 lg:mb-8 mt-12 h-auto mx-auto self-start" /> + <div className="uwu-visible flex justify-center"> + <img + alt="logo by @sawaratsuki1004" + title="logo by @sawaratsuki1004" + className="uwu-visible mb-10 lg:mb-8 h-24 lg:h-32" + src="/images/uwu.png" + /> + </div> + <Logo className="uwu-hidden text-brand dark:text-brand-dark w-24 lg:w-28 mb-10 lg:mb-8 mt-12 h-auto mx-auto self-start" /> <Header> Welcome to the <br className="hidden lg:inline" /> React community @@ -1620,7 +1640,7 @@ function Thumbnail({video}) { </div> <div className="mt-1"> <span className="inline-flex text-xs font-normal items-center text-primary-dark py-1 whitespace-nowrap outline-link px-1.5 rounded-lg"> - <Logo className="text-xs me-1 w-4 h-4 text-link-dark" /> + <Logo className="text-xs me-1 w-4 h-4 text-brand text-brand-dark" /> React Conf </span> </div> diff --git a/src/components/Layout/Page.tsx b/src/components/Layout/Page.tsx index ee3c899d0..24d379589 100644 --- a/src/components/Layout/Page.tsx +++ b/src/components/Layout/Page.tsx @@ -8,19 +8,19 @@ import {useRouter} from 'next/router'; import {SidebarNav} from './SidebarNav'; import {Footer} from './Footer'; import {Toc} from './Toc'; -import SocialBanner from '../SocialBanner'; +// import SocialBanner from '../SocialBanner'; import {DocsPageFooter} from 'components/DocsFooter'; import {Seo} from 'components/Seo'; -import ButtonLink from 'components/ButtonLink'; -import {IconNavArrow} from 'components/Icon/IconNavArrow'; import PageHeading from 'components/PageHeading'; import {getRouteMeta} from './getRouteMeta'; import {TocContext} from '../MDX/TocContext'; +import {Languages, LanguagesContext} from '../MDX/LanguagesContext'; import type {TocItem} from 'components/MDX/TocContext'; import type {RouteItem} from 'components/Layout/getRouteMeta'; import {HomeContent} from './HomeContent'; import {TopNav} from './TopNav'; import cn from 'classnames'; +import Head from 'next/head'; import(/* webpackPrefetch: true */ '../MDX/CodeBlock/CodeBlock'); @@ -35,9 +35,17 @@ interface PageProps { description?: string; }; section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown'; + languages?: Languages | null; } -export function Page({children, toc, routeTree, meta, section}: PageProps) { +export function Page({ + children, + toc, + routeTree, + meta, + section, + languages = null, +}: PageProps) { const {asPath} = useRouter(); const cleanedPath = asPath.split(/[\?\#]/)[0]; const {route, nextRoute, prevRoute, breadcrumbs, order} = getRouteMeta( @@ -74,7 +82,11 @@ export function Page({children, toc, routeTree, meta, section}: PageProps) { 'max-w-7xl mx-auto', section === 'blog' && 'lg:flex lg:flex-col lg:items-center' )}> - <TocContext.Provider value={toc}>{children}</TocContext.Provider> + <TocContext.Provider value={toc}> + <LanguagesContext.Provider value={languages}> + {children} + </LanguagesContext.Provider> + </TocContext.Provider> </div> {!isBlogIndex && ( <DocsPageFooter @@ -91,12 +103,10 @@ export function Page({children, toc, routeTree, meta, section}: PageProps) { let hasColumns = true; let showSidebar = true; let showToc = true; - let showSurvey = true; if (isHomePage || isBlogIndex) { hasColumns = false; showSidebar = false; showToc = false; - showSurvey = false; } else if (section === 'blog') { showToc = false; hasColumns = false; @@ -117,7 +127,17 @@ export function Page({children, toc, routeTree, meta, section}: PageProps) { image={`/images/og-` + section + '.png'} searchOrder={searchOrder} /> - <SocialBanner /> + {(isHomePage || isBlogIndex) && ( + <Head> + <link + rel="alternate" + type="application/rss+xml" + title="React Blog RSS Feed" + href="/rss.xml" + /> + </Head> + )} + {/*<SocialBanner />*/} <TopNav section={section} routeTree={routeTree} @@ -154,33 +174,7 @@ export function Page({children, toc, routeTree, meta, section}: PageProps) { )}> {!isHomePage && ( <div className="w-full px-5 pt-10 mx-auto sm:px-12 md:px-12 md:pt-12 lg:pt-10"> - { - <hr className="mx-auto max-w-7xl border-border dark:border-border-dark" /> - } - {showSurvey && ( - <> - <div className="flex flex-col items-center p-4 m-4"> - <p className="mb-4 text-lg font-bold text-primary dark:text-primary-dark"> - How do you like these docs? - </p> - <div> - <ButtonLink - href="https://www.surveymonkey.co.uk/r/PYRPF3X" - className="mt-1" - type="primary" - size="md" - target="_blank"> - Take our survey! - <IconNavArrow - displayDirection="end" - className="inline ms-1" - /> - </ButtonLink> - </div> - </div> - <hr className="mx-auto max-w-7xl border-border dark:border-border-dark" /> - </> - )} + <hr className="mx-auto max-w-7xl border-border dark:border-border-dark" /> </div> )} <div diff --git a/src/components/Layout/Sidebar/SidebarRouteTree.tsx b/src/components/Layout/Sidebar/SidebarRouteTree.tsx index a9fa575b5..3f058073c 100644 --- a/src/components/Layout/Sidebar/SidebarRouteTree.tsx +++ b/src/components/Layout/Sidebar/SidebarRouteTree.tsx @@ -10,6 +10,7 @@ import {SidebarLink} from './SidebarLink'; import {useCollapse} from 'react-collapsed'; import usePendingRoute from 'hooks/usePendingRoute'; import type {RouteItem} from 'components/Layout/getRouteMeta'; +import {siteConfig} from 'siteConfig'; interface SidebarRouteTreeProps { isForceExpanded: boolean; @@ -150,8 +151,12 @@ export function SidebarRouteTree({ ); } if (hasSectionHeader) { + let sectionHeaderText = + sectionHeader != null + ? sectionHeader.replace('{{version}}', siteConfig.version) + : ''; return ( - <Fragment key={`${sectionHeader}-${level}-separator`}> + <Fragment key={`${sectionHeaderText}-${level}-separator`}> {index !== 0 && ( <li role="separator" @@ -163,7 +168,7 @@ export function SidebarRouteTree({ 'mb-1 text-sm font-bold ms-5 text-tertiary dark:text-tertiary-dark', index !== 0 && 'mt-2' )}> - {sectionHeader} + {sectionHeaderText} </h3> </Fragment> ); diff --git a/src/components/Layout/TopNav/BrandMenu.tsx b/src/components/Layout/TopNav/BrandMenu.tsx new file mode 100644 index 000000000..3bd8776f2 --- /dev/null +++ b/src/components/Layout/TopNav/BrandMenu.tsx @@ -0,0 +1,145 @@ +import * as ContextMenu from '@radix-ui/react-context-menu'; +import {IconCopy} from 'components/Icon/IconCopy'; +import {IconDownload} from 'components/Icon/IconDownload'; +import {IconNewPage} from 'components/Icon/IconNewPage'; +import {ExternalLink} from 'components/ExternalLink'; +import {IconClose} from '../../Icon/IconClose'; + +function MenuItem({ + children, + onSelect, +}: { + children: React.ReactNode; + onSelect?: () => void; +}) { + return ( + <ContextMenu.Item + className="flex items-center hover:bg-border dark:hover:bg-border-dark ps-6 pe-4 py-2 w-full text-base cursor-pointer" + onSelect={onSelect}> + {children} + </ContextMenu.Item> + ); +} + +function DownloadMenuItem({ + fileName, + href, + children, +}: { + fileName: string; + href: string; + children: React.ReactNode; +}) { + return ( + <a download={fileName} href={href} className="flex items-center w-full"> + <MenuItem>{children}</MenuItem> + </a> + ); +} + +export default function BrandMenu({children}: {children: React.ReactNode}) { + return ( + <ContextMenu.Root> + <ContextMenu.Trigger className="flex items-center"> + {children} + </ContextMenu.Trigger> + <ContextMenu.Portal> + <ContextMenu.Content + className="hidden lg:block z-50 mt-6 bg-wash border border-border dark:border-border-dark dark:bg-wash-dark rounded min-w-56 overflow-hidden shadow" + // @ts-ignore + sideOffset={0} + align="end"> + <ContextMenu.Label className="ps-4 pt-2 text-base text-tertiary dark:text-tertiary-dark"> + Dark Mode + </ContextMenu.Label> + <DownloadMenuItem + fileName="react_logo_dark.svg" + href="/images/brand/logo_dark.svg"> + <span className="w-8"> + <IconDownload /> + </span> + <span>Logo SVG</span> + </DownloadMenuItem> + <DownloadMenuItem + fileName="react_wordmark_dark.svg" + href="/images/brand/wordmark_dark.svg"> + <span className="w-8"> + <IconDownload /> + </span> + <span>Wordmark SVG</span> + </DownloadMenuItem> + <MenuItem + onSelect={async () => { + await navigator.clipboard.writeText('#58C4DC'); + }}> + <span className="w-8"> + <IconCopy /> + </span> + <span>Copy dark mode color</span> + </MenuItem> + <ContextMenu.Label className="ps-4 text-base text-tertiary dark:text-tertiary-dark"> + Light Mode + </ContextMenu.Label> + <DownloadMenuItem + fileName="react_logo_light.svg" + href="/images/brand/logo_light.svg"> + <span className="w-8"> + <IconDownload /> + </span> + <span>Logo SVG</span> + </DownloadMenuItem> + <DownloadMenuItem + fileName="react_wordmark_light.svg" + href="/images/brand/wordmark_light.svg"> + <span className="w-8"> + <IconDownload /> + </span> + <span>Wordmark SVG</span> + </DownloadMenuItem> + <MenuItem + onSelect={async () => { + await navigator.clipboard.writeText('#087EA4'); + }}> + <span className="w-8"> + <IconCopy /> + </span> + <span>Copy light mode color</span> + </MenuItem> + <div className="uwu-visible flex flex-col"> + <ContextMenu.Separator className="" /> + <ContextMenu.Label className="ps-4 text-base text-tertiary dark:text-tertiary-dark"> + uwu + </ContextMenu.Label> + <MenuItem + onSelect={() => { + // @ts-ignore + window.__setUwu(false); + }}> + <span className="w-8"> + <IconClose /> + </span> + <span>Turn off</span> + </MenuItem> + <DownloadMenuItem fileName="react_uwu_png" href="/images/uwu.png"> + <span className="w-8"> + <IconDownload /> + </span> + <span>Logo PNG</span> + </DownloadMenuItem> + + <ExternalLink + className="flex items-center" + href="https://github.com/SAWARATSUKI/KawaiiLogos"> + <MenuItem> + <span className="w-8"> + <IconNewPage /> + </span> + <span>Logo by @sawaratsuki1004</span> + </MenuItem> + </ExternalLink> + </div> + </ContextMenu.Content> + </ContextMenu.Portal> + </ContextMenu.Root> + ); +} diff --git a/src/components/Layout/TopNav/TopNav.tsx b/src/components/Layout/TopNav/TopNav.tsx index b6e276ff7..cc5c654e3 100644 --- a/src/components/Layout/TopNav/TopNav.tsx +++ b/src/components/Layout/TopNav/TopNav.tsx @@ -10,6 +10,7 @@ import { startTransition, Suspense, } from 'react'; +import Image from 'next/image'; import * as React from 'react'; import cn from 'classnames'; import NextLink from 'next/link'; @@ -24,6 +25,8 @@ import {Logo} from '../../Logo'; import {Feedback} from '../Feedback'; import {SidebarRouteTree} from '../Sidebar'; import type {RouteItem} from '../getRouteMeta'; +import {siteConfig} from 'siteConfig'; +import BrandMenu from './BrandMenu'; declare global { interface Window { @@ -77,6 +80,19 @@ const lightIcon = ( </svg> ); +const languageIcon = ( + <svg + xmlns="http://www.w3.org/2000/svg" + width="24" + height="24" + viewBox="0 0 24 24"> + <path + fill="currentColor" + d=" M12.87 15.07l-2.54-2.51.03-.03c1.74-1.94 2.98-4.17 3.71-6.53H17V4h-7V2H8v2H1v1.99h11.17C11.5 7.92 10.44 9.75 9 11.35 8.07 10.32 7.3 9.19 6.69 8h-2c.73 1.63 1.73 3.17 2.98 4.56l-5.09 5.02L4 19l5-5 3.11 3.11.76-2.04zM18.5 10h-2L12 22h2l1.12-3h4.75L21 22h2l-4.5-12zm-2.62 7l1.62-4.33L19.12 17h-3.24z " + /> + </svg> +); + const githubIcon = ( <svg xmlns="http://www.w3.org/2000/svg" @@ -110,7 +126,7 @@ function NavItem({url, isActive, children}: any) { <Link href={url} className={cn( - 'active:scale-95 transition-transform w-full text-center outline-link py-1.5 px-1.5 xs:px-3 sm:px-4 rounded-full capitalize', + 'active:scale-95 transition-transform w-full text-center outline-link py-1.5 px-1.5 xs:px-3 sm:px-4 rounded-full capitalize whitespace-nowrap', !isActive && 'hover:bg-primary/5 hover:dark:bg-primary-dark/5', isActive && 'bg-highlight dark:bg-highlight-dark text-link dark:text-link-dark' @@ -142,10 +158,11 @@ export default function TopNav({ breadcrumbs: RouteItem[]; section: 'learn' | 'reference' | 'community' | 'blog' | 'home' | 'unknown'; }) { - const [isOpen, setIsOpen] = useState(false); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [showSearch, setShowSearch] = useState(false); + const [isScrolled, setIsScrolled] = useState(false); const scrollParentRef = useRef<HTMLDivElement>(null); const {asPath} = useRouter(); - const [isScrolled, setIsScrolled] = useState(false); // HACK. Fix up the data structures instead. if ((routeTree as any).routes.length === 1) { @@ -154,18 +171,18 @@ export default function TopNav({ // While the overlay is open, disable body scroll. useEffect(() => { - if (isOpen) { + if (isMenuOpen) { const preferredScrollParent = scrollParentRef.current!; disableBodyScroll(preferredScrollParent); return () => enableBodyScroll(preferredScrollParent); } else { return undefined; } - }, [isOpen]); + }, [isMenuOpen]); // Close the overlay on any navigation. useEffect(() => { - setIsOpen(false); + setIsMenuOpen(false); }, [asPath]); // Also close the overlay if the window gets resized past mobile layout. @@ -175,7 +192,7 @@ export default function TopNav({ function closeIfNeeded() { if (!media.matches) { - setIsOpen(false); + setIsMenuOpen(false); } } @@ -204,7 +221,6 @@ export default function TopNav({ return () => observer.disconnect(); }, []); - const [showSearch, setShowSearch] = useState(false); const onOpenSearch = useCallback(() => { startTransition(() => { setShowSearch(true); @@ -224,39 +240,63 @@ export default function TopNav({ <div ref={scrollDetectorRef} /> <div className={cn( - isOpen + isMenuOpen ? 'h-screen sticky top-0 lg:bottom-0 lg:h-screen flex flex-col shadow-nav dark:shadow-nav-dark z-20' - : 'z-50 sticky top-0' + : 'z-40 sticky top-0' )}> <nav className={cn( - 'duration-300 backdrop-filter backdrop-blur-lg backdrop-saturate-200 transition-shadow bg-opacity-90 items-center w-full flex justify-between bg-wash dark:bg-wash-dark dark:bg-opacity-95 px-1.5 lg:pe-5 lg:ps-4 z-50', - {'dark:shadow-nav-dark shadow-nav': isScrolled || isOpen} + 'duration-300 backdrop-filter backdrop-blur-lg backdrop-saturate-200 transition-shadow bg-opacity-90 items-center w-full flex justify-between bg-wash dark:bg-wash-dark dark:bg-opacity-95 px-1.5 lg:pe-5 lg:ps-4 z-40', + {'dark:shadow-nav-dark shadow-nav': isScrolled || isMenuOpen} )}> <div className="flex items-center justify-between w-full h-16 gap-0 sm:gap-3"> - <div className="flex flex-row 3xl:flex-1 "> + <div className="flex flex-row 3xl:flex-1 items-centers"> <button type="button" aria-label="Menu" - onClick={() => setIsOpen(!isOpen)} + onClick={() => setIsMenuOpen(!isMenuOpen)} className={cn( 'active:scale-95 transition-transform flex lg:hidden w-12 h-12 rounded-full items-center justify-center hover:bg-primary/5 hover:dark:bg-primary-dark/5 outline-link', { - 'text-link dark:text-link-dark': isOpen, + 'text-link dark:text-link-dark': isMenuOpen, } )}> - {isOpen ? <IconClose /> : <IconHamburger />} + {isMenuOpen ? <IconClose /> : <IconHamburger />} </button> - <div className="flex 3xl:flex-1 align-center"> + <BrandMenu> + <div className="flex items-center"> + <div className="uwu-visible flex items-center justify-center h-full"> + <NextLink href="/"> + <Image + alt="logo by @sawaratsuki1004" + title="logo by @sawaratsuki1004" + className="h-8" + priority + width={63} + height={32} + src="/images/uwu.png" + /> + </NextLink> + </div> + <div className="uwu-hidden"> + <NextLink + href="/" + className={`active:scale-95 overflow-hidden transition-transform relative items-center text-primary dark:text-primary-dark p-1 whitespace-nowrap outline-link rounded-full 3xl:rounded-xl inline-flex text-lg font-normal gap-2`}> + <Logo + className={cn( + 'text-sm me-0 w-10 h-10 text-brand dark:text-brand-dark flex origin-center transition-all ease-in-out' + )} + /> + <span className="sr-only 3xl:not-sr-only">React</span> + </NextLink> + </div> + </div> + </BrandMenu> + <div className="flex flex-column justify-center items-center"> <NextLink - href="/" - className={`active:scale-95 overflow-hidden transition-transform relative items-center text-primary dark:text-primary-dark p-1 whitespace-nowrap outline-link rounded-full 3xl:rounded-xl inline-flex text-lg font-normal gap-2`}> - <Logo - className={cn( - 'text-sm me-0 w-10 h-10 text-link dark:text-link-dark flex origin-center transition-all ease-in-out' - )} - /> - <span className="sr-only 3xl:not-sr-only">React</span> + href="/versions" + className=" flex py-2 flex-column justify-center items-center text-gray-50 dark:text-gray-30 hover:text-link hover:dark:text-link-dark hover:underline text-sm ms-1 cursor-pointer"> + v{siteConfig.version} </NextLink> </div> </div> @@ -328,6 +368,14 @@ export default function TopNav({ {lightIcon} </button> </div> + <div className="flex"> + <Link + href="/community/translations" + aria-label="Translations" + className="active:scale-95 transition-transform flex w-12 h-12 rounded-full items-center justify-center hover:bg-primary/5 hover:dark:bg-primary-dark/5 outline-link"> + {languageIcon} + </Link> + </div> <div className="flex"> <Link href="https://github.com/facebook/react/releases" @@ -343,14 +391,14 @@ export default function TopNav({ </div> </nav> - {isOpen && ( + {isMenuOpen && ( <div ref={scrollParentRef} className="overflow-y-scroll isolate no-bg-scrollbar lg:w-[342px] grow bg-wash dark:bg-wash-dark"> <aside className={cn( - `lg:grow lg:flex flex-col w-full pb-8 lg:pb-0 lg:max-w-custom-xs z-50`, - isOpen ? 'block z-40' : 'hidden lg:block' + `lg:grow lg:flex flex-col w-full pb-8 lg:pb-0 lg:max-w-custom-xs z-40`, + isMenuOpen ? 'block z-30' : 'hidden lg:block' )}> <nav role="navigation" @@ -383,10 +431,10 @@ export default function TopNav({ <SidebarRouteTree // Don't share state between the desktop and mobile versions. // This avoids unnecessary animations and visual flicker. - key={isOpen ? 'mobile-overlay' : 'desktop-or-hidden'} + key={isMenuOpen ? 'mobile-overlay' : 'desktop-or-hidden'} routeTree={routeTree} breadcrumbs={breadcrumbs} - isForceExpanded={isOpen} + isForceExpanded={isMenuOpen} /> </Suspense> <div className="h-16" /> diff --git a/src/components/MDX/ConsoleBlock.tsx b/src/components/MDX/ConsoleBlock.tsx index 5683d6dcf..6e704b417 100644 --- a/src/components/MDX/ConsoleBlock.tsx +++ b/src/components/MDX/ConsoleBlock.tsx @@ -15,6 +15,10 @@ interface ConsoleBlockProps { children: React.ReactNode; } +interface ConsoleBlockMultiProps { + children: React.ReactNode; +} + const Box = ({ width = '60px', height = '17px', @@ -29,7 +33,7 @@ const Box = ({ <div className={className} style={{width, height, ...customStyles}}></div> ); -function ConsoleBlock({level = 'error', children}: ConsoleBlockProps) { +export function ConsoleBlock({level = 'error', children}: ConsoleBlockProps) { let message: React.ReactNode | null; if (typeof children === 'string') { message = children; @@ -38,7 +42,10 @@ function ConsoleBlock({level = 'error', children}: ConsoleBlockProps) { } return ( - <div className="mb-4 text-secondary" translate="no" dir="ltr"> + <div + className="console-block mb-4 text-secondary bg-wash dark:bg-wash-dark rounded-lg" + translate="no" + dir="ltr"> <div className="flex w-full rounded-t-lg bg-gray-200 dark:bg-gray-80"> <div className="px-4 py-2 border-gray-300 dark:border-gray-90 border-r"> <Box className="bg-gray-300 dark:bg-gray-70" width="15px" /> @@ -73,4 +80,72 @@ function ConsoleBlock({level = 'error', children}: ConsoleBlockProps) { ); } -export default ConsoleBlock; +export function ConsoleBlockMulti({children}: ConsoleBlockMultiProps) { + return ( + <div + className="console-block mb-4 text-secondary bg-wash dark:bg-wash-dark rounded-lg" + translate="no" + dir="ltr"> + <div className="flex w-full rounded-t-lg bg-gray-200 dark:bg-gray-80"> + <div className="px-4 py-2 border-gray-300 dark:border-gray-90 border-r"> + <Box className="bg-gray-300 dark:bg-gray-70" width="15px" /> + </div> + <div className="flex text-sm px-4"> + <div className="border-b-2 border-gray-300 dark:border-gray-90 text-tertiary dark:text-tertiary-dark"> + Console + </div> + <div className="px-4 py-2 flex"> + <Box className="me-2 bg-gray-300 dark:bg-gray-70" /> + <Box className="me-2 hidden md:block bg-gray-300 dark:bg-gray-70" /> + <Box className="hidden md:block bg-gray-300 dark:bg-gray-70" /> + </div> + </div> + </div> + <div className="grid grid-cols-1 divide-y divide-gray-300 dark:divide-gray-70 text-base"> + {children} + </div> + </div> + ); +} + +export function ConsoleLogLine({children, level}: ConsoleBlockProps) { + let message: React.ReactNode | null; + if (typeof children === 'string') { + message = children; + } else if (isValidElement(children)) { + message = children.props.children; + } else if (Array.isArray(children)) { + message = children.reduce((result, child) => { + if (typeof child === 'string') { + result += child; + } else if (isValidElement(child)) { + // @ts-ignore + result += child.props.children; + } + return result; + }, ''); + } + + return ( + <div + className={cn( + 'ps-4 pe-2 pt-1 pb-2 grid grid-cols-[18px_auto] font-mono rounded-b-md', + { + 'bg-red-30 text-red-50 dark:text-red-30 bg-opacity-5': + level === 'error', + 'bg-yellow-5 text-yellow-50': level === 'warning', + 'bg-gray-5 text-secondary dark:text-secondary-dark': level === 'info', + } + )}> + {level === 'error' && ( + <IconError className="self-start mt-1.5 text-[.7rem] w-6" /> + )} + {level === 'warning' && ( + <IconWarning className="self-start mt-1 text-[.65rem] w-6" /> + )} + <div className="px-2 pt-1 whitespace-break-spaces text-code leading-tight"> + {message} + </div> + </div> + ); +} diff --git a/src/components/MDX/ExpandableCallout.tsx b/src/components/MDX/ExpandableCallout.tsx index c46898026..415d5d867 100644 --- a/src/components/MDX/ExpandableCallout.tsx +++ b/src/components/MDX/ExpandableCallout.tsx @@ -2,7 +2,6 @@ * Copyright (c) Facebook, Inc. and its affiliates. */ -import {useRef} from 'react'; import * as React from 'react'; import cn from 'classnames'; import {IconNote} from '../Icon/IconNote'; @@ -63,7 +62,6 @@ const variantMap = { }; function ExpandableCallout({children, type = 'note'}: ExpandableCalloutProps) { - const contentRef = useRef<HTMLDivElement>(null); const variant = variantMap[type]; return ( @@ -80,9 +78,7 @@ function ExpandableCallout({children, type = 'note'}: ExpandableCalloutProps) { {variant.title} </h3> <div className="relative"> - <div ref={contentRef} className="py-2"> - {children} - </div> + <div className="py-2">{children}</div> </div> </div> ); diff --git a/src/components/MDX/LanguagesContext.tsx b/src/components/MDX/LanguagesContext.tsx new file mode 100644 index 000000000..776a11c0d --- /dev/null +++ b/src/components/MDX/LanguagesContext.tsx @@ -0,0 +1,14 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ + +import {createContext} from 'react'; + +export type LanguageItem = { + code: string; + name: string; + enName: string; +}; +export type Languages = Array<LanguageItem>; + +export const LanguagesContext = createContext<Languages | null>(null); diff --git a/src/components/MDX/MDXComponents.tsx b/src/components/MDX/MDXComponents.tsx index 76bf86eaa..6f99121f7 100644 --- a/src/components/MDX/MDXComponents.tsx +++ b/src/components/MDX/MDXComponents.tsx @@ -8,7 +8,7 @@ import cn from 'classnames'; import CodeBlock from './CodeBlock'; import {CodeDiagram} from './CodeDiagram'; -import ConsoleBlock from './ConsoleBlock'; +import {ConsoleBlock, ConsoleLogLine, ConsoleBlockMulti} from './ConsoleBlock'; import ExpandableCallout from './ExpandableCallout'; import ExpandableExample from './ExpandableExample'; import {H1, H2, H3, H4, H5} from './Heading'; @@ -31,8 +31,11 @@ import ButtonLink from 'components/ButtonLink'; import {TocContext} from './TocContext'; import type {Toc, TocItem} from './TocContext'; import {TeamMember} from './TeamMember'; +import {LanguagesContext} from './LanguagesContext'; +import {finishedTranslations} from 'utils/finishedTranslations'; import ErrorDecoder from './ErrorDecoder'; +import {IconCanary} from '../Icon/IconCanary'; function CodeStep({children, step}: {children: any; step: number}) { return ( @@ -94,6 +97,20 @@ const Canary = ({children}: {children: React.ReactNode}) => ( <ExpandableCallout type="canary">{children}</ExpandableCallout> ); +const CanaryBadge = ({title}: {title: string}) => ( + <span + title={title} + className={ + 'text-base font-display px-1 py-0.5 font-bold bg-gray-10 dark:bg-gray-60 text-gray-60 dark:text-gray-10 rounded' + }> + <IconCanary + size="s" + className={'inline me-1 mb-0.5 text-sm text-gray-60 dark:text-gray-10'} + /> + Canary only + </span> +); + const Blockquote = ({ children, ...props @@ -191,7 +208,7 @@ function Recipes(props: any) { function AuthorCredit({ author = 'Rachel Lee Nabors', - authorLink = 'http://rachelnabors.com/', + authorLink = 'https://nearestnabors.com/', }: { author: string; authorLink: string; @@ -365,6 +382,38 @@ function InlineTocItem({items}: {items: Array<NestedTocNode>}) { ); } +type TranslationProgress = 'complete' | 'in-progress'; + +function LanguageList({progress}: {progress: TranslationProgress}) { + const allLanguages = React.useContext(LanguagesContext) ?? []; + const languages = allLanguages + .filter( + ({code}) => + code !== 'en' && + (progress === 'complete' + ? finishedTranslations.includes(code) + : !finishedTranslations.includes(code)) + ) + .sort((a, b) => a.enName.localeCompare(b.enName)); + return ( + <UL> + {languages.map(({code, name, enName}) => { + return ( + <LI key={code}> + <Link href={`https://${code}.react.dev/`}> + {enName} ({name}) + </Link>{' '} + —{' '} + <Link href={`https://github.com/reactjs/${code}.react.dev`}> + Contribute + </Link> + </LI> + ); + })} + </UL> + ); +} + function YouTubeIframe(props: any) { return ( <div className="relative h-0 overflow-hidden pt-[56.25%]"> @@ -405,6 +454,8 @@ export const MDXComponents = { pre: CodeBlock, CodeDiagram, ConsoleBlock, + ConsoleBlockMulti, + ConsoleLogLine, DeepDive: (props: { children: React.ReactNode; title: string; @@ -425,11 +476,13 @@ export const MDXComponents = { IllustrationBlock, Intro, InlineToc, + LanguageList, LearnMore, Math, MathI, Note, Canary, + CanaryBadge, PackageImport, ReadBlogPost, Recap, diff --git a/src/components/MDX/TeamMember.tsx b/src/components/MDX/TeamMember.tsx index c9e83ebc6..eaf74187e 100644 --- a/src/components/MDX/TeamMember.tsx +++ b/src/components/MDX/TeamMember.tsx @@ -68,7 +68,7 @@ export function TeamMember({ <ExternalLink aria-label="React on Twitter" href={`https://twitter.com/${twitter}`} - className="hover:text-primary dark:text-primary-dark flex flex-row items-center"> + className="hover:text-primary hover:underline dark:text-primary-dark flex flex-row items-center"> <IconTwitter className="pe-1" /> {twitter} </ExternalLink> @@ -90,7 +90,7 @@ export function TeamMember({ <ExternalLink aria-label="GitHub Profile" href={`https://github.com/${github}`} - className="hover:text-primary dark:text-primary-dark flex flex-row items-center"> + className="hover:text-primary hover:underline dark:text-primary-dark flex flex-row items-center"> <IconGitHub className="pe-1" /> {github} </ExternalLink> </div> @@ -99,7 +99,7 @@ export function TeamMember({ <ExternalLink aria-label="Personal Site" href={`https://${personal}`} - className="hover:text-primary dark:text-primary-dark flex flex-row items-center"> + className="hover:text-primary hover:underline dark:text-primary-dark flex flex-row items-center"> <IconLink className="pe-1" /> {personal} </ExternalLink> )} diff --git a/src/components/PageHeading.tsx b/src/components/PageHeading.tsx index 659295d0a..6000c8e51 100644 --- a/src/components/PageHeading.tsx +++ b/src/components/PageHeading.tsx @@ -22,7 +22,6 @@ function PageHeading({ title, status, canary, - description, tags = [], breadcrumbs, }: PageHeadingProps) { @@ -40,11 +39,6 @@ function PageHeading({ )} {status ? <em>—{status}</em> : ''} </H1> - {description && ( - <p className="mt-4 mb-6 dark:text-primary-dark text-xl text-primary leading-large"> - {description} - </p> - )} {tags?.length > 0 && ( <div className="mt-4"> {tags.map((tag) => ( diff --git a/src/components/Search.tsx b/src/components/Search.tsx index 8bc47297a..cff7f8852 100644 --- a/src/components/Search.tsx +++ b/src/components/Search.tsx @@ -110,7 +110,6 @@ export function Search({ createPortal( <DocSearchModal {...options} - initialScrollY={window.scrollY} searchParameters={searchParameters} onClose={onClose} navigator={{ diff --git a/src/components/Seo.tsx b/src/components/Seo.tsx index dfc4f6104..628085744 100644 --- a/src/components/Seo.tsx +++ b/src/components/Seo.tsx @@ -6,6 +6,7 @@ import * as React from 'react'; import Head from 'next/head'; import {withRouter, Router} from 'next/router'; import {siteConfig} from '../siteConfig'; +import {finishedTranslations} from 'utils/finishedTranslations'; export interface SeoProps { title: string; @@ -18,17 +19,8 @@ export interface SeoProps { searchOrder?: number; } -const deployedTranslations = [ - 'en', - 'zh-hans', - 'es', - 'fr', - 'ja', - 'tr', - // We'll add more languages when they have enough content. - // Please DO NOT edit this list without a discussion in the reactjs/react.dev repo. - // It must be the same between all translations. -]; +// If you are a maintainer of a language fork, +// deployedTranslations has been moved to src/utils/finishedTranslations.ts. function getDomain(languageCode: string): string { const subdomain = languageCode === 'en' ? '' : languageCode + '.'; @@ -71,7 +63,7 @@ export const Seo = withRouter( href={canonicalUrl.replace(siteDomain, getDomain('en'))} hrefLang="x-default" /> - {deployedTranslations.map((languageCode) => ( + {finishedTranslations.map((languageCode) => ( <link key={'alt-' + languageCode} rel="alternate" diff --git a/src/components/SocialBanner.tsx b/src/components/SocialBanner.tsx index 4e1bf8e3d..2db62c994 100644 --- a/src/components/SocialBanner.tsx +++ b/src/components/SocialBanner.tsx @@ -7,7 +7,7 @@ import {useRef, useEffect} from 'react'; import cn from 'classnames'; import {ExternalLink} from './ExternalLink'; -const bannerText = 'Join us for React Conf on May 15-16.'; +const bannerText = 'Stream React Conf on May 15-16.'; const bannerLink = 'https://conf.react.dev/'; const bannerLinkText = 'Learn more.'; diff --git a/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md b/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md index 948096c0f..b38853494 100644 --- a/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md +++ b/src/content/blog/2020/12/21/data-fetching-with-react-server-components.md @@ -1,5 +1,8 @@ --- title: "Introducing Zero-Bundle-Size React Server Components" +author: Dan Abramov, Lauren Tan, Joseph Savona, and Sebastian Markbage +date: 2020/12/21 +description: 2020 has been a long year. As it comes to an end we wanted to share a special Holiday Update on our research into zero-bundle-size React Server Components. --- December 21, 2020 by [Dan Abramov](https://twitter.com/dan_abramov), [Lauren Tan](https://twitter.com/potetotes), [Joseph Savona](https://twitter.com/en_JS), and [Sebastian Markbåge](https://twitter.com/sebmarkbage) diff --git a/src/content/blog/2021/06/08/the-plan-for-react-18.md b/src/content/blog/2021/06/08/the-plan-for-react-18.md index 0bf744c1d..42843cc42 100644 --- a/src/content/blog/2021/06/08/the-plan-for-react-18.md +++ b/src/content/blog/2021/06/08/the-plan-for-react-18.md @@ -1,5 +1,8 @@ --- title: "The Plan for React 18" +author: Andrew Clark, Brian Vaughn, Christine Abernathy, Dan Abramov, Rachel Nabors, Rick Hanlon, Sebastian Markbage, and Seth Webster +date: 2021/06/08 +description: The React team is excited to share a few updates. We’ve started work on the React 18 release, which will be our next major version. We’ve created a Working Group to prepare the community for gradual adoption of new features in React 18. We’ve published a React 18 Alpha so that library authors can try it and provide feedback... --- June 8, 2021 by [Andrew Clark](https://twitter.com/acdlite), [Brian Vaughn](https://github.com/bvaughn), [Christine Abernathy](https://twitter.com/abernathyca), [Dan Abramov](https://twitter.com/dan_abramov), [Rachel Nabors](https://twitter.com/rachelnabors), [Rick Hanlon](https://twitter.com/rickhanlonii), [Sebastian Markbåge](https://twitter.com/sebmarkbage), and [Seth Webster](https://twitter.com/sethwebster) diff --git a/src/content/blog/2021/12/17/react-conf-2021-recap.md b/src/content/blog/2021/12/17/react-conf-2021-recap.md index 89e407af3..1806c757f 100644 --- a/src/content/blog/2021/12/17/react-conf-2021-recap.md +++ b/src/content/blog/2021/12/17/react-conf-2021-recap.md @@ -1,5 +1,8 @@ --- title: "React Conf 2021 Recap" +author: Jesslyn Tannady and Rick Hanlon +date: 2021/12/17 +description: Last week we hosted our 6th React Conf. In previous years, we've used the React Conf stage to deliver industry changing announcements such as React Native and React Hooks. This year, we shared our multi-platform vision for React, starting with the release of React 18 and gradual adoption of concurrent features. --- December 17, 2021 by [Jesslyn Tannady](https://twitter.com/jtannady) and [Rick Hanlon](https://twitter.com/rickhanlonii) diff --git a/src/content/blog/2022/03/08/react-18-upgrade-guide.md b/src/content/blog/2022/03/08/react-18-upgrade-guide.md index 66da896ec..9d34dfaaa 100644 --- a/src/content/blog/2022/03/08/react-18-upgrade-guide.md +++ b/src/content/blog/2022/03/08/react-18-upgrade-guide.md @@ -1,5 +1,8 @@ --- title: "How to Upgrade to React 18" +author: Rick Hanlon +date: 2022/03/08 +description: As we shared in the release post, React 18 introduces features powered by our new concurrent renderer, with a gradual adoption strategy for existing applications. In this post, we will guide you through the steps for upgrading to React 18. --- March 08, 2022 by [Rick Hanlon](https://twitter.com/rickhanlonii) diff --git a/src/content/blog/2022/03/29/react-v18.md b/src/content/blog/2022/03/29/react-v18.md index 743404c1a..d21eeb1f5 100644 --- a/src/content/blog/2022/03/29/react-v18.md +++ b/src/content/blog/2022/03/29/react-v18.md @@ -1,5 +1,8 @@ --- title: "React v18.0" +author: The React Team +date: 2022/03/08 +description: React 18 is now available on npm! In our last post, we shared step-by-step instructions for upgrading your app to React 18. In this post, we'll give an overview of what's new in React 18, and what it means for the future. --- March 29, 2022 by [The React Team](/community/team) @@ -237,7 +240,7 @@ With Strict Mode in React 18, React will simulate unmounting and remounting the #### useTransition {/*usetransition*/} -`useTransition` and `startTransition` let you mark some state updates as not urgent. Other state updates are considered urgent by default. React will allow urgent state updates (for example, updating a text input) to interrupt non-urgent state updates (for example, rendering a list of search results). [See docs here](/reference/react/useTransition) +`useTransition` and `startTransition` let you mark some state updates as not urgent. Other state updates are considered urgent by default. React will allow urgent state updates (for example, updating a text input) to interrupt non-urgent state updates (for example, rendering a list of search results). [See docs here](/reference/react/useTransition). #### useDeferredValue {/*usedeferredvalue*/} diff --git a/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md b/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md index 5e8456ea3..134990991 100644 --- a/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md +++ b/src/content/blog/2022/06/15/react-labs-what-we-have-been-working-on-june-2022.md @@ -1,5 +1,8 @@ --- title: "React Labs: What We've Been Working On – June 2022" +author: Andrew Clark, Dan Abramov, Jan Kassens, Joseph Savona, Josh Story, Lauren Tan, Luna Ruan, Mengdi Chen, Rick Hanlon, Robert Zhang, Sathya Gunasekaran, Sebastian Markbage, and Xuan Huang +date: 2022/06/15 +description: React 18 was years in the making, and with it brought valuable lessons for the React team. Its release was the result of many years of research and exploring many paths. Some of those paths were successful; many more were dead-ends that led to new insights. One lesson we’ve learned is that it’s frustrating for the community to wait for new features without having insight into these paths that we’re exploring. --- June 15, 2022 by [Andrew Clark](https://twitter.com/acdlite), [Dan Abramov](https://twitter.com/dan_abramov), [Jan Kassens](https://twitter.com/kassens), [Joseph Savona](https://twitter.com/en_JS), [Josh Story](https://twitter.com/joshcstory), [Lauren Tan](https://twitter.com/potetotes), [Luna Ruan](https://twitter.com/lunaruan), [Mengdi Chen](https://twitter.com/mengdi_en), [Rick Hanlon](https://twitter.com/rickhanlonii), [Robert Zhang](https://twitter.com/jiaxuanzhang01), [Sathya Gunasekaran](https://twitter.com/_gsathya), [Sebastian Markbåge](https://twitter.com/sebmarkbage), and [Xuan Huang](https://twitter.com/Huxpro) @@ -74,6 +77,6 @@ We are working on a new version for the Interaction Tracing API (tentatively cal Last year, we announced the beta version of the new React documentation website ([later shipped as react.dev](/blog/2023/03/16/introducing-react-dev)) of the new React documentation website. The new learning materials teach Hooks first and has new diagrams, illustrations, as well as many interactive examples and challenges. We took a break from that work to focus on the React 18 release, but now that React 18 is out, we’re actively working to finish and ship the new documentation. -We are currently writing a detailed section about effects, as we’ve heard that is one of the more challenging topics for both new and experienced React users. [Synchronizing with Effects](/learn/synchronizing-with-effects) is the first published page in the series, and there are more to come in the following weeks. When we first started writing a detailed section about effects, we’ve realized that many common effect patterns can be simplified by adding a new primitive to React. We’ve shared some initial thoughts on that in the [useEvent RFC](https://github.com/reactjs/rfcs/pull/220). It is currently in early research, and we are still iterating on the idea. We appreciate the community’s comments on the RFC so far, as well as the [feedback](https://github.com/reactjs/reactjs.org/issues/3308) and contributions to the ongoing documentation rewrite. We’d specifically like to thank [Harish Kumar](https://github.com/harish-sethuraman) for submitting and reviewing many improvements to the new website implementation. +We are currently writing a detailed section about effects, as we’ve heard that is one of the more challenging topics for both new and experienced React users. [Synchronizing with Effects](/learn/synchronizing-with-effects) is the first published page in the series, and there are more to come in the following weeks. When we first started writing a detailed section about effects, we’ve realized that many common effect patterns can be simplified by adding a new primitive to React. We’ve shared some initial thoughts on that in the [useEvent RFC](https://github.com/reactjs/rfcs/pull/220). It is currently in early research, and we are still iterating on the idea. We appreciate the community’s comments on the RFC so far, as well as the [feedback](https://github.com/reactjs/react.dev/issues/3308) and contributions to the ongoing documentation rewrite. We’d specifically like to thank [Harish Kumar](https://github.com/harish-sethuraman) for submitting and reviewing many improvements to the new website implementation. *Thanks to [Sophie Alpert](https://twitter.com/sophiebits) for reviewing this blog post!* diff --git a/src/content/blog/2023/03/16/introducing-react-dev.md b/src/content/blog/2023/03/16/introducing-react-dev.md index 4ce209d71..2498f40dc 100644 --- a/src/content/blog/2023/03/16/introducing-react-dev.md +++ b/src/content/blog/2023/03/16/introducing-react-dev.md @@ -1,5 +1,8 @@ --- title: "Introducing react.dev" +author: Dan Abramov and Rachel Nabors +date: 2023/03/16 +description: Today we are thrilled to launch react.dev, the new home for React and its documentation. In this post, we would like to give you a tour of the new site. --- March 16, 2023 by [Dan Abramov](https://twitter.com/dan_abramov) and [Rachel Nabors](https://twitter.com/rachelnabors) @@ -610,7 +613,7 @@ We hope that this approach will make the API reference useful not only as a way ## What's next? {/*whats-next*/} -That's a wrap for our little tour! Have a look around the new website, see what you like or don't like, and keep the feedback coming in the [anonymous survey](https://www.surveymonkey.co.uk/r/PYRPF3X) or in our [issue tracker](https://github.com/reactjs/reactjs.org/issues). +That's a wrap for our little tour! Have a look around the new website, see what you like or don't like, and keep the feedback coming in our [issue tracker](https://github.com/reactjs/react.dev/issues). We acknowledge this project has taken a long time to ship. We wanted to maintain a high quality bar that the React community deserves. While writing these docs and creating all of the examples, we found mistakes in some of our own explanations, bugs in React, and even gaps in the React design that we are now working to address. We hope that the new documentation will help us hold React itself to a higher bar in the future. diff --git a/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md b/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md index 1f6b911e1..aeb677f31 100644 --- a/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md +++ b/src/content/blog/2023/03/22/react-labs-what-we-have-been-working-on-march-2023.md @@ -1,5 +1,8 @@ --- title: "React Labs: What We've Been Working On – March 2023" +author: Joseph Savona, Josh Story, Lauren Tan, Mengdi Chen, Samuel Susla, Sathya Gunasekaran, Sebastian Markbage, and Andrew Clark +date: 2023/03/22 +description: In React Labs posts, we write about projects in active research and development. We've made significant progress on them since our last update, and we'd like to share what we learned. --- March 22, 2023 by [Joseph Savona](https://twitter.com/en_JS), [Josh Story](https://twitter.com/joshcstory), [Lauren Tan](https://twitter.com/potetotes), [Mengdi Chen](https://twitter.com/mengdi_en), [Samuel Susla](https://twitter.com/SamuelSusla), [Sathya Gunasekaran](https://twitter.com/_gsathya), [Sebastian Markbåge](https://twitter.com/sebmarkbage), and [Andrew Clark](https://twitter.com/acdlite) diff --git a/src/content/blog/2023/05/03/react-canaries.md b/src/content/blog/2023/05/03/react-canaries.md index 81da3fd00..19d9960b0 100644 --- a/src/content/blog/2023/05/03/react-canaries.md +++ b/src/content/blog/2023/05/03/react-canaries.md @@ -1,5 +1,8 @@ --- title: "React Canaries: Enabling Incremental Feature Rollout Outside Meta" +author: Dan Abramov, Sophie Alpert, Rick Hanlon, Sebastian Markbage, and Andrew Clark +date: 2023/05/03 +description: We'd like to offer the React community an option to adopt individual new features as soon as their design is close to final, before they're released in a stable version--similar to how Meta has long used bleeding-edge versions of React internally. We are introducing a new officially supported [Canary release channel](/community/versioning-policy#canary-channel). It lets curated setups like frameworks decouple adoption of individual React features from the React release schedule. --- May 3, 2023 by [Dan Abramov](https://twitter.com/dan_abramov), [Sophie Alpert](https://twitter.com/sophiebits), [Rick Hanlon](https://twitter.com/rickhanlonii), [Sebastian Markbåge](https://twitter.com/sebmarkbage), and [Andrew Clark](https://twitter.com/acdlite) diff --git a/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md b/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md index 03fc85c37..fee21f4ec 100644 --- a/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md +++ b/src/content/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024.md @@ -1,5 +1,8 @@ --- title: "React Labs: What We've Been Working On – February 2024" +author: Joseph Savona, Ricky Hanlon, Andrew Clark, Matt Carroll, and Dan Abramov +date: 2024/02/15 +description: In React Labs posts, we write about projects in active research and development. We’ve made significant progress since our last update, and we’d like to share our progress. --- February 15, 2024 by [Joseph Savona](https://twitter.com/en_JS), [Ricky Hanlon](https://twitter.com/rickhanlonii), [Andrew Clark](https://twitter.com/acdlite), [Matt Carroll](https://twitter.com/mattcarrollcode), and [Dan Abramov](https://twitter.com/dan_abramov). @@ -52,7 +55,7 @@ We refer to this broader collection of features as simply "Actions". Actions all </form> ``` -The `action` function can operate synchronously or asynchronously. You can define them on the client side using standard JavaScript or on the server with the [`'use server'`](/reference/react/use-server) directive. When using an action, React will manage the life cycle of the data submission for you, providing hooks like [`useFormStatus`](/reference/react-dom/hooks/useFormStatus), and [`useFormState`](/reference/react-dom/hooks/useFormState) to access the current state and response of the form action. +The `action` function can operate synchronously or asynchronously. You can define them on the client side using standard JavaScript or on the server with the [`'use server'`](/reference/rsc/use-server) directive. When using an action, React will manage the life cycle of the data submission for you, providing hooks like [`useFormStatus`](/reference/react-dom/hooks/useFormStatus), and [`useActionState`](/reference/react/useActionState) to access the current state and response of the form action. By default, Actions are submitted within a [transition](/reference/react/useTransition), keeping the current page interactive while the action is processing. Since Actions support async functions, we've also added the ability to use `async/await` in transitions. This allows you to show pending UI with the `isPending` state of a transition when an async request like `fetch` starts, and show the pending UI all the way through the update being applied. @@ -72,13 +75,13 @@ Canaries are a change to the way we develop React. Previously, features would be React Server Components, Asset Loading, Document Metadata, and Actions have all landed in the React Canary, and we've added docs for these features on react.dev: -- **Directives**: [`"use client"`](/reference/react/use-client) and [`"use server"`](/reference/react/use-server) are bundler features designed for full-stack React frameworks. They mark the "split points" between the two environments: `"use client"` instructs the bundler to generate a `<script>` tag (like [Astro Islands](https://docs.astro.build/en/concepts/islands/#creating-an-island)), while `"use server"` tells the bundler to generate a POST endpoint (like [tRPC Mutations](https://trpc.io/docs/concepts)). Together, they let you write reusable components that compose client-side interactivity with the related server-side logic. +- **Directives**: [`"use client"`](/reference/rsc/use-client) and [`"use server"`](/reference/rsc/use-server) are bundler features designed for full-stack React frameworks. They mark the "split points" between the two environments: `"use client"` instructs the bundler to generate a `<script>` tag (like [Astro Islands](https://docs.astro.build/en/concepts/islands/#creating-an-island)), while `"use server"` tells the bundler to generate a POST endpoint (like [tRPC Mutations](https://trpc.io/docs/concepts)). Together, they let you write reusable components that compose client-side interactivity with the related server-side logic. - **Document Metadata**: we added built-in support for rendering [`<title>`](/reference/react-dom/components/title), [`<meta>`](/reference/react-dom/components/meta), and metadata [`<link>`](/reference/react-dom/components/link) tags anywhere in your component tree. These work the same way in all environments, including fully client-side code, SSR, and RSC. This provides built-in support for features pioneered by libraries like [React Helmet](https://github.com/nfl/react-helmet). - **Asset Loading**: we integrated Suspense with the loading lifecycle of resources such as stylesheets, fonts, and scripts so that React takes them into account to determine whether the content in elements like [`<style>`](/reference/react-dom/components/style), [`<link>`](/reference/react-dom/components/link), and [`<script>`](/reference/react-dom/components/script) are ready to be displayed. We’ve also added new [Resource Loading APIs](/reference/react-dom#resource-preloading-apis) like `preload` and `preinit` to provide greater control for when a resource should load and initialize. -- **Actions**: As shared above, we've added Actions to manage sending data from the client to the server. You can add `action` to elements like [`<form/>`](/reference/react-dom/components/form), access the status with [`useFormStatus`](/reference/react-dom/hooks/useFormStatus), handle the result with [`useFormState`](/reference/react-dom/hooks/useFormState), and optimistically update the UI with [`useOptimistic`](/reference/react/useOptimistic). +- **Actions**: As shared above, we've added Actions to manage sending data from the client to the server. You can add `action` to elements like [`<form/>`](/reference/react-dom/components/form), access the status with [`useFormStatus`](/reference/react-dom/hooks/useFormStatus), handle the result with [`useActionState`](/reference/react/useActionState), and optimistically update the UI with [`useOptimistic`](/reference/react/useOptimistic). Since all of these features work together, it’s difficult to release them in the Stable channel individually. Releasing Actions without the complementary hooks for accessing form states would limit the practical usability of Actions. Introducing React Server Components without integrating Server Actions would complicate modifying data on the server. diff --git a/src/content/blog/2024/04/25/react-19-upgrade-guide.md b/src/content/blog/2024/04/25/react-19-upgrade-guide.md new file mode 100644 index 000000000..f464df959 --- /dev/null +++ b/src/content/blog/2024/04/25/react-19-upgrade-guide.md @@ -0,0 +1,741 @@ +--- +title: "React 19 RC Upgrade Guide" +author: Ricky Hanlon +date: 2024/04/25 +description: The improvements added to React 19 require some breaking changes, but we've worked to make the upgrade as smooth as possible and we don't expect the changes to impact most apps. In this post, we will guide you through the steps for upgrading apps and libraries to React 19. +--- + +April 25, 2024 by [Ricky Hanlon](https://twitter.com/rickhanlonii) + +--- + + +<Intro> + +The improvements added to React 19 RC require some breaking changes, but we've worked to make the upgrade as smooth as possible, and we don't expect the changes to impact most apps. + +</Intro> + +<Note> + +#### React 18.3 has also been published {/*react-18-3*/} + +To help make the upgrade to React 19 easier, we've published a `react@18.3` release that is identical to 18.2 but adds warnings for deprecated APIs and other changes that are needed for React 19. + +We recommend upgrading to React 18.3 first to help identify any issues before upgrading to React 19. + +For a list of changes in 18.3 see the [Release Notes](https://github.com/facebook/react/blob/main/CHANGELOG.md). + +</Note> + +In this post, we will guide you through the steps for upgrading to React 19: + +- [Installing](#installing) +- [Codemods](#codemods) +- [Breaking changes](#breaking-changes) +- [New deprecations](#new-deprecations) +- [Notable changes](#notable-changes) +- [TypeScript changes](#typescript-changes) +- [Changelog](#changelog) + +If you'd like to help us test React 19, follow the steps in this upgrade guide and [report any issues](https://github.com/facebook/react/issues/new?assignees=&labels=React+19&projects=&template=19.md&title=%5BReact+19%5D) you encounter. For a list of new features added to React 19, see the [React 19 release post](/blog/2024/04/25/react-19). + +--- +## Installing {/*installing*/} + +<Note> + +#### New JSX Transform is now required {/*new-jsx-transform-is-now-required*/} + +We introduced a [new JSX transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) in 2020 to improve bundle size and use JSX without importing React. In React 19, we're adding additional improvements like using ref as a prop and JSX speed improvements that require the new transform. + +If the new transform is not enabled, you will see this warning: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +Your app (or one of its dependencies) is using an outdated JSX transform. Update to the modern JSX transform for faster performance: https://react.dev/link/new-jsx-transform + +</ConsoleLogLine> + +</ConsoleBlockMulti> + + +We expect most apps will not be affected since the transform is enabled in most environments already. For manual instructions on how to upgrade, please see the [announcement post](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html). + +</Note> + + +To install the latest version of React and React DOM: + +```bash +npm install --save-exact react@rc react-dom@rc +``` + +Or, if you're using Yarn: + +```bash +yarn add --exact react@rc react-dom@rc +``` + +If you're using TypeScript, you also need to update the types. Once React 19 is released as stable, you can install the types as usual from `@types/react` and `@types/react-dom`. Until the stable release, the types are available in different packages which need to be enforced in your `package.json`: + +```json +{ + "dependencies": { + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc" + }, + "overrides": { + "@types/react": "npm:types-react@rc", + "@types/react-dom": "npm:types-react-dom@rc" + } +} +``` + +We're also including a codemod for the most common replacements. See [TypeScript changes](#typescript-changes) below. + +## Codemods {/*codemods*/} + +To help with the upgrade, we've worked with the team at [codemod.com](https://codemod.com) to publish codemods that will automatically update your code to many of the new APIs and patterns in React 19. + +All codemods are available in the [`react-codemod` repo](https://github.com/reactjs/react-codemod) and the Codemod team have joined in helping maintain the codemods. To run these codemods, we recommend using the `codemod` command instead of the `react-codemod` because it runs faster, handles more complex code migrations, and provides better support for TypeScript. + + +<Note> + +#### Run all React 19 codemods {/*run-all-react-19-codemods*/} + +Run all codemods listed in this guide with the React 19 `codemod` recipe: + +```bash +npx codemod@latest react/19/migration-recipe +``` + +This will run the following codemods from `react-codemod`: +- [`replace-reactdom-render`](https://github.com/reactjs/react-codemod?tab=readme-ov-file#replace-reactdom-render) +- [`replace-string-ref`](https://github.com/reactjs/react-codemod?tab=readme-ov-file#replace-string-ref) +- [`replace-act-import`](https://github.com/reactjs/react-codemod?tab=readme-ov-file#replace-act-import) +- [`replace-use-form-state`](https://github.com/reactjs/react-codemod?tab=readme-ov-file#replace-use-form-state) +- [`prop-types-typescript`](TODO) + +This does not include the TypeScript changes. See [TypeScript changes](#typescript-changes) below. + +</Note> + +Changes that include a codemod include the command below. + +For a list of all available codemods, see the [`react-codemod` repo](https://github.com/reactjs/react-codemod). + +## Breaking changes {/*breaking-changes*/} + +### Errors in render are not re-thrown {/*errors-in-render-are-not-re-thrown*/} + +In previous versions of React, errors thrown during render were caught and rethrown. In DEV, we would also log to `console.error`, resulting in duplicate error logs. + +In React 19, we've [improved how errors are handled](/blog/2024/04/25/react-19#error-handling) to reduce duplication by not re-throwing: + +- **Uncaught Errors**: Errors that are not caught by an Error Boundary are reported to `window.reportError`. +- **Caught Errors**: Errors that are caught by an Error Boundary are reported to `console.error`. + +This change should not impact most apps, but if your production error reporting relies on errors being re-thrown, you may need to update your error handling. To support this, we've added new methods to `createRoot` and `hydrateRoot` for custom error handling: + +```js [[1, 2, "onUncaughtError"], [2, 5, "onCaughtError"]] +const root = createRoot(container, { + onUncaughtError: (error, errorInfo) => { + // ... log error report + }, + onCaughtError: (error, errorInfo) => { + // ... log error report + } +}); +``` + +For more info, see the docs for [`createRoot`](https://react.dev/reference/react-dom/client/createRoot) and [`hydrateRoot`](https://react.dev/reference/react-dom/client/hydrateRoot). + + +### Removed deprecated React APIs {/*removed-deprecated-react-apis*/} + +#### Removed: `propTypes` and `defaultProps` for functions {/*removed-proptypes-and-defaultprops*/} +`PropTypes` were deprecated in [April 2017 (v15.5.0)](https://legacy.reactjs.org/blog/2017/04/07/react-v15.5.0.html#new-deprecation-warnings). + +In React 19, we're removing the `propType` checks from the React package, and using them will be silently ignored. If you're using `propTypes`, we recommend migrating to TypeScript or another type-checking solution. + +We're also removing `defaultProps` from function components in place of ES6 default parameters. Class components will continue to support `defaultProps` since there is no ES6 alternative. + +```js +// Before +import PropTypes from 'prop-types'; + +function Heading({text}) { + return <h1>{text}</h1>; +} +Heading.propTypes = { + text: PropTypes.string, +}; +Heading.defaultProps = { + text: 'Hello, world!', +}; +``` +```ts +// After +interface Props { + text?: string; +} +function Heading({text = 'Hello, world!'}: Props) { + return <h1>{text}</h1>; +} +``` + +<Note> + +Codemod `propTypes` to TypeScript with: + +```bash +npx codemod@latest react/prop-types-typescript +``` + +</Note> + +#### Removed: Legacy Context using `contextTypes` and `getChildContext` {/*removed-removing-legacy-context*/} + +Legacy Context was deprecated in [October 2018 (v16.6.0)](https://legacy.reactjs.org/blog/2018/10/23/react-v-16-6.html). + +Legacy Context was only available in class components using the APIs `contextTypes` and `getChildContext`, and was replaced with `contextType` due to subtle bugs that were easy to miss. In React 19, we're removing Legacy Context to make React slightly smaller and faster. + +If you're still using Legacy Context in class components, you'll need to migrate to the new `contextType` API: + +```js {5-11,19-21} +// Before +import PropTypes from 'prop-types'; + +class Parent extends React.Component { + static childContextTypes = { + foo: PropTypes.string.isRequired, + }; + + getChildContext() { + return { foo: 'bar' }; + } + + render() { + return <Child />; + } +} + +class Child extends React.Component { + static contextTypes = { + foo: PropTypes.string.isRequired, + }; + + render() { + return <div>{this.context.foo}</div>; + } +} +``` + +```js {2,7,9,15} +// After +const FooContext = React.createContext(); + +class Parent extends React.Component { + render() { + return ( + <FooContext value='bar'> + <Child /> + </FooContext> + ); + } +} + +class Child extends React.Component { + static contextType = FooContext; + + render() { + return <div>{this.context}</div>; + } +} +``` + +#### Removed: string refs {/*removed-string-refs*/} +String refs were deprecated in [March, 2018 (v16.3.0)](https://legacy.reactjs.org/blog/2018/03/27/update-on-async-rendering.html). + +Class components supported string refs before being replaced by ref callbacks due to [multiple downsides](https://github.com/facebook/react/issues/1373). In React 19, we're removing string refs to make React simpler and easier to understand. + +If you're still using string refs in class components, you'll need to migrate to ref callbacks: + +```js {4,8} +// Before +class MyComponent extends React.Component { + componentDidMount() { + this.refs.input.focus(); + } + + render() { + return <input ref='input' />; + } +} +``` + +```js {4,8} +// After +class MyComponent extends React.Component { + componentDidMount() { + this.input.focus(); + } + + render() { + return <input ref={input => this.input = input} />; + } +} +``` + +<Note> + +Codemod string refs with `ref` callbacks: + +```bash +npx codemod@latest react/19/replace-string-ref +``` + +</Note> + +#### Removed: Module pattern factories {/*removed-module-pattern-factories*/} +Module pattern factories were deprecated in [August 2019 (v16.9.0)](https://legacy.reactjs.org/blog/2019/08/08/react-v16.9.0.html#deprecating-module-pattern-factories). + +This pattern was rarely used and supporting it causes React to be slightly larger and slower than necessary. In React 19, we're removing support for module pattern factories, and you'll need to migrate to regular functions: + +```js +// Before +function FactoryComponent() { + return { render() { return <div />; } } +} +``` + +```js +// After +function FactoryComponent() { + return <div />; +} +``` + +#### Removed: `React.createFactory` {/*removed-createfactory*/} +`createFactory` was deprecated in [February 2020 (v16.13.0)](https://legacy.reactjs.org/blog/2020/02/26/react-v16.13.0.html#deprecating-createfactory). + +Using `createFactory` was common before broad support for JSX, but it's rarely used today and can be replaced with JSX. In React 19, we're removing `createFactory` and you'll need to migrate to JSX: + +```js +// Before +import { createFactory } from 'react'; + +const button = createFactory('button'); +``` + +```js +// After +const button = <button />; +``` + +#### Removed: `react-test-renderer/shallow` {/*removed-react-test-renderer-shallow*/} + +In React 18, we updated `react-test-renderer/shallow` to re-export [react-shallow-renderer](https://github.com/enzymejs/react-shallow-renderer). In React 19, we're removing `react-test-render/shallow` to prefer installing the package directly: + +```bash +npm install react-shallow-renderer --save-dev +``` +```diff +- import ShallowRenderer from 'react-test-renderer/shallow'; ++ import ShallowRenderer from 'react-shallow-renderer'; +``` + +<Note> + +##### Please reconsider shallow rendering {/*please-reconsider-shallow-rendering*/} + +Shallow rendering depends on React internals and can block you from future upgrades. We recommend migrating your tests to [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) or [@testing-library/react-native](https://callstack.github.io/react-native-testing-library/docs/getting-started). + +</Note> + +### Removed deprecated React DOM APIs {/*removed-deprecated-react-dom-apis*/} + +#### Removed: `react-dom/test-utils` {/*removed-react-dom-test-utils*/} + +We've moved `act` from `react-dom/test-utils` to the `react` package: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +`ReactDOMTestUtils.act` is deprecated in favor of `React.act`. Import `act` from `react` instead of `react-dom/test-utils`. See https://react.dev/warnings/react-dom-test-utils for more info. + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +To fix this warning, you can import `act` from `react`: + +```diff +- import {act} from 'react-dom/test-utils' ++ import {act} from 'react'; +``` + +All other `test-utils` functions have been removed. These utilities were uncommon, and made it too easy to depend on low level implementation details of your components and React. In React 19, these functions will error when called and their exports will be removed in a future version. + +See the [warning page](https://react.dev/warnings/react-dom-test-utils) for alternatives. + +<Note> + +Codemod `ReactDOMTestUtils.act` to `React.act`: + +```bash +npx codemod@latest react/19/replace-act-import +``` + +</Note> + +#### Removed: `ReactDOM.render` {/*removed-reactdom-render*/} + +`ReactDOM.render` was deprecated in [March 2022 (v18.0.0)](https://react.dev/blog/2022/03/08/react-18-upgrade-guide). In React 19, we're removing `ReactDOM.render` and you'll need to migrate to using [`ReactDOM.createRoot`](https://react.dev/reference/react-dom/client/createRoot): + +```js +// Before +import {render} from 'react-dom'; +render(<App />, document.getElementById('root')); + +// After +import {createRoot} from 'react-dom/client'; +const root = createRoot(document.getElementById('root')); +root.render(<App />); +``` + +<Note> + +Codemod `ReactDOM.render` to `ReactDOMClient.createRoot`: + +```bash +npx codemod@latest react/19/replace-reactdom-render +``` + +</Note> + +#### Removed: `ReactDOM.hydrate` {/*removed-reactdom-hydrate*/} + +`ReactDOM.hydrate` was deprecated in [March 2022 (v18.0.0)](https://react.dev/blog/2022/03/08/react-18-upgrade-guide). In React 19, we're removing `ReactDOM.hydrate` you'll need to migrate to using [`ReactDOM.hydrateRoot`](https://react.dev/reference/react-dom/client/hydrateRoot), + +```js +// Before +import {hydrate} from 'react-dom'; +hydrate(<App />, document.getElementById('root')); + +// After +import {hydrateRoot} from 'react-dom/client'; +hydrateRoot(document.getElementById('root'), <App />); +``` + +<Note> + +Codemod `ReactDOM.hydrate` to `ReactDOMClient.hydrateRoot`: + +```bash +npx codemod@latest react/19/replace-reactdom-render +``` + +</Note> + +#### Removed: `unmountComponentAtNode` {/*removed-unmountcomponentatnode*/} + +`ReactDOM.unmountComponentAtNode` was deprecated in [March 2022 (v18.0.0)](https://react.dev/blog/2022/03/08/react-18-upgrade-guide). In React 19, you'll need to migrate to using `root.unmount()`. + + +```js +// Before +unmountComponentAtNode(document.getElementById('root')); + +// After +root.unmount(); +``` + +For more see `root.unmount()` for [`createRoot`](https://react.dev/reference/react-dom/client/createRoot#root-unmount) and [`hydrateRoot`](https://react.dev/reference/react-dom/client/hydrateRoot#root-unmount). + +<Note> + +Codemod `unmountComponentAtNode` to `root.unmount`: + +```bash +npx codemod@latest react/19/replace-reactdom-render +``` + +</Note> + +#### Removed: `ReactDOM.findDOMNode` {/*removed-reactdom-finddomnode*/} + +`ReactDOM.findDOMNode` was [deprecated in October 2018 (v16.6.0)](https://legacy.reactjs.org/blog/2018/10/23/react-v-16-6.html#deprecations-in-strictmode). + +We're removing `findDOMNode` because it was a legacy escape hatch that was slow to execute, fragile to refactoring, only returned the first child, and broke abstraction levels (see more [here](https://legacy.reactjs.org/docs/strict-mode.html#warning-about-deprecated-finddomnode-usage)). You can replace `ReactDOM.findDOMNode` with [DOM refs](/learn/manipulating-the-dom-with-refs): + +```js +// Before +import {findDOMNode} from 'react-dom'; + +function AutoselectingInput() { + useEffect(() => { + const input = findDOMNode(this); + input.select() + }, []); + + return <input defaultValue="Hello" />; +} +``` + +```js +// After +function AutoselectingInput() { + const ref = useRef(null); + useEffect(() => { + ref.current.select(); + }, []); + + return <input ref={ref} defaultValue="Hello" /> +} +``` + +## New deprecations {/*new-deprecations*/} + +### Deprecated: `element.ref` {/*deprecated-element-ref*/} + +React 19 supports [`ref` as a prop](/blog/2024/04/25/react-19#ref-as-a-prop), so we're deprecating the `element.ref` in place of `element.props.ref`. + +Accessing `element.ref` will warn: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +Accessing element.ref is no longer supported. ref is now a regular prop. It will be removed from the JSX Element type in a future release. + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +### Deprecated: `react-test-renderer` {/*deprecated-react-test-renderer*/} + +We are deprecating `react-test-renderer` because it implements its own renderer environment that doesn't match the environment users use, promotes testing implementation details, and relies on introspection of React's internals. + +The test renderer was created before there were more viable testing strategies available like [React Testing Library](https://testing-library.com), and we now recommend using a modern testing library instead. + +In React 19, `react-test-renderer` logs a deprecation warning, and has switched to concurrent rendering. We recommend migrating your tests to [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) or [@testing-library/react-native](https://callstack.github.io/react-native-testing-library/docs/getting-started) for a modern and well supported testing experience. + +## Notable changes {/*notable-changes*/} + +### StrictMode changes {/*strict-mode-improvements*/} + +React 19 includes several fixes and improvements to Strict Mode. + +When double rendering in Strict Mode in development, `useMemo` and `useCallback` will reuse the memoized results from the first render during the second render. Components that are already Strict Mode compatible should not notice a difference in behavior. + +As with all Strict Mode behaviors, these features are designed to proactively surface bugs in your components during development so you can fix them before they are shipped to production. For example, during development, Strict Mode will double-invoke ref callback functions on initial mount, to simulate what happens when a mounted component is replaced by a Suspense fallback. + +### UMD builds removed {/*umd-builds-removed*/} + +UMD was widely used in the past as a convenient way to load React without a build step. Now, there are modern alternatives for loading modules as scripts in HTML documents. Starting with React 19, React will no longer produce UMD builds to reduce the complexity of its testing and release process. + +To load React 19 with a script tag, we recommend using an ESM-based CDN such as [esm.sh](https://esm.sh/). + +```html +<script type="module"> + import React from "https://esm.sh/react@19/?dev" + import ReactDOMClient from "https://esm.sh/react-dom@19/client?dev" + ... +</script> +``` + +### Libraries depending on React internals may block upgrades {/*libraries-depending-on-react-internals-may-block-upgrades*/} + +This release includes changes to React internals that may impact libraries that ignore our pleas to not use internals like `SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED`. These changes are necessary to land improvements in React 19, and will not break libraries that follow our guidelines. + +Based on our [Versioning Policy](https://react.dev/community/versioning-policy#what-counts-as-a-breaking-change), these updates are not listed as breaking changes, and we are not including docs for how to upgrade them. The recommendation is to remove any code that depends on internals. + +To reflect the impact of using internals, we have renamed the `SECRET_INTERNALS` suffix to: + +`_DO_NOT_USE_OR_WARN_USERS_THEY_CANNOT_UPGRADE` + +In the future we will more aggressively block accessing internals from React to discourage usage and ensure users are not blocked from upgrading. + +## TypeScript changes {/*typescript-changes*/} + +### Removed deprecated TypeScript types {/*removed-deprecated-typescript-types*/} + +We've cleaned up the TypeScript types based on the removed APIs in React 19. Some of the removed have types been moved to more relevant packages, and others are no longer needed to describe React's behavior. + +<Note> +We've published [`types-react-codemod`](https://github.com/eps1lon/types-react-codemod/) to migrate most type related breaking changes: + +```bash +npx types-react-codemod@latest preset-19 ./path-to-app +``` + +If you have a lot of unsound access to `element.props`, you can run this additional codemod: + +```bash +npx types-react-codemod@latest react-element-default-any-props ./path-to-your-react-ts-files +``` + +</Note> + +Check out [`types-react-codemod`](https://github.com/eps1lon/types-react-codemod/) for a list of supported replacements. If you feel a codemod is missing, it can be tracked in the [list of missing React 19 codemods](https://github.com/eps1lon/types-react-codemod/issues?q=is%3Aissue+is%3Aopen+sort%3Aupdated-desc+label%3A%22React+19%22+label%3Aenhancement). + + +### `ref` cleanups required {/*ref-cleanup-required*/} + +_This change is included in the `react-19` codemod preset as [`no-implicit-ref-callback-return +`](https://github.com/eps1lon/types-react-codemod/#no-implicit-ref-callback-return)._ + +Due to the introduction of ref cleanup functions, returning anything else from a ref callback will now be rejected by TypeScript. The fix is usually to stop using implicit returns: + +```diff [[1, 1, "("], [1, 1, ")"], [2, 2, "{", 15], [2, 2, "}", 1]] +- <div ref={current => (instance = current)} /> ++ <div ref={current => {instance = current}} /> +``` + +The original code returned the instance of the `HTMLDivElement` and TypeScript wouldn't know if this was supposed to be a cleanup function or not. + +### `useRef` requires an argument {/*useref-requires-argument*/} + +_This change is included in the `react-19` codemod preset as [`refobject-defaults`](https://github.com/eps1lon/types-react-codemod/#refobject-defaults)._ + +A long-time complaint of how TypeScript and React work has been `useRef`. We've changed the types so that `useRef` now requires an argument. This significantly simplifies its type signature. It'll now behave more like `createContext`. + +```ts +// @ts-expect-error: Expected 1 argument but saw none +useRef(); +// Passes +useRef(undefined); +// @ts-expect-error: Expected 1 argument but saw none +createContext(); +// Passes +createContext(undefined); +``` + +This now also means that all refs are mutable. You'll no longer hit the issue where you can't mutate a ref because you initialised it with `null`: + +```ts +const ref = useRef<number>(null); + +// Cannot assign to 'current' because it is a read-only property +ref.current = 1; +``` + +`MutableRef` is now deprecated in favor of a single `RefObject` type which `useRef` will always return: + +```ts +interface RefObject<T> { + current: T +} + +declare function useRef<T>: RefObject<T> +``` + +`useRef` still has a convenience overload for `useRef<T>(null)` that automatically returns `RefObject<T | null>`. To ease migration due to the required argument for `useRef`, a convenience overload for `useRef(undefined)` was added that automatically returns `RefObject<T | undefined>`. + +Check out [[RFC] Make all refs mutable](https://github.com/DefinitelyTyped/DefinitelyTyped/pull/64772) for prior discussions about this change. + +### Changes to the `ReactElement` TypeScript type {/*changes-to-the-reactelement-typescript-type*/} + +_This change is included in the [`react-element-default-any-props`](https://github.com/eps1lon/types-react-codemod#react-element-default-any-props) codemod._ + +The `props` of React elements now default to `unknown` instead of `any` if the element is typed as `ReactElement`. This does not affect you if you pass a type argument to `ReactElement`: + +```ts +type Example2 = ReactElement<{ id: string }>["props"]; +// ^? { id: string } +``` + +But if you relied on the default, you now have to handle `unknown`: + +```ts +type Example = ReactElement["props"]; +// ^? Before, was 'any', now 'unknown' +``` + +You should only need it if you have a lot of legacy code relying on unsound access of element props. Element introspection only exists as an escape hatch, and you should make it explicit that your props access is unsound via an explicit `any`. + +### The JSX namespace in TypeScript {/*the-jsx-namespace-in-typescript*/} +This change is included in the `react-19` codemod preset as [`scoped-jsx`](https://github.com/eps1lon/types-react-codemod#scoped-jsx) + +A long-time request is to remove the global `JSX` namespace from our types in favor of `React.JSX`. This helps prevent pollution of global types which prevents conflicts between different UI libraries that leverage JSX. + +You'll now need to wrap module augmentation of the JSX namespace in `declare module "....": + +```diff +// global.d.ts ++ declare module "react" { + namespace JSX { + interface IntrinsicElements { + "my-element": { + myElementProps: string; + }; + } + } ++ } +``` + +The exact module specifier depends on the JSX runtime you specified in the `compilerOptions` of your `tsconfig.json`: + +- For `"jsx": "react-jsx"` it would be `react/jsx-runtime`. +- For `"jsx": "react-jsxdev"` it would be `react/jsx-dev-runtime`. +- For `"jsx": "react"` and `"jsx": "preserve"` it would be `react`. + +### Better `useReducer` typings {/*better-usereducer-typings*/} + +`useReducer` now has improved type inference thanks to [@mfp22](https://github.com/mfp22). + +However, this required a breaking change where `useReducer` doesn't accept the full reducer type as a type parameter but instead either needs none (and rely on contextual typing) or needs both the state and action type. + +The new best practice is _not_ to pass type arguments to `useReducer`. +```diff +- useReducer<React.Reducer<State, Action>>(reducer) ++ useReducer(reducer) +``` +This may not work in edge cases where you can explicitly type the state and action, by passing in the `Action` in a tuple: +```diff +- useReducer<React.Reducer<State, Action>>(reducer) ++ useReducer<State, [Action]>(reducer) +``` +If you define the reducer inline, we encourage to annotate the function parameters instead: +```diff +- useReducer<React.Reducer<State, Action>>((state, action) => state) ++ useReducer((state: State, action: Action) => state) +``` +This is also what you'd also have to do if you move the reducer outside of the `useReducer` call: + +```ts +const reducer = (state: State, action: Action) => state; +``` + +## Changelog {/*changelog*/} + +### Other breaking changes {/*other-breaking-changes*/} + +- **react-dom**: Error for javascript URLs in src/href [#26507](https://github.com/facebook/react/pull/26507) +- **react-dom**: Remove `errorInfo.digest` from `onRecoverableError` [#28222](https://github.com/facebook/react/pull/28222) +- **react-dom**: Remove `unstable_flushControlled` [#26397](https://github.com/facebook/react/pull/26397) +- **react-dom**: Remove `unstable_createEventHandle` [#28271](https://github.com/facebook/react/pull/28271) +- **react-dom**: Remove `unstable_renderSubtreeIntoContainer` [#28271](https://github.com/facebook/react/pull/28271) +- **react-dom**: Remove `unstable_runWithPrioirty` [#28271](https://github.com/facebook/react/pull/28271) +- **react-is**: Remove deprecated methods from `react-is` [28224](https://github.com/facebook/react/pull/28224) + +### Other notable changes {/*other-notable-changes*/} + +- **react**: Batch sync, default and continuous lanes [#25700](https://github.com/facebook/react/pull/25700) +- **react**: Don't prerender siblings of suspended component [#26380](https://github.com/facebook/react/pull/26380) +- **react**: Detect infinite update loops caused by render phase updates [#26625](https://github.com/facebook/react/pull/26625) +- **react-dom**: Transitions in popstate are now synchronous [#26025](https://github.com/facebook/react/pull/26025) +- **react-dom**: Remove layout effect warning during SSR [#26395](https://github.com/facebook/react/pull/26395) +- **react-dom**: Warn and don’t set empty string for src/href (except anchor tags) [#28124](https://github.com/facebook/react/pull/28124) + +We'll publish the full changelog with the stable release of React 19. + +--- + +Thanks to [Andrew Clark](https://twitter.com/acdlite), [Eli White](https://twitter.com/Eli_White), [Jack Pope](https://github.com/jackpope), [Jan Kassens](https://github.com/kassens), [Josh Story](https://twitter.com/joshcstory), [Matt Carroll](https://twitter.com/mattcarrollcode), [Noah Lemen](https://twitter.com/noahlemen), [Sophie Alpert](https://twitter.com/sophiebits), and [Sebastian Silbermann](https://twitter.com/sebsilbermann) for reviewing and editing this post. diff --git a/src/content/blog/2024/04/25/react-19.md b/src/content/blog/2024/04/25/react-19.md new file mode 100644 index 000000000..1b19c3546 --- /dev/null +++ b/src/content/blog/2024/04/25/react-19.md @@ -0,0 +1,775 @@ +--- +title: "React 19 RC" +author: The React Team +date: 2024/04/25 +description: React 19 RC is now available on npm! In this post, we'll give an overview of the new features in React 19, and how you can adopt them. +--- + +April 25, 2024 by [The React Team](/community/team) + +--- + +<Intro> + +React 19 RC is now available on npm! + +</Intro> + +In our [React 19 RC Upgrade Guide](/blog/2024/04/25/react-19-upgrade-guide), we shared step-by-step instructions for upgrading your app to React 19. In this post, we'll give an overview of the new features in React 19, and how you can adopt them. + +- [What's new in React 19](#whats-new-in-react-19) +- [Improvements in React 19](#improvements-in-react-19) +- [How to upgrade](#how-to-upgrade) + +For a list of breaking changes, see the [Upgrade Guide](/blog/2024/04/25/react-19-upgrade-guide). + +--- + +## What's new in React 19 {/*whats-new-in-react-19*/} + +### Actions {/*actions*/} + +A common use case in React apps is to perform a data mutation and then update state in response. For example, when a user submits a form to change their name, you will make an API request, and then handle the response. In the past, you would need to handle pending states, errors, optimistic updates, and sequential requests manually. + +For example, you could handle the pending and error state in `useState`: + +```js +// Before Actions +function UpdateName({}) { + const [name, setName] = useState(""); + const [error, setError] = useState(null); + const [isPending, setIsPending] = useState(false); + + const handleSubmit = async () => { + setIsPending(true); + const error = await updateName(name); + setIsPending(false); + if (error) { + setError(error); + return; + } + redirect("/path"); + }; + + return ( + <div> + <input value={name} onChange={(event) => setName(event.target.value)} /> + <button onClick={handleSubmit} disabled={isPending}> + Update + </button> + {error && <p>{error}</p>} + </div> + ); +} +``` + +In React 19, we're adding support for using async functions in transitions to handle pending states, errors, forms, and optimistic updates automatically. + +For example, you can use `useTransition` to handle the pending state for you: + +```js +// Using pending state from Actions +function UpdateName({}) { + const [name, setName] = useState(""); + const [error, setError] = useState(null); + const [isPending, startTransition] = useTransition(); + + const handleSubmit = () => { + startTransition(async () => { + const error = await updateName(name); + if (error) { + setError(error); + return; + } + redirect("/path"); + }) + }; + + return ( + <div> + <input value={name} onChange={(event) => setName(event.target.value)} /> + <button onClick={handleSubmit} disabled={isPending}> + Update + </button> + {error && <p>{error}</p>} + </div> + ); +} +``` + +The async transition will immediately set the `isPending` state to true, make the async request(s), and switch `isPending` to false after any transitions. This allows you to keep the current UI responsive and interactive while the data is changing. + +<Note> + +#### By convention, functions that use async transitions are called "Actions". {/*by-convention-functions-that-use-async-transitions-are-called-actions*/} + +Actions automatically manage submitting data for you: + +- **Pending state**: Actions provide a pending state that starts at the beginning of a request and automatically resets when the final state update is committed. +- **Optimistic updates**: Actions support the new [`useOptimistic`](#new-hook-optimistic-updates) hook so you can show users instant feedback while the requests are submitting. +- **Error handling**: Actions provide error handling so you can display Error Boundaries when a request fails, and revert optimistic updates to their original value automatically. +- **Forms**: `<form>` elements now support passing functions to the `action` and `formAction` props. Passing functions to the `action` props use Actions by default and reset the form automatically after submission. + +</Note> + +Building on top of Actions, React 19 introduces [`useOptimistic`](#new-hook-optimistic-updates) to manage optimistic updates, and a new hook [`React.useActionState`](#new-hook-useactionstate) to handle common cases for Actions. In `react-dom` we're adding [`<form>` Actions](#form-actions) to manage forms automatically and [`useFormStatus`](#new-hook-useformstatus) to support the common cases for Actions in forms. + +In React 19, the above example can be simplified to: + +```js +// Using <form> Actions and useActionState +function ChangeName({ name, setName }) { + const [error, submitAction, isPending] = useActionState( + async (previousState, formData) => { + const error = await updateName(formData.get("name")); + if (error) { + return error; + } + redirect("/path"); + return null; + }, + null, + ); + + return ( + <form action={submitAction}> + <input type="text" name="name" /> + <button type="submit" disabled={isPending}>Update</button> + {error && <p>{error}</p>} + </form> + ); +} +``` + +In the next section, we'll break down each of the new Action features in React 19. + +### New hook: `useActionState` {/*new-hook-useactionstate*/} + +To make the common cases easier for Actions, we've added a new hook called `useActionState`: + +```js +const [error, submitAction, isPending] = useActionState( + async (previousState, newName) => { + const error = await updateName(newName); + if (error) { + // You can return any result of the action. + // Here, we return only the error. + return error; + } + + // handle success + return null; + }, + null, +); +``` + +`useActionState` accepts a function (the "Action"), and returns a wrapped Action to call. This works because Actions compose. When the wrapped Action is called, `useActionState` will return the last result of the Action as `data`, and the pending state of the Action as `pending`. + +<Note> + +`React.useActionState` was previously called `ReactDOM.useFormState` in the Canary releases, but we've renamed it and deprecated `useFormState`. + +See [#28491](https://github.com/facebook/react/pull/28491) for more info. + +</Note> + +For more information, see the docs for [`useActionState`](/reference/react/useActionState). + +### React DOM: `<form>` Actions {/*form-actions*/} + +Actions are also integrated with React 19's new `<form>` features for `react-dom`. We've added support for passing functions as the `action` and `formAction` props of `<form>`, `<input>`, and `<button>` elements to automatically submit forms with Actions: + +```js [[1,1,"actionFunction"]] +<form action={actionFunction}> +``` + +When a `<form>` Action succeeds, React will automatically reset the form for uncontrolled components. If you need to reset the `<form>` manually, you can call the new `requestFormReset` React DOM API. + +For more information, see the `react-dom` docs for [`<form>`](/reference/react-dom/components/form), [`<input>`](/reference/react-dom/components/input), and `<button>`. + +### React DOM: New hook: `useFormStatus` {/*new-hook-useformstatus*/} + +In design systems, it's common to write design components that need access to information about the `<form>` they're in, without drilling props down to the component. This can be done via Context, but to make the common case easier, we've added a new hook `useFormStatus`: + +```js [[1, 4, "pending"], [1, 5, "pending"]] +import {useFormStatus} from 'react-dom'; + +function DesignButton() { + const {pending} = useFormStatus(); + return <button type="submit" disabled={pending} /> +} +``` + +`useFormStatus` reads the status of the parent `<form>` as if the form was a Context provider. + +For more information, see the `react-dom` docs for [`useFormStatus`](/reference/react-dom/hooks/useFormStatus). + +### New hook: `useOptimistic` {/*new-hook-optimistic-updates*/} + +Another common UI pattern when performing a data mutation is to show the final state optimistically while the async request is underway. In React 19, we're adding a new hook called `useOptimistic` to make this easier: + +```js {2,6,13,19} +function ChangeName({currentName, onUpdateName}) { + const [optimisticName, setOptimisticName] = useOptimistic(currentName); + + const submitAction = async formData => { + const newName = formData.get("name"); + setOptimisticName(newName); + const updatedName = await updateName(newName); + onUpdateName(updatedName); + }; + + return ( + <form action={submitAction}> + <p>Your name is: {optimisticName}</p> + <p> + <label>Change Name:</label> + <input + type="text" + name="name" + disabled={currentName !== optimisticName} + /> + </p> + </form> + ); +} +``` + +The `useOptimistic` hook will immediately render the `optimisticName` while the `updateName` request is in progress. When the update finishes or errors, React will automatically switch back to the `currentName` value. + +For more information, see the docs for [`useOptimistic`](/reference/react/useOptimistic). + +### New API: `use` {/*new-feature-use*/} + +In React 19 we're introducing a new API to read resources in render: `use`. + +For example, you can read a promise with `use`, and React will Suspend until the promise resolves: + +```js {1,5} +import {use} from 'react'; + +function Comments({commentsPromise}) { + // `use` will suspend until the promise resolves. + const comments = use(commentsPromise); + return comments.map(comment => <p key={comment.id}>{comment}</p>); +} + +function Page({commentsPromise}) { + // When `use` suspends in Comments, + // this Suspense boundary will be shown. + return ( + <Suspense fallback={<div>Loading...</div>}> + <Comments commentsPromise={commentsPromise} /> + </Suspense> + ) +} +``` + +<Note> + +#### `use` does not support promises created in render. {/*use-does-not-support-promises-created-in-render*/} + +If you try to pass a promise created in render to `use`, React will warn: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +A component was suspended by an uncached promise. Creating promises inside a Client Component or hook is not yet supported, except via a Suspense-compatible library or framework. + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +To fix, you need to pass a promise from a suspense powered library or framework that supports caching for promises. In the future we plan to ship features to make it easier to cache promises in render. + +</Note> + +You can also read context with `use`, allowing you to read Context conditionally such as after early returns: + +```js {1,11} +import {use} from 'react'; +import ThemeContext from './ThemeContext' + +function Heading({children}) { + if (children == null) { + return null; + } + + // This would not work with useContext + // because of the early return. + const theme = use(ThemeContext); + return ( + <h1 style={{color: theme.color}}> + {children} + </h1> + ); +} +``` + +The `use` API can only be called in render, similar to hooks. Unlike hooks, `use` can be called conditionally. In the future we plan to support more ways to consume resources in render with `use`. + +For more information, see the docs for [`use`](/reference/react/use). + + +## React Server Components {/*react-server-components*/} + +### Server Components {/*server-components*/} + +Server Components are a new option that allows rendering components ahead of time, before bundling, in an environment separate from your client application or SSR server. This separate environment is the "server" in React Server Components. Server Components can run once at build time on your CI server, or they can be run for each request using a web server. + +React 19 includes all of the React Server Components features included from the Canary channel. This means libraries that ship with Server Components can now target React 19 as a peer dependency with a `react-server` [export condition](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md#react-server-conditional-exports) for use in frameworks that support the [Full-stack React Architecture](/learn/start-a-new-react-project#which-features-make-up-the-react-teams-full-stack-architecture-vision). + + +<Note> + +#### How do I build support for Server Components? {/*how-do-i-build-support-for-server-components*/} + +While React Server Components in React 19 are stable and will not break between major versions, the underlying APIs used to implement a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x. + +To support React Server Components as a bundler or framework, we recommend pinning to a specific React version, or using the Canary release. We will continue working with bundlers and frameworks to stabilize the APIs used to implement React Server Components in the future. + +</Note> + + +For more, see the docs for [React Server Components](/reference/rsc/server-components). + +### Server Actions {/*server-actions*/} + +Server Actions allow Client Components to call async functions executed on the server. + +When a Server Action is defined with the `"use server"` directive, your framework will automatically create a reference to the server function, and pass that reference to the Client Component. When that function is called on the client, React will send a request to the server to execute the function, and return the result. + +<Note> + +#### There is no directive for Server Components. {/*there-is-no-directive-for-server-components*/} + +A common misunderstanding is that Server Components are denoted by `"use server"`, but there is no directive for Server Components. The `"use server"` directive is used for Server Actions. + +For more info, see the docs for [Directives](/reference/rsc/directives). + +</Note> + +Server Actions can be created in Server Components and passed as props to Client Components, or they can be imported and used in Client Components. + +For more, see the docs for [React Server Actions](/reference/rsc/server-actions). + +## Improvements in React 19 {/*improvements-in-react-19*/} + +### `ref` as a prop {/*ref-as-a-prop*/} + +Starting in React 19, you can now access `ref` as a prop for function components: + +```js [[1, 1, "ref"], [1, 2, "ref", 45], [1, 6, "ref", 14]] +function MyInput({placeholder, ref}) { + return <input placeholder={placeholder} ref={ref} /> +} + +//... +<MyInput ref={ref} /> +``` + +New function components will no longer need `forwardRef`, and we will be publishing a codemod to automatically update your components to use the new `ref` prop. In future versions we will deprecate and remove `forwardRef`. + +<Note> + +`refs` passed to classes are not passed as props since they reference the component instance. + +</Note> + +### Diffs for hydration errors {/*diffs-for-hydration-errors*/} + +We also improved error reporting for hydration errors in `react-dom`. For example, instead of logging multiple errors in DEV without any information about the mismatch: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +Warning: Text content did not match. Server: "Server" Client: "Client" +{' '}at span +{' '}at App + +</ConsoleLogLine> + +<ConsoleLogLine level="error"> + +Warning: An error occurred during hydration. The server HTML was replaced with client content in \<div\>. + +</ConsoleLogLine> + +<ConsoleLogLine level="error"> + +Warning: Text content did not match. Server: "Server" Client: "Client" +{' '}at span +{' '}at App + +</ConsoleLogLine> + +<ConsoleLogLine level="error"> + +Warning: An error occurred during hydration. The server HTML was replaced with client content in \<div\>. + +</ConsoleLogLine> + +<ConsoleLogLine level="error"> + +Uncaught Error: Text content does not match server-rendered HTML. +{' '}at checkForUnmatchedText +{' '}... + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +We now log a single message with a diff of the mismatch: + + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +Uncaught Error: Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if an SSR-ed Client Component used:{'\n'} +\- A server/client branch `if (typeof window !== 'undefined')`. +\- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called. +\- Date formatting in a user's locale which doesn't match the server. +\- External changing data without sending a snapshot of it along with the HTML. +\- Invalid HTML tag nesting.{'\n'} +It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.{'\n'} +https://react.dev/link/hydration-mismatch {'\n'} +{' '}\<App\> +{' '}\<span\> +{'+ '}Client +{'- '}Server{'\n'} +{' '}at throwOnHydrationMismatch +{' '}... + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +### `<Context>` as a provider {/*context-as-a-provider*/} + +In React 19, you can render `<Context>` as a provider instead of `<Context.Provider>`: + + +```js {5,7} +const ThemeContext = createContext(''); + +function App({children}) { + return ( + <ThemeContext value="dark"> + {children} + </ThemeContext> + ); +} +``` + +New Context providers can use `<Context>` and we will be publishing a codemod to convert existing providers. In future versions we will deprecate `<Context.Provider>`. + +### Cleanup functions for refs {/*cleanup-functions-for-refs*/} + +We now support returning a cleanup function from `ref` callbacks: + +```js {7-9} +<input + ref={(ref) => { + // ref created + + // NEW: return a cleanup function to reset + // the ref when element is removed from DOM. + return () => { + // ref cleanup + }; + }} +/> +``` + +When the component unmounts, React will call the cleanup function returned from the `ref` callback. This works for DOM refs, refs to class components, and `useImperativeHandle`. + +<Note> + +Previously, React would call `ref` functions with `null` when unmounting the component. If your `ref` returns a cleanup function, React will now skip this step. + +In future versions, we will deprecate calling refs with `null` when unmounting components. + +</Note> + +Due to the introduction of ref cleanup functions, returning anything else from a `ref` callback will now be rejected by TypeScript. The fix is usually to stop using implicit returns, for example: + +```diff [[1, 1, "("], [1, 1, ")"], [2, 2, "{", 15], [2, 2, "}", 1]] +- <div ref={current => (instance = current)} /> ++ <div ref={current => {instance = current}} /> +``` + +The original code returned the instance of the `HTMLDivElement` and TypeScript wouldn't know if this was _supposed_ to be a cleanup function or if you didn't want to return a cleanup function. + +You can codemod this pattern with [`no-implicit-ref-callback-return`](https://github.com/eps1lon/types-react-codemod/#no-implicit-ref-callback-return). + +### `useDeferredValue` initial value {/*use-deferred-value-initial-value*/} + +We've added an `initialValue` option to `useDeferredValue`: + +```js [[1, 1, "deferredValue"], [1, 4, "deferredValue"], [2, 4, "''"]] +function Search({deferredValue}) { + // On initial render the value is ''. + // Then a re-render is scheduled with the deferredValue. + const value = useDeferredValue(deferredValue, ''); + + return ( + <Results query={value} /> + ); +} +```` + +When <CodeStep step={2}>initialValue</CodeStep> is provided, `useDeferredValue` will return it as `value` for the initial render of the component, and schedules a re-render in the background with the <CodeStep step={1}>deferredValue</CodeStep> returned. + +For more, see [`useDeferredValue`](/reference/react/useDeferredValue). + +### Support for Document Metadata {/*support-for-metadata-tags*/} + +In HTML, document metadata tags like `<title>`, `<link>`, and `<meta>` are reserved for placement in the `<head>` section of the document. In React, the component that decides what metadata is appropriate for the app may be very far from the place where you render the `<head>` or React does not render the `<head>` at all. In the past, these elements would need to be inserted manually in an effect, or by libraries like [`react-helmet`](https://github.com/nfl/react-helmet), and required careful handling when server rendering a React application. + +In React 19, we're adding support for rendering document metadata tags in components natively: + +```js {5-8} +function BlogPost({post}) { + return ( + <article> + <h1>{post.title}</h1> + <title>{post.title}</title> + <meta name="author" content="Josh" /> + <link rel="author" href="https://twitter.com/joshcstory/" /> + <meta name="keywords" content={post.keywords} /> + <p> + Eee equals em-see-squared... + </p> + </article> + ); +} +``` + +When React renders this component, it will see the `<title>` `<link>` and `<meta>` tags, and automatically hoist them to the `<head>` section of document. By supporting these metadata tags natively, we're able to ensure they work with client-only apps, streaming SSR, and Server Components. + +<Note> + +#### You may still want a Metadata library {/*you-may-still-want-a-metadata-library*/} + +For simple use cases, rendering Document Metadata as tags may be suitable, but libraries can offer more powerful features like overriding generic metadata with specific metadata based on the current route. These features make it easier for frameworks and libraries like [`react-helmet`](https://github.com/nfl/react-helmet) to support metadata tags, rather than replace them. + +</Note> + +For more info, see the docs for [`<title>`](/reference/react-dom/components/title), [`<link>`](/reference/react-dom/components/link), and [`<meta>`](/reference/react-dom/components/meta). + +### Support for stylesheets {/*support-for-stylesheets*/} + +Stylesheets, both externally linked (`<link rel="stylesheet" href="...">`) and inline (`<style>...</style>`), require careful positioning in the DOM due to style precedence rules. Building a stylesheet capability that allows for composability within components is hard, so users often end up either loading all of their styles far from the components that may depend on them, or they use a style library which encapsulates this complexity. + +In React 19, we're addressing this complexity and providing even deeper integration into Concurrent Rendering on the Client and Streaming Rendering on the Server with built in support for stylesheets. If you tell React the `precedence` of your stylesheet it will manage the insertion order of the stylesheet in the DOM and ensure that the stylesheet (if external) is loaded before revealing content that depends on those style rules. + +```js {4,5,17} +function ComponentOne() { + return ( + <Suspense fallback="loading..."> + <link rel="stylesheet" href="foo" precedence="default" /> + <link rel="stylesheet" href="bar" precedence="high" /> + <article class="foo-class bar-class"> + {...} + </article> + </Suspense> + ) +} + +function ComponentTwo() { + return ( + <div> + <p>{...}</p> + <link rel="stylesheet" href="baz" precedence="default" /> <-- will be inserted between foo & bar + </div> + ) +} +``` + +During Server Side Rendering React will include the stylesheet in the `<head>`, which ensures that the browser will not paint until it has loaded. If the stylesheet is discovered late after we've already started streaming, React will ensure that the stylesheet is inserted into the `<head>` on the client before revealing the content of a Suspense boundary that depends on that stylesheet. + +During Client Side Rendering React will wait for newly rendered stylesheets to load before committing the render. If you render this component from multiple places within your application React will only include the stylesheet once in the document: + +```js {5} +function App() { + return <> + <ComponentOne /> + ... + <ComponentOne /> // won't lead to a duplicate stylesheet link in the DOM + </> +} +``` + +For users accustomed to loading stylesheets manually this is an opportunity to locate those stylesheets alongside the components that depend on them allowing for better local reasoning and an easier time ensuring you only load the stylesheets that you actually depend on. + +Style libraries and style integrations with bundlers can also adopt this new capability so even if you don't directly render your own stylesheets, you can still benefit as your tools are upgraded to use this feature. + +For more details, read the docs for [`<link>`](/reference/react-dom/components/link) and [`<style>`](/reference/react-dom/components/style). + +### Support for async scripts {/*support-for-async-scripts*/} + +In HTML normal scripts (`<script src="...">`) and deferred scripts (`<script defer="" src="...">`) load in document order which makes rendering these kinds of scripts deep within your component tree challenging. Async scripts (`<script async="" src="...">`) however will load in arbitrary order. + +In React 19 we've included better support for async scripts by allowing you to render them anywhere in your component tree, inside the components that actually depend on the script, without having to manage relocating and deduplicating script instances. + +```js {4,15} +function MyComponent() { + return ( + <div> + <script async={true} src="..." /> + Hello World + </div> + ) +} + +function App() { + <html> + <body> + <MyComponent> + ... + <MyComponent> // won't lead to duplicate script in the DOM + </body> + </html> +} +``` + +In all rendering environments, async scripts will be deduplicated so that React will only load and execute the script once even if it is rendered by multiple different components. + +In Server Side Rendering, async scripts will be included in the `<head>` and prioritized behind more critical resources that block paint such as stylesheets, fonts, and image preloads. + +For more details, read the docs for [`<script>`](/reference/react-dom/components/script). + +### Support for preloading resources {/*support-for-preloading-resources*/} + +During initial document load and on client side updates, telling the Browser about resources that it will likely need to load as early as possible can have a dramatic effect on page performance. + +React 19 includes a number of new APIs for loading and preloading Browser resources to make it as easy as possible to build great experiences that aren't held back by inefficient resource loading. + +```js +import { prefetchDNS, preconnect, preload, preinit } from 'react-dom' +function MyComponent() { + preinit('https://.../path/to/some/script.js', {as: 'script' }) // loads and executes this script eagerly + preload('https://.../path/to/font.woff', { as: 'font' }) // preloads this font + preload('https://.../path/to/stylesheet.css', { as: 'style' }) // preloads this stylesheet + prefetchDNS('https://...') // when you may not actually request anything from this host + preconnect('https://...') // when you will request something but aren't sure what +} +``` +```html +<!-- the above would result in the following DOM/HTML --> +<html> + <head> + <!-- links/scripts are prioritized by their utility to early loading, not call order --> + <link rel="prefetch-dns" href="https://..."> + <link rel="preconnect" href="https://..."> + <link rel="preload" as="font" href="https://.../path/to/font.woff"> + <link rel="preload" as="style" href="https://.../path/to/stylesheet.css"> + <script async="" src="https://.../path/to/some/script.js"></script> + </head> + <body> + ... + </body> +</html> +``` + +These APIs can be used to optimize initial page loads by moving discovery of additional resources like fonts out of stylesheet loading. They can also make client updates faster by prefetching a list of resources used by an anticipated navigation and then eagerly preloading those resources on click or even on hover. + +For more details see [Resource Preloading APIs](/reference/react-dom#resource-preloading-apis). + +### Compatibility with third-party scripts and extensions {/*compatibility-with-third-party-scripts-and-extensions*/} + +We've improved hydration to account for third-party scripts and browser extensions. + +When hydrating, if an element that renders on the client doesn't match the element found in the HTML from the server, React will force a client re-render to fix up the content. Previously, if an element was inserted by third-party scripts or browser extensions, it would trigger a mismatch error and client render. + +In React 19, unexpected tags in the `<head>` and `<body>` will be skipped over, avoiding the mismatch errors. If React needs to re-render the entire document due to an unrelated hydration mismatch, it will leave in place stylesheets inserted by third-party scripts and browser extensions. + +### Better error reporting {/*error-handling*/} + +We improved error handling in React 19 to remove duplication and provide options for handling caught and uncaught errors. For example, when there's an error in render caught by an Error Boundary, previously React would throw the error twice (once for the original error, then again after failing to automatically recover), and then call `console.error` with info about where the error occurred. + +This resulted in three errors for every caught error: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +Uncaught Error: hit +{' '}at Throws +{' '}at renderWithHooks +{' '}... + +</ConsoleLogLine> + +<ConsoleLogLine level="error"> + +Uncaught Error: hit<span className="ms-2 text-gray-30">{' <--'} Duplicate</span> +{' '}at Throws +{' '}at renderWithHooks +{' '}... + +</ConsoleLogLine> + +<ConsoleLogLine level="error"> + +The above error occurred in the Throws component: +{' '}at Throws +{' '}at ErrorBoundary +{' '}at App{'\n'} +React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary. + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +In React 19, we log a single error with all the error information included: + +<ConsoleBlockMulti> + +<ConsoleLogLine level="error"> + +Error: hit +{' '}at Throws +{' '}at renderWithHooks +{' '}...{'\n'} +The above error occurred in the Throws component: +{' '}at Throws +{' '}at ErrorBoundary +{' '}at App{'\n'} +React will try to recreate this component tree from scratch using the error boundary you provided, ErrorBoundary. +{' '}at ErrorBoundary +{' '}at App + +</ConsoleLogLine> + +</ConsoleBlockMulti> + +Additionally, we've added two new root options to complement `onRecoverableError`: + +- `onCaughtError`: called when React catches an error in an Error Boundary. +- `onUncaughtError`: called when an error is thrown and not caught by an Error Boundary. +- `onRecoverableError`: called when an error is thrown and automatically recovered. + +For more info and examples, see the docs for [`createRoot`](/reference/react-dom/client/createRoot) and [`hydrateRoot`](/reference/react-dom/client/hydrateRoot). + +### Support for Custom Elements {/*support-for-custom-elements*/} + +React 19 adds full support for custom elements and passes all tests on [Custom Elements Everywhere](https://custom-elements-everywhere.com/). + +In past versions, using Custom Elements in React has been difficult because React treated unrecognized props as attributes rather than properties. In React 19, we've added support for properties that works on the client and during SSR with the following strategy: + +- **Server Side Rendering**: props passed to a custom element will render as attributes if their type is a primitive value like `string`, `number`, or the value is `true`. Props with non-primitive types like `object`, `symbol`, `function`, or value `false` will be omitted. +- **Client Side Rendering**: props that match a property on the Custom Element instance will be assigned as properties, otherwise they will be assigned as attributes. + +Thanks to [Joey Arhar](https://github.com/josepharhar) for driving the design and implementation of Custom Element support in React. + + +#### How to upgrade {/*how-to-upgrade*/} +See the [React 19 Upgrade Guide](/blog/2024/04/25/react-19-upgrade-guide) for step-by-step instructions and a full list of breaking and notable changes. + + + diff --git a/src/content/blog/2024/05/22/react-conf-2024-recap.md b/src/content/blog/2024/05/22/react-conf-2024-recap.md new file mode 100644 index 000000000..96417fd8b --- /dev/null +++ b/src/content/blog/2024/05/22/react-conf-2024-recap.md @@ -0,0 +1,124 @@ +--- +title: "React Conf 2024 Recap" +author: Ricky Hanlon +date: 2024/05/22 +description: Last week we hosted React Conf 2024, a two-day conference in Henderson, Nevada where 700+ attendees gathered in-person to discuss the latest in UI engineering. In this post, we'll summarize the talks and announcements from the event. +--- + +May 22, 2024 by [Ricky Hanlon](https://twitter.com/rickhanlonii). + +--- + +<Intro> + +Last week we hosted React Conf 2024, a two-day conference in Henderson, Nevada where 700+ attendees gathered in-person to discuss the latest in UI engineering. This was our first in-person conference since 2019, and we were thrilled to be able to bring the community together again. + +</Intro> + +--- + +At React Conf 2024, we announced the [React 19 RC](/blog/2024/04/25/react-19), the [React Native New Architecture Beta](https://github.com/reactwg/react-native-new-architecture/discussions/189), and an experimental release of the [React Compiler](/learn/react-compiler). The community also took the stage to announce [React Router v7](https://remix.run/blog/merging-remix-and-react-router), [Universal Server Components](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=20765s) in Expo Router, React Server Components in [RedwoodJS](https://redwoodjs.com/blog/rsc-now-in-redwoodjs), and much more. + +The entire [day 1](https://www.youtube.com/watch?v=T8TZQ6k4SLE) and [day 2](https://www.youtube.com/watch?v=0ckOUBiuxVY) streams are available online. In this post, we'll summarize the talks and announcements from the event. + +## Day 1 {/*day-1*/} + +_[Watch the full day 1 stream here.](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=973s)_ + +To kick off day 1, Meta CTO [Andrew "Boz" Bosworth](https://www.threads.net/@boztank) shared a welcome message followed by an introduction by [Seth Webster](https://twitter.com/sethwebster), who manages the React Org at Meta, and our MC [Ashley Narcisse](https://twitter.com/_darkfadr). + +In the day 1 keynote, [Joe Savona](https://twitter.com/en_JS) shared our goals and vision for React to make it easy for anyone to build great user experiences. [Lauren Tan](https://twitter.com/potetotes) followed with a State of React, where she shared that React was downloaded over 1 billion times in 2023, and that 37% of new developers learn to program with React. Finally, she highlighted the work of the React community to make React, React. + +For more, check out these talks from the community later in the conference: + +- [Vanilla React](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=5542s) by [Ryan Florence](https://twitter.com/ryanflorence) +- [React Rhythm & Blues](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=12728s) by [Lee Robinson](https://twitter.com/leeerob) +- [RedwoodJS, now with React Server Components](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=26815s) by [Amy Dutton](https://twitter.com/selfteachme) +- [Introducing Universal React Server Components in Expo Router](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=20765s) by [Evan Bacon](https://twitter.com/Baconbrix) + +Next in the keynote, [Josh Story](https://twitter.com/joshcstory) and [Andrew Clark](https://twitter.com/acdlite) shared new features coming in React 19, and announced the React 19 RC which is ready for testing in production. Check out all the features in the [React 19 release post](/blog/2024/04/25/react-19), and see these talks for deep dives on the new features: + +- [What's new in React 19](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=8880s) by [Lydia Hallie](https://twitter.com/lydiahallie) +- [React Unpacked: A Roadmap to React 19](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=10112s) by [Sam Selikoff](https://twitter.com/samselikoff) +- [React 19 Deep Dive: Coordinating HTML](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=24916s) by [Josh Story](https://twitter.com/joshcstory) +- [Enhancing Forms with React Server Components](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=25280s) by [Aurora Walberg Scharff](https://twitter.com/aurorascharff) +- [React for Two Computers](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=18825s) by [Dan Abramov](https://twitter.com/dan_abramov2) +- [And Now You Understand React Server Components](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=11256s) by [Kent C. Dodds](https://twitter.com/kentcdodds) + +Finally, we ended the keynote with [Joe Savona](https://twitter.com/en_JS), [Sathya Gunasekaran](https://twitter.com/_gsathya), and [Mofei Zhang](https://twitter.com/zmofei) announcing that the React Compiler is now [Open Source](https://github.com/facebook/react/pull/29061), and sharing an experimental version of the React Compiler to try out. + +For more information on using the Compiler and how it works, check out [the docs](/learn/react-compiler) and these talks: + +- [Forget About Memo](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=12020s) by [Lauren Tan](https://twitter.com/potetotes) +- [React Compiler Deep Dive](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=9313s) by [Sathya Gunasekaran](https://twitter.com/_gsathya) and [Mofei Zhang](https://twitter.com/zmofei) + +Watch the full day 1 keynote here: + +<YouTubeIframe src="https://www.youtube.com/embed/T8TZQ6k4SLE?t=973s" /> + +## Day 2 {/*day-2*/} + +_[Watch the full day 2 stream here.](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=1720s)_ + +To kick off day 2, [Seth Webster](https://twitter.com/sethwebster) shared a welcome message, followed by a Thank You from [Eli White](https://x.com/Eli_White) and an introduction by our Chief Vibes Officer [Ashley Narcisse](https://twitter.com/_darkfadr). + +In the day 2 keynote, [Nicola Corti](https://twitter.com/cortinico) shared the State of React Native, including 78 million downloads in 2023. He also highlighted apps using React Native including 2000+ screens used inside of Meta; the product details page in Facebook Marketplace, which is visited more than 2 billion times per day; and part of the Microsoft Windows Start Menu and some features in almost every Microsoft Office product across mobile and desktop. + +Nicola also highlighted all the work the community does to support React Native including libraries, frameworks, and multiple platforms. For more, check out these talks from the community: + +- [Extending React Native beyond Mobile and Desktop Apps](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=5798s) by [Chris Traganos](https://twitter.com/chris_trag) and [Anisha Malde](https://twitter.com/anisha_malde) +- [Spatial computing with React](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=22525s) by [Michał Pierzchała](https://twitter.com/thymikee) + +[Riccardo Cipolleschi](https://twitter.com/cipolleschir) continued the day 2 keynote by announcing that the React Native New Architecture is now in Beta and ready for apps to adopt in production. He shared new features and improvements in the new architecture, and shared the roadmap for the future of React Native. For more check out: + +- [Cross Platform React](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=26569s) by [Olga Zinoveva](https://github.com/SlyCaptainFlint) and [Naman Goel](https://twitter.com/naman34) + +Next in the keynote, Nicola announced that we are now recommending starting with a framework like Expo for all new apps created with React Native. With the change, he also announced a new React Native homepage and new Getting Started docs. You can view the new Getting Started guide in the [React Native docs](https://reactnative.dev/docs/next/environment-setup). + +Finally, to end the keynote, [Kadi Kraman](https://twitter.com/kadikraman) shared the latest features and improvements in Expo, and how to get started developing with React Native using Expo. + +Watch the full day 2 keynote here: + +<YouTubeIframe src="https://www.youtube.com/embed/0ckOUBiuxVY?t=1720s" /> + +## Q&A {/*q-and-a*/} + +The React and React Native teams also ended each day with a Q&A session: + +- [React Q&A](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=27518s) hosted by [Michael Chan](https://twitter.com/chantastic) +- [React Native Q&A](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=27935s) hosted by [Jamon Holmgren](https://twitter.com/jamonholmgren) + +## And more... {/*and-more*/} + +We also heard talks on accessibility, error reporting, css, and more: + +- [Demystifying accessibility in React apps](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=20655s) by [Kateryna Porshnieva](https://twitter.com/krambertech) +- [Pigment CSS, CSS in the server component age](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=21696s) by [Olivier Tassinari](https://twitter.com/olivtassinari) +- [Real-time React Server Components](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=24070s) by [Sunil Pai](https://twitter.com/threepointone) +- [Let's break React Rules](https://www.youtube.com/watch?v=T8TZQ6k4SLE&t=25862s) by [Charlotte Isambert](https://twitter.com/c_isambert) +- [Solve 100% of your errors](https://www.youtube.com/watch?v=0ckOUBiuxVY&t=19881s) by [Ryan Albrecht](https://github.com/ryan953) + +## Thank you {/*thank-you*/} + +Thank you to all the staff, speakers, and participants who made React Conf 2024 possible. There are too many to list, but we want to thank a few in particular. + +Thank you to [Barbara Markiewicz](https://twitter.com/barbara_markie), the team at [Callstack](https://www.callstack.com/), and our React Team Developer Advocate [Matt Carroll](https://twitter.com/mattcarrollcode) for helping to plan the entire event; and to [Sunny Leggett](https://zeroslopeevents.com/about) and everyone from [Zero Slope](https://zeroslopeevents.com) for helping to organize the event. + +Thank you [Ashley Narcisse](https://twitter.com/_darkfadr) for being our MC and Chief Vibes Officer; and to [Michael Chan](https://twitter.com/chantastic) and [Jamon Holmgren](https://twitter.com/jamonholmgren) for hosting the Q&A sessions. + +Thank you [Seth Webster](https://twitter.com/sethwebster) and [Eli White](https://x.com/Eli_White) for welcoming us each day and providing direction on structure and content; and to [Tom Occhino](https://twitter.com/tomocchino) for joining us with a special message during the after-party. + +Thank you [Ricky Hanlon](https://www.youtube.com/watch?v=FxTZL2U-uKg&t=1263s) for providing detailed feedback on talks, working on slide designs, and generally filling in the gaps to sweat the details. + +Thank you [Callstack](https://www.callstack.com/) for building the conference website; and to [Kadi Kraman](https://twitter.com/kadikraman) and the [Expo](https://expo.dev/) team for building the conference mobile app. + +Thank you to all the sponsors who made the event possible: [Remix](https://remix.run/), [Amazon](https://developer.amazon.com/apps-and-games?cmp=US_2024_05_3P_React-Conf-2024&ch=prtnr&chlast=prtnr&pub=ref&publast=ref&type=org&typelast=org), [MUI](https://mui.com/), [Sentry](https://sentry.io/for/react/?utm_source=sponsored-conf&utm_medium=sponsored-event&utm_campaign=frontend-fy25q2-evergreen&utm_content=logo-reactconf2024-learnmore), [Abbott](https://www.jobs.abbott/software), [Expo](https://expo.dev/), [RedwoodJS](https://redwoodjs.com/), and [Vercel](https://vercel.com). + +Thank you to the AV Team for the visuals, stage, and sound; and to the Westin Hotel for hosting us. + +Thank you to all the speakers who shared their knowledge and experiences with the community. + +Finally, thank you to everyone who attended in person and online to show what makes React, React. React is more than a library, it is a community, and it was inspiring to see everyone come together to share and learn together. + +See you next time! + diff --git a/src/content/blog/index.md b/src/content/blog/index.md index 409f33701..4a1a165a3 100644 --- a/src/content/blog/index.md +++ b/src/content/blog/index.md @@ -10,6 +10,24 @@ This blog is the official source for the updates from the React team. Anything i <div className="sm:-mx-5 flex flex-col gap-5 mt-12"> +<BlogCard title="React Conf 2024 Recap" date="May 22, 2024" url="/blog/2024/05/22/react-conf-2024-recap"> + +Last week we hosted React Conf 2024, a two-day conference in Henderson, Nevada where 700+ attendees gathered in-person to discuss the latest in UI engineering. This was our first in-person conference since 2019, and we were thrilled to be able to bring the community together again ... + +</BlogCard> + +<BlogCard title="React 19 RC " date="April 25, 2024" url="/blog/2024/04/25/react-19"> + +In the React 19 RC Upgrade Guide, we shared step-by-step instructions for upgrading your app to React 19. In this post, we'll give an overview of the new features in React 19, and how you can adopt them ... + +</BlogCard> + +<BlogCard title="React 19 RC Upgrade Guide" date="April 25, 2024" url="/blog/2024/04/25/react-19-upgrade-guide"> + +The improvements added to React 19 require some breaking changes, but we've worked to make the upgrade as smooth as possible, and we don't expect the changes to impact most apps. In this post, we will guide you through the steps for upgrading libraries to React 19 ... + +</BlogCard> + <BlogCard title="React Labs: What We've Been Working On – February 2024" date="February 15, 2024" url="/blog/2024/02/15/react-labs-what-we-have-been-working-on-february-2024"> In React Labs posts, we write about projects in active research and development. Since our last update, we've made significant progress on React Compiler, new features, and React 19, and we'd like to share what we learned. diff --git a/src/content/community/conferences.md b/src/content/community/conferences.md index b44704147..8b5ad7107 100644 --- a/src/content/community/conferences.md +++ b/src/content/community/conferences.md @@ -10,88 +10,107 @@ 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) +### React Nexus 2024 {/*react-nexus-2024*/} +July 04 & 05, 2024. Bangalore, India (In-person event) -[Website](https://react.paris/) - [Twitter](https://twitter.com/BeJS_) - [LinkedIn](https://www.linkedin.com/events/7150816372074192900/comments/) +[Website](https://reactnexus.com/) - [Twitter](https://twitter.com/ReactNexus) - [Linkedin](https://www.linkedin.com/company/react-nexus) - [YouTube](https://www.youtube.com/reactify_in) -### Epic Web Conf 2024 {/*epic-web-2024*/} -April 10 - 11, 2024. In-person in Park City, UT, USA +### Chain React 2024 {/*chain-react-2024*/} +July 17-19, 2024. In-person in Portland, OR, USA -[Website](https://www.epicweb.dev/conf) - [YouTube](https://www.youtube.com/@EpicWebDev) +[Website](https://chainreactconf.com) - [Twitter](https://twitter.com/ChainReactConf) -### React Miami 2024 {/*react-miami-2024*/} -April 19 - 20, 2024. In-person in Miami, FL, USA +### The Geek Conf 2024 {/*the-geek-conf-2024*/} +July 25, 2024. In-person in Berlin, Germany + remote (hybrid event) -[Website](https://reactmiami.com/) - [Twitter](https://twitter.com/ReactMiamiConf) +[Website](https://thegeekconf.com) - [Twitter](https://twitter.com/thegeekconf) -### React Connection 2024 {/*react-connection-2024*/} -April 22, 2024. In-person in Paris, France +### React Rally 2024 🐙 {/*react-rally-2024*/} +August 12-13, 2024. Park City, UT, USA -[Website](https://reactconnection.io/) - [Twitter](https://twitter.com/ReactConn) +[Website](https://reactrally.com) - [Twitter](https://twitter.com/ReactRally) - [YouTube](https://www.youtube.com/channel/UCXBhQ05nu3L1abBUGeQ0ahw) -### React Native Connection 2024 {/*react-native-connection-2024*/} -April 23, 2024. In-person in Paris, France +### React Universe Conf 2024 {/*react-universe-conf-2024*/} +September 5-6, 2024. Wrocław, Poland. -[Website](https://reactnativeconnection.io/) - [Twitter](https://twitter.com/ReactNativeConn) +[Website](https://www.reactuniverseconf.com/) - [Twitter](https://twitter.com/react_native_eu) - [LinkedIn](https://www.linkedin.com/events/reactuniverseconf7163919537074118657/) -### React Conf 2024 {/*react-conf-2024*/} -May 15 - 16, 2024. In-person in Henderson, NV, USA + remote +### React Alicante 2024 {/*react-alicante-2024*/} +September 19-21, 2024. Alicante, Spain. -[Website](https://conf.react.dev) - [Twitter](https://twitter.com/reactjs) +[Website](https://reactalicante.es/) - [Twitter](https://twitter.com/ReactAlicante) - [YouTube](https://www.youtube.com/channel/UCaSdUaITU1Cz6PvC97A7e0w) -### App.js Conf 2024 {/*appjs-conf-2024*/} -May 22 - 24, 2024. In-person in Kraków, Poland + remote +### RenderCon Kenya 2024 {/*rendercon-kenya-2024*/} +October 04 - 05, 2024. Nairobi, Kenya -[Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) +[Website](https://rendercon.org/) - [Twitter](https://twitter.com/renderconke) - [LinkedIn](https://www.linkedin.com/company/renderconke/) - [YouTube](https://www.youtube.com/channel/UC0bCcG8gHUL4njDOpQGcMIA) + +### React India 2024 {/*react-india-2024*/} +October 17 - 19, 2024. In-person in Goa, India (hybrid event) + Oct 15 2024 - remote day + +[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) - [Youtube](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w) + +### React Brussels 2024 {/*react-brussels-2024*/} +October 18, 2024. In-person in Brussels, Belgium (hybrid event) + +[Website](https://www.react.brussels/) - [Twitter](https://x.com/BrusselsReact) + +### React Africa 2024 {/*react-africa-2024*/} +November 29, 2024. In-person in Casablanca, Morocco (hybrid event) + +[Website](https://react-africa.com/) - [Twitter](https://x.com/BeJS_) + +## Past Conferences {/*past-conferences*/} ### React Summit 2024 {/*react-summit-2024*/} June 14 & 18, 2024. In-person in Amsterdam, Netherlands + remote (hybrid event) [Website](https://reactsummit.com/) - [Twitter](https://twitter.com/reactsummit) - [Videos](https://portal.gitnation.org/) -### Render(ATL) 2024 🍑 {/*renderatl-2024-*/} -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 Norway 2024 {/*react-norway-2024*/} June 14, 2024. In-person at Farris Bad Hotel in Larvik, Norway and online (hybrid event). [Website](https://reactnorway.com/) - [Twitter](https://twitter.com/ReactNorway) -### React Nexus 2024 {/*react-nexus-2024*/} -July 04 & 05, 2024. Bangalore, India (In-person event) +### Render(ATL) 2024 🍑 {/*renderatl-2024-*/} +June 12 - June 14, 2024. Atlanta, GA, USA -[Website](https://reactnexus.com/) - [Twitter](https://twitter.com/ReactNexus) - [Linkedin](https://www.linkedin.com/company/react-nexus) - [YouTube](https://www.youtube.com/reactify_in) +[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#/) -### Chain React 2024 {/*chain-react-2024*/} -July 17-19, 2024. In-person in Portland, OR, USA +### Frontend Nation 2024 {/*frontend-nation-2024*/} +June 4 - 7, 2024. Online -[Website](https://chainreactconf.com) - [Twitter](https://twitter.com/ChainReactConf) +[Website](https://frontendnation.com/) - [Twitter](https://twitter.com/frontendnation) -### The Geek Conf 2024 {/*the-geek-conf-2024*/} -July 25, 2024. In-person in Berlin, Germany + remote (hybrid event) +### App.js Conf 2024 {/*appjs-conf-2024*/} +May 22 - 24, 2024. In-person in Kraków, Poland + remote -[Website](https://thegeekconf.com) - [Twitter](https://twitter.com/thegeekconf) +[Website](https://appjs.co) - [Twitter](https://twitter.com/appjsconf) -### React Universe Conf 2024 {/*react-universe-conf-2024*/} -September 5-6, 2024. Wrocław, Poland. +### React Conf 2024 {/*react-conf-2024*/} +May 15 - 16, 2024. In-person in Henderson, NV, USA + remote -[Website](https://www.reactuniverseconf.com/) - [Twitter](https://twitter.com/react_native_eu) - [LinkedIn](https://www.linkedin.com/events/reactuniverseconf7163919537074118657/) +[Website](https://conf.react.dev) - [Twitter](https://twitter.com/reactjs) -### React Alicante 2024 {/*react-alicante-2024*/} -September 19-21, 2024. Alicante, Spain. +### React Native Connection 2024 {/*react-native-connection-2024*/} +April 23, 2024. In-person in Paris, France -[Website](https://reactalicante.es/) - [Twitter](https://twitter.com/ReactAlicante) - [YouTube](https://www.youtube.com/channel/UCaSdUaITU1Cz6PvC97A7e0w) +[Website](https://reactnativeconnection.io/) - [Twitter](https://twitter.com/ReactNativeConn) +### React Miami 2024 {/*react-miami-2024*/} +April 19 - 20, 2024. In-person in Miami, FL, USA -### React India 2024 {/*react-india-2024*/} -October 17 - 19, 2024. In-person in Goa, India (hybrid event) + Oct 15 2024 - remote day +[Website](https://reactmiami.com/) - [Twitter](https://twitter.com/ReactMiamiConf) -[Website](https://www.reactindia.io) - [Twitter](https://twitter.com/react_india) - [Facebook](https://www.facebook.com/ReactJSIndia) - [Youtube](https://www.youtube.com/channel/UCaFbHCBkPvVv1bWs_jwYt3w) +### Epic Web Conf 2024 {/*epic-web-2024*/} +April 10 - 11, 2024. In-person in Park City, UT, USA -## Past Conferences {/*past-conferences*/} +[Website](https://www.epicweb.dev/conf) - [YouTube](https://www.youtube.com/@EpicWebDev) + +### 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/) - [Videos](https://www.youtube.com/playlist?list=PL53Z0yyYnpWhUzgvr2Nys3kZBBLcY0TA7) ### React Day Berlin 2023 {/*react-day-berlin-2023*/} December 8 & 12, 2023. In-person in Berlin, Germany + remote first interactivity (hybrid event) diff --git a/src/content/community/docs-contributors.md b/src/content/community/docs-contributors.md index cbdbf7d7f..0f9d002d6 100644 --- a/src/content/community/docs-contributors.md +++ b/src/content/community/docs-contributors.md @@ -4,7 +4,7 @@ title: Docs Contributors <Intro> -React documentation is written and maintained by the [React team](/community/team) and [external contributors.](https://github.com/reactjs/reactjs.org/graphs/contributors) On this page, we'd like to thank a few people who've made significant contributions to this site. +React documentation is written and maintained by the [React team](/community/team) and [external contributors.](https://github.com/reactjs/react.dev/graphs/contributors) On this page, we'd like to thank a few people who've made significant contributions to this site. </Intro> diff --git a/src/content/community/meetups.md b/src/content/community/meetups.md index a12a5349c..d8887c3de 100644 --- a/src/content/community/meetups.md +++ b/src/content/community/meetups.md @@ -100,7 +100,7 @@ Do you have a local React.js meetup? Add it here! (Please keep the list alphabet * [Ahmedabad](https://www.meetup.com/react-ahmedabad/) * [Bangalore (React)](https://www.meetup.com/ReactJS-Bangalore/) * [Bangalore (React Native)](https://www.meetup.com/React-Native-Bangalore-Meetup) -* [Chennai](https://www.meetup.com/React-Chennai/) +* [Chennai](https://www.linkedin.com/company/chennaireact) * [Delhi NCR](https://www.meetup.com/React-Delhi-NCR/) * [Mumbai](https://reactmumbai.dev) * [Pune](https://www.meetup.com/ReactJS-and-Friends/) diff --git a/src/content/community/team.md b/src/content/community/team.md index 07eea6ca6..331f4f5f0 100644 --- a/src/content/community/team.md +++ b/src/content/community/team.md @@ -31,7 +31,7 @@ Current members of the React team are listed in alphabetical order below. </TeamMember> <TeamMember name="Jason Bonta" permalink="jason-bonta" photo="/images/team/jasonbonta.jpg" threads="someextent" title="Engineering Manager at Meta"> - Jason likes having large volumes of Amazon packages delivered to the office so that he can build forts. Despite literally walling himself off from his team at times and not understanding how for-of loops work, we appreciate him for the unique qualities he brings to his work. + Jason abandoned embedded C for a career in front-end engineering and never looked back. Armed with esoteric CSS knowledge and a passion for beautiful UI, Jason joined Facebook in 2010, where he now feels privileged to have seen JavaScript development come of age. Though he may not understand how `for...of` loops work, he loves getting to work with brilliant people on projects that enable amazing UX. </TeamMember> <TeamMember name="Joe Savona" permalink="joe-savona" photo="/images/team/joe.jpg" github="josephsavona" twitter="en_JS" threads="joesavona" title="Engineer at Meta"> @@ -43,11 +43,11 @@ Current members of the React team are listed in alphabetical order below. </TeamMember> <TeamMember name="Lauren Tan" permalink="lauren-tan" photo="/images/team/lauren.jpg" github="poteto" twitter="potetotes" threads="potetotes" personal="no.lol" title="Engineer at Meta"> - Lauren’s programming career peaked when she first discovered the `<marquee>` tag. She’s been chasing that high ever since. When she’s not adding bugs into React, she enjoys dropping cheeky memes in chat, and playing all too many video games with her partner, and her dog Zelda. + Lauren's programming career peaked when she first discovered the `<marquee>` tag. She’s been chasing that high ever since. She studied Finance instead of CS in college, so she learned to code using Excel instead of Java. Lauren enjoys dropping cheeky memes in chat, playing video games with her partner, and petting her dog Zelda. </TeamMember> <TeamMember name="Luna Wei" permalink="luna-wei" photo="/images/team/luna-wei.jpg" github="lunaleaps" twitter="lunaleaps" threads="lunaleaps" title="Engineer at Meta"> - Luna first learnt the fundamentals of python at the age of 6 from her father. Since then, she has been unstoppable. Luna aspires to be a gen z, and the road to success is paved with environmental advocacy, urban gardening and lots of quality time with her Voo-Doo’d (as pictured). + Luna first learnt the fundamentals of python at the age of 6 from her father. Since then, she has been unstoppable. Luna aspires to be a gen z, and the road to success is paved with environmental advocacy, urban gardening and lots of quality time with her Voo-Doo’d (as pictured). </TeamMember> <TeamMember name="Matt Carroll" permalink="matt-carroll" photo="/images/team/matt-carroll.png" github="mattcarrollcode" twitter="mattcarrollcode" threads="mattcarrollcode" title="Developer Advocate at Meta"> @@ -66,6 +66,10 @@ Current members of the React team are listed in alphabetical order below. Ricky majored in theoretical math and somehow found himself on the React Native team for a couple years before joining the React team. When he's not programming you can find him snowboarding, biking, climbing, golfing, or closing GitHub issues that do not match the issue template. </TeamMember> +<TeamMember name="Ruslan Lesiutin" permalink="ruslan-lesiutin" photo="/images/team/lesiutin.jpg" github="hoxyq" twitter="ruslanlesiutin" threads="lesiutin" title="Engineer at Meta"> + Ruslan's introduction to UI programming started when he was a kid by manually editing HTML templates for his custom gaming forums. Somehow, he ended up majoring in Computer Science. He enjoys music, games, and memes. Mostly memes. +</TeamMember> + <TeamMember name="Sathya Gunasekaran " permalink="sathya-gunasekaran" photo="/images/team/sathya.jpg" github="gsathya" twitter="_gsathya" threads="gsathya.03" title="Engineer at Meta"> Sathya hated the Dragon Book in school but somehow ended up working on compilers all his career. When he's not compiling React components, he's either drinking coffee or eating yet another Dosa. </TeamMember> diff --git a/src/content/community/translations.md b/src/content/community/translations.md new file mode 100644 index 000000000..4c07e6a1e --- /dev/null +++ b/src/content/community/translations.md @@ -0,0 +1,35 @@ +--- +title: Translations +--- + +<Intro> + +React docs are translated by the global community into many languages all over the world. + +</Intro> + +## Source site {/*main-site*/} + +All translations are provided from the canonical source docs: + +- [English](https://react.dev/) — [Contribute](https://github.com/reactjs/react.dev/) + +## Full translations {/*full-translations*/} + +{/* If you are a language maintainer and want to add your language here, finish the "Core" translations and edit `deployedTranslations` under `src/utils`. */} + +<LanguageList progress="complete" /> + +## In-progress translations {/*in-progress-translations*/} + +For the progress of each translation, see: [Is React Translated Yet?](https://translations.react.dev/) + +<LanguageList progress="in-progress" /> + +## How to contribute {/*how-to-contribute*/} + +You can contribute to the translation efforts! + +The community conducts the translation work for the React docs on each language-specific fork of react.dev. Typical translation work involves directly translating a Markdown file and creating a pull request. Click the "contribute" link above to the GitHub repository for your language, and follow the instructions there to help with the translation effort. + +If you want to start a new translation for your language, visit: [translations.react.dev](https://github.com/reactjs/translations.react.dev) \ No newline at end of file diff --git a/src/content/community/versioning-policy.md b/src/content/community/versioning-policy.md index fad926c57..7aa71efd2 100644 --- a/src/content/community/versioning-policy.md +++ b/src/content/community/versioning-policy.md @@ -8,6 +8,8 @@ All stable builds of React go through a high level of testing and follow semanti </Intro> +For a list of previous releases, see the [Versions](/versions) page. + ## Stable releases {/*stable-releases*/} Stable React releases (also known as "Latest" release channel) follow [semantic versioning (semver)](https://semver.org/) principles. diff --git a/src/content/learn/adding-interactivity.md b/src/content/learn/adding-interactivity.md index 0d4a3b23f..5c87a3e79 100644 --- a/src/content/learn/adding-interactivity.md +++ b/src/content/learn/adding-interactivity.md @@ -265,7 +265,7 @@ setCount(count + 1); // Request a re-render with 1 console.log(count); // Still 0! ``` -This behavior help you avoid subtle bugs. Here is a little chat app. Try to guess what happens if you press "Send" first and *then* change the recipient to Bob. Whose name will appear in the `alert` five seconds later? +This behavior helps you avoid subtle bugs. Here is a little chat app. Try to guess what happens if you press "Send" first and *then* change the recipient to Bob. Whose name will appear in the `alert` five seconds later? <Sandpack> diff --git a/src/content/learn/installation.md b/src/content/learn/installation.md index c5426ea94..7251fc31b 100644 --- a/src/content/learn/installation.md +++ b/src/content/learn/installation.md @@ -37,7 +37,7 @@ export default function App() { You can edit it directly or open it in a new tab by pressing the "Fork" button in the upper right corner. -Most pages in the React documentation contain sandboxes like this. Outside of the React documentation, there are many online sandboxes that support React: for example, [CodeSandbox](https://codesandbox.io/s/new), [StackBlitz](https://stackblitz.com/fork/react), or [CodePen.](https://codepen.io/pen?&editors=0010&layout=left&prefill_data_id=3f4569d1-1b11-4bce-bd46-89090eed5ddb) +Most pages in the React documentation contain sandboxes like this. Outside of the React documentation, there are many online sandboxes that support React: for example, [CodeSandbox](https://codesandbox.io/s/new), [StackBlitz](https://stackblitz.com/fork/react), or [CodePen.](https://codepen.io/pen?template=QWYVwWN) ### Try React locally {/*try-react-locally*/} diff --git a/src/content/learn/manipulating-the-dom-with-refs.md b/src/content/learn/manipulating-the-dom-with-refs.md index bc9a3eac4..2d44d7353 100644 --- a/src/content/learn/manipulating-the-dom-with-refs.md +++ b/src/content/learn/manipulating-the-dom-with-refs.md @@ -218,18 +218,19 @@ This example shows how you can use this approach to scroll to an arbitrary node <Sandpack> ```js -import { useRef } from 'react'; +import { useRef, useState } from "react"; export default function CatFriends() { const itemsRef = useRef(null); + const [catList, setCatList] = useState(setupCatList); - function scrollToId(itemId) { + function scrollToCat(cat) { const map = getMap(); - const node = map.get(itemId); + const node = map.get(cat); node.scrollIntoView({ - behavior: 'smooth', - block: 'nearest', - inline: 'center' + behavior: "smooth", + block: "nearest", + inline: "center", }); } @@ -244,34 +245,25 @@ export default function CatFriends() { return ( <> <nav> - <button onClick={() => scrollToId(0)}> - Tom - </button> - <button onClick={() => scrollToId(5)}> - Maru - </button> - <button onClick={() => scrollToId(9)}> - Jellylorum - </button> + <button onClick={() => scrollToCat(catList[0])}>Tom</button> + <button onClick={() => scrollToCat(catList[5])}>Maru</button> + <button onClick={() => scrollToCat(catList[9])}>Jellylorum</button> </nav> <div> <ul> - {catList.map(cat => ( + {catList.map((cat) => ( <li - key={cat.id} + key={cat} ref={(node) => { const map = getMap(); if (node) { - map.set(cat.id, node); + map.set(cat, node); } else { - map.delete(cat.id); + map.delete(cat); } }} > - <img - src={cat.imageUrl} - alt={'Cat #' + cat.id} - /> + <img src={cat} /> </li> ))} </ul> @@ -280,12 +272,13 @@ export default function CatFriends() { ); } -const catList = []; -for (let i = 0; i < 10; i++) { - catList.push({ - id: i, - imageUrl: 'https://placekitten.com/250/200?image=' + i - }); +function setupCatList() { + const catList = []; + for (let i = 0; i < 10; i++) { + catList.push("https://loremflickr.com/320/240/cat?lock=" + i); + } + + return catList; } ``` @@ -316,6 +309,16 @@ li { } ``` +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0" + } +} +``` + </Sandpack> In this example, `itemsRef` doesn't hold a single DOM node. Instead, it holds a [Map](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Map) from item ID to a DOM node. ([Refs can hold any values!](/learn/referencing-values-with-refs)) The [`ref` callback](/reference/react-dom/components/common#ref-callback) on every list item takes care to update the Map: @@ -327,10 +330,10 @@ In this example, `itemsRef` doesn't hold a single DOM node. Instead, it holds a const map = getMap(); if (node) { // Add to the Map - map.set(cat.id, node); + map.set(cat, node); } else { // Remove from the Map - map.delete(cat.id); + map.delete(cat); } }} > @@ -338,6 +341,28 @@ In this example, `itemsRef` doesn't hold a single DOM node. Instead, it holds a This lets you read individual DOM nodes from the Map later. +<Canary> + +This example shows another approach for managing the Map with a `ref` callback cleanup function. + +```js +<li + key={cat.id} + ref={node => { + const map = getMap(); + // Add to the Map + map.set(cat, node); + + return () => { + // Remove from the Map + map.delete(cat); + }; + }} +> +``` + +</Canary> + </DeepDive> ## Accessing another component's DOM nodes {/*accessing-another-components-dom-nodes*/} diff --git a/src/content/learn/react-compiler.md b/src/content/learn/react-compiler.md new file mode 100644 index 000000000..2920e8643 --- /dev/null +++ b/src/content/learn/react-compiler.md @@ -0,0 +1,410 @@ +--- +title: React Compiler +--- + +<Intro> +This page will give you an introduction to the new experimental React Compiler and how to try it out successfully. +</Intro> + +<Wip> +These docs are still a work in progress. More documentation is available in the [React Compiler Working Group repo](https://github.com/reactwg/react-compiler/discussions), and will be upstreamed into these docs when they are more stable. +</Wip> + +<YouWillLearn> + +* Getting started with the compiler +* Installing the compiler and eslint plugin +* Troubleshooting + +</YouWillLearn> + +<Note> +React Compiler is a new experimental compiler that we've open sourced to get early feedback from the community. It still has rough edges and is not yet fully ready for production. + +React Compiler requires React 19 RC. If you are unable to upgrade to React 19, you may try a userspace implementation of the cache function as described in the [Working Group](https://github.com/reactwg/react-compiler/discussions/6). However, please note that this is not recommended and you should upgrade to React 19 when possible. +</Note> + +React Compiler is a new experimental compiler that we've open sourced to get early feedback from the community. It is a build-time only tool that automatically optimizes your React app. It works with plain JavaScript, and understands the [Rules of React](/reference/rules), so you don't need to rewrite any code to use it. + +The compiler also includes an [eslint plugin](#installing-eslint-plugin-react-compiler) that surfaces the analysis from the compiler right in your editor. The plugin runs independently of the compiler and can be used even if you aren't using the compiler in your app. We recommend all React developers to use this eslint plugin to help improve the quality of your codebase. + +### What does the compiler do? {/*what-does-the-compiler-do*/} + +In order to optimize applications, React Compiler automatically memoizes your code. You may be familiar today with memoization through APIs such as `useMemo`, `useCallback`, and `React.memo`. With these APIs you can tell React that certain parts of your application don't need to recompute if their inputs haven't changed, reducing work on updates. While powerful, it's easy to forget to apply memoization or apply them incorrectly. This can lead to inefficient updates as React has to check parts of your UI that don't have any _meaningful_ changes. + +The compiler uses its knowledge of JavaScript and React's rules to automatically memoize values or groups of values within your components and hooks. If it detects breakages of the rules, it will automatically skip over just those components or hooks, and continue safely compiling other code. + +If your codebase is already very well-memoized, you might not expect to see major performance improvements with the compiler. However, in practice memoizing the correct dependencies that cause performance issues is tricky to get right by hand. + +<DeepDive> +#### What kind of memoization does React Compiler add? {/*what-kind-of-memoization-does-react-compiler-add*/} + +The initial release of React Compiler is primarily focused on **improving update performance** (re-rendering existing components), so it focuses on these two use cases: + +1. **Skipping cascading re-rendering of components** + * Re-rendering `<Parent />` causes many components in its component tree to re-render, even though only `<Parent />` has changed +1. **Skipping expensive calculations from outside of React** + * For example, calling `expensivelyProcessAReallyLargeArrayOfObjects()` inside of your component or hook that needs that data + +#### Optimizing Re-renders {/*optimizing-re-renders*/} + +React lets you express your UI as a function of their current state (more concretely: their props, state, and context). In its current implementation, when a component's state changes, React will re-render that component _and all of its children_ — unless you have applied some form of manual memoization with `useMemo()`, `useCallback()`, or `React.memo()`. For example, in the following example, `<MessageButton>` will re-render whenever `<FriendList>`'s state changes: + +```javascript +function FriendList({ friends }) { + const onlineCount = useFriendOnlineCount(); + if (friends.length === 0) { + return <NoFriends />; + } + return ( + <div> + <span>{onlineCount} online</span> + {friends.map((friend) => ( + <FriendListCard key={friend.id} friend={friend} /> + ))} + <MessageButton /> + </div> + ); +} +``` +[_See this example in the React Compiler Playground_](https://playground.react.dev/#N4Igzg9grgTgxgUxALhAMygOzgFwJYSYAEAYjHgpgCYAyeYOAFMEWuZVWEQL4CURwADrEicQgyKEANnkwIAwtEw4iAXiJQwCMhWoB5TDLmKsTXgG5hRInjRFGbXZwB0UygHMcACzWr1ABn4hEWsYBBxYYgAeADkIHQ4uAHoAPksRbisiMIiYYkYs6yiqPAA3FMLrIiiwAAcAQ0wU4GlZBSUcbklDNqikusaKkKrgR0TnAFt62sYHdmp+VRT7SqrqhOo6Bnl6mCoiAGsEAE9VUfmqZzwqLrHqM7ubolTVol5eTOGigFkEMDB6u4EAAhKA4HCEZ5DNZ9ErlLIWYTcEDcIA) + +React Compiler automatically applies the equivalent of manual memoization, ensuring that only the relevant parts of an app re-render as state changes, which is sometimes referred to as "fine-grained reactivity". In the above example, React Compiler determines that the return value of `<FriendListCard />` can be reused even as `friends` changes, and can avoid recreating this JSX _and_ avoid re-rendering `<MessageButton>` as the count changes. + +#### Expensive calculations also get memoized {/*expensive-calculations-also-get-memoized*/} + +The compiler can also automatically memoize for expensive calculations used during rendering: + +```js +// **Not** memoized by React Compiler, since this is not a component or hook +function expensivelyProcessAReallyLargeArrayOfObjects() { /* ... */ } + +// Memoized by React Compiler since this is a component +function TableContainer({ items }) { + // This function call would be memoized: + const data = expensivelyProcessAReallyLargeArrayOfObjects(items); + // ... +} +``` +[_See this example in the React Compiler Playground_](https://playground.react.dev/#N4Igzg9grgTgxgUxALhAejQAgFTYHIQAuumAtgqRAJYBeCAJpgEYCemASggIZyGYDCEUgAcqAGwQwANJjBUAdokyEAFlTCZ1meUUxdMcIcIjyE8vhBiYVECAGsAOvIBmURYSonMCAB7CzcgBuCGIsAAowEIhgYACCnFxioQAyXDAA5gixMDBcLADyzvlMAFYIvGAAFACUmMCYaNiYAHStOFgAvk5OGJgAshTUdIysHNy8AkbikrIKSqpaWvqGIiZmhE6u7p7ymAAqXEwSguZcCpKV9VSEFBodtcBOmAYmYHz0XIT6ALzefgFUYKhCJRBAxeLcJIsVIZLI5PKFYplCqVa63aoAbm6u0wMAQhFguwAPPRAQA+YAfL4dIloUmBMlODogDpAA) + +However, if `expensivelyProcessAReallyLargeArrayOfObjects` is truly an expensive function, you may want to consider implementing its own memoization outside of React, because: + +- React Compiler only memoizes React components and hooks, not every function +- React Compiler's memoization is not shared across multiple components or hooks + +So if `expensivelyProcessAReallyLargeArrayOfObjects` was used in many different components, even if the same exact items were passed down, that expensive calculation would be run repeatedly. We recommend [profiling](https://react.dev/reference/react/useMemo#how-to-tell-if-a-calculation-is-expensive) first to see if it really is that expensive before making code more complicated. +</DeepDive> + +### What does the compiler assume? {/*what-does-the-compiler-assume*/} + +React Compiler assumes that your code: + +1. Is valid, semantic JavaScript +2. Tests that nullable/optional values and properties are defined before accessing them (for example, by enabling [`strictNullChecks`](https://www.typescriptlang.org/tsconfig/#strictNullChecks) if using TypeScript), i.e., `if (object.nullableProperty) { object.nullableProperty.foo }` or with optional-chaining `object.nullableProperty?.foo` +3. Follows the [Rules of React](https://react.dev/reference/rules) + +React Compiler can verify many of the Rules of React statically, and will safely skip compilation when it detects an error. To see the errors we recommend also installing [eslint-plugin-react-compiler](https://www.npmjs.com/package/eslint-plugin-react-compiler). + +### Should I try out the compiler? {/*should-i-try-out-the-compiler*/} + +Please note that the compiler is still experimental and has many rough edges. While it has been used in production at companies like Meta, rolling out the compiler to production for your app will depend on the health of your codebase and how well you've followed the [Rules of React](/reference/rules). + +**You don't have to rush into using the compiler now. It's okay to wait until it reaches a stable release before adopting it.** However, we do appreciate trying it out in small experiments in your apps so that you can [provide feedback](#reporting-issues) to us to help make the compiler better. + +## Getting Started {/*getting-started*/} + +In addition to these docs, we recommend checking the [React Compiler Working Group](https://github.com/reactwg/react-compiler) for additional information and discussion about the compiler. + +### Checking compatibility {/*checking-compatibility*/} + +Prior to installing the compiler, you can first check to see if your codebase is compatible: + +<TerminalBlock> +npx react-compiler-healthcheck@experimental +</TerminalBlock> + +This script will: + +- Check how many components can be successfully optimized: higher is better +- Check for `<StrictMode>` usage: having this enabled and followed means a higher chance that the [Rules of React](/reference/rules) are followed +- Check for incompatible library usage: known libraries that are incompatible with the compiler + +As an example: + +<TerminalBlock> +Successfully compiled 8 out of 9 components. +StrictMode usage not found. +Found no usage of incompatible libraries. +</TerminalBlock> + +### Installing eslint-plugin-react-compiler {/*installing-eslint-plugin-react-compiler*/} + +React Compiler also powers an eslint plugin. The eslint plugin can be used **independently** of the compiler, meaning you can use the eslint plugin even if you don't use the compiler. + +<TerminalBlock> +npm install eslint-plugin-react-compiler@experimental +</TerminalBlock> + +Then, add it to your eslint config: + +```js +module.exports = { + plugins: [ + 'eslint-plugin-react-compiler', + ], + rules: { + 'react-compiler/react-compiler': "error", + }, +} +``` + +The eslint plugin will display any violations of the rules of React in your editor. When it does this, it means that the compiler has skipped over optimizing that component or hook. This is perfectly okay, and the compiler can recover and continue optimizing other components in your codebase. + +**You don't have to fix all eslint violations straight away.** You can address them at your own pace to increase the amount of components and hooks being optimized, but it is not required to fix everything before you can use the compiler. + +### Rolling out the compiler to your codebase {/*using-the-compiler-effectively*/} + +#### Existing projects {/*existing-projects*/} +The compiler is designed to compile functional components and hooks that follow the [Rules of React](/reference/rules). It can also handle code that breaks those rules by bailing out (skipping over) those components or hooks. However, due to the flexible nature of JavaScript, the compiler cannot catch every possible violation and may compile with false negatives: that is, the compiler may accidentally compile a component/hook that breaks the Rules of React which can lead to undefined behavior. + +For this reason, to adopt the compiler successfully on existing projects, we recommend running it on a small directory in your product code first. You can do this by configuring the compiler to only run on a specific set of directories: + +```js {3} +const ReactCompilerConfig = { + sources: (filename) => { + return filename.indexOf('src/path/to/dir') !== -1; + }, +}; +``` + +In rare cases, you can also configure the compiler to run in "opt-in" mode using the `compilationMode: "annotation"` option. This makes it so the compiler will only compile components and hooks annotated with a `"use memo"` directive. Please note that the `annotation` mode is a temporary one to aid early adopters, and that we don't intend for the `"use memo"` directive to be used for the long term. + +```js {2,7} +const ReactCompilerConfig = { + compilationMode: "annotation", +}; + +// src/app.jsx +export default function App() { + "use memo"; + // ... +} +``` + +When you have more confidence with rolling out the compiler, you can expand coverage to other directories as well and slowly roll it out to your whole app. + +#### New projects {/*new-projects*/} + +If you're starting a new project, you can enable the compiler on your entire codebase, which is the default behavior. + +## Usage {/*installation*/} + +### Babel {/*usage-with-babel*/} + +<TerminalBlock> +npm install babel-plugin-react-compiler@experimental +</TerminalBlock> + +The compiler includes a Babel plugin which you can use in your build pipeline to run the compiler. + +After installing, add it to your Babel config. Please note that it's critical that the compiler run **first** in the pipeline: + +```js {7} +// babel.config.js +const ReactCompilerConfig = { /* ... */ }; + +module.exports = function () { + return { + plugins: [ + ['babel-plugin-react-compiler', ReactCompilerConfig], // must run first! + // ... + ], + }; +}; +``` + +`babel-plugin-react-compiler` should run first before other Babel plugins as the compiler requires the input source information for sound analysis. + +### Vite {/*usage-with-vite*/} + +If you use Vite, you can add the plugin to vite-plugin-react: + +```js {10} +// vite.config.js +const ReactCompilerConfig = { /* ... */ }; + +export default defineConfig(() => { + return { + plugins: [ + react({ + babel: { + plugins: [ + ["babel-plugin-react-compiler", ReactCompilerConfig], + ], + }, + }), + ], + // ... + }; +}); +``` + +### Next.js {/*usage-with-nextjs*/} + +Next.js has an experimental configuration to enable the React Compiler. It automatically ensures Babel is set up with `babel-plugin-react-compiler`. + +- Install Next.js canary, which uses React 19 Release Candidate +- Install `babel-plugin-react-compiler` + +<TerminalBlock> +npm install next@canary babel-plugin-react-compiler@experimental +</TerminalBlock> + +Then configure the experimental option in `next.config.js`: + +```js {4,5,6} +// next.config.js +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + reactCompiler: true, + }, +}; + +module.exports = nextConfig; +``` + +Using the experimental option ensures support for the React Compiler in: + +- App Router +- Pages Router +- Webpack (default) +- Turbopack (opt-in through `--turbo`) + + +### Remix {/*usage-with-remix*/} +Install `vite-plugin-babel`, and add the compiler's Babel plugin to it: + +<TerminalBlock> +npm install vite-plugin-babel +</TerminalBlock> + +```js {2,14} +// vite.config.js +import babel from "vite-plugin-babel"; + +const ReactCompilerConfig = { /* ... */ }; + +export default defineConfig({ + plugins: [ + remix({ /* ... */}), + babel({ + filter: /\.[jt]sx?$/, + babelConfig: { + presets: ["@babel/preset-typescript"], // if you use TypeScript + plugins: [ + ["babel-plugin-react-compiler", ReactCompilerConfig], + ], + }, + }), + ], +}); +``` + +### Webpack {/*usage-with-webpack*/} + +You can create your own loader for React Compiler, like so: + +```js +const ReactCompilerConfig = { /* ... */ }; +const BabelPluginReactCompiler = require('babel-plugin-react-compiler'); + +function reactCompilerLoader(sourceCode, sourceMap) { + // ... + const result = transformSync(sourceCode, { + // ... + plugins: [ + [BabelPluginReactCompiler, ReactCompilerConfig], + ], + // ... + }); + + if (result === null) { + this.callback( + Error( + `Failed to transform "${options.filename}"` + ) + ); + return; + } + + this.callback( + null, + result.code, + result.map === null ? undefined : result.map + ); +} + +module.exports = reactCompilerLoader; +``` + +### Expo {/*usage-with-expo*/} + +Please refer to [Expo's docs](https://docs.expo.dev/preview/react-compiler/) to enable and use the React Compiler in Expo apps. + +### Metro (React Native) {/*usage-with-react-native-metro*/} + +React Native uses Babel via Metro, so refer to the [Usage with Babel](#usage-with-babel) section for installation instructions. + +### Rspack {/*usage-with-rspack*/} + +Please refer to [Rspack's docs](https://rspack.dev/guide/tech/react#react-compiler) to enable and use the React Compiler in Rspack apps. + +### Rsbuild {/*usage-with-rsbuild*/} + +Please refer to [Rsbuild's docs](https://rsbuild.dev/guide/framework/react#react-compiler) to enable and use the React Compiler in Rsbuild apps. + +## Troubleshooting {/*troubleshooting*/} + +To report issues, please first create a minimal repro on the [React Compiler Playground](https://playground.react.dev/) and include it in your bug report. You can open issues in the [facebook/react](https://github.com/facebook/react/issues) repo. + +You can also provide feedback in the React Compiler Working Group by applying to be a member. Please see [the README for more details on joining](https://github.com/reactwg/react-compiler). + +### `(0 , _c) is not a function` error {/*0--_c-is-not-a-function-error*/} + +This occurs if you are not using React 19 RC and up. To fix this, [upgrade your app to React 19 RC](https://react.dev/blog/2024/04/25/react-19-upgrade-guide) first. + +If you are unable to upgrade to React 19, you may try a userspace implementation of the cache function as described in the [Working Group](https://github.com/reactwg/react-compiler/discussions/6). However, please note that this is not recommended and you should upgrade to React 19 when possible. + +### How do I know my components have been optimized? {/*how-do-i-know-my-components-have-been-optimized*/} + +[React Devtools](/learn/react-developer-tools) (v5.0+) has built-in support for React Compiler and will display a "Memo ✨" badge next to components that have been optimized by the compiler. + +### Something is not working after compilation {/*something-is-not-working-after-compilation*/} +If you have eslint-plugin-react-compiler installed, the compiler will display any violations of the rules of React in your editor. When it does this, it means that the compiler has skipped over optimizing that component or hook. This is perfectly okay, and the compiler can recover and continue optimizing other components in your codebase. **You don't have to fix all eslint violations straight away.** You can address them at your own pace to increase the amount of components and hooks being optimized. + +Due to the flexible and dynamic nature of JavaScript however, it's not possible to comprehensively detect all cases. Bugs and undefined behavior such as infinite loops may occur in those cases. + +If your app doesn't work properly after compilation and you aren't seeing any eslint errors, the compiler may be incorrectly compiling your code. To confirm this, try to make the issue go away by aggressively opting out any component or hook you think might be related via the [`"use no memo"` directive](#opt-out-of-the-compiler-for-a-component). + +```js {2} +function SuspiciousComponent() { + "use no memo"; // opts out this component from being compiled by React Compiler + // ... +} +``` + +<Note> +#### `"use no memo"` {/*use-no-memo*/} + +`"use no memo"` is a _temporary_ escape hatch that lets you opt-out components and hooks from being compiled by the React Compiler. This directive is not meant to be long lived the same way as eg [`"use client"`](/reference/rsc/use-client) is. + +It is not recommended to reach for this directive unless it's strictly necessary. Once you opt-out a component or hook, it is opted-out forever until the directive is removed. This means that even if you fix the code, the compiler will still skip over compiling it unless you remove the directive. +</Note> + +When you make the error go away, confirm that removing the opt out directive makes the issue come back. Then share a bug report with us (you can try to reduce it to a small repro, or if it's open source code you can also just paste the entire source) using the [React Compiler Playground](https://playground.react.dev) so we can identify and help fix the issue. + +### Other issues {/*other-issues*/} + +Please see https://github.com/reactwg/react-compiler/discussions/7. diff --git a/src/content/learn/reusing-logic-with-custom-hooks.md b/src/content/learn/reusing-logic-with-custom-hooks.md index 13a556c7b..67de5e97f 100644 --- a/src/content/learn/reusing-logic-with-custom-hooks.md +++ b/src/content/learn/reusing-logic-with-custom-hooks.md @@ -1899,7 +1899,7 @@ export default function Counter() { } ``` -You'll need to write your custom Hook in `useCounter.js` and import it into the `Counter.js` file. +You'll need to write your custom Hook in `useCounter.js` and import it into the `App.js` file. <Sandpack> diff --git a/src/content/learn/separating-events-from-effects.md b/src/content/learn/separating-events-from-effects.md index ac65d2b60..21276c287 100644 --- a/src/content/learn/separating-events-from-effects.md +++ b/src/content/learn/separating-events-from-effects.md @@ -44,7 +44,7 @@ function ChatRoom({ roomId }) { return ( <> <input value={message} onChange={e => setMessage(e.target.value)} /> - <button onClick={handleSendClick}>Send</button>; + <button onClick={handleSendClick}>Send</button> </> ); } diff --git a/src/content/learn/synchronizing-with-effects.md b/src/content/learn/synchronizing-with-effects.md index f1aa98438..48e99cc27 100644 --- a/src/content/learn/synchronizing-with-effects.md +++ b/src/content/learn/synchronizing-with-effects.md @@ -45,7 +45,7 @@ Here and later in this text, capitalized "Effect" refers to the React-specific d To write an Effect, follow these three steps: -1. **Declare an Effect.** By default, your Effect will run after every render. +1. **Declare an Effect.** By default, your Effect will run after every [commit](/learn/render-and-commit). 2. **Specify the Effect dependencies.** Most Effects should only re-run *when needed* rather than after every render. For example, a fade-in animation should only trigger when a component appears. Connecting and disconnecting to a chat room should only happen when the component appears and disappears, or when the chat room changes. You will learn how to control this by specifying *dependencies.* 3. **Add cleanup if needed.** Some Effects need to specify how to stop, undo, or clean up whatever they were doing. For example, "connect" needs "disconnect", "subscribe" needs "unsubscribe", and "fetch" needs either "cancel" or "ignore". You will learn how to do this by returning a *cleanup function*. @@ -598,6 +598,33 @@ Usually, the answer is to implement the cleanup function. The cleanup function Most of the Effects you'll write will fit into one of the common patterns below. +<Pitfall> + +#### Don't use refs to prevent Effects from firing {/*dont-use-refs-to-prevent-effects-from-firing*/} + +A common pitfall for preventing Effects firing twice in development is to use a `ref` to prevent the Effect from running more than once. For example, you could "fix" the above bug with a `useRef`: + +```js {1,3-4} + const connectionRef = useRef(null); + useEffect(() => { + // 🚩 This wont fix the bug!!! + if (!connectionRef.current) { + connectionRef.current = createConnection(); + connectionRef.current.connect(); + } + }, []); +``` + +This makes it so you only see `"✅ Connecting..."` once in development, but it doesn't fix the bug. + +When the user navigates away, the connection still isn't closed and when they navigate back, a new connection is created. As the user navigates across the app, the connections would keep piling up, the same as it would before the "fix". + +To fix the bug, it is not enough to just make the Effect run once. The effect needs to work after re-mounting, which means the connection needs to be cleaned up like in the solution above. + +See the examples below for how to handle common patterns. + +</Pitfall> + ### Controlling non-React widgets {/*controlling-non-react-widgets*/} Sometimes you need to add UI widgets that aren't written to React. For example, let's say you're adding a map component to your page. It has a `setZoomLevel()` method, and you'd like to keep the zoom level in sync with a `zoomLevel` state variable in your React code. Your Effect would look similar to this: @@ -1573,7 +1600,7 @@ Each render's Effect has its own `ignore` variable. Initially, the `ignore` vari - Fetching `'Bob'` completes - The Effect from the `'Bob'` render **does not do anything because its `ignore` flag was set to `true`** -In addition to ignoring the result of an outdated API call, you can also use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel the requests that are no longer needed. However, by itself this is not enough to protect against race conditions. More asynchronous steps could be chained after the fetch, so using an explicit flag like `ignore` is the most reliable way to fix this type of problems. +In addition to ignoring the result of an outdated API call, you can also use [`AbortController`](https://developer.mozilla.org/en-US/docs/Web/API/AbortController) to cancel the requests that are no longer needed. However, by itself this is not enough to protect against race conditions. More asynchronous steps could be chained after the fetch, so using an explicit flag like `ignore` is the most reliable way to fix this type of problem. </Solution> diff --git a/src/content/learn/tutorial-tic-tac-toe.md b/src/content/learn/tutorial-tic-tac-toe.md index d37791456..f18ec4939 100644 --- a/src/content/learn/tutorial-tic-tac-toe.md +++ b/src/content/learn/tutorial-tic-tac-toe.md @@ -2915,4 +2915,4 @@ If you have extra time or want to practice your new React skills, here are some 1. When someone wins, highlight the three squares that caused the win (and when no one wins, display a message about the result being a draw). 1. Display the location for each move in the format (row, col) in the move history list. -Throughout this tutorial, you've touched on React concepts including elements, components, props, and state. Now that you've seen how these concepts work when building a game, check out [Thinking in React](/learn/thinking-in-react) to see how the same React concepts work when build an app's UI. +Throughout this tutorial, you've touched on React concepts including elements, components, props, and state. Now that you've seen how these concepts work when building a game, check out [Thinking in React](/learn/thinking-in-react) to see how the same React concepts work when building an app's UI. diff --git a/src/content/learn/typescript.md b/src/content/learn/typescript.md index 6f49c7656..7edf8eb6e 100644 --- a/src/content/learn/typescript.md +++ b/src/content/learn/typescript.md @@ -137,7 +137,7 @@ The [`useState` Hook](/reference/react/useState) will re-use the value passed in const [enabled, setEnabled] = useState(false); ``` -Will assign the type of `boolean` to `enabled`, and `setEnabled` will be a function accepting either a `boolean` argument, or a function that returns a `boolean`. If you want to explicitly provide a type for the state, you can do so by providing a type argument to the `useState` call: +This will assign the type of `boolean` to `enabled`, and `setEnabled` will be a function accepting either a `boolean` argument, or a function that returns a `boolean`. If you want to explicitly provide a type for the state, you can do so by providing a type argument to the `useState` call: ```ts // Explicitly set the type to "boolean" diff --git a/src/content/reference/react-dom/client/createRoot.md b/src/content/reference/react-dom/client/createRoot.md index afddb4177..b336b6e5e 100644 --- a/src/content/reference/react-dom/client/createRoot.md +++ b/src/content/reference/react-dom/client/createRoot.md @@ -45,7 +45,9 @@ An app fully built with React will usually only have one `createRoot` call for i * **optional** `options`: An object with options for this React root. - * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. + * <CanaryBadge title="This feature is only available in the Canary channel" /> **optional** `onCaughtError`: Callback called when React catches an error in an Error Boundary. Called with the `error` caught by the Error Boundary, and an `errorInfo` object containing the `componentStack`. + * <CanaryBadge title="This feature is only available in the Canary channel" /> **optional** `onUncaughtError`: Callback called when an error is thrown and not caught by an Error Boundary. Called with the `error` that was thrown, and an `errorInfo` object containing the `componentStack`. + * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. Called with an `error` React throws, and an `errorInfo` object containing the `componentStack`. Some recoverable errors may include the original error cause as `error.cause`. * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. #### Returns {/*returns*/} @@ -342,6 +344,797 @@ export default function App({counter}) { It is uncommon to call `render` multiple times. Usually, your components will [update state](/reference/react/useState) instead. +### Show a dialog for uncaught errors {/*show-a-dialog-for-uncaught-errors*/} + +<Canary> + +`onUncaughtError` is only available in the latest React Canary release. + +</Canary> + +By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option: + +```js [[1, 6, "onUncaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack"]] +import { createRoot } from 'react-dom/client'; + +const root = createRoot( + document.getElementById('root'), + { + onUncaughtError: (error, errorInfo) => { + console.error( + 'Uncaught error', + error, + errorInfo.componentStack + ); + } + } +); +root.render(<App />); +``` + +The <CodeStep step={1}>onUncaughtError</CodeStep> option is a function called with two arguments: + +1. The <CodeStep step={2}>error</CodeStep> that was thrown. +2. An <CodeStep step={3}>errorInfo</CodeStep> object that contains the <CodeStep step={4}>componentStack</CodeStep> of the error. + +You can use the `onUncaughtError` root option to display error dialogs: + +<Sandpack> + +```html index.html hidden +<!DOCTYPE html> +<html> +<head> + <title>My app</title> +</head> +<body> +<!-- + Error dialog in raw HTML + since an error in the React app may crash. +--> +<div id="error-dialog" class="hidden"> + <h1 id="error-title" class="text-red"></h1> + <h3> + <pre id="error-message"></pre> + </h3> + <p> + <pre id="error-body"></pre> + </p> + <h4 class="-mb-20">This error occurred at:</h4> + <pre id="error-component-stack" class="nowrap"></pre> + <h4 class="mb-0">Call stack:</h4> + <pre id="error-stack" class="nowrap"></pre> + <div id="error-cause"> + <h4 class="mb-0">Caused by:</h4> + <pre id="error-cause-message"></pre> + <pre id="error-cause-stack" class="nowrap"></pre> + </div> + <button + id="error-close" + class="mb-10" + onclick="document.getElementById('error-dialog').classList.add('hidden')" + > + Close + </button> + <h3 id="error-not-dismissible">This error is not dismissible.</h3> +</div> +<!-- This is the DOM node --> +<div id="root"></div> +</body> +</html> +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { createRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportUncaughtError} from "./reportError"; +import "./styles.css"; + +const container = document.getElementById("root"); +const root = createRoot(container, { + onUncaughtError: (error, errorInfo) => { + if (error.message !== 'Known error') { + reportUncaughtError({ + error, + componentStack: errorInfo.componentStack + }); + } + } +}); +root.render(<App />); +``` + +```js src/App.js +import { useState } from 'react'; + +export default function App() { + const [throwError, setThrowError] = useState(false); + + if (throwError) { + foo.bar = 'baz'; + } + + return ( + <div> + <span>This error shows the error dialog:</span> + <button onClick={() => setThrowError(true)}> + Throw error + </button> + </div> + ); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0" + }, + "main": "/index.js" +} +``` + +</Sandpack> + + +### Displaying Error Boundary errors {/*displaying-error-boundary-errors*/} + +<Canary> + +`onCaughtError` is only available in the latest React Canary release. + +</Canary> + +By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option to handle errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary): + +```js [[1, 6, "onCaughtError"], [2, 6, "error", 1], [3, 6, "errorInfo"], [4, 10, "componentStack"]] +import { createRoot } from 'react-dom/client'; + +const root = createRoot( + document.getElementById('root'), + { + onCaughtError: (error, errorInfo) => { + console.error( + 'Caught error', + error, + errorInfo.componentStack + ); + } + } +); +root.render(<App />); +``` + +The <CodeStep step={1}>onCaughtError</CodeStep> option is a function called with two arguments: + +1. The <CodeStep step={2}>error</CodeStep> that was caught by the boundary. +2. An <CodeStep step={3}>errorInfo</CodeStep> object that contains the <CodeStep step={4}>componentStack</CodeStep> of the error. + +You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging: + +<Sandpack> + +```html index.html hidden +<!DOCTYPE html> +<html> +<head> + <title>My app</title> +</head> +<body> +<!-- + Error dialog in raw HTML + since an error in the React app may crash. +--> +<div id="error-dialog" class="hidden"> + <h1 id="error-title" class="text-red"></h1> + <h3> + <pre id="error-message"></pre> + </h3> + <p> + <pre id="error-body"></pre> + </p> + <h4 class="-mb-20">This error occurred at:</h4> + <pre id="error-component-stack" class="nowrap"></pre> + <h4 class="mb-0">Call stack:</h4> + <pre id="error-stack" class="nowrap"></pre> + <div id="error-cause"> + <h4 class="mb-0">Caused by:</h4> + <pre id="error-cause-message"></pre> + <pre id="error-cause-stack" class="nowrap"></pre> + </div> + <button + id="error-close" + class="mb-10" + onclick="document.getElementById('error-dialog').classList.add('hidden')" + > + Close + </button> + <h3 id="error-not-dismissible">This error is not dismissible.</h3> +</div> +<!-- This is the DOM node --> +<div id="root"></div> +</body> +</html> +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { createRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportCaughtError} from "./reportError"; +import "./styles.css"; + +const container = document.getElementById("root"); +const root = createRoot(container, { + onCaughtError: (error, errorInfo) => { + if (error.message !== 'Known error') { + reportCaughtError({ + error, + componentStack: errorInfo.componentStack, + }); + } + } +}); +root.render(<App />); +``` + +```js src/App.js +import { useState } from 'react'; +import { ErrorBoundary } from "react-error-boundary"; + +export default function App() { + const [error, setError] = useState(null); + + function handleUnknown() { + setError("unknown"); + } + + function handleKnown() { + setError("known"); + } + + return ( + <> + <ErrorBoundary + fallbackRender={fallbackRender} + onReset={(details) => { + setError(null); + }} + > + {error != null && <Throw error={error} />} + <span>This error will not show the error dialog:</span> + <button onClick={handleKnown}> + Throw known error + </button> + <span>This error will show the error dialog:</span> + <button onClick={handleUnknown}> + Throw unknown error + </button> + </ErrorBoundary> + + </> + ); +} + +function fallbackRender({ resetErrorBoundary }) { + return ( + <div role="alert"> + <h3>Error Boundary</h3> + <p>Something went wrong.</p> + <button onClick={resetErrorBoundary}>Reset</button> + </div> + ); +} + +function Throw({error}) { + if (error === "known") { + throw new Error('Known error') + } else { + foo.bar = 'baz'; + } +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js" +} +``` + +</Sandpack> + +### Displaying a dialog for recoverable errors {/*displaying-a-dialog-for-recoverable-errors*/} + +React may automatically render a component a second time to attempt to recover from an error thrown in render. If successful, React will log a recoverable error to the console to notify the developer. To override this behavior, you can provide the optional `onRecoverableError` root option: + +```js [[1, 6, "onRecoverableError"], [2, 6, "error", 1], [3, 10, "error.cause"], [4, 6, "errorInfo"], [5, 11, "componentStack"]] +import { createRoot } from 'react-dom/client'; + +const root = createRoot( + document.getElementById('root'), + { + onRecoverableError: (error, errorInfo) => { + console.error( + 'Recoverable error', + error, + error.cause, + errorInfo.componentStack, + ); + } + } +); +root.render(<App />); +``` + +The <CodeStep step={1}>onRecoverableError</CodeStep> option is a function called with two arguments: + +1. The <CodeStep step={2}>error</CodeStep> that React throws. Some errors may include the original cause as <CodeStep step={3}>error.cause</CodeStep>. +2. An <CodeStep step={4}>errorInfo</CodeStep> object that contains the <CodeStep step={5}>componentStack</CodeStep> of the error. + +You can use the `onRecoverableError` root option to display error dialogs: + +<Sandpack> + +```html index.html hidden +<!DOCTYPE html> +<html> +<head> + <title>My app</title> +</head> +<body> +<!-- + Error dialog in raw HTML + since an error in the React app may crash. +--> +<div id="error-dialog" class="hidden"> + <h1 id="error-title" class="text-red"></h1> + <h3> + <pre id="error-message"></pre> + </h3> + <p> + <pre id="error-body"></pre> + </p> + <h4 class="-mb-20">This error occurred at:</h4> + <pre id="error-component-stack" class="nowrap"></pre> + <h4 class="mb-0">Call stack:</h4> + <pre id="error-stack" class="nowrap"></pre> + <div id="error-cause"> + <h4 class="mb-0">Caused by:</h4> + <pre id="error-cause-message"></pre> + <pre id="error-cause-stack" class="nowrap"></pre> + </div> + <button + id="error-close" + class="mb-10" + onclick="document.getElementById('error-dialog').classList.add('hidden')" + > + Close + </button> + <h3 id="error-not-dismissible">This error is not dismissible.</h3> +</div> +<!-- This is the DOM node --> +<div id="root"></div> +</body> +</html> +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { createRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportRecoverableError} from "./reportError"; +import "./styles.css"; + +const container = document.getElementById("root"); +const root = createRoot(container, { + onRecoverableError: (error, errorInfo) => { + reportRecoverableError({ + error, + cause: error.cause, + componentStack: errorInfo.componentStack, + }); + } +}); +root.render(<App />); +``` + +```js src/App.js +import { useState } from 'react'; +import { ErrorBoundary } from "react-error-boundary"; + +// 🚩 Bug: Never do this. This will force an error. +let errorThrown = false; +export default function App() { + return ( + <> + <ErrorBoundary + fallbackRender={fallbackRender} + > + {!errorThrown && <Throw />} + <p>This component threw an error, but recovered during a second render.</p> + <p>Since it recovered, no Error Boundary was shown, but <code>onRecoverableError</code> was used to show an error dialog.</p> + </ErrorBoundary> + + </> + ); +} + +function fallbackRender() { + return ( + <div role="alert"> + <h3>Error Boundary</h3> + <p>Something went wrong.</p> + </div> + ); +} + +function Throw({error}) { + // Simulate an external value changing during concurrent render. + errorThrown = true; + foo.bar = 'baz'; +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js" +} +``` + +</Sandpack> + + --- ## Troubleshooting {/*troubleshooting*/} @@ -361,6 +1154,28 @@ Until you do that, nothing is displayed. --- +### I'm getting an error: "You passed a second argument to root.render" {/*im-getting-an-error-you-passed-a-second-argument-to-root-render*/} + +A common mistake is to pass the options for `createRoot` to `root.render(...)`: + +<ConsoleBlock level="error"> + +Warning: You passed a second argument to root.render(...) but it only accepts one argument. + +</ConsoleBlock> + +To fix, pass the root options to `createRoot(...)`, not `root.render(...)`: +```js {2,5} +// 🚩 Wrong: root.render only takes one argument. +root.render(App, {onUncaughtError}); + +// ✅ Correct: pass options to createRoot. +const root = createRoot(container, {onUncaughtError}); +root.render(<App />); +``` + +--- + ### I'm getting an error: "Target container is not a DOM element" {/*im-getting-an-error-target-container-is-not-a-dom-element*/} This error means that whatever you're passing to `createRoot` is not a DOM node. diff --git a/src/content/reference/react-dom/client/hydrateRoot.md b/src/content/reference/react-dom/client/hydrateRoot.md index c137cdda9..cc30ce22c 100644 --- a/src/content/reference/react-dom/client/hydrateRoot.md +++ b/src/content/reference/react-dom/client/hydrateRoot.md @@ -41,7 +41,9 @@ React will attach to the HTML that exists inside the `domNode`, and take over ma * **optional** `options`: An object with options for this React root. - * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. + * <CanaryBadge title="This feature is only available in the Canary channel" /> **optional** `onCaughtError`: Callback called when React catches an error in an Error Boundary. Called with the `error` caught by the Error Boundary, and an `errorInfo` object containing the `componentStack`. + * <CanaryBadge title="This feature is only available in the Canary channel" /> **optional** `onUncaughtError`: Callback called when an error is thrown and not caught by an Error Boundary. Called with the `error` that was thrown and an `errorInfo` object containing the `componentStack`. + * **optional** `onRecoverableError`: Callback called when React automatically recovers from errors. Called with the `error` React throws, and an `errorInfo` object containing the `componentStack`. Some recoverable errors may include the original error cause as `error.cause`. * **optional** `identifierPrefix`: A string prefix React uses for IDs generated by [`useId`.](/reference/react/useId) Useful to avoid conflicts when using multiple roots on the same page. Must be the same prefix as used on the server. @@ -371,3 +373,826 @@ export default function App({counter}) { </Sandpack> It is uncommon to call [`root.render`](#root-render) on a hydrated root. Usually, you'll [update state](/reference/react/useState) inside one of the components instead. + +### Show a dialog for uncaught errors {/*show-a-dialog-for-uncaught-errors*/} + +<Canary> + +`onUncaughtError` is only available in the latest React Canary release. + +</Canary> + +By default, React will log all uncaught errors to the console. To implement your own error reporting, you can provide the optional `onUncaughtError` root option: + +```js [[1, 7, "onUncaughtError"], [2, 7, "error", 1], [3, 7, "errorInfo"], [4, 11, "componentStack"]] +import { hydrateRoot } from 'react-dom/client'; + +const root = hydrateRoot( + document.getElementById('root'), + <App />, + { + onUncaughtError: (error, errorInfo) => { + console.error( + 'Uncaught error', + error, + errorInfo.componentStack + ); + } + } +); +root.render(<App />); +``` + +The <CodeStep step={1}>onUncaughtError</CodeStep> option is a function called with two arguments: + +1. The <CodeStep step={2}>error</CodeStep> that was thrown. +2. An <CodeStep step={3}>errorInfo</CodeStep> object that contains the <CodeStep step={4}>componentStack</CodeStep> of the error. + +You can use the `onUncaughtError` root option to display error dialogs: + +<Sandpack> + +```html index.html hidden +<!DOCTYPE html> +<html> +<head> + <title>My app</title> +</head> +<body> +<!-- + Error dialog in raw HTML + since an error in the React app may crash. +--> +<div id="error-dialog" class="hidden"> + <h1 id="error-title" class="text-red"></h1> + <h3> + <pre id="error-message"></pre> + </h3> + <p> + <pre id="error-body"></pre> + </p> + <h4 class="-mb-20">This error occurred at:</h4> + <pre id="error-component-stack" class="nowrap"></pre> + <h4 class="mb-0">Call stack:</h4> + <pre id="error-stack" class="nowrap"></pre> + <div id="error-cause"> + <h4 class="mb-0">Caused by:</h4> + <pre id="error-cause-message"></pre> + <pre id="error-cause-stack" class="nowrap"></pre> + </div> + <button + id="error-close" + class="mb-10" + onclick="document.getElementById('error-dialog').classList.add('hidden')" + > + Close + </button> + <h3 id="error-not-dismissible">This error is not dismissible.</h3> +</div> +<!-- + HTML content inside <div id="root">...</div> + was generated from App by react-dom/server. +--> +<div id="root"><div><span>This error shows the error dialog:</span><button>Throw error</button></div></div> +</body> +</html> +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { hydrateRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportUncaughtError} from "./reportError"; +import "./styles.css"; +import {renderToString} from 'react-dom/server'; + +const container = document.getElementById("root"); +const root = hydrateRoot(container, <App />, { + onUncaughtError: (error, errorInfo) => { + if (error.message !== 'Known error') { + reportUncaughtError({ + error, + componentStack: errorInfo.componentStack + }); + } + } +}); +``` + +```js src/App.js +import { useState } from 'react'; + +export default function App() { + const [throwError, setThrowError] = useState(false); + + if (throwError) { + foo.bar = 'baz'; + } + + return ( + <div> + <span>This error shows the error dialog:</span> + <button onClick={() => setThrowError(true)}> + Throw error + </button> + </div> + ); +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0" + }, + "main": "/index.js" +} +``` + +</Sandpack> + + +### Displaying Error Boundary errors {/*displaying-error-boundary-errors*/} + +<Canary> + +`onCaughtError` is only available in the latest React Canary release. + +</Canary> + +By default, React will log all errors caught by an Error Boundary to `console.error`. To override this behavior, you can provide the optional `onCaughtError` root option for errors caught by an [Error Boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary): + +```js [[1, 7, "onCaughtError"], [2, 7, "error", 1], [3, 7, "errorInfo"], [4, 11, "componentStack"]] +import { hydrateRoot } from 'react-dom/client'; + +const root = hydrateRoot( + document.getElementById('root'), + <App />, + { + onCaughtError: (error, errorInfo) => { + console.error( + 'Caught error', + error, + errorInfo.componentStack + ); + } + } +); +root.render(<App />); +``` + +The <CodeStep step={1}>onCaughtError</CodeStep> option is a function called with two arguments: + +1. The <CodeStep step={2}>error</CodeStep> that was caught by the boundary. +2. An <CodeStep step={3}>errorInfo</CodeStep> object that contains the <CodeStep step={4}>componentStack</CodeStep> of the error. + +You can use the `onCaughtError` root option to display error dialogs or filter known errors from logging: + +<Sandpack> + +```html index.html hidden +<!DOCTYPE html> +<html> +<head> + <title>My app</title> +</head> +<body> +<!-- + Error dialog in raw HTML + since an error in the React app may crash. +--> +<div id="error-dialog" class="hidden"> + <h1 id="error-title" class="text-red"></h1> + <h3> + <pre id="error-message"></pre> + </h3> + <p> + <pre id="error-body"></pre> + </p> + <h4 class="-mb-20">This error occurred at:</h4> + <pre id="error-component-stack" class="nowrap"></pre> + <h4 class="mb-0">Call stack:</h4> + <pre id="error-stack" class="nowrap"></pre> + <div id="error-cause"> + <h4 class="mb-0">Caused by:</h4> + <pre id="error-cause-message"></pre> + <pre id="error-cause-stack" class="nowrap"></pre> + </div> + <button + id="error-close" + class="mb-10" + onclick="document.getElementById('error-dialog').classList.add('hidden')" + > + Close + </button> + <h3 id="error-not-dismissible">This error is not dismissible.</h3> +</div> +<!-- + HTML content inside <div id="root">...</div> + was generated from App by react-dom/server. +--> +<div id="root"><span>This error will not show the error dialog:</span><button>Throw known error</button><span>This error will show the error dialog:</span><button>Throw unknown error</button></div> +</body> +</html> +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { hydrateRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportCaughtError} from "./reportError"; +import "./styles.css"; + +const container = document.getElementById("root"); +const root = hydrateRoot(container, <App />, { + onCaughtError: (error, errorInfo) => { + if (error.message !== 'Known error') { + reportCaughtError({ + error, + componentStack: errorInfo.componentStack + }); + } + } +}); +``` + +```js src/App.js +import { useState } from 'react'; +import { ErrorBoundary } from "react-error-boundary"; + +export default function App() { + const [error, setError] = useState(null); + + function handleUnknown() { + setError("unknown"); + } + + function handleKnown() { + setError("known"); + } + + return ( + <> + <ErrorBoundary + fallbackRender={fallbackRender} + onReset={(details) => { + setError(null); + }} + > + {error != null && <Throw error={error} />} + <span>This error will not show the error dialog:</span> + <button onClick={handleKnown}> + Throw known error + </button> + <span>This error will show the error dialog:</span> + <button onClick={handleUnknown}> + Throw unknown error + </button> + </ErrorBoundary> + + </> + ); +} + +function fallbackRender({ resetErrorBoundary }) { + return ( + <div role="alert"> + <h3>Error Boundary</h3> + <p>Something went wrong.</p> + <button onClick={resetErrorBoundary}>Reset</button> + </div> + ); +} + +function Throw({error}) { + if (error === "known") { + throw new Error('Known error') + } else { + foo.bar = 'baz'; + } +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js" +} +``` + +</Sandpack> + +### Show a dialog for recoverable hydration mismatch errors {/*show-a-dialog-for-recoverable-hydration-mismatch-errors*/} + +When React encounters a hydration mismatch, it will automatically attempt to recover by rendering on the client. By default, React will log hydration mismatch errors to `console.error`. To override this behavior, you can provide the optional `onRecoverableError` root option: + +```js [[1, 7, "onRecoverableError"], [2, 7, "error", 1], [3, 11, "error.cause", 1], [4, 7, "errorInfo"], [5, 12, "componentStack"]] +import { hydrateRoot } from 'react-dom/client'; + +const root = hydrateRoot( + document.getElementById('root'), + <App />, + { + onRecoverableError: (error, errorInfo) => { + console.error( + 'Caught error', + error, + error.cause, + errorInfo.componentStack + ); + } + } +); +``` + +The <CodeStep step={1}>onRecoverableError</CodeStep> option is a function called with two arguments: + +1. The <CodeStep step={2}>error</CodeStep> React throws. Some errors may include the original cause as <CodeStep step={3}>error.cause</CodeStep>. +2. An <CodeStep step={4}>errorInfo</CodeStep> object that contains the <CodeStep step={5}>componentStack</CodeStep> of the error. + +You can use the `onRecoverableError` root option to display error dialogs for hydration mismatches: + +<Sandpack> + +```html index.html hidden +<!DOCTYPE html> +<html> +<head> + <title>My app</title> +</head> +<body> +<!-- + Error dialog in raw HTML + since an error in the React app may crash. +--> +<div id="error-dialog" class="hidden"> + <h1 id="error-title" class="text-red"></h1> + <h3> + <pre id="error-message"></pre> + </h3> + <p> + <pre id="error-body"></pre> + </p> + <h4 class="-mb-20">This error occurred at:</h4> + <pre id="error-component-stack" class="nowrap"></pre> + <h4 class="mb-0">Call stack:</h4> + <pre id="error-stack" class="nowrap"></pre> + <div id="error-cause"> + <h4 class="mb-0">Caused by:</h4> + <pre id="error-cause-message"></pre> + <pre id="error-cause-stack" class="nowrap"></pre> + </div> + <button + id="error-close" + class="mb-10" + onclick="document.getElementById('error-dialog').classList.add('hidden')" + > + Close + </button> + <h3 id="error-not-dismissible">This error is not dismissible.</h3> +</div> +<!-- + HTML content inside <div id="root">...</div> + was generated from App by react-dom/server. +--> +<div id="root"><span>Server</span></div> +</body> +</html> +``` + +```css src/styles.css active +label, button { display: block; margin-bottom: 20px; } +html, body { min-height: 300px; } + +#error-dialog { + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background-color: white; + padding: 15px; + opacity: 0.9; + text-wrap: wrap; + overflow: scroll; +} + +.text-red { + color: red; +} + +.-mb-20 { + margin-bottom: -20px; +} + +.mb-0 { + margin-bottom: 0; +} + +.mb-10 { + margin-bottom: 10px; +} + +pre { + text-wrap: wrap; +} + +pre.nowrap { + text-wrap: nowrap; +} + +.hidden { + display: none; +} +``` + +```js src/reportError.js hidden +function reportError({ title, error, componentStack, dismissable }) { + const errorDialog = document.getElementById("error-dialog"); + const errorTitle = document.getElementById("error-title"); + const errorMessage = document.getElementById("error-message"); + const errorBody = document.getElementById("error-body"); + const errorComponentStack = document.getElementById("error-component-stack"); + const errorStack = document.getElementById("error-stack"); + const errorClose = document.getElementById("error-close"); + const errorCause = document.getElementById("error-cause"); + const errorCauseMessage = document.getElementById("error-cause-message"); + const errorCauseStack = document.getElementById("error-cause-stack"); + const errorNotDismissible = document.getElementById("error-not-dismissible"); + + // Set the title + errorTitle.innerText = title; + + // Display error message and body + const [heading, body] = error.message.split(/\n(.*)/s); + errorMessage.innerText = heading; + if (body) { + errorBody.innerText = body; + } else { + errorBody.innerText = ''; + } + + // Display component stack + errorComponentStack.innerText = componentStack; + + // Display the call stack + // Since we already displayed the message, strip it, and the first Error: line. + errorStack.innerText = error.stack.replace(error.message, '').split(/\n(.*)/s)[1]; + + // Display the cause, if available + if (error.cause) { + errorCauseMessage.innerText = error.cause.message; + errorCauseStack.innerText = error.cause.stack; + errorCause.classList.remove('hidden'); + } else { + errorCause.classList.add('hidden'); + } + // Display the close button, if dismissible + if (dismissable) { + errorNotDismissible.classList.add('hidden'); + errorClose.classList.remove("hidden"); + } else { + errorNotDismissible.classList.remove('hidden'); + errorClose.classList.add("hidden"); + } + + // Show the dialog + errorDialog.classList.remove("hidden"); +} + +export function reportCaughtError({error, cause, componentStack}) { + reportError({ title: "Caught Error", error, componentStack, dismissable: true}); +} + +export function reportUncaughtError({error, cause, componentStack}) { + reportError({ title: "Uncaught Error", error, componentStack, dismissable: false }); +} + +export function reportRecoverableError({error, cause, componentStack}) { + reportError({ title: "Recoverable Error", error, componentStack, dismissable: true }); +} +``` + +```js src/index.js active +import { hydrateRoot } from "react-dom/client"; +import App from "./App.js"; +import {reportRecoverableError} from "./reportError"; +import "./styles.css"; + +const container = document.getElementById("root"); +const root = hydrateRoot(container, <App />, { + onRecoverableError: (error, errorInfo) => { + reportRecoverableError({ + error, + cause: error.cause, + componentStack: errorInfo.componentStack + }); + } +}); +``` + +```js src/App.js +import { useState } from 'react'; +import { ErrorBoundary } from "react-error-boundary"; + +export default function App() { + const [error, setError] = useState(null); + + function handleUnknown() { + setError("unknown"); + } + + function handleKnown() { + setError("known"); + } + + return ( + <span>{typeof window !== 'undefined' ? 'Client' : 'Server'}</span> + ); +} + +function fallbackRender({ resetErrorBoundary }) { + return ( + <div role="alert"> + <h3>Error Boundary</h3> + <p>Something went wrong.</p> + <button onClick={resetErrorBoundary}>Reset</button> + </div> + ); +} + +function Throw({error}) { + if (error === "known") { + throw new Error('Known error') + } else { + foo.bar = 'baz'; + } +} +``` + +```json package.json hidden +{ + "dependencies": { + "react": "canary", + "react-dom": "canary", + "react-scripts": "^5.0.0", + "react-error-boundary": "4.0.3" + }, + "main": "/index.js" +} +``` + +</Sandpack> + +## Troubleshooting {/*troubleshooting*/} + + +### I'm getting an error: "You passed a second argument to root.render" {/*im-getting-an-error-you-passed-a-second-argument-to-root-render*/} + +A common mistake is to pass the options for `hydrateRoot` to `root.render(...)`: + +<ConsoleBlock level="error"> + +Warning: You passed a second argument to root.render(...) but it only accepts one argument. + +</ConsoleBlock> + +To fix, pass the root options to `hydrateRoot(...)`, not `root.render(...)`: +```js {2,5} +// 🚩 Wrong: root.render only takes one argument. +root.render(App, {onUncaughtError}); + +// ✅ Correct: pass options to createRoot. +const root = hydrateRoot(container, <App />, {onUncaughtError}); +``` diff --git a/src/content/reference/react-dom/components/common.md b/src/content/reference/react-dom/components/common.md index 610742735..62ee08139 100644 --- a/src/content/reference/react-dom/components/common.md +++ b/src/content/reference/react-dom/components/common.md @@ -257,11 +257,32 @@ React will also call your `ref` callback whenever you pass a *different* `ref` c #### Parameters {/*ref-callback-parameters*/} -* `node`: A DOM node or `null`. React will pass you the DOM node when the ref gets attached, and `null` when the ref gets detached. Unless you pass the same function reference for the `ref` callback on every render, the callback will get temporarily detached and re-attached during every re-render of the component. +* `node`: A DOM node or `null`. React will pass you the DOM node when the ref gets attached, and `null` when the `ref` gets detached. Unless you pass the same function reference for the `ref` callback on every render, the callback will get temporarily detached and re-attached during every re-render of the component. + +<Canary> #### Returns {/*returns*/} -Do not return anything from the `ref` callback. +* **optional** `cleanup function`: When the `ref` is detached, React will call the cleanup function. If a function is not returned by the `ref` callback, React will call the callback again with `null` as the argument when the `ref` gets detached. + +```js + +<div ref={(node) => { + console.log(node); + + return () => { + console.log('Clean up', node) + } +}}> + +``` + +#### Caveats {/*caveats*/} + +* When Strict Mode is on, React will **run one extra development-only setup+cleanup cycle** before the first real setup. This is a stress-test that ensures that your cleanup logic "mirrors" your setup logic and that it stops or undoes whatever the setup is doing. If this causes a problem, implement the cleanup function. +* When you pass a *different* `ref` callback, React will call the *previous* callback's cleanup function if provided. If not cleanup function is defined, the `ref` callback will be called with `null` as the argument. The *next* function will be called with the DOM node. + +</Canary> --- diff --git a/src/content/reference/react-dom/components/form.md b/src/content/reference/react-dom/components/form.md index 4b7d5d8a0..8f6ab00e0 100644 --- a/src/content/reference/react-dom/components/form.md +++ b/src/content/reference/react-dom/components/form.md @@ -93,7 +93,7 @@ export default function Search() { ### Handle form submission with a Server Action {/*handle-form-submission-with-a-server-action*/} -Render a `<form>` with an input and submit button. Pass a Server Action (a function marked with [`'use server'`](/reference/react/use-server)) to the `action` prop of form to run the function when the form is submitted. +Render a `<form>` with an input and submit button. Pass a Server Action (a function marked with [`'use server'`](/reference/rsc/use-server)) to the `action` prop of form to run the function when the form is submitted. Passing a Server Action to `<form action>` allow users to submit forms without JavaScript enabled or before the code has loaded. This is beneficial to users who have a slow connection, device, or have JavaScript disabled and is similar to the way forms work when a URL is passed to the `action` prop. @@ -137,7 +137,7 @@ function AddToCart({productId}) { } ``` -When `<form>` is rendered by a [Server Component](/reference/react/use-client), and a [Server Action](/reference/react/use-server) is passed to the `<form>`'s `action` prop, the form is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). +When `<form>` is rendered by a [Server Component](/reference/rsc/use-client), and a [Server Action](/reference/rsc/use-server) is passed to the `<form>`'s `action` prop, the form is [progressively enhanced](https://developer.mozilla.org/en-US/docs/Glossary/Progressive_Enhancement). ### Display a pending state during form submission {/*display-a-pending-state-during-form-submission*/} To display a pending state when a form is being submitted, you can call the `useFormStatus` Hook in a component rendered in a `<form>` and read the `pending` property returned. @@ -322,16 +322,16 @@ export default function Search() { Displaying a form submission error message before the JavaScript bundle loads for progressive enhancement requires that: -1. `<form>` be rendered by a [Server Component](/reference/react/use-client) -1. the function passed to the `<form>`'s `action` prop be a [Server Action](/reference/react/use-server) -1. the `useFormState` Hook be used to display the error message +1. `<form>` be rendered by a [Server Component](/reference/rsc/use-client) +1. the function passed to the `<form>`'s `action` prop be a [Server Action](/reference/rsc/use-server) +1. the `useActionState` Hook be used to display the error message -`useFormState` takes two parameters: a [Server Action](/reference/react/use-server) and an initial state. `useFormState` returns two values, a state variable and an action. The action returned by `useFormState` should be passed to the `action` prop of the form. The state variable returned by `useFormState` can be used to displayed an error message. The value returned by the [Server Action](/reference/react/use-server) passed to `useFormState` will be used to update the state variable. +`useActionState` takes two parameters: a [Server Action](/reference/rsc/use-server) and an initial state. `useActionState` returns two values, a state variable and an action. The action returned by `useActionState` should be passed to the `action` prop of the form. The state variable returned by `useActionState` can be used to displayed an error message. The value returned by the [Server Action](/reference/rsc/use-server) passed to `useActionState` will be used to update the state variable. <Sandpack> ```js src/App.js -import { useFormState } from "react-dom"; +import { useActionState } from "react"; import { signUpNewUser } from "./api"; export default function Page() { @@ -345,12 +345,12 @@ export default function Page() { return err.toString(); } } - const [message, formAction] = useFormState(signup, null); + const [message, signupAction] = useActionState(signup, null); return ( <> <h1>Signup for my newsletter</h1> <p>Signup with the same email twice to see an error</p> - <form action={formAction} id="signup-form"> + <form action={signupAction} id="signup-form"> <label htmlFor="email">Email: </label> <input name="email" id="email" placeholder="react@example.com" /> <button>Sign up</button> @@ -375,8 +375,8 @@ export async function signUpNewUser(newEmail) { ```json package.json hidden { "dependencies": { - "react": "18.3.0-canary-6db7f4209-20231021", - "react-dom": "18.3.0-canary-6db7f4209-20231021", + "react": "canary", + "react-dom": "canary", "react-scripts": "^5.0.0" }, "main": "/index.js", @@ -386,7 +386,7 @@ export async function signUpNewUser(newEmail) { </Sandpack> -Learn more about updating state from a form action with the [`useFormState`](/reference/react-dom/hooks/useFormState) docs +Learn more about updating state from a form action with the [`useActionState`](/reference/react/useActionState) docs ### Handling multiple submission types {/*handling-multiple-submission-types*/} diff --git a/src/content/reference/react-dom/components/link.md b/src/content/reference/react-dom/components/link.md index c3331d94c..730d9e995 100644 --- a/src/content/reference/react-dom/components/link.md +++ b/src/content/reference/react-dom/components/link.md @@ -43,13 +43,13 @@ To link to external resources such as stylesheets, fonts, and icons, or to annot These props apply when `rel="stylesheet"`: -* `precedence`: a string. Tells React where to rank the `<link>` DOM node relative to others in the document `<head>`, which determines which stylesheet can override the other. Its value can be (in order of precedence) `"reset"`, `"low"`, `"medium"`, `"high"`. Stylesheets with the same precedence go together whether they are `<link>` or inline `<style>` tags or loaded using the [`preload`](/reference/react-dom/preload) or [`preinit`](/reference/react-dom/preinit) functions. -* `media`: a string. Restricts the spreadsheet to a certain [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries). +* `precedence`: a string. Tells React where to rank the `<link>` DOM node relative to others in the document `<head>`, which determines which stylesheet can override the other. React will infer that precedence values it discovers first are "lower" and precedence values it discovers later are "higher". Many style systems can work fine using a single precedence value because style rules are atomic. Stylesheets with the same precedence go together whether they are `<link>` or inline `<style>` tags or loaded using [`preinit`](/reference/react-dom/preinit) functions. +* `media`: a string. Restricts the stylesheet to a certain [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries). * `title`: a string. Specifies the name of an [alternative stylesheet](https://developer.mozilla.org/en-US/docs/Web/CSS/Alternative_style_sheets). These props apply when `rel="stylesheet"` but disable React's [special treatment of stylesheets](#special-rendering-behavior): -* `disabled`: a boolean. Disables the spreadsheet. +* `disabled`: a boolean. Disables the stylesheet. * `onError`: a function. Called when the stylesheet fails to load. * `onLoad`: a function. Called when the stylesheet finishes being loaded. diff --git a/src/content/reference/react-dom/components/script.md b/src/content/reference/react-dom/components/script.md index fc4b01277..fe020f1c6 100644 --- a/src/content/reference/react-dom/components/script.md +++ b/src/content/reference/react-dom/components/script.md @@ -68,12 +68,10 @@ Props that are **not recommended** for use with React: #### Special rendering behavior {/*special-rendering-behavior*/} -React can move `<script>` components to the document's `<head>`, de-duplicate identical scripts, and [suspend](/reference/react/Suspense) while the script is loading. +React can move `<script>` components to the document's `<head>` and de-duplicate identical scripts. To opt into this behavior, provide the `src` and `async={true}` props. React will de-duplicate scripts if they have the same `src`. The `async` prop must be true to allow scripts to be safely moved. -If you supply any of the `onLoad` or `onError` props, there is no special behavior, because these props indicate that you are managing the loading of the script manually within your component. - This special treatment comes with two caveats: * React will ignore changes to props after the script has been rendered. (React will issue a warning in development if this happens.) @@ -86,8 +84,10 @@ This special treatment comes with two caveats: ### Rendering an external script {/*rendering-an-external-script*/} If a component depends on certain scripts in order to be displayed correctly, you can render a `<script>` within the component. +However, the component might be committed before the script has finished loading. +You can start depending on the script content once the `load` event is fired e.g. by using the `onLoad` prop. -If you supply an `src` and `async` prop, your component will suspend while the script is loading. React will de-duplicate scripts that have the same `src`, inserting only one of them into the DOM even if multiple components render it. +React will de-duplicate scripts that have the same `src`, inserting only one of them into the DOM even if multiple components render it. <SandpackWithHTMLOutput> @@ -97,7 +97,7 @@ import ShowRenderedHTML from './ShowRenderedHTML.js'; function Map({lat, long}) { return ( <> - <script async src="map-api.js" /> + <script async src="map-api.js" onLoad={() => console.log('script loaded')} /> <div id="map" data-lat={lat} data-long={long} /> </> ); @@ -120,7 +120,7 @@ When you want to use a script, it can be beneficial to call the [preinit](/refer ### Rendering an inline script {/*rendering-an-inline-script*/} -To include an inline script, render the `<script>` component with the script source code as its children. Inline scripts are not de-duplicated or moved to the document `<head>`, and since they don't load any external resources, they will not cause your component to suspend. +To include an inline script, render the `<script>` component with the script source code as its children. Inline scripts are not de-duplicated or moved to the document `<head>`. <SandpackWithHTMLOutput> diff --git a/src/content/reference/react-dom/components/style.md b/src/content/reference/react-dom/components/style.md index 2c5d7b97b..84dfde3a0 100644 --- a/src/content/reference/react-dom/components/style.md +++ b/src/content/reference/react-dom/components/style.md @@ -40,9 +40,9 @@ To add inline styles to your document, render the [built-in browser `<style>` co `<style>` supports all [common element props.](/reference/react-dom/components/common#props) * `children`: a string, required. The contents of the stylesheet. -* `precedence`: a string. Tells React where to rank the `<style>` DOM node relative to others in the document `<head>`, which determines which stylesheet can override the other. Its value can be (in order of precedence) `"reset"`, `"low"`, `"medium"`, `"high"`. Stylesheets with the same precedence go together whether they are `<link>` or inline `<style>` tags or loaded using the [`preload`](/reference/react-dom/preload) or [`preinit`](/reference/react-dom/preinit) functions. +* `precedence`: a string. Tells React where to rank the `<style>` DOM node relative to others in the document `<head>`, which determines which stylesheet can override the other. React will infer that precedence values it discovers first are "lower" and precedence values it discovers later are "higher". Many style systems can work fine using a single precedence value because style rules are atomic. Stylesheets with the same precedence go together whether they are `<link>` or inline `<style>` tags or loaded using [`preinit`](/reference/react-dom/preinit) functions. * `href`: a string. Allows React to [de-duplicate styles](#special-rendering-behavior) that have the same `href`. -* `media`: a string. Restricts the spreadsheet to a certain [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries). +* `media`: a string. Restricts the stylesheet to a certain [media query](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_media_queries/Using_media_queries). * `nonce`: a string. A cryptographic [nonce to allow the resource](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/nonce) when using a strict Content Security Policy. * `title`: a string. Specifies the name of an [alternative stylesheet](https://developer.mozilla.org/en-US/docs/Web/CSS/Alternative_style_sheets). diff --git a/src/content/reference/react-dom/hooks/index.md b/src/content/reference/react-dom/hooks/index.md index 937de808c..681488b57 100644 --- a/src/content/reference/react-dom/hooks/index.md +++ b/src/content/reference/react-dom/hooks/index.md @@ -21,14 +21,13 @@ Form Hooks are currently only available in React's canary and experimental chann *Forms* let you create interactive controls for submitting information. To manage forms in your components, use one of these Hooks: * [`useFormStatus`](/reference/react-dom/hooks/useFormStatus) allows you to make updates to the UI based on the status of the a form. -* [`useFormState`](/reference/react-dom/hooks/useFormState) allows you to manage state inside a form. ```js function Form({ action }) { async function increment(n) { return n + 1; } - const [count, incrementFormAction] = useFormState(increment, 0); + const [count, incrementFormAction] = useActionState(increment, 0); return ( <form action={action}> <button formAction={incrementFormAction}>Count: {count}</button> @@ -46,4 +45,3 @@ function Button() { ); } ``` - diff --git a/src/content/reference/react-dom/preinit.md b/src/content/reference/react-dom/preinit.md index 3c9a879b4..49791b104 100644 --- a/src/content/reference/react-dom/preinit.md +++ b/src/content/reference/react-dom/preinit.md @@ -20,7 +20,7 @@ The `preinit` function is currently only available in React's Canary and experim `preinit` lets you eagerly fetch and evaluate a stylesheet or external script. ```js -preinit("https://example.com/script.js", {as: "style"}); +preinit("https://example.com/script.js", {as: "script"}); ``` </Intro> diff --git a/src/content/reference/react/Suspense.md b/src/content/reference/react/Suspense.md index abb77a9df..7622aa182 100644 --- a/src/content/reference/react/Suspense.md +++ b/src/content/reference/react/Suspense.md @@ -1741,7 +1741,7 @@ function Router() { // ... ``` -This tells React that the state Transition is not urgent, and it's better to keep showing the previous page instead of hiding any already revealed content. Now clicking the button "waits" for the `Biography` to load: +This tells React that the state transition is not urgent, and it's better to keep showing the previous page instead of hiding any already revealed content. Now clicking the button "waits" for the `Biography` to load: <Sandpack> diff --git a/src/content/reference/react/act.md b/src/content/reference/react/act.md new file mode 100644 index 000000000..256befa31 --- /dev/null +++ b/src/content/reference/react/act.md @@ -0,0 +1,177 @@ +--- +title: act +--- + +<Intro> + +`act` is a test helper to apply pending React updates before making assertions. + +```js +await act(async actFn) +``` + +</Intro> + +To prepare a component for assertions, wrap the code rendering it and performing updates inside an `await act()` call. This makes your test run closer to how React works in the browser. + +<Note> +You might find using `act()` directly a bit too verbose. To avoid some of the boilerplate, you could use a library like [React Testing Library](https://testing-library.com/docs/react-testing-library/intro), whose helpers are wrapped with `act()`. +</Note> + + +<InlineToc /> + +--- + +## Reference {/*reference*/} + +### `await act(async actFn)` {/*await-act-async-actfn*/} + +When writing UI tests, tasks like rendering, user events, or data fetching can be considered as “units” of interaction with a user interface. React provides a helper called `act()` that makes sure all updates related to these “units” have been processed and applied to the DOM before you make any assertions. + +The name `act` comes from the [Arrange-Act-Assert](https://wiki.c2.com/?ArrangeActAssert) pattern. + +```js {2,4} +it ('renders with button disabled', async () => { + await act(async () => { + root.render(<TestComponent />) + }); + expect(container.querySelector('button')).toBeDisabled(); +}); +``` + +<Note> + +We recommend using `act` with `await` and an `async` function. Although the sync version works in many cases, it doesn't work in all cases and due to the way React schedules updates internally, it's difficult to predict when you can use the sync version. + +We will deprecate and remove the sync version in the future. + +</Note> + +#### Parameters {/*parameters*/} + +* `async actFn`: An async function wrapping renders or interactions for components being tested. Any updates triggered within the `actFn`, are added to an internal act queue, which are then flushed together to process and apply any changes to the DOM. Since it is async, React will also run any code that crosses an async boundary, and flush any updates scheduled. + +#### Returns {/*returns*/} + +`act` does not return anything. + +## Usage {/*usage*/} + +When testing a component, you can use `act` to make assertions about its output. + +For example, let’s say we have this `Counter` component, the usage examples below show how to test it: + +```js +function Counter() { + const [count, setCount] = useState(0); + const handleClick = () => { + setCount(prev => prev + 1); + } + + useEffect(() => { + document.title = `You clicked ${this.state.count} times`; + }, [count]); + + return ( + <div> + <p>You clicked {this.state.count} times</p> + <button onClick={this.handleClick}> + Click me + </button> + </div> + ) +} +``` + +### Rendering components in tests {/*rendering-components-in-tests*/} + +To test the render output of a component, wrap the render inside `act()`: + +```js {10,12} +import {act} from 'react'; +import ReactDOM from 'react-dom/client'; +import Counter from './Counter'; + +it('can render and update a counter', async () => { + container = document.createElement('div'); + document.body.appendChild(container); + + // ✅ Render the component inside act(). + await act(() => { + ReactDOM.createRoot(container).render(<Counter />); + }); + + const button = container.querySelector('button'); + const label = container.querySelector('p'); + expect(label.textContent).toBe('You clicked 0 times'); + expect(document.title).toBe('You clicked 0 times'); +}); +``` + +Here, we create a container, append it to the document, and render the `Counter` component inside `act()`. This ensures that the component is rendered and its effects are applied before making assertions. + +Using `act` ensures that all updates have been applied before we make assertions. + +### Dispatching events in tests {/*dispatching-events-in-tests*/} + +To test events, wrap the event dispatch inside `act()`: + +```js {14,16} +import {act} from 'react'; +import ReactDOM from 'react-dom/client'; +import Counter from './Counter'; + +it.only('can render and update a counter', async () => { + const container = document.createElement('div'); + document.body.appendChild(container); + + await act( async () => { + ReactDOMClient.createRoot(container).render(<Counter />); + }); + + // ✅ Dispatch the event inside act(). + await act(async () => { + button.dispatchEvent(new MouseEvent('click', { bubbles: true })); + }); + + const button = container.querySelector('button'); + const label = container.querySelector('p'); + expect(label.textContent).toBe('You clicked 1 times'); + expect(document.title).toBe('You clicked 1 times'); +}); +``` + +Here, we render the component with `act`, and then dispatch the event inside another `act()`. This ensures that all updates from the event are applied before making assertions. + +<Pitfall> + +Don’t forget that dispatching DOM events only works when the DOM container is added to the document. You can use a library like [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) to reduce the boilerplate code. + +</Pitfall> + +## Troubleshooting {/*troubleshooting*/} + +### I'm getting an error: "The current testing environment is not configured to support act"(...)" {/*error-the-current-testing-environment-is-not-configured-to-support-act*/} + +Using `act` requires setting `global.IS_REACT_ACT_ENVIRONMENT=true` in your test environment. This is to ensure that `act` is only used in the correct environment. + +If you don't set the global, you will see an error like this: + +<ConsoleBlock level="error"> + +Warning: The current testing environment is not configured to support act(...) + +</ConsoleBlock> + +To fix, add this to your global setup file for React tests: + +```js +global.IS_REACT_ACT_ENVIRONMENT=true +``` + +<Note> + +In testing frameworks like [React Testing Library](https://testing-library.com/docs/react-testing-library/intro), `IS_REACT_ACT_ENVIRONMENT` is already set for you. + +</Note> \ No newline at end of file diff --git a/src/content/reference/react/apis.md b/src/content/reference/react/apis.md index 9c1437870..cbab4a0e6 100644 --- a/src/content/reference/react/apis.md +++ b/src/content/reference/react/apis.md @@ -15,3 +15,21 @@ In addition to [Hooks](/reference/react) and [Components](/reference/react/compo * [`lazy`](/reference/react/lazy) lets you defer loading a component's code until it's rendered for the first time. * [`memo`](/reference/react/memo) lets your component skip re-renders with same props. Used with [`useMemo`](/reference/react/useMemo) and [`useCallback`.](/reference/react/useCallback) * [`startTransition`](/reference/react/startTransition) lets you mark a state update as non-urgent. Similar to [`useTransition`.](/reference/react/useTransition) +* [`act`](/reference/react/act) lets you wrap renders and interactions in tests to ensure updates have processed before making assertions. + +--- + +## Resource APIs {/*resource-apis*/} + +*Resources* can be accessed by a component without having them as part of their state. For example, a component can read a message from a Promise or read styling information from a context. + +To read a value from a resource, use this API: + +* [`use`](/reference/react/use) lets you read the value of a resource like a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context). +```js +function MessageComponent({ messagePromise }) { + const message = use(messagePromise); + const theme = use(ThemeContext); + // ... +} +``` diff --git a/src/content/reference/react/cache.md b/src/content/reference/react/cache.md index 2179ea42a..5195f04d4 100644 --- a/src/content/reference/react/cache.md +++ b/src/content/reference/react/cache.md @@ -228,7 +228,7 @@ By caching a long-running data fetch, you can kick off asynchronous work prior t ```jsx [[2, 6, "await getUser(id)"], [1, 17, "getUser(id)"]] const getUser = cache(async (id) => { return await db.user.query(id); -} +}) async function Profile({id}) { const user = await getUser(id); diff --git a/src/content/reference/react/experimental_taintUniqueValue.md b/src/content/reference/react/experimental_taintUniqueValue.md index 12978758d..09d277eb3 100644 --- a/src/content/reference/react/experimental_taintUniqueValue.md +++ b/src/content/reference/react/experimental_taintUniqueValue.md @@ -14,7 +14,7 @@ You can try it by upgrading React packages to the most recent experimental versi Experimental versions of React may contain bugs. Don't use them in production. -This API is only available inside [React Server Components](/reference/react/use-client). +This API is only available inside [React Server Components](/reference/rsc/use-client). </Wip> diff --git a/src/content/reference/react/hooks.md b/src/content/reference/react/hooks.md index cec71ce8f..6dea3a0fd 100644 --- a/src/content/reference/react/hooks.md +++ b/src/content/reference/react/hooks.md @@ -106,24 +106,6 @@ To prioritize rendering, use one of these Hooks: --- -## Resource Hooks {/*resource-hooks*/} - -*Resources* can be accessed by a component without having them as part of their state. For example, a component can read a message from a Promise or read styling information from a context. - -To read a value from a resource, use this Hook: - -- [`use`](/reference/react/use) lets you read the value of a resource like a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context). - -```js -function MessageComponent({ messagePromise }) { - const message = use(messagePromise); - const theme = use(ThemeContext); - // ... -} -``` - ---- - ## Other Hooks {/*other-hooks*/} These Hooks are mostly useful to library authors and aren't commonly used in the application code. @@ -131,6 +113,7 @@ These Hooks are mostly useful to library authors and aren't commonly used in the - [`useDebugValue`](/reference/react/useDebugValue) lets you customize the label React DevTools displays for your custom Hook. - [`useId`](/reference/react/useId) lets a component associate a unique ID with itself. Typically used with accessibility APIs. - [`useSyncExternalStore`](/reference/react/useSyncExternalStore) lets a component subscribe to an external store. +* [`useActionState`](/reference/react/useActionState) allows you to manage state of actions. --- diff --git a/src/content/reference/react/index.md b/src/content/reference/react/index.md index 710903321..5663af392 100644 --- a/src/content/reference/react/index.md +++ b/src/content/reference/react/index.md @@ -17,7 +17,7 @@ Programmatic React features: * [Hooks](/reference/react/hooks) - Use different React features from your components. * [Components](/reference/react/components) - Documents built-in components that you can use in your JSX. * [APIs](/reference/react/apis) - APIs that are useful for defining components. -* [Directives](/reference/react/directives) - Provide instructions to bundlers compatible with React Server Components. +* [Directives](/reference/rsc/directives) - Provide instructions to bundlers compatible with React Server Components. ## React DOM {/*react-dom*/} diff --git a/src/content/reference/react/use.md b/src/content/reference/react/use.md index c3c8c92d2..b7a6fc701 100644 --- a/src/content/reference/react/use.md +++ b/src/content/reference/react/use.md @@ -5,13 +5,13 @@ canary: true <Canary> -The `use` Hook is currently only available in React's Canary and experimental channels. Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). +The `use` API is currently only available in React's Canary and experimental channels. Learn more about [React's release channels here](/community/versioning-policy#all-release-channels). </Canary> <Intro> -`use` is a React Hook that lets you read the value of a resource like a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context). +`use` is a React API that lets you read the value of a resource like a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context). ```js const value = use(resource); @@ -38,9 +38,9 @@ function MessageComponent({ messagePromise }) { // ... ``` -Unlike all other React Hooks, `use` can be called within loops and conditional statements like `if`. Like other React Hooks, the function that calls `use` must be a Component or Hook. +Unlike React Hooks, `use` can be called within loops and conditional statements like `if`. Like React Hooks, the function that calls `use` must be a Component or Hook. -When called with a Promise, the `use` Hook integrates with [`Suspense`](/reference/react/Suspense) and [error boundaries](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). The component calling `use` *suspends* while the Promise passed to `use` is pending. If the component that calls `use` is wrapped in a Suspense boundary, the fallback will be displayed. Once the Promise is resolved, the Suspense fallback is replaced by the rendered components using the data returned by the `use` Hook. If the Promise passed to `use` is rejected, the fallback of the nearest Error Boundary will be displayed. +When called with a Promise, the `use` API integrates with [`Suspense`](/reference/react/Suspense) and [error boundaries](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). The component calling `use` *suspends* while the Promise passed to `use` is pending. If the component that calls `use` is wrapped in a Suspense boundary, the fallback will be displayed. Once the Promise is resolved, the Suspense fallback is replaced by the rendered components using the data returned by the `use` API. If the Promise passed to `use` is rejected, the fallback of the nearest Error Boundary will be displayed. [See more examples below.](#usage) @@ -50,13 +50,13 @@ When called with a Promise, the `use` Hook integrates with [`Suspense`](/referen #### Returns {/*returns*/} -The `use` Hook returns the value that was read from the resource like the resolved value of a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context). +The `use` API returns the value that was read from the resource like the resolved value of a [Promise](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) or [context](/learn/passing-data-deeply-with-context). #### Caveats {/*caveats*/} -* The `use` Hook must be called inside a Component or a Hook. -* When fetching data in a [Server Component](/reference/react/use-server), prefer `async` and `await` over `use`. `async` and `await` pick up rendering from the point where `await` was invoked, whereas `use` re-renders the component after the data is resolved. -* Prefer creating Promises in [Server Components](/reference/react/use-server) and passing them to [Client Components](/reference/react/use-client) over creating Promises in Client Components. Promises created in Client Components are recreated on every render. Promises passed from a Server Component to a Client Component are stable across re-renders. [See this example](#streaming-data-from-server-to-client). +* The `use` API must be called inside a Component or a Hook. +* When fetching data in a [Server Component](/reference/rsc/use-server), prefer `async` and `await` over `use`. `async` and `await` pick up rendering from the point where `await` was invoked, whereas `use` re-renders the component after the data is resolved. +* Prefer creating Promises in [Server Components](/reference/rsc/use-server) and passing them to [Client Components](/reference/rsc/use-client) over creating Promises in Client Components. Promises created in Client Components are recreated on every render. Promises passed from a Server Component to a Client Component are stable across re-renders. [See this example](#streaming-data-from-server-to-client). --- @@ -230,7 +230,7 @@ export default function App() { } ``` -The <CodeStep step={2}>Client Component</CodeStep> then takes <CodeStep step={4}>the Promise it received as a prop</CodeStep> and passes it to the <CodeStep step={5}>`use`</CodeStep> Hook. This allows the <CodeStep step={2}>Client Component</CodeStep> to read the value from <CodeStep step={4}>the Promise</CodeStep> that was initially created by the Server Component. +The <CodeStep step={2}>Client Component</CodeStep> then takes <CodeStep step={4}>the Promise it received as a prop</CodeStep> and passes it to the <CodeStep step={5}>`use`</CodeStep> API. This allows the <CodeStep step={2}>Client Component</CodeStep> to read the value from <CodeStep step={4}>the Promise</CodeStep> that was initially created by the Server Component. ```js [[2, 6, "Message"], [4, 6, "messagePromise"], [4, 7, "messagePromise"], [5, 7, "use"]] // message.js @@ -243,7 +243,7 @@ export function Message({ messagePromise }) { return <p>Here is the message: {messageContent}</p>; } ``` -Because <CodeStep step={2}>`Message`</CodeStep> is wrapped in <CodeStep step={3}>[`Suspense`](/reference/react/Suspense)</CodeStep>, the fallback will be displayed until the Promise is resolved. When the Promise is resolved, the value will be read by the <CodeStep step={5}>`use`</CodeStep> Hook and the <CodeStep step={2}>`Message`</CodeStep> component will replace the Suspense fallback. +Because <CodeStep step={2}>`Message`</CodeStep> is wrapped in <CodeStep step={3}>[`Suspense`](/reference/react/Suspense)</CodeStep>, the fallback will be displayed until the Promise is resolved. When the Promise is resolved, the value will be read by the <CodeStep step={5}>`use`</CodeStep> API and the <CodeStep step={2}>`Message`</CodeStep> component will replace the Suspense fallback. <Sandpack> @@ -293,7 +293,7 @@ export default function App() { ```js src/index.js hidden // TODO: update to import from stable // react instead of canary once the `use` -// Hook is in a stable release of React +// API is in a stable release of React import React, { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import './styles.css'; @@ -334,7 +334,7 @@ When passing a Promise from a Server Component to a Client Component, its resolv #### Should I resolve a Promise in a Server or Client Component? {/*resolve-promise-in-server-or-client-component*/} -A Promise can be passed from a Server Component to a Client Component and resolved in the Client Component with the `use` Hook. You can also resolve the Promise in a Server Component with `await` and pass the required data to the Client Component as a prop. +A Promise can be passed from a Server Component to a Client Component and resolved in the Client Component with the `use` API. You can also resolve the Promise in a Server Component with `await` and pass the required data to the Client Component as a prop. ```js export default async function App() { @@ -360,7 +360,7 @@ In some cases a Promise passed to `use` could be rejected. You can handle reject #### Displaying an error to users with an error boundary {/*displaying-an-error-to-users-with-error-boundary*/} -If you'd like to display an error to your users when a Promise is rejected, you can use an [error boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). To use an error boundary, wrap the component where you are calling the `use` Hook in an error boundary. If the Promise passed to `use` is rejected the fallback for the error boundary will be displayed. +If you'd like to display an error to your users when a Promise is rejected, you can use an [error boundary](/reference/react/Component#catching-rendering-errors-with-an-error-boundary). To use an error boundary, wrap the component where you are calling the `use` API in an error boundary. If the Promise passed to `use` is rejected the fallback for the error boundary will be displayed. <Sandpack> @@ -413,7 +413,7 @@ export default function App() { ```js src/index.js hidden // TODO: update to import from stable // react instead of canary once the `use` -// Hook is in a stable release of React +// API is in a stable release of React import React, { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import './styles.css'; @@ -474,9 +474,9 @@ To use the Promise's <CodeStep step={1}>`catch`</CodeStep> method, call <CodeSte ### "Suspense Exception: This is not a real error!" {/*suspense-exception-error*/} -You are either calling `use` outside of a React component or Hook function, or calling `use` in a try–catch block. If you are calling `use` inside a try–catch block, wrap your component in an error boundary, or call the Promise's `catch` to catch the error and resolve the Promise with another value. [See these examples](#dealing-with-rejected-promises). +You are either calling `use` outside of a React Component or Hook function, or calling `use` in a try–catch block. If you are calling `use` inside a try–catch block, wrap your component in an error boundary, or call the Promise's `catch` to catch the error and resolve the Promise with another value. [See these examples](#dealing-with-rejected-promises). -If you are calling `use` outside a React component or Hook function, move the `use` call to a React component or Hook function. +If you are calling `use` outside a React Component or Hook function, move the `use` call to a React Component or Hook function. ```jsx function MessageComponent({messagePromise}) { @@ -486,7 +486,7 @@ function MessageComponent({messagePromise}) { // ... ``` -Instead, call `use` outside any component closures, where the function that calls `use` is a component or Hook. +Instead, call `use` outside any component closures, where the function that calls `use` is a Component or Hook. ```jsx function MessageComponent({messagePromise}) { diff --git a/src/content/reference/react-dom/hooks/useFormState.md b/src/content/reference/react/useActionState.md similarity index 67% rename from src/content/reference/react-dom/hooks/useFormState.md rename to src/content/reference/react/useActionState.md index e2a8d5394..6f5924e3d 100644 --- a/src/content/reference/react-dom/hooks/useFormState.md +++ b/src/content/reference/react/useActionState.md @@ -1,20 +1,26 @@ --- -title: useFormState +title: useActionState canary: true --- <Canary> -The `useFormState` Hook is currently only available in React's Canary and experimental channels. Learn more about [release channels here](/community/versioning-policy#all-release-channels). In addition, you need to use a framework that supports [React Server Components](/reference/react/use-client) to get the full benefit of `useFormState`. +The `useActionState` Hook is currently only available in React's Canary and experimental channels. Learn more about [release channels here](/community/versioning-policy#all-release-channels). In addition, you need to use a framework that supports [React Server Components](/reference/rsc/use-client) to get the full benefit of `useActionState`. </Canary> +<Note> + +In earlier React Canary versions, this API was part of React DOM and called `useFormState`. + +</Note> + <Intro> -`useFormState` is a Hook that allows you to update state based on the result of a form action. +`useActionState` is a Hook that allows you to update state based on the result of a form action. ```js -const [state, formAction] = useFormState(fn, initialState, permalink?); +const [state, formAction] = useActionState(fn, initialState, permalink?); ``` </Intro> @@ -25,21 +31,21 @@ const [state, formAction] = useFormState(fn, initialState, permalink?); ## Reference {/*reference*/} -### `useFormState(action, initialState, permalink?)` {/*useformstate*/} +### `useActionState(action, initialState, permalink?)` {/*useactionstate*/} {/* TODO T164397693: link to actions documentation once it exists */} -Call `useFormState` at the top level of your component to create component state that is updated [when a form action is invoked](/reference/react-dom/components/form). You pass `useFormState` an existing form action function as well as an initial state, and it returns a new action that you use in your form, along with the latest form state. The latest form state is also passed to the function that you provided. +Call `useActionState` at the top level of your component to create component state that is updated [when a form action is invoked](/reference/react-dom/components/form). You pass `useActionState` an existing form action function as well as an initial state, and it returns a new action that you use in your form, along with the latest form state. The latest form state is also passed to the function that you provided. ```js -import { useFormState } from "react-dom"; +import { useActionState } from "react"; async function increment(previousState, formData) { return previousState + 1; } function StatefulForm({}) { - const [state, formAction] = useFormState(increment, 0); + const [state, formAction] = useActionState(increment, 0); return ( <form> {state} @@ -51,7 +57,7 @@ function StatefulForm({}) { The form state is the value returned by the action when the form was last submitted. If the form has not yet been submitted, it is the initial state that you pass. -If used with a Server Action, `useFormState` allows the server's response from submitting the form to be shown even before hydration has completed. +If used with a Server Action, `useActionState` allows the server's response from submitting the form to be shown even before hydration has completed. [See more examples below.](#usage) @@ -59,21 +65,21 @@ If used with a Server Action, `useFormState` allows the server's response from s * `fn`: The function to be called when the form is submitted or button pressed. When the function is called, it will receive the previous state of the form (initially the `initialState` that you pass, subsequently its previous return value) as its initial argument, followed by the arguments that a form action normally receives. * `initialState`: The value you want the state to be initially. It can be any serializable value. This argument is ignored after the action is first invoked. -* **optional** `permalink`: A string containing the unique page URL that this form modifies. For use on pages with dynamic content (eg: feeds) in conjunction with progressive enhancement: if `fn` is a [server action](/reference/react/use-server) and the form is submitted before the JavaScript bundle loads, the browser will navigate to the specified permalink URL, rather than the current page's URL. Ensure that the same form component is rendered on the destination page (including the same action `fn` and `permalink`) so that React knows how to pass the state through. Once the form has been hydrated, this parameter has no effect. +* **optional** `permalink`: A string containing the unique page URL that this form modifies. For use on pages with dynamic content (eg: feeds) in conjunction with progressive enhancement: if `fn` is a [server action](/reference/rsc/use-server) and the form is submitted before the JavaScript bundle loads, the browser will navigate to the specified permalink URL, rather than the current page's URL. Ensure that the same form component is rendered on the destination page (including the same action `fn` and `permalink`) so that React knows how to pass the state through. Once the form has been hydrated, this parameter has no effect. {/* TODO T164397693: link to serializable values docs once it exists */} #### Returns {/*returns*/} -`useFormState` returns an array with exactly two values: +`useActionState` returns an array with exactly two values: 1. The current state. During the first render, it will match the `initialState` you have passed. After the action is invoked, it will match the value returned by the action. 2. A new action that you can pass as the `action` prop to your `form` component or `formAction` prop to any `button` component within the form. #### Caveats {/*caveats*/} -* When used with a framework that supports React Server Components, `useFormState` lets you make forms interactive before JavaScript has executed on the client. When used without Server Components, it is equivalent to component local state. -* The function passed to `useFormState` receives an extra argument, the previous or initial state, as its first argument. This makes its signature different than if it were used directly as a form action without using `useFormState`. +* When used with a framework that supports React Server Components, `useActionState` lets you make forms interactive before JavaScript has executed on the client. When used without Server Components, it is equivalent to component local state. +* The function passed to `useActionState` receives an extra argument, the previous or initial state, as its first argument. This makes its signature different than if it were used directly as a form action without using `useActionState`. --- @@ -81,14 +87,14 @@ If used with a Server Action, `useFormState` allows the server's response from s ### Using information returned by a form action {/*using-information-returned-by-a-form-action*/} -Call `useFormState` at the top level of your component to access the return value of an action from the last time a form was submitted. +Call `useActionState` at the top level of your component to access the return value of an action from the last time a form was submitted. ```js [[1, 5, "state"], [2, 5, "formAction"], [3, 5, "action"], [4, 5, "null"], [2, 8, "formAction"]] -import { useFormState } from 'react-dom'; +import { useActionState } from 'react'; import { action } from './actions.js'; function MyComponent() { - const [state, formAction] = useFormState(action, null); + const [state, formAction] = useActionState(action, null); // ... return ( <form action={formAction}> @@ -98,14 +104,14 @@ function MyComponent() { } ``` -`useFormState` returns an array with exactly two items: +`useActionState` returns an array with exactly two items: 1. The <CodeStep step={1}>current state</CodeStep> of the form, which is initially set to the <CodeStep step={4}>initial state</CodeStep> you provided, and after the form is submitted is set to the return value of the <CodeStep step={3}>action</CodeStep> you provided. 2. A <CodeStep step={2}>new action</CodeStep> that you pass to `<form>` as its `action` prop. When the form is submitted, the <CodeStep step={3}>action</CodeStep> function that you provided will be called. Its return value will become the new <CodeStep step={1}>current state</CodeStep> of the form. -The <CodeStep step={3}>action</CodeStep> that you provide will also receive a new first argument, namely the <CodeStep step={1}>current state</CodeStep> of the form. The first time the form is submitted, this will be the <CodeStep step={4}>initial state</CodeStep> you provided, while with subsequent submissions, it will be the return value from the last time the action was called. The rest of the arguments are the same as if `useFormState` had not been used. +The <CodeStep step={3}>action</CodeStep> that you provide will also receive a new first argument, namely the <CodeStep step={1}>current state</CodeStep> of the form. The first time the form is submitted, this will be the <CodeStep step={4}>initial state</CodeStep> you provided, while with subsequent submissions, it will be the return value from the last time the action was called. The rest of the arguments are the same as if `useActionState` had not been used. ```js [[3, 1, "action"], [1, 1, "currentState"]] function action(currentState, formData) { @@ -118,17 +124,16 @@ function action(currentState, formData) { #### Display form errors {/*display-form-errors*/} -To display messages such as an error message or toast that's returned by a Server Action, wrap the action in a call to `useFormState`. +To display messages such as an error message or toast that's returned by a Server Action, wrap the action in a call to `useActionState`. <Sandpack> ```js src/App.js -import { useState } from "react"; -import { useFormState } from "react-dom"; +import { useActionState, useState } from "react"; import { addToCart } from "./actions.js"; function AddToCartForm({itemID, itemTitle}) { - const [message, formAction] = useFormState(addToCart, null); + const [message, formAction] = useActionState(addToCart, null); return ( <form action={formAction}> <h2>{itemTitle}</h2> @@ -196,12 +201,11 @@ The return value from a Server Action can be any serializable value. For example <Sandpack> ```js src/App.js -import { useState } from "react"; -import { useFormState } from "react-dom"; +import { useActionState, useState } from "react"; import { addToCart } from "./actions.js"; function AddToCartForm({itemID, itemTitle}) { - const [formState, formAction] = useFormState(addToCart, {}); + const [formState, formAction] = useActionState(addToCart, {}); return ( <form action={formAction}> <h2>{itemTitle}</h2> @@ -283,7 +287,7 @@ form button { ### My action can no longer read the submitted form data {/*my-action-can-no-longer-read-the-submitted-form-data*/} -When you wrap an action with `useFormState`, it gets an extra argument *as its first argument*. The submitted form data is therefore its *second* argument instead of its first as it would usually be. The new first argument that gets added is the current state of the form. +When you wrap an action with `useActionState`, it gets an extra argument *as its first argument*. The submitted form data is therefore its *second* argument instead of its first as it would usually be. The new first argument that gets added is the current state of the form. ```js function action(currentState, formData) { diff --git a/src/content/reference/react/useDeferredValue.md b/src/content/reference/react/useDeferredValue.md index f97bf0a28..6d055f1f9 100644 --- a/src/content/reference/react/useDeferredValue.md +++ b/src/content/reference/react/useDeferredValue.md @@ -18,7 +18,7 @@ const deferredValue = useDeferredValue(value) ## Reference {/*reference*/} -### `useDeferredValue(value)` {/*usedeferredvalue*/} +### `useDeferredValue(value, initialValue?)` {/*usedeferredvalue*/} Call `useDeferredValue` at the top level of your component to get a deferred version of that value. @@ -37,13 +37,23 @@ function SearchPage() { #### Parameters {/*parameters*/} * `value`: The value you want to defer. It can have any type. +* <CanaryBadge title="This feature is only available in the Canary channel" /> **optional** `initialValue`: A value to use during the initial render of a component. If this option is omitted, `useDeferredValue` will not defer during the initial render, because there's no previous version of `value` that it can render instead. + #### Returns {/*returns*/} -During the initial render, the returned deferred value will be the same as the value you provided. During updates, React will first attempt a re-render with the old value (so it will return the old value), and then try another re-render in the background with the new value (so it will return the updated value). +- `currentValue`: During the initial render, the returned deferred value will be the same as the value you provided. During updates, React will first attempt a re-render with the old value (so it will return the old value), and then try another re-render in the background with the new value (so it will return the updated value). + +<Canary> + +In the latest React Canary versions, `useDeferredValue` returns the `initialValue` on initial render, and schedules a re-render in the background with the `value` returned. + +</Canary> #### Caveats {/*caveats*/} +- When an update is inside a Transition, `useDeferredValue` always returns the new `value` and does not spawn a deferred render, since the update is already deferred. + - The values you pass to `useDeferredValue` should either be primitive values (like strings and numbers) or objects created outside of rendering. If you create a new object during rendering and immediately pass it to `useDeferredValue`, it will be different on every render, causing unnecessary background re-renders. - When `useDeferredValue` receives a different value (compared with [`Object.is`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)), in addition to the current render (when it still uses the previous value), it schedules a re-render in the background with the new value. The background re-render is interruptible: if there's another update to the `value`, React will restart the background re-render from scratch. For example, if the user is typing into an input faster than a chart receiving its deferred value can re-render, the chart will only re-render after the user stops typing. diff --git a/src/content/reference/react/useEffect.md b/src/content/reference/react/useEffect.md index d7659c5e9..bf148339b 100644 --- a/src/content/reference/react/useEffect.md +++ b/src/content/reference/react/useEffect.md @@ -64,7 +64,9 @@ function ChatRoom({ roomId }) { * If your Effect wasn't caused by an interaction (like a click), React will generally let the browser **paint the updated screen first before running your Effect.** If your Effect is doing something visual (for example, positioning a tooltip), and the delay is noticeable (for example, it flickers), replace `useEffect` with [`useLayoutEffect`.](/reference/react/useLayoutEffect) -* Even if your Effect was caused by an interaction (like a click), **the browser may repaint the screen before processing the state updates inside your Effect.** Usually, that's what you want. However, if you must block the browser from repainting the screen, you need to replace `useEffect` with [`useLayoutEffect`.](/reference/react/useLayoutEffect) +* If your Effect is caused by an interaction (like a click), **React may run your Effect before the browser paints the updated screen**. This ensures that the result of the Effect can be observed by the event system. Usually, this works as expected. However, if you must defer the work until after paint, such as an `alert()`, you can use `setTimeout`. See [reactwg/react-18/128](https://github.com/reactwg/react-18/discussions/128) for more information. + +* Even if your Effect was caused by an interaction (like a click), **React may allow the browser to repaint the screen before processing the state updates inside your Effect.** Usually, this works as expected. However, if you must block the browser from repainting the screen, you need to replace `useEffect` with [`useLayoutEffect`.](/reference/react/useLayoutEffect) * Effects **only run on the client.** They don't run during server rendering. diff --git a/src/content/reference/react/useLayoutEffect.md b/src/content/reference/react/useLayoutEffect.md index 0d7b04841..d38458f14 100644 --- a/src/content/reference/react/useLayoutEffect.md +++ b/src/content/reference/react/useLayoutEffect.md @@ -67,6 +67,8 @@ function Tooltip() { * The code inside `useLayoutEffect` and all state updates scheduled from it **block the browser from repainting the screen.** When used excessively, this makes your app slow. When possible, prefer [`useEffect`.](/reference/react/useEffect) +* If you trigger a state update inside `useLayoutEffect`, React will execute all remaining Effects immediately including `useEffect`. + --- ## Usage {/*usage*/} diff --git a/src/content/reference/react/directives.md b/src/content/reference/rsc/directives.md similarity index 69% rename from src/content/reference/react/directives.md rename to src/content/reference/rsc/directives.md index 4854310b3..0157ba4dd 100644 --- a/src/content/reference/react/directives.md +++ b/src/content/reference/rsc/directives.md @@ -19,5 +19,5 @@ Directives provide instructions to [bundlers compatible with React Server Compon ## Source code directives {/*source-code-directives*/} -* [`'use client'`](/reference/react/use-client) lets you mark what code runs on the client. -* [`'use server'`](/reference/react/use-server) marks server-side functions that can be called from client-side code. \ No newline at end of file +* [`'use client'`](/reference/rsc/use-client) lets you mark what code runs on the client. +* [`'use server'`](/reference/rsc/use-server) marks server-side functions that can be called from client-side code. diff --git a/src/content/reference/rsc/server-actions.md b/src/content/reference/rsc/server-actions.md new file mode 100644 index 000000000..06613cb7c --- /dev/null +++ b/src/content/reference/rsc/server-actions.md @@ -0,0 +1,213 @@ +--- +title: Server Actions +canary: true +--- + +<Intro> + +Server Actions allow Client Components to call async functions executed on the server. + +</Intro> + +<InlineToc /> + +<Note> + +#### How do I build support for Server Actions? {/*how-do-i-build-support-for-server-actions*/} + +While Server Actions in React 19 are stable and will not break between major versions, the underlying APIs used to implement Server Actions in a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x. + +To support Server Actions as a bundler or framework, we recommend pinning to a specific React version, or using the Canary release. We will continue working with bundlers and frameworks to stabilize the APIs used to implement Server Actions in the future. + +</Note> + +When a Server Action is defined with the `"use server"` directive, your framework will automatically create a reference to the server function, and pass that reference to the Client Component. When that function is called on the client, React will send a request to the server to execute the function, and return the result. + +Server Actions can be created in Server Components and passed as props to Client Components, or they can be imported and used in Client Components. + +### Creating a Server Action from a Server Component {/*creating-a-server-action-from-a-server-component*/} + +Server Components can define Server Actions with the `"use server"` directive: + +```js [[2, 7, "'use server'"], [1, 5, "createNoteAction"], [1, 12, "createNoteAction"]] +// Server Component +import Button from './Button'; + +function EmptyNote () { + async function createNoteAction() { + // Server Action + 'use server'; + + await db.notes.create(); + } + + return <Button onClick={createNoteAction}/>; +} +``` + +When React renders the `EmptyNote` Server Component, it will create a reference to the `createNoteAction` function, and pass that reference to the `Button` Client Component. When the button is clicked, React will send a request to the server to execute the `createNoteAction` function with the reference provided: + +```js {5} +"use client"; + +export default function Button({onClick}) { + console.log(onClick); + // {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'} + return <button onClick={onClick}>Create Empty Note</button> +} +``` + +For more, see the docs for [`"use server"`](/reference/rsc/use-server). + + +### Importing Server Actions from Client Components {/*importing-server-actions-from-client-components*/} + +Client Components can import Server Actions from files that use the `"use server"` directive: + +```js [[1, 3, "createNoteAction"]] +"use server"; + +export async function createNoteAction() { + await db.notes.create(); +} + +``` + +When the bundler builds the `EmptyNote` Client Component, it will create a reference to the `createNoteAction` function in the bundle. When the `button` is clicked, React will send a request to the server to execute the `createNoteAction` function using the reference provided: + +```js [[1, 2, "createNoteAction"], [1, 5, "createNoteAction"], [1, 7, "createNoteAction"]] +"use client"; +import {createNoteAction} from './actions'; + +function EmptyNote() { + console.log(createNoteAction); + // {$$typeof: Symbol.for("react.server.reference"), $$id: 'createNoteAction'} + <button onClick={createNoteAction} /> +} +``` + +For more, see the docs for [`"use server"`](/reference/rsc/use-server). + +### Composing Server Actions with Actions {/*composing-server-actions-with-actions*/} + +Server Actions can be composed with Actions on the client: + +```js [[1, 3, "updateName"]] +"use server"; + +export async function updateName(name) { + if (!name) { + return {error: 'Name is required'}; + } + await db.users.updateName(name); +} +``` + +```js [[1, 3, "updateName"], [1, 13, "updateName"], [2, 11, "submitAction"], [2, 23, "submitAction"]] +"use client"; + +import {updateName} from './actions'; + +function UpdateName() { + const [name, setName] = useState(''); + const [error, setError] = useState(null); + + const [isPending, startTransition] = useTransition(); + + const submitAction = async () => { + startTransition(async () => { + const {error} = await updateName(name); + if (!error) { + setError(error); + } else { + setName(''); + } + }) + } + + return ( + <form action={submitAction}> + <input type="text" name="name" disabled={isPending}/> + {state.error && <span>Failed: {state.error}</span>} + </form> + ) +} +``` + +This allows you to access the `isPending` state of the Server Action by wrapping it in an Action on the client. + +For more, see the docs for [Calling a Server Action outside of `<form>`](/reference/rsc/use-server#calling-a-server-action-outside-of-form) + +### Form Actions with Server Actions {/*form-actions-with-server-actions*/} + +Server Actions work with the new Form features in React 19. + +You can pass a Server Action to a Form to automatically submit the form to the server: + + +```js [[1, 3, "updateName"], [1, 7, "updateName"]] +"use client"; + +import {updateName} from './actions'; + +function UpdateName() { + return ( + <form action={updateName}> + <input type="text" name="name" /> + </form> + ) +} +``` + +When the Form submission succeeds, React will automatically reset the form. You can add `useActionState` to access the pending state, last response, or to support progressive enhancement. + +For more, see the docs for [Server Actions in Forms](/reference/rsc/use-server#server-actions-in-forms). + +### Server Actions with `useActionState` {/*server-actions-with-use-action-state*/} + +You can compose Server Actions with `useActionState` for the common case where you just need access to the action pending state and last returned response: + +```js [[1, 3, "updateName"], [1, 6, "updateName"], [2, 6, "submitAction"], [2, 9, "submitAction"]] +"use client"; + +import {updateName} from './actions'; + +function UpdateName() { + const [state, submitAction, isPending] = useActionState(updateName, {error: null}); + + return ( + <form action={submitAction}> + <input type="text" name="name" disabled={isPending}/> + {state.error && <span>Failed: {state.error}</span>} + </form> + ); +} +``` + +When using `useActionState` with Server Actions, React will also automatically replay form submissions entered before hydration finishes. This means users can interact with your app even before the app has hydrated. + +For more, see the docs for [`useActionState`](/reference/react-dom/hooks/useFormState). + +### Progressive enhancement with `useActionState` {/*progressive-enhancement-with-useactionstate*/} + +Server Actions also support progressive enhancement with the third argument of `useActionState`. + +```js [[1, 3, "updateName"], [1, 6, "updateName"], [2, 6, "/name/update"], [3, 6, "submitAction"], [3, 9, "submitAction"]] +"use client"; + +import {updateName} from './actions'; + +function UpdateName() { + const [, submitAction] = useActionState(updateName, null, `/name/update`); + + return ( + <form action={submitAction}> + ... + </form> + ); +} +``` + +When the <CodeStep step={2}>permalink</CodeStep> is provided to `useActionState`, React will redirect to the provided URL if the form is submitted before the JavaScript bundle loads. + +For more, see the docs for [`useActionState`](/reference/react-dom/hooks/useFormState). diff --git a/src/content/reference/rsc/server-components.md b/src/content/reference/rsc/server-components.md new file mode 100644 index 000000000..a4ee4dd2b --- /dev/null +++ b/src/content/reference/rsc/server-components.md @@ -0,0 +1,297 @@ +--- +title: React Server Components +canary: true +--- + +<Intro> + +Server Components are a new type of Component that renders ahead of time, before bundling, in an environment separate from your client app or SSR server. + +</Intro> + +This separate environment is the "server" in React Server Components. Server Components can run once at build time on your CI server, or they can be run for each request using a web server. + +<InlineToc /> + +<Note> + +#### How do I build support for Server Components? {/*how-do-i-build-support-for-server-components*/} + +While React Server Components in React 19 are stable and will not break between major versions, the underlying APIs used to implement a React Server Components bundler or framework do not follow semver and may break between minors in React 19.x. + +To support React Server Components as a bundler or framework, we recommend pinning to a specific React version, or using the Canary release. We will continue working with bundlers and frameworks to stabilize the APIs used to implement React Server Components in the future. + +</Note> + +### Server Components without a Server {/*server-components-without-a-server*/} +Server components can run at build time to read from the filesystem or fetch static content, so a web server is not required. For example, you may want to read static data from a content management system. + +Without Server Components, it's common to fetch static data on the client with an Effect: +```js +// bundle.js +import marked from 'marked'; // 35.9K (11.2K gzipped) +import sanitizeHtml from 'sanitize-html'; // 206K (63.3K gzipped) + +function Page({page}) { + const [content, setContent] = useState(''); + // NOTE: loads *after* first page render. + useEffect(() => { + fetch(`/api/content/${page}`).then((data) => { + setContent(data.content); + }); + }, [page]); + + return <div>{sanitizeHtml(marked(content))}</div>; +} +``` +```js +// api.js +app.get(`/api/content/:page`, async (req, res) => { + const page = req.params.page; + const content = await file.readFile(`${page}.md`); + res.send({content}); +}); +``` + +This pattern means users need to download and parse an additional 75K (gzipped) of libraries, and wait for a second request to fetch the data after the page loads, just to render static content that will not change for the lifetime of the page. + +With Server Components, you can render these components once at build time: + +```js +import marked from 'marked'; // Not included in bundle +import sanitizeHtml from 'sanitize-html'; // Not included in bundle + +async function Page({page}) { + // NOTE: loads *during* render, when the app is built. + const content = await file.readFile(`${page}.md`); + + return <div>{sanitizeHtml(marked(content))}</div>; +} +``` + +The rendered output can then be server-side rendered (SSR) to HTML and uploaded to a CDN. When the app loads, the client will not see the original `Page` component, or the expensive libraries for rendering the markdown. The client will only see the rendered output: + +```js +<div><!-- html for markdown --></div> +``` + +This means the content is visible during first page load, and the bundle does not include the expensive libraries needed to render the static content. + +<Note> + +You may notice that the Server Component above is an async function: + +```js +async function Page({page}) { + //... +} +``` + +Async Components are a new feature of Server Components that allow you to `await` in render. + +See [Async components with Server Components](#async-components-with-server-components) below. + +</Note> + +### Server Components with a Server {/*server-components-with-a-server*/} +Server Components can also run on a web server during a request for a page, letting you access your data layer without having to build an API. They are rendered before your application is bundled, and can pass data and JSX as props to Client Components. + +Without Server Components, it's common to fetch dynamic data on the client in an Effect: + +```js +// bundle.js +function Note({id}) { + const [note, setNote] = useState(''); + // NOTE: loads *after* first render. + useEffect(() => { + fetch(`/api/notes/${id}`).then(data => { + setNote(data.note); + }); + }, [id]); + + return ( + <div> + <Author id={note.authorId} /> + <p>{note}</p> + </div> + ); +} + +function Author({id}) { + const [author, setAuthor] = useState(''); + // NOTE: loads *after* Note renders. + // Causing an expensive client-server waterfall. + useEffect(() => { + fetch(`/api/authors/${id}`).then(data => { + setAuthor(data.author); + }); + }, [id]); + + return <span>By: {author.name}</span>; +} +``` +```js +// api +import db from './database'; + +app.get(`/api/notes/:id`, async (req, res) => { + const note = await db.notes.get(id); + res.send({note}); +}); + +app.get(`/api/authors/:id`, async (req, res) => { + const author = await db.authors.get(id); + res.send({author}); +}); +``` + +With Server Components, you can read the data and render it in the component: + +```js +import db from './database'; + +async function Note({id}) { + // NOTE: loads *during* render. + const note = await db.notes.get(id); + return ( + <div> + <Author id={note.authorId} /> + <p>{note}</p> + </div> + ); +} + +async function Author({id}) { + // NOTE: loads *after* Note, + // but is fast if data is co-located. + const author = await db.authors.get(id); + return <span>By: {author.name}</span>; +} +``` + +The bundler then combines the data, rendered Server Components and dynamic Client Components into a bundle. Optionally, that bundle can then be server-side rendered (SSR) to create the initial HTML for the page. When the page loads, the browser does not see the original `Note` and `Author` components; only the rendered output is sent to the client: + +```js +<div> + <span>By: The React Team</span> + <p>React 19 is...</p> +</div> +``` + +Server Components can be made dynamic by re-fetching them from a server, where they can access the data and render again. This new application architecture combines the simple “request/response” mental model of server-centric Multi-Page Apps with the seamless interactivity of client-centric Single-Page Apps, giving you the best of both worlds. + +### Adding interactivity to Server Components {/*adding-interactivity-to-server-components*/} + +Server Components are not sent to the browser, so they cannot use interactive APIs like `useState`. To add interactivity to Server Components, you can compose them with Client Component using the `"use client"` directive. + +<Note> + +#### There is no directive for Server Components. {/*there-is-no-directive-for-server-components*/} + +A common misunderstanding is that Server Components are denoted by `"use server"`, but there is no directive for Server Components. The `"use server"` directive is used for Server Actions. + +For more info, see the docs for [Directives](/reference/rsc/directives). + +</Note> + + +In the following example, the `Notes` Server Component imports an `Expandable` Client Component that uses state to toggle its `expanded` state: +```js +// Server Component +import Expandable from './Expandable'; + +async function Notes() { + const notes = await db.notes.getAll(); + return ( + <div> + {notes.map(note => ( + <Expandable key={note.id}> + <p note={note} /> + </Expandable> + ))} + </div> + ) +} +``` +```js +// Client Component +"use client" + +export default function Expandable({children}) { + const [expanded, setExpanded] = useState(false); + return ( + <div> + <button + onClick={() => setExpanded(!expanded)} + > + Toggle + </button> + {expanded && children} + </div> + ) +} +``` + +This works by first rendering `Notes` as a Server Component, and then instructing the bundler to create a bundle for the Client Component `Expandable`. In the browser, the Client Components will see output of the Server Components passed as props: + +```js +<head> + <!-- the bundle for Client Components --> + <script src="bundle.js" /> +</head> +<body> + <div> + <Expandable key={1}> + <p>this is the first note</p> + </Expandable> + <Expandable key={2}> + <p>this is the second note</p> + </Expandable> + <!--...--> + </div> +</body> +``` + +### Async components with Server Components {/*async-components-with-server-components*/} + +Server Components introduce a new way to write Components using async/await. When you `await` in an async component, React will suspend and wait for the promise to resolve before resuming rendering. This works across server/client boundaries with streaming support for Suspense. + +You can even create a promise on the server, and await it on the client: + +```js +// Server Component +import db from './database'; + +async function Page({id}) { + // Will suspend the Server Component. + const note = await db.notes.get(id); + + // NOTE: not awaited, will start here and await on the client. + const commentsPromise = db.comments.get(note.id); + return ( + <div> + {note} + <Suspense fallback={<p>Loading Comments...</p>}> + <Comments commentsPromise={commentsPromise} /> + </Suspense> + </div> + ); +} +``` + +```js +// Client Component +"use client"; +import {use} from 'react'; + +function Comments({commentsPromise}) { + // NOTE: this will resume the promise from the server. + // It will suspend until the data is available. + const comments = use(commentsPromise); + return comments.map(commment => <p>{comment}</p>); +} +``` + +The `note` content is important data for the page to render, so we `await` it on the server. The comments are below the fold and lower-priority, so we start the promise on the server, and wait for it on the client with the `use` API. This will Suspend on the client, without blocking the `note` content from rendering. + +Since async components are [not supported on the client](#why-cant-i-use-async-components-on-the-client), we await the promise with `use`. diff --git a/src/content/reference/react/use-client.md b/src/content/reference/rsc/use-client.md similarity index 99% rename from src/content/reference/react/use-client.md rename to src/content/reference/rsc/use-client.md index 07c2e9893..513578df9 100644 --- a/src/content/reference/react/use-client.md +++ b/src/content/reference/rsc/use-client.md @@ -269,12 +269,12 @@ Serializable props include: * [TypedArray](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray) and [ArrayBuffer](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer) * [Date](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date) * Plain [objects](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object): those created with [object initializers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Object_initializer), with serializable properties -* Functions that are [Server Actions](/reference/react/use-server) +* Functions that are [Server Actions](/reference/rsc/use-server) * Client or Server Component elements (JSX) * [Promises](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise) Notably, these are not supported: -* [Functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) that are not exported from client-marked modules or marked with [`'use server'`](/reference/react/use-server) +* [Functions](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function) that are not exported from client-marked modules or marked with [`'use server'`](/reference/rsc/use-server) * [Classes](https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Objects/Classes_in_JavaScript) * Objects that are instances of any class (other than the built-ins mentioned) or objects with [a null prototype](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object#null-prototype_objects) * Symbols not registered globally, ex. `Symbol('my new symbol')` diff --git a/src/content/reference/react/use-server.md b/src/content/reference/rsc/use-server.md similarity index 93% rename from src/content/reference/react/use-server.md rename to src/content/reference/rsc/use-server.md index 3c93564f8..8f3a0095b 100644 --- a/src/content/reference/react/use-server.md +++ b/src/content/reference/rsc/use-server.md @@ -41,7 +41,7 @@ Instead of individually marking functions with `'use server'`, you can add the d #### Caveats {/*caveats*/} * `'use server'` must be at the very beginning of their function or module; above any other code including imports (comments above directives are OK). They must be written with single or double quotes, not backticks. * `'use server'` can only be used in server-side files. The resulting Server Actions can be passed to Client Components through props. See supported [types for serialization](#serializable-parameters-and-return-values). -* To import a Server Action from [client code](/reference/react/use-client), the directive must be used on a module level. +* To import a Server Action from [client code](/reference/rsc/use-client), the directive must be used on a module level. * Because the underlying network calls are always asynchronous, `'use server'` can only be used on async functions. * Always treat arguments to Server Actions as untrusted input and authorize any mutations. See [security considerations](#security). * Server Actions should be called in a [Transition](/reference/react/useTransition). Server Actions passed to [`<form action>`](/reference/react-dom/components/form#props) or [`formAction`](/reference/react-dom/components/input#props) will automatically be called in a transition. @@ -95,7 +95,7 @@ Notably, these are not supported: * Symbols not registered globally, ex. `Symbol('my new symbol')` -Supported serializable return values are the same as [serializable props](/reference/react/use-client#passing-props-from-server-to-client-components) for a boundary Client Component. +Supported serializable return values are the same as [serializable props](/reference/rsc/use-client#passing-props-from-server-to-client-components) for a boundary Client Component. ## Usage {/*usage*/} @@ -133,7 +133,7 @@ By passing a Server Action to the form `action`, React can [progressively enhanc In the username request form, there might be the chance that a username is not available. `requestUsername` should tell us if it fails or not. -To update the UI based on the result of a Server Action while supporting progressive enhancement, use [`useFormState`](/reference/react-dom/hooks/useFormState). +To update the UI based on the result of a Server Action while supporting progressive enhancement, use [`useActionState`](/reference/react/useActionState). ```js // requestUsername.js @@ -153,11 +153,11 @@ export default async function requestUsername(formData) { // UsernameForm.js 'use client'; -import { useFormState } from 'react-dom'; +import { useActionState } from 'react'; import requestUsername from './requestUsername'; function UsernameForm() { - const [returnValue, action] = useFormState(requestUsername, 'n/a'); + const [state, action] = useActionState(requestUsername, null, 'n/a'); return ( <> @@ -165,13 +165,13 @@ function UsernameForm() { <input type="text" name="username" /> <button type="submit">Request</button> </form> - <p>Last submission request returned: {returnValue}</p> + <p>Last submission request returned: {state}</p> </> ); } ``` -Note that like most Hooks, `useFormState` can only be called in <CodeStep step={1}>[client code](/reference/react/use-client)</CodeStep>. +Note that like most Hooks, `useActionState` can only be called in <CodeStep step={1}>[client code](/reference/rsc/use-client)</CodeStep>. ### Calling a Server Action outside of `<form>` {/*calling-a-server-action-outside-of-form*/} diff --git a/src/content/reference/rules/components-and-hooks-must-be-pure.md b/src/content/reference/rules/components-and-hooks-must-be-pure.md index 686006396..733597c63 100644 --- a/src/content/reference/rules/components-and-hooks-must-be-pure.md +++ b/src/content/reference/rules/components-and-hooks-must-be-pure.md @@ -16,7 +16,7 @@ This reference page covers advanced topics and requires familiarity with the con One of the key concepts that makes React, _React_ is _purity_. A pure component or hook is one that is: -* **Idempotent** – You [always get the same result everytime](/learn/keeping-components-pure#purity-components-as-formulas) you run it with the same inputs – props, state, context for component inputs; and arguments for hook inputs. +* **Idempotent** – You [always get the same result every time](/learn/keeping-components-pure#purity-components-as-formulas) you run it with the same inputs – props, state, context for component inputs; and arguments for hook inputs. * **Has no side effects in render** – Code with side effects should run [**separately from rendering**](#how-does-react-run-your-code). For example as an [event handler](/learn/responding-to-events) – where the user interacts with the UI and causes it to update; or as an [Effect](/reference/react/useEffect) – which runs after render. * **Does not mutate non-local values**: Components and Hooks should [never modify values that aren't created locally](#mutation) in render. @@ -70,7 +70,7 @@ function Dropdown() { ## Components and Hooks must be idempotent {/*components-and-hooks-must-be-idempotent*/} -Components must always return the same output with respect to their inputs – props, state, and context. This is known as _idempotency_. [Idempotency](https://en.wikipedia.org/wiki/Idempotence) is a term popularized in functional programming. It refers to the idea that you [always get the same result everytime](learn/keeping-components-pure) you run that piece of code with the same inputs. +Components must always return the same output with respect to their inputs – props, state, and context. This is known as _idempotency_. [Idempotency](https://en.wikipedia.org/wiki/Idempotence) is a term popularized in functional programming. It refers to the idea that you [always get the same result every time](learn/keeping-components-pure) you run that piece of code with the same inputs. This means that _all_ code that runs [during render](#how-does-react-run-your-code) must also be idempotent in order for this rule to hold. For example, this line of code is not idempotent (and therefore, neither is the component): diff --git a/src/content/versions.md b/src/content/versions.md new file mode 100644 index 000000000..fe0dac559 --- /dev/null +++ b/src/content/versions.md @@ -0,0 +1,282 @@ +--- +title: React Versions +--- + +<Intro> + +The React docs at [react.dev](https://react.dev) provide documentation for the latest version of React. + +</Intro> + +We aim to keep the docs updated within major versions, and do not publish versions for each minor or patch version. When a new major is released, we archive the docs for the previous version as `x.react.dev`. See our [versioning policy](/community/versioning-policy) for more info. + +You can find an archive of previous major versions below. +## Future versions {/*future-versions*/} + +- [19.react.dev](https://19.react.dev) {/*docs-19*/} + +## Previous versions {/*previous-versions*/} + +- [18.react.dev](https://18.react.dev) {/*docs-18*/} +- [17.react.dev](https://17.react.dev) {/*docs-17*/} +- [16.react.dev](https://16.react.dev) {/*docs-16*/} +- [15.react.dev](https://15.react.dev) {/*docs-15*/} + +<Note> + +#### Legacy Docs {/*legacy-docs*/} + +In 2023, we [launched our new docs](/blog/2023/03/16/introducing-react-dev) for React 18 as [react.dev](https://react.dev). The legacy React 18 docs are available at [legacy.reactjs.org](https://legacy.reactjs.org). Versions 17 and below are hosted on legacy sites. + +For versions older than React 15, see [15.react.dev](https://15.react.dev). + +</Note> + +## Changelog {/*changelog*/} + +### React 18 {/*react-18*/} + +**Blog Posts** +- [React v18.0](/blog/2022/03/29/react-v18) +- [How to Upgrade to React 18](/blog/2022/03/08/react-18-upgrade-guide) +- [The Plan for React 18](/blog/2021/06/08/the-plan-for-react-18) + +**Talks** +- [React 18 Keynote](https://www.youtube.com/watch?v=FZ0cG47msEk) +- [React 18 for app developers](https://www.youtube.com/watch?v=ytudH8je5ko) +- [Streaming Server Rendering with Suspense](https://www.youtube.com/watch?v=pj5N-Khihgc) +- [React without memo](https://www.youtube.com/watch?v=lGEMwh32soc) +- [React Docs Keynote](https://www.youtube.com/watch?v=mneDaMYOKP8) +- [React Developer Tooling](https://www.youtube.com/watch?v=oxDfrke8rZg) +- [The first React Working Group](https://www.youtube.com/watch?v=qn7gRClrC9U) +- [React 18 for External Store Libraries](https://www.youtube.com/watch?v=oPfSC5bQPR8) + +**Releases** +- [v18.3.1 (April, 2024)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1831-april-26-2024) +- [v18.3.0 (April, 2024)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1830-april-25-2024) +- [v18.2.0 (June, 2022)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1820-june-14-2022) +- [v18.1.0 (April, 2022)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1810-april-26-2022) +- [v18.0.0 (March 2022)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1800-march-29-2022) + +### React 17 {/*react-17*/} + +**Blog Posts** +- [React v17.0](https://legacy.reactjs.org/blog/2020/10/20/react-v17.html) +- [Introducing the New JSX Transform](https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html) +- [React v17.0 Release Candidate: No New Features](https://legacy.reactjs.org/blog/2020/08/10/react-v17-rc.html) + +**Releases** +- [v17.0.2 (March 2021)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1702-march-22-2021) +- [v17.0.1 (October 2020)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1701-october-22-2020) +- [v17.0.0 (October 2020)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1700-october-20-2020) + +### React 16 {/*react-16*/} + +**Blog Posts** +- [React v16.0](https://legacy.reactjs.org/blog/2017/09/26/react-v16.0.html) +- [DOM Attributes in React 16](https://legacy.reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html) +- [Error Handling in React 16](https://legacy.reactjs.org/blog/2017/07/26/error-handling-in-react-16.html) +- [React v16.2.0: Improved Support for Fragments](https://legacy.reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html) +- [React v16.4.0: Pointer Events](https://legacy.reactjs.org/blog/2018/05/23/react-v-16-4.html) +- [React v16.4.2: Server-side vulnerability fix](https://legacy.reactjs.org/blog/2018/08/01/react-v-16-4-2.html) +- [React v16.6.0: lazy, memo and contextType](https://legacy.reactjs.org/blog/2018/10/23/react-v-16-6.html) +- [React v16.7: No, This Is Not the One With Hooks](https://legacy.reactjs.org/blog/2018/12/19/react-v-16-7.html) +- [React v16.8: The One With Hooks](https://legacy.reactjs.org/blog/2019/02/06/react-v16.8.0.html) +- [React v16.9.0 and the Roadmap Update](https://legacy.reactjs.org/blog/2019/08/08/react-v16.9.0.html) +- [React v16.13.0](https://legacy.reactjs.org/blog/2020/02/26/react-v16.13.0.html) + +**Releases** +- [v16.14.0 (October 2020)](https://github.com/facebook/react/blob/main/CHANGELOG.md#16140-october-14-2020) +- [v16.13.1 (March 2020)](https://github.com/facebook/react/blob/main/CHANGELOG.md#16131-march-19-2020) +- [v16.13.0 (February 2020)](https://github.com/facebook/react/blob/main/CHANGELOG.md#16130-february-26-2020) +- [v16.12.0 (November 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#16120-november-14-2019) +- [v16.11.0 (October 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#16110-october-22-2019) +- [v16.10.2 (October 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#16102-october-3-2019) +- [v16.10.1 (September 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#16101-september-28-2019) +- [v16.10.0 (September 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#16100-september-27-2019) +- [v16.9.0 (August 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1690-august-8-2019) +- [v16.8.6 (March 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1686-march-27-2019) +- [v16.8.5 (March 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1685-march-22-2019) +- [v16.8.4 (March 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1684-march-5-2019) +- [v16.8.3 (February 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1683-february-21-2019) +- [v16.8.2 (February 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1682-february-14-2019) +- [v16.8.1 (February 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1681-february-6-2019) +- [v16.8.0 (February 2019)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1680-february-6-2019) +- [v16.7.0 (December 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1670-december-19-2018) +- [v16.6.3 (November 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1663-november-12-2018) +- [v16.6.2 (November 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1662-november-12-2018) +- [v16.6.1 (November 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1661-november-6-2018) +- [v16.6.0 (October 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1660-october-23-2018) +- [v16.5.2 (September 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1652-september-18-2018) +- [v16.5.1 (September 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1651-september-13-2018) +- [v16.5.0 (September 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1650-september-5-2018) +- [v16.4.2 (August 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1642-august-1-2018) +- [v16.4.1 (June 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1641-june-13-2018) +- [v16.4.0 (May 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1640-may-23-2018) +- [v16.3.3 (August 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1633-august-1-2018) +- [v16.3.2 (April 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1632-april-16-2018) +- [v16.3.1 (April 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1631-april-3-2018) +- [v16.3.0 (March 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1630-march-29-2018) +- [v16.2.1 (August 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1621-august-1-2018) +- [v16.2.0 (November 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1620-november-28-2017) +- [v16.1.2 (August 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1612-august-1-2018) +- [v16.1.1 (November 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1611-november-13-2017) +- [v16.1.0 (November 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1610-november-9-2017) +- [v16.0.1 (August 2018)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1601-august-1-2018) +- [v16.0 (September 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1600-september-26-2017) + +### React 15 {/*react-15*/} + +**Blog Posts** +- [React v15.0](https://legacy.reactjs.org/blog/2016/04/07/react-v15.html) +- [React v15.0 Release Candidate 2](https://legacy.reactjs.org/blog/2016/03/16/react-v15-rc2.html) +- [React v15.0 Release Candidate](https://legacy.reactjs.org/blog/2016/03/07/react-v15-rc1.html) +- [New Versioning Scheme](https://legacy.reactjs.org/blog/2016/02/19/new-versioning-scheme.html) +- [Discontinuing IE 8 Support in React DOM](https://legacy.reactjs.org/blog/2016/01/12/discontinuing-ie8-support.html) +- [Introducing React's Error Code System](https://legacy.reactjs.org/blog/2016/07/11/introducing-reacts-error-code-system.html) +- [React v15.0.1](https://legacy.reactjs.org/blog/2016/04/08/react-v15.0.1.html) +- [React v15.4.0](https://legacy.reactjs.org/blog/2016/11/16/react-v15.4.0.html) +- [React v15.5.0](https://legacy.reactjs.org/blog/2017/04/07/react-v15.5.0.html) +- [React v15.6.0](https://legacy.reactjs.org/blog/2017/06/13/react-v15.6.0.html) +- [React v15.6.2](https://legacy.reactjs.org/blog/2017/09/25/react-v15.6.2.html) + +**Releases** +- [v15.7.0 (October 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1570-october-14-2020) +- [v15.6.2 (September 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1562-september-25-2017) +- [v15.6.1 (June 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1561-june-14-2017) +- [v15.6.0 (June 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1560-june-13-2017) +- [v15.5.4 (April 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1554-april-11-2017) +- [v15.5.3 (April 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1553-april-7-2017) +- [v15.5.2 (April 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1552-april-7-2017) +- [v15.5.1 (April 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1551-april-7-2017) +- [v15.5.0 (April 2017)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1550-april-7-2017) +- [v15.4.2 (January 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1542-january-6-2017) +- [v15.4.1 (November 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1541-november-22-2016) +- [v15.4.0 (November 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1540-november-16-2016) +- [v15.3.2 (September 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1532-september-19-2016) +- [v15.3.1 (August 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1531-august-19-2016) +- [v15.3.0 (July 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1530-july-29-2016) +- [v15.2.1 (July 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1521-july-8-2016) +- [v15.2.0 (July 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1520-july-1-2016) +- [v15.1.0 (May 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1510-may-20-2016) +- [v15.0.2 (April 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1502-april-29-2016) +- [v15.0.1 (April 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1501-april-8-2016) +- [v15.0.0 (April 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#1500-april-7-2016) + +### React 0.14 {/*react-14*/} + +**Blog Posts** +- [React v0.14](https://legacy.reactjs.org/blog/2015/10/07/react-v0.14.html) +- [React v0.14 Release Candidate](https://legacy.reactjs.org/blog/2015/09/10/react-v0.14-rc1.html) +- [React v0.14 Beta 1](https://legacy.reactjs.org/blog/2015/07/03/react-v0.14-beta-1.html) +- [New React Developer Tools](https://legacy.reactjs.org/blog/2015/09/02/new-react-developer-tools.html) +- [New React Devtools Beta](https://legacy.reactjs.org/blog/2015/08/03/new-react-devtools-beta.html) +- [React v0.14.1](https://legacy.reactjs.org/blog/2015/10/28/react-v0.14.1.html) +- [React v0.14.2](https://legacy.reactjs.org/blog/2015/11/02/react-v0.14.2.html) +- [React v0.14.3](https://legacy.reactjs.org/blog/2015/11/18/react-v0.14.3.html) +- [React v0.14.4](https://legacy.reactjs.org/blog/2015/12/29/react-v0.14.4.html) +- [React v0.14.8](https://legacy.reactjs.org/blog/2016/03/29/react-v0.14.8.html) + +**Releases** +- [v0.14.10 (October 2020)](https://github.com/facebook/react/blob/main/CHANGELOG.md#01410-october-14-2020) +- [v0.14.8 (March 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0148-march-29-2016) +- [v0.14.7 (January 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0147-january-28-2016) +- [v0.14.6 (January 2016)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0146-january-6-2016) +- [v0.14.5 (December 2015)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0145-december-29-2015) +- [v0.14.4 (December 2015)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0144-december-29-2015) +- [v0.14.3 (November 2015)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0143-november-18-2015) +- [v0.14.2 (November 2015)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0142-november-2-2015) +- [v0.14.1 (October 2015)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0141-october-28-2015) +- [v0.14.0 (October 2015)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0140-october-7-2015) + +### React 0.13 {/*react-13*/} + +**Blog Posts** +- [React Native v0.4](https://legacy.reactjs.org/blog/2015/04/17/react-native-v0.4.html) +- [React v0.13](https://legacy.reactjs.org/blog/2015/03/10/react-v0.13.html) +- [React v0.13 RC2](https://legacy.reactjs.org/blog/2015/03/03/react-v0.13-rc2.html) +- [React v0.13 RC](https://legacy.reactjs.org/blog/2015/02/24/react-v0.13-rc1.html) +- [React v0.13.0 Beta 1](https://legacy.reactjs.org/blog/2015/01/27/react-v0.13.0-beta-1.html) +- [Streamlining React Elements](https://legacy.reactjs.org/blog/2015/02/24/streamlining-react-elements.html) +- [Introducing Relay and GraphQL](https://legacy.reactjs.org/blog/2015/02/20/introducing-relay-and-graphql.html) +- [Introducing React Native](https://legacy.reactjs.org/blog/2015/03/26/introducing-react-native.html) +- [React v0.13.1](https://legacy.reactjs.org/blog/2015/03/16/react-v0.13.1.html) +- [React v0.13.2](https://legacy.reactjs.org/blog/2015/04/18/react-v0.13.2.html) +- [React v0.13.3](https://legacy.reactjs.org/blog/2015/05/08/react-v0.13.3.html) + +**Releases** +- [v0.13.3 (May 2015)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0133-may-8-2015) +- [v0.13.2 (April 2015)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0132-april-18-2015) +- [v0.13.1 (March 2015)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0131-march-16-2015) +- [v0.13.0 (March 2015)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0130-march-10-2015) + +### React 0.12 {/*react-12*/} + +**Blog Posts** +- [React v0.12](https://legacy.reactjs.org/blog/2014/10/28/react-v0.12.html) +- [React v0.12 RC](https://legacy.reactjs.org/blog/2014/10/16/react-v0.12-rc1.html) +- [Introducing React Elements](https://legacy.reactjs.org/blog/2014/10/14/introducing-react-elements.html) +- [React v0.12.2](https://legacy.reactjs.org/blog/2014/12/18/react-v0.12.2.html) + +**Releases** +- [v0.12.2 (December 2014)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0122-december-18-2014) +- [v0.12.1 (November 2014)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0121-november-18-2014) +- [v0.12.0 (October 2014)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0120-october-28-2014) + +### React 0.11 {/*react-11*/} + +**Blog Posts** +- [React v0.11](https://legacy.reactjs.org/blog/2014/07/17/react-v0.11.html) +- [React v0.11 RC](https://legacy.reactjs.org/blog/2014/07/13/react-v0.11-rc1.html) +- [One Year of Open-Source React](https://legacy.reactjs.org/blog/2014/05/29/one-year-of-open-source-react.html) +- [The Road to 1.0](https://legacy.reactjs.org/blog/2014/03/28/the-road-to-1.0.html) +- [React v0.11.1](https://legacy.reactjs.org/blog/2014/07/25/react-v0.11.1.html) +- [React v0.11.2](https://legacy.reactjs.org/blog/2014/09/16/react-v0.11.2.html) +- [Introducing the JSX Specificaion](https://legacy.reactjs.org/blog/2014/09/03/introducing-the-jsx-specification.html) + +**Releases** +- [v0.11.2 (September 2014)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0112-september-16-2014) +- [v0.11.1 (July 2014)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0111-july-24-2014) +- [v0.11.0 (July 2014)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0110-july-17-2014) + +### React 0.10 and below {/*react-10-and-below*/} + +**Blog Posts** +- [React v0.10](https://legacy.reactjs.org/blog/2014/03/21/react-v0.10.html) +- [React v0.10 RC](https://legacy.reactjs.org/blog/2014/03/19/react-v0.10-rc1.html) +- [React v0.9](https://legacy.reactjs.org/blog/2014/02/20/react-v0.9.html) +- [React v0.9 RC](https://legacy.reactjs.org/blog/2014/02/16/react-v0.9-rc1.html) +- [React Chrome Developer Tools](https://legacy.reactjs.org/blog/2014/01/02/react-chrome-developer-tools.html) +- [React v0.8](https://legacy.reactjs.org/blog/2013/12/19/react-v0.8.0.html) +- [React v0.5.2, v0.4.2](https://legacy.reactjs.org/blog/2013/12/18/react-v0.5.2-v0.4.2.html) +- [React v0.5.1](https://legacy.reactjs.org/blog/2013/10/29/react-v0-5-1.html) +- [React v0.5](https://legacy.reactjs.org/blog/2013/10/16/react-v0.5.0.html) +- [React v0.4.1](https://legacy.reactjs.org/blog/2013/07/26/react-v0-4-1.html) +- [React v0.4.0](https://legacy.reactjs.org/blog/2013/07/17/react-v0-4-0.html) +- [New in React v0.4: Prop Validation and Default Values](https://legacy.reactjs.org/blog/2013/07/11/react-v0-4-prop-validation-and-default-values.html) +- [New in React v0.4: Autobind by Default](https://legacy.reactjs.org/blog/2013/07/02/react-v0-4-autobind-by-default.html) +- [React v0.3.3](https://legacy.reactjs.org/blog/2013/07/02/react-v0-4-autobind-by-default.html) + +**Releases** +- [v0.10.0 (March 2014)](https://github.com/facebook/react/blob/main/CHANGELOG.md#0100-march-21-2014) +- [v0.9.0 (February 2014)](https://github.com/facebook/react/blob/main/CHANGELOG.md#090-february-20-2014) +- [v0.8.0 (December 2013)](https://github.com/facebook/react/blob/main/CHANGELOG.md#080-december-19-2013) +- [v0.5.2 (December 2013)](https://github.com/facebook/react/blob/main/CHANGELOG.md#052-042-december-18-2013) +- [v0.5.1 (October 2013)](https://github.com/facebook/react/blob/main/CHANGELOG.md#051-october-29-2013) +- [v0.5.0 (October 2013)](https://github.com/facebook/react/blob/main/CHANGELOG.md#050-october-16-2013) +- [v0.4.1 (July 2013)](https://github.com/facebook/react/blob/main/CHANGELOG.md#041-july-26-2013) +- [v0.4.0 (July 2013)](https://github.com/facebook/react/blob/main/CHANGELOG.md#040-july-17-2013) +- [v0.3.3 (June 2013)](https://github.com/facebook/react/blob/main/CHANGELOG.md#033-june-20-2013) +- [v0.3.2 (May 2013)](https://github.com/facebook/react/blob/main/CHANGELOG.md#032-may-31-2013) +- [v0.3.1 (May 2013)](https://github.com/facebook/react/blob/main/CHANGELOG.md#031-may-30-2013) +- [v0.3.0 (May 2013)](https://github.com/facebook/react/blob/main/CHANGELOG.md#031-may-30-2013) + +### Initial Commit {/*initial-commit*/} + +React was open-sourced on May 29, 2013. The initial commit is: [`75897c`: Initial public release](https://github.com/facebook/react/commit/75897c2dcd1dd3a6ca46284dd37e13d22b4b16b4) + +See the first blog post: [Why did we build React?](https://legacy.reactjs.org/blog/2013/06/05/why-react.html) + +React was open sourced at Facebook Seattle in 2013: + +<iframe width="560" height="315" src="https://www.youtube.com/embed/XxVg_s8xAms?si=466vSJrnXTn05j9A" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe> diff --git a/src/content/warnings/react-test-renderer.md b/src/content/warnings/react-test-renderer.md index 7926922d1..cc78f1fb1 100644 --- a/src/content/warnings/react-test-renderer.md +++ b/src/content/warnings/react-test-renderer.md @@ -6,7 +6,7 @@ title: react-test-renderer Deprecation Warnings react-test-renderer is deprecated. A warning will fire whenever calling ReactTestRenderer.create() or ReactShallowRender.render(). The react-test-renderer package will remain available on NPM but will not be maintained and may break with new React features or changes to React's internals. -The React Team recommends migrating your tests to [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) or [@testing-library/react-native](https://callstack.github.io/react-native-testing-library/docs/getting-started) for a modern and well supported testing experience. +The React Team recommends migrating your tests to [@testing-library/react](https://testing-library.com/docs/react-testing-library/intro/) or [@testing-library/react-native](https://callstack.github.io/react-native-testing-library/docs/start/intro) for a modern and well supported testing experience. ## new ShallowRenderer() warning {/*new-shallowrenderer-warning*/} diff --git a/src/pages/[[...markdownPath]].js b/src/pages/[[...markdownPath]].js index 76532361b..63fcfcc81 100644 --- a/src/pages/[[...markdownPath]].js +++ b/src/pages/[[...markdownPath]].js @@ -12,7 +12,9 @@ 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}) { +import {generateRssFeed} from '../utils/rss'; + +export default function Layout({content, toc, meta, languages}) { const parsedContent = useMemo( () => JSON.parse(content, reviveNodeOnClient), [content] @@ -39,7 +41,12 @@ export default function Layout({content, toc, meta}) { break; } return ( - <Page toc={parsedToc} routeTree={routeTree} meta={meta} section={section}> + <Page + toc={parsedToc} + routeTree={routeTree} + meta={meta} + section={section} + languages={languages}> {parsedContent} </Page> ); @@ -96,6 +103,7 @@ function reviveNodeOnClient(key, val) { // Put MDX output into JSON for client. export async function getStaticProps(context) { + generateRssFeed(); const fs = require('fs'); const rootDir = process.cwd() + '/src/content/'; @@ -108,12 +116,13 @@ export async function getStaticProps(context) { mdx = fs.readFileSync(rootDir + path + '/index.md', 'utf8'); } - const {toc, content, meta} = await compileMDX(mdx, path, {}); + const {toc, content, meta, languages} = await compileMDX(mdx, path, {}); return { props: { toc, content, meta, + languages, }, }; } diff --git a/src/pages/_document.tsx b/src/pages/_document.tsx index 823ca0b71..6849df35d 100644 --- a/src/pages/_document.tsx +++ b/src/pages/_document.tsx @@ -9,6 +9,27 @@ const MyDocument = () => { return ( <Html lang={siteConfig.languageCode} dir={siteConfig.isRTL ? 'rtl' : 'ltr'}> <Head /> + <link + rel="apple-touch-icon" + sizes="180x180" + href="/apple-touch-icon.png" + /> + <link + rel="icon" + type="image/png" + sizes="32x32" + href="/favicon-32x32.png" + /> + <link + rel="icon" + type="image/png" + sizes="16x16" + href="/favicon-16x16.png" + /> + <link rel="manifest" href="/site.webmanifest" /> + <link rel="mask-icon" href="/safari-pinned-tab.svg" color="#404756" /> + <meta name="msapplication-TileColor" content="#2b5797" /> + <meta name="theme-color" content="#23272f" /> <script async src={`https://www.googletagmanager.com/gtag/js?id=${process.env.NEXT_PUBLIC_GA_TRACKING_ID}`} @@ -19,6 +40,63 @@ const MyDocument = () => { }} /> <body className="font-text font-medium antialiased text-lg bg-wash dark:bg-wash-dark text-secondary dark:text-secondary-dark leading-base"> + <script + dangerouslySetInnerHTML={{ + __html: ` + (function () { + try { + let logShown = false; + function setUwu(isUwu) { + try { + if (isUwu) { + localStorage.setItem('uwu', true); + document.documentElement.classList.add('uwu'); + if (!logShown) { + console.log('uwu mode! turn off with ?uwu=0'); + console.log('logo credit to @sawaratsuki1004 via https://github.com/SAWARATSUKI/ServiceLogos'); + logShown = true; + } + } else { + localStorage.removeItem('uwu'); + document.documentElement.classList.remove('uwu'); + console.log('uwu mode off. turn on with ?uwu'); + } + } catch (err) { } + } + window.__setUwu = setUwu; + function checkQueryParam() { + const params = new URLSearchParams(window.location.search); + const value = params.get('uwu'); + switch(value) { + case '': + case 'true': + case '1': + return true; + case 'false': + case '0': + return false; + default: + return null; + } + } + function checkLocalStorage() { + try { + return localStorage.getItem('uwu') === 'true'; + } catch (err) { + return false; + } + } + const uwuQueryParam = checkQueryParam(); + if (uwuQueryParam != null) { + setUwu(uwuQueryParam); + } else if (checkLocalStorage()) { + document.documentElement.classList.add('uwu'); + } + } catch (err) { } + })(); + `, + }} + /> <script dangerouslySetInnerHTML={{ __html: ` diff --git a/src/pages/errors/[errorCode].tsx b/src/pages/errors/[errorCode].tsx index f1a54a3d6..519af3b07 100644 --- a/src/pages/errors/[errorCode].tsx +++ b/src/pages/errors/[errorCode].tsx @@ -36,7 +36,7 @@ export default function ErrorDecoderPage({ }} routeTree={sidebarLearn as RouteItem} section="unknown"> - {parsedContent} + <div className="whitespace-pre-line">{parsedContent}</div> {/* <MaxWidth> <P> We highly recommend using the development build locally when debugging diff --git a/src/sidebarBlog.json b/src/sidebarBlog.json index f90d23efe..26dcdc4dd 100644 --- a/src/sidebarBlog.json +++ b/src/sidebarBlog.json @@ -11,6 +11,27 @@ "path": "/blog", "skipBreadcrumb": true, "routes": [ + { + "title": "React Conf 2024 Recap", + "titleForHomepage": "React Conf 2024 Recap", + "icon": "blog", + "date": "May 22, 2024", + "path": "/blog/2024/05/22/react-conf-2024-recap" + }, + { + "title": "React 19 RC", + "titleForHomepage": "React 19 RC", + "icon": "blog", + "date": "April 25, 2024", + "path": "/blog/2024/04/25/react-19" + }, + { + "title": "React 19 RC Upgrade Guide", + "titleForHomepage": "React 19 RC Upgrade Guide", + "icon": "blog", + "date": "April 25, 2024", + "path": "/blog/2024/04/25/react-19-upgrade-guide" + }, { "title": "React Labs: What We've Been Working On – February 2024", "titleForHomepage": "React Labs: February 2024", diff --git a/src/sidebarCommunity.json b/src/sidebarCommunity.json index 6b3e4eca3..ac8a172d5 100644 --- a/src/sidebarCommunity.json +++ b/src/sidebarCommunity.json @@ -31,6 +31,10 @@ "title": "Docs Contributors", "path": "/community/docs-contributors" }, + { + "title": "Translations", + "path": "/community/translations" + }, { "title": "Acknowledgements", "path": "/community/acknowledgements" diff --git a/src/sidebarLearn.json b/src/sidebarLearn.json index 02806dc0c..335fc3556 100644 --- a/src/sidebarLearn.json +++ b/src/sidebarLearn.json @@ -43,6 +43,11 @@ { "title": "React Developer Tools", "path": "/learn/react-developer-tools" + }, + { + "title": "React Compiler", + "path": "/learn/react-compiler", + "canary": true } ] }, @@ -194,7 +199,7 @@ }, { "title": "Removing Effect Dependencies", - "path": "/learn/removing-effect-dependencies" + "path": "/learn/removing-effect-dependencies" }, { "title": "Reusing Logic with Custom Hooks", diff --git a/src/sidebarReference.json b/src/sidebarReference.json index 2b13649fa..50e0a3dff 100644 --- a/src/sidebarReference.json +++ b/src/sidebarReference.json @@ -4,7 +4,7 @@ "routes": [ { "hasSectionHeader": true, - "sectionHeader": "react@18.2.0" + "sectionHeader": "react@{{version}}" }, { "title": "Overview", @@ -15,8 +15,8 @@ "path": "/reference/react/hooks", "routes": [ { - "title": "use", - "path": "/reference/react/use", + "title": "useActionState", + "path": "/reference/react/useActionState", "canary": true }, { @@ -112,6 +112,10 @@ "title": "APIs", "path": "/reference/react/apis", "routes": [ + { + "title": "act", + "path": "/reference/react/act" + }, { "title": "cache", "path": "/reference/react/cache", @@ -137,6 +141,11 @@ "title": "startTransition", "path": "/reference/react/startTransition" }, + { + "title": "use", + "path": "/reference/react/use", + "canary": true + }, { "title": "experimental_taintObjectReference", "path": "/reference/react/experimental_taintObjectReference", @@ -149,36 +158,14 @@ } ] }, - { - "title": "Directives", - "path": "/reference/react/directives", - "canary": true, - "routes": [ - { - "title": "'use client'", - "path": "/reference/react/use-client", - "canary": true - }, - { - "title": "'use server'", - "path": "/reference/react/use-server", - "canary": true - } - ] - }, { "hasSectionHeader": true, - "sectionHeader": "react-dom@18.2.0" + "sectionHeader": "react-dom@{{version}}" }, { "title": "Hooks", "path": "/reference/react-dom/hooks", "routes": [ - { - "title": "useFormState", - "path": "/reference/react-dom/hooks/useFormState", - "canary": true - }, { "title": "useFormStatus", "path": "/reference/react-dom/hooks/useFormStatus", @@ -356,19 +343,52 @@ }, { "title": "Overview", - "path": "/reference/rules" + "path": "/reference/rules", + "routes": [ + { + "title": "Components and Hooks must be pure", + "path": "/reference/rules/components-and-hooks-must-be-pure" + }, + { + "title": "React calls Components and Hooks", + "path": "/reference/rules/react-calls-components-and-hooks" + }, + { + "title": "Rules of Hooks", + "path": "/reference/rules/rules-of-hooks" + } + ] + }, + { + "hasSectionHeader": true, + "sectionHeader": "React Server Components" }, { - "title": "Components and Hooks must be pure", - "path": "/reference/rules/components-and-hooks-must-be-pure" + "title": "Server Components", + "path": "/reference/rsc/server-components", + "canary": true }, { - "title": "React calls Components and Hooks", - "path": "/reference/rules/react-calls-components-and-hooks" + "title": "Server Actions", + "path": "/reference/rsc/server-actions", + "canary": true }, { - "title": "Rules of Hooks", - "path": "/reference/rules/rules-of-hooks" + "title": "Directives", + "path": "/reference/rsc/directives", + "canary": true, + "routes": [ + { + "title": "'use client'", + "path": "/reference/rsc/use-client", + "canary": true + }, + { + "title": "'use server'", + "path": "/reference/rsc/use-server", + "canary": true + } + ] }, { "hasSectionHeader": true, @@ -413,4 +433,4 @@ ] } ] -} +} \ No newline at end of file diff --git a/src/siteConfig.js b/src/siteConfig.js index 0ada6b934..6d37e10fd 100644 --- a/src/siteConfig.js +++ b/src/siteConfig.js @@ -3,6 +3,7 @@ */ exports.siteConfig = { + version: '18.3.1', // -------------------------------------- // Translations should replace these lines: languageCode: 'en', diff --git a/src/styles/index.css b/src/styles/index.css index cfd82dc0b..281111092 100644 --- a/src/styles/index.css +++ b/src/styles/index.css @@ -450,7 +450,7 @@ } html.dark a > code { - color: #149eca !important; /* blue-40 */ + color: #58c4dc !important; /* blue-40 */ } .text-code { @@ -559,6 +559,17 @@ background: none !important; padding: 2px !important; } + +.dark .console-block code { + background: rgba(235 236 240 / 0.05) !important; + color: rgba(208, 125, 119) !important; +} + +.console-block code { + background: rgba(235 236 240 / 0.95) !important; + color: rgb(166, 66, 58) !important; +} + html.dark .code-step * { color: inherit !important; } @@ -726,3 +737,18 @@ ol.mdx-illustration-block { transition-delay: 1s; pointer-events: none; } + +.uwu-visible { + display: none; +} +.uwu-hidden { + display: flex; +} + +.uwu .uwu-visible { + display: flex; +} + +.uwu .uwu-hidden { + display: none; +} diff --git a/src/styles/sandpack.css b/src/styles/sandpack.css index d909f7221..cc236209a 100644 --- a/src/styles/sandpack.css +++ b/src/styles/sandpack.css @@ -58,7 +58,7 @@ html .sandpack { /* Dark theme */ html.dark .sp-wrapper { - --sp-colors-accent: #149eca; + --sp-colors-accent: #58c4dc; --sp-colors-clickable: #999; --sp-colors-disabled: #fff; --sp-colors-error: #811e18; @@ -593,7 +593,7 @@ html.dark .sp-devtools > div { -webkit-text-size-adjust: none; } -/** +/** * For iOS: prevent browser zoom when clicking on sandbox. * Does NOT apply to code blocks. */ diff --git a/src/utils/compileMDX.ts b/src/utils/compileMDX.ts index 5f54d12b4..4c5ee15b7 100644 --- a/src/utils/compileMDX.ts +++ b/src/utils/compileMDX.ts @@ -1,8 +1,9 @@ +import {LanguageItem} from 'components/MDX/LanguagesContext'; import {MDXComponents} from 'components/MDX/MDXComponents'; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // ~~~~ IMPORTANT: BUMP THIS IF YOU CHANGE ANY CODE BELOW ~~~ -const DISK_CACHE_BREAKER = 8; +const DISK_CACHE_BREAKER = 9; // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ export default async function compileMDX( @@ -124,10 +125,21 @@ export default async function compileMDX( const fm = require('gray-matter'); const meta = fm(mdx).data; + // Load the list of translated languages conditionally. + let languages: Array<LanguageItem> | null = null; + if (typeof path === 'string' && path.endsWith('/translations')) { + languages = await ( + await fetch( + 'https://raw.githubusercontent.com/reactjs/translations.react.dev/main/langs/langs.json' + ) + ).json(); // { code: string; name: string; enName: string}[] + } + const output = { content: JSON.stringify(children, stringifyNodeOnServer), toc: JSON.stringify(toc, stringifyNodeOnServer), meta, + languages, }; // Serialize a server React tree node to JSON. diff --git a/src/utils/finishedTranslations.ts b/src/utils/finishedTranslations.ts new file mode 100644 index 000000000..b2aceb172 --- /dev/null +++ b/src/utils/finishedTranslations.ts @@ -0,0 +1,15 @@ +// This is a list of languages with enough translated content. +// Add more languages here when they have enough content. +// Please DO NOT edit this list without a discussion in the reactjs/react.dev repo. +// It must be the same between all translations. +// This will also affect the 'Translations' article. + +// prettier-ignore +export const finishedTranslations = [ + 'en', + 'zh-hans', + 'es', + 'fr', + 'ja', + 'tr' +]; diff --git a/src/utils/rss.js b/src/utils/rss.js new file mode 100644 index 000000000..c6fb82410 --- /dev/null +++ b/src/utils/rss.js @@ -0,0 +1,82 @@ +/* + * Copyright (c) Facebook, Inc. and its affiliates. + */ +const Feed = require('rss'); +const fs = require('fs'); +const path = require('path'); +const matter = require('gray-matter'); + +const getAllFiles = function (dirPath, arrayOfFiles) { + const files = fs.readdirSync(dirPath); + + arrayOfFiles = arrayOfFiles || []; + + files.forEach(function (file) { + if (fs.statSync(dirPath + '/' + file).isDirectory()) { + arrayOfFiles = getAllFiles(dirPath + '/' + file, arrayOfFiles); + } else { + arrayOfFiles.push(path.join(dirPath, '/', file)); + } + }); + + return arrayOfFiles; +}; + +exports.generateRssFeed = function () { + const feed = new Feed({ + title: 'React Blog', + description: + 'This blog is the official source for the updates from the React team. Anything important, including release notes or deprecation notices, will be posted here first.', + feed_url: 'https://react.dev/rss.xml', + site_url: 'https://react.dev/', + language: 'en', + favicon: 'https://react.dev/favicon.ico', + pubDate: new Date(), + generator: 'react.dev rss module', + }); + + const dirPath = path.join(process.cwd(), 'src/content/blog'); + const filesByOldest = getAllFiles(dirPath); + const files = filesByOldest.reverse(); + + for (const filePath of files) { + const id = filePath.split('/').slice(-1).join(''); + if (id !== 'index.md') { + const content = fs.readFileSync(filePath, 'utf-8'); + const {data} = matter(content); + const slug = filePath.split('/').slice(-4).join('/').replace('.md', ''); + + if (data.title == null || data.title.trim() === '') { + throw new Error( + `${id}: Blog posts must include a title in the metadata, for RSS feeds` + ); + } + if (data.author == null || data.author.trim() === '') { + throw new Error( + `${id}: Blog posts must include an author in the metadata, for RSS feeds` + ); + } + if (data.date == null || data.date.trim() === '') { + throw new Error( + `${id}: Blog posts must include a date in the metadata, for RSS feeds` + ); + } + if (data.description == null || data.description.trim() === '') { + throw new Error( + `${id}: Blog posts must include a description in the metadata, for RSS feeds` + ); + } + + feed.item({ + id, + title: data.title, + author: data.author || '', + date: new Date(data.date), + url: `https://react.dev/blog/${slug}`, + description: data.description, + }); + } + } + + fs.writeFileSync('./public/rss.xml', feed.xml({indent: true})); +}; diff --git a/vercel.json b/vercel.json index 8d610c26e..eac0efb9c 100644 --- a/vercel.json +++ b/vercel.json @@ -9,6 +9,11 @@ "destination": "/reference/react", "permanent": true }, + { + "source": "/reference/react-dom/hooks/useFormState", + "destination": "/reference/react/useActionState", + "permanent": true + }, { "source": "/learn/meet-the-team", "destination": "/community/team", @@ -19,7 +24,6 @@ "destination": "/learn/rendering-lists#keeping-list-items-in-order-with-key", "permanent": false }, - { "source": "/link/invalid-hook-call", "destination": "/warnings/invalid-hook-call-warning", @@ -87,7 +91,7 @@ }, { "source": "/link/legacy-context", - "destination": "https://legacy.reactjs.org/docs/legacy-context.html", + "destination": "https://react.dev/blog/2024/04/25/react-19-upgrade-guide#removed-removing-legacy-context", "permanent": false }, { @@ -149,6 +153,36 @@ "source": "/link/setstate-in-render", "destination": "https://github.com/facebook/react/issues/18178#issuecomment-595846312", "permanent": false + }, + { + "source": "/link/new-jsx-transform", + "destination": "https://legacy.reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html", + "permanent": false + }, + { + "source": "/warnings/version-mismatch", + "destination": "/warnings/invalid-hook-call-warning#mismatching-versions-of-react-and-react-dom", + "permanent": false + }, + { + "source": "/reference/react/directives", + "destination": "/reference/rsc/directives", + "permanent": true + }, + { + "source": "/reference/react/use-client", + "destination": "/reference/rsc/use-client", + "permanent": true + }, + { + "source": "/reference/react/use-server", + "destination": "/reference/rsc/use-server", + "permanent": true + }, + { + "source": "/feed.xml", + "destination": "/rss.xml", + "permanent": true } ], "headers": [ diff --git a/yarn.lock b/yarn.lock index b20c796ef..d8da351ba 100644 --- a/yarn.lock +++ b/yarn.lock @@ -451,6 +451,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.13.10": + version "7.24.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.24.7.tgz#f4f0d5530e8dbdf59b3451b9b3e594b6ba082e12" + integrity sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -690,6 +697,33 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@floating-ui/core@^1.0.0": + version "1.6.2" + resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.6.2.tgz#d37f3e0ac1f1c756c7de45db13303a266226851a" + integrity sha512-+2XpQV9LLZeanU4ZevzRnGFg2neDeKHgFLjP6YLW+tly0IvrhqT4u8enLGjLH3qeh85g19xY5rsAusfwTdn5lg== + dependencies: + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/dom@^1.0.0": + version "1.6.5" + resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.6.5.tgz#323f065c003f1d3ecf0ff16d2c2c4d38979f4cb9" + integrity sha512-Nsdud2X65Dz+1RHjAIP0t8z5e2ff/IRbei6BqFrl1urT8sDVzM1HMQ+R0XcU5ceRfyO3I6ayeqIfh+6Wb8LGTw== + dependencies: + "@floating-ui/core" "^1.0.0" + "@floating-ui/utils" "^0.2.0" + +"@floating-ui/react-dom@^2.0.0": + version "2.1.0" + resolved "https://registry.yarnpkg.com/@floating-ui/react-dom/-/react-dom-2.1.0.tgz#4f0e5e9920137874b2405f7d6c862873baf4beff" + integrity sha512-lNzj5EQmEKn5FFKc04+zasr09h/uX8RtJRNj5gUXsSQIXHVWTVh+hVAg1vOMCexkX8EgvemMvIFpQfkosnVNyA== + dependencies: + "@floating-ui/dom" "^1.0.0" + +"@floating-ui/utils@^0.2.0": + version "0.2.2" + resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.2.2.tgz#d8bae93ac8b815b2bd7a98078cf91e2724ef11e5" + integrity sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw== + "@headlessui/react@^1.7.0": version "1.7.0" resolved "https://registry.yarnpkg.com/@headlessui/react/-/react-1.7.0.tgz#7e36e6bbc25a24b02011527ae157a000dda88b85" @@ -904,6 +938,247 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.21.tgz#5de5a2385a35309427f6011992b544514d559aa1" integrity sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g== +"@radix-ui/primitive@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/primitive/-/primitive-1.0.1.tgz#e46f9958b35d10e9f6dc71c497305c22e3e55dbd" + integrity sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-arrow@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" + integrity sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + +"@radix-ui/react-collection@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.3.tgz#9595a66e09026187524a36c6e7e9c7d286469159" + integrity sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + +"@radix-ui/react-compose-refs@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz#7ed868b66946aa6030e580b1ffca386dd4d21989" + integrity sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-context-menu@^2.1.5": + version "2.1.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context-menu/-/react-context-menu-2.1.5.tgz#1bdbd72761439f9166f75dc4598f276265785c83" + integrity sha512-R5XaDj06Xul1KGb+WP8qiOh7tKJNz2durpLBXAGZjSVtctcRFCuEvy2gtMwRJGePwQQE5nV77gs4FwRi8T+r2g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-menu" "2.0.6" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-controllable-state" "1.0.1" + +"@radix-ui/react-context@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-context/-/react-context-1.0.1.tgz#fe46e67c96b240de59187dcb7a1a50ce3e2ec00c" + integrity sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-direction@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-direction/-/react-direction-1.0.1.tgz#9cb61bf2ccf568f3421422d182637b7f47596c9b" + integrity sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-dismissable-layer@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz#3f98425b82b9068dfbab5db5fff3df6ebf48b9d4" + integrity sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-escape-keydown" "1.0.3" + +"@radix-ui/react-focus-guards@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad" + integrity sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-focus-scope@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525" + integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + +"@radix-ui/react-id@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.1.tgz#73cdc181f650e4df24f0b6a5b7aa426b912c88c0" + integrity sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-layout-effect" "1.0.1" + +"@radix-ui/react-menu@2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.0.6.tgz#2c9e093c1a5d5daa87304b2a2f884e32288ae79e" + integrity sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-collection" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-focus-guards" "1.0.1" + "@radix-ui/react-focus-scope" "1.0.4" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-popper" "1.1.3" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-roving-focus" "1.0.4" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-callback-ref" "1.0.1" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.5" + +"@radix-ui/react-popper@1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.3.tgz#24c03f527e7ac348fabf18c89795d85d21b00b42" + integrity sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w== + dependencies: + "@babel/runtime" "^7.13.10" + "@floating-ui/react-dom" "^2.0.0" + "@radix-ui/react-arrow" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-use-rect" "1.0.1" + "@radix-ui/react-use-size" "1.0.1" + "@radix-ui/rect" "1.0.1" + +"@radix-ui/react-portal@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-portal/-/react-portal-1.0.4.tgz#df4bfd353db3b1e84e639e9c63a5f2565fb00e15" + integrity sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-primitive" "1.0.3" + +"@radix-ui/react-presence@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-presence/-/react-presence-1.0.1.tgz#491990ba913b8e2a5db1b06b203cb24b5cdef9ba" + integrity sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + +"@radix-ui/react-primitive@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz#d49ea0f3f0b2fe3ab1cb5667eb03e8b843b914d0" + integrity sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-slot" "1.0.2" + +"@radix-ui/react-roving-focus@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz#e90c4a6a5f6ac09d3b8c1f5b5e81aab2f0db1974" + integrity sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-collection" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-controllable-state" "1.0.1" + +"@radix-ui/react-slot@1.0.2": + version "1.0.2" + resolved "https://registry.yarnpkg.com/@radix-ui/react-slot/-/react-slot-1.0.2.tgz#a9ff4423eade67f501ffb32ec22064bc9d3099ab" + integrity sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + +"@radix-ui/react-use-callback-ref@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz#f4bb1f27f2023c984e6534317ebc411fc181107a" + integrity sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-use-controllable-state@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz#ecd2ced34e6330caf89a82854aa2f77e07440286" + integrity sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.1" + +"@radix-ui/react-use-escape-keydown@1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz#217b840c250541609c66f67ed7bab2b733620755" + integrity sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-callback-ref" "1.0.1" + +"@radix-ui/react-use-layout-effect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz#be8c7bc809b0c8934acf6657b577daf948a75399" + integrity sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ== + dependencies: + "@babel/runtime" "^7.13.10" + +"@radix-ui/react-use-rect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz#fde50b3bb9fd08f4a1cd204572e5943c244fcec2" + integrity sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/rect" "1.0.1" + +"@radix-ui/react-use-size@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz#1c5f5fea940a7d7ade77694bb98116fb49f870b2" + integrity sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-use-layout-effect" "1.0.1" + +"@radix-ui/rect@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@radix-ui/rect/-/rect-1.0.1.tgz#bf8e7d947671996da2e30f4904ece343bc4a883f" + integrity sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ== + dependencies: + "@babel/runtime" "^7.13.10" + "@react-hook/intersection-observer@^3.1.1": version "3.1.1" resolved "https://registry.yarnpkg.com/@react-hook/intersection-observer/-/intersection-observer-3.1.1.tgz#6b8fdb80d133c9c28bc8318368ecb3a1f8befc50" @@ -1365,6 +1640,13 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +aria-hidden@^1.1.1: + version "1.2.4" + resolved "https://registry.yarnpkg.com/aria-hidden/-/aria-hidden-1.2.4.tgz#b78e383fdbc04d05762c78b4a25a501e736c4522" + integrity sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A== + dependencies: + tslib "^2.0.0" + aria-query@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/aria-query/-/aria-query-4.2.2.tgz#0d2ca6c9aceb56b8977e9fed6aed7e15bbd2f83b" @@ -2036,6 +2318,11 @@ destroy@1.2.0: resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.2.0.tgz#4803735509ad8be552934c67df614f94e66fa015" integrity sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg== +detect-node-es@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/detect-node-es/-/detect-node-es-1.1.0.tgz#163acdf643330caa0b4cd7c21e7ee7755d6fa493" + integrity sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ== + didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" @@ -2773,6 +3060,11 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" +get-nonce@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" + integrity sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q== + get-stream@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7" @@ -3100,6 +3392,13 @@ intersection-observer@^0.10.0: resolved "https://registry.yarnpkg.com/intersection-observer/-/intersection-observer-0.10.0.tgz#4d11d63c1ff67e21e62987be24d55218da1a1a69" integrity sha512-fn4bQ0Xq8FTej09YC/jqKZwtijpvARlRp6wxL5WTA6yPe2YWSJ5RJh7Nm79rK2qB0wr6iDQzH60XGq5V/7u8YQ== +invariant@^2.2.4: + version "2.2.4" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== + dependencies: + loose-envify "^1.0.0" + ipaddr.js@1.9.1: version "1.9.1" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" @@ -3587,7 +3886,7 @@ longest-streak@^3.0.0: resolved "https://registry.yarnpkg.com/longest-streak/-/longest-streak-3.0.1.tgz#c97315b7afa0e7d9525db9a5a2953651432bdc5d" integrity sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -5315,6 +5614,34 @@ react-is@^17.0.2: resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0" integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w== +react-remove-scroll-bar@^2.3.3: + version "2.3.6" + resolved "https://registry.yarnpkg.com/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.6.tgz#3e585e9d163be84a010180b18721e851ac81a29c" + integrity sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g== + dependencies: + react-style-singleton "^2.2.1" + tslib "^2.0.0" + +react-remove-scroll@2.5.5: + version "2.5.5" + resolved "https://registry.yarnpkg.com/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz#1e31a1260df08887a8a0e46d09271b52b3a37e77" + integrity sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw== + dependencies: + react-remove-scroll-bar "^2.3.3" + react-style-singleton "^2.2.1" + tslib "^2.1.0" + use-callback-ref "^1.3.0" + use-sidecar "^1.1.2" + +react-style-singleton@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/react-style-singleton/-/react-style-singleton-2.2.1.tgz#f99e420492b2d8f34d38308ff660b60d0b1205b4" + integrity sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g== + dependencies: + get-nonce "^1.0.0" + invariant "^2.2.4" + tslib "^2.0.0" + react@^0.0.0-experimental-16d053d59-20230506: version "0.0.0-experimental-16d053d59-20230506" resolved "https://registry.yarnpkg.com/react/-/react-0.0.0-experimental-16d053d59-20230506.tgz#98a7a9d19ab1820f882111ce4fc4e23f3cb8aaad" @@ -5355,6 +5682,11 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regexp.prototype.flags@^1.3.1: version "1.4.1" resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.4.1.tgz#b3f4c0059af9e47eca9f3f660e51d81307e72307" @@ -6228,6 +6560,11 @@ tslib@^1.8.1: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== +tslib@^2.0.0: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + tslib@^2.1.0: version "2.3.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01" @@ -6500,6 +6837,21 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +use-callback-ref@^1.3.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/use-callback-ref/-/use-callback-ref-1.3.2.tgz#6134c7f6ff76e2be0b56c809b17a650c942b1693" + integrity sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA== + dependencies: + tslib "^2.0.0" + +use-sidecar@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/use-sidecar/-/use-sidecar-1.1.2.tgz#2f43126ba2d7d7e117aa5855e5d8f0276dfe73c2" + integrity sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw== + dependencies: + detect-node-es "^1.1.0" + tslib "^2.0.0" + util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"