From fdab641dd00fe86517dbd7b5cf71fcf80fbe78d7 Mon Sep 17 00:00:00 2001 From: hbar1st <35541470+hbar1st@users.noreply.github.com> Date: Mon, 25 May 2026 03:47:01 -0400 Subject: [PATCH] feat(curriculum): add async/await to workshop-fcc-authors-page (#66972) Signed-off-by: Naomi Carrigan Co-authored-by: Naomi Carrigan --- .../641dab13c1b6f14b9828e6b1.md | 149 ------- .../69df434b56d2e0faf1a7424c.md | 239 +++++++++++ .../69e14a2fd8ecf9210c0739ee.md | 175 ++++++++ .../69e16a610b33e847d85be16e.md | 201 +++++++++ .../69e1e83ac6424325f77331e8.md | 208 ++++++++++ .../69e1f5fa037cd7c2c4d6b8eb.md | 383 ++++++++++++++++++ .../blocks/workshop-fcc-authors-page.json | 125 ++---- 7 files changed, 1235 insertions(+), 245 deletions(-) create mode 100644 curriculum/challenges/english/blocks/workshop-fcc-authors-page/69df434b56d2e0faf1a7424c.md create mode 100644 curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e14a2fd8ecf9210c0739ee.md create mode 100644 curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e16a610b33e847d85be16e.md create mode 100644 curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e1e83ac6424325f77331e8.md create mode 100644 curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e1f5fa037cd7c2c4d6b8eb.md diff --git a/curriculum/challenges/english/blocks/workshop-fcc-authors-page/641dab13c1b6f14b9828e6b1.md b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/641dab13c1b6f14b9828e6b1.md index 8009123a4f1..9b9f33059b5 100644 --- a/curriculum/challenges/english/blocks/workshop-fcc-authors-page/641dab13c1b6f14b9828e6b1.md +++ b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/641dab13c1b6f14b9828e6b1.md @@ -11,8 +11,6 @@ One more thing. If you keep clicking the `Load More Authors` button until there' Access the `style` property of the `Load More Authors` button and set `cursor` to `"not-allowed"`. -With that, your author page is complete! - # --before-all-- ```js @@ -189,150 +187,3 @@ const displayAuthors = (authors) => { loadMoreBtn.addEventListener('click', fetchMoreAuthors); ``` - -# --solutions-- - -```html - - - - - - - freeCodeCamp News Author Page - - - -

freeCodeCamp News Author Page

- -
-
- -
- - - - -``` - -```css -* { - margin: 0; - padding: 0; - box-sizing: border-box; -} - -:root { - --main-bg-color: #1b1b32; - --light-grey: #f5f6f7; - --dark-purple: #5a01a7; - --golden-yellow: #feac32; -} - -body { - background-color: var(--main-bg-color); - text-align: center; -} - -.title { - color: var(--light-grey); - margin: 20px 0; -} - -#author-container { - display: flex; - flex-wrap: wrap; - justify-content: center; -} - -.user-card { - border-radius: 15px; - width: 300px; - height: 350px; - background-color: var(--light-grey); - margin: 20px; -} - -.user-img { - width: 150px; - height: 150px; - object-fit: cover; -} - -.purple-divider { - background-color: var(--dark-purple); - width: 100%; - height: 15px; -} - -.author-name { - margin: 10px; -} - -.bio { - margin: 20px; -} - -.error-msg { - color: var(--light-grey); -} - -.btn { - cursor: pointer; - width: 200px; - margin: 10px; - color: var(--main-bg-color); - font-size: 14px; - background-color: var(--golden-yellow); - background-image: linear-gradient(#fecc4c, #ffac33); - border-color: var(--golden-yellow); - border-width: 3px; -} -``` - -```js -const authorContainer = document.getElementById('author-container'); -const loadMoreBtn = document.getElementById('load-more-btn'); - -let startingIndex = 0; -let endingIndex = 8; -let authorDataArr = []; - -fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json') - .then((res) => res.json()) - .then((data) => { - authorDataArr = data; - displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); - }) - .catch((err) => { - authorContainer.innerHTML = '

There was an error loading the authors

'; - }); - -const fetchMoreAuthors = () => { - startingIndex += 8; - endingIndex += 8; - - displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); - if (authorDataArr.length <= endingIndex) { - loadMoreBtn.disabled = true; - loadMoreBtn.style.cursor = "not-allowed" - loadMoreBtn.textContent = 'No more data to load'; - } -}; - -const displayAuthors = (authors) => { - authors.forEach(({ author, image, url, bio }, index) => { - authorContainer.innerHTML += ` -
-

${author}

- ${author} avatar -
-

${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}

- ${author} author page -
- `; - }); -}; - -loadMoreBtn.addEventListener('click', fetchMoreAuthors); -``` diff --git a/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69df434b56d2e0faf1a7424c.md b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69df434b56d2e0faf1a7424c.md new file mode 100644 index 00000000000..88bcae4bb51 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69df434b56d2e0faf1a7424c.md @@ -0,0 +1,239 @@ +--- +id: 69df434b56d2e0faf1a7424c +title: Step 25 +challengeType: 0 +dashedName: step-25 +--- + +# --description-- + +Now that your fCC Authors Page is fully functional, let's refactor it to improve its readability by using `async` and `await` instead of the promise chaining method `then`. + +Recall that in order to use the `await` operator to wait for a function that returns a `Promise`, you need to wrap the function call in an `async` function (if it is not in the main body of a module). + +Since your `fetch` call is not defined in either an asynchronous function nor in the main body of a module, you cannot use `await` before fixing that. + +Wrap the `fetch` statement and the entire sequence of chained then and catch methods as a whole in a new asynchronous arrow function called `initialFetch` which takes no arguments. + +An example of an `async` function declaration is: + +```js +const functionName = async () => {} +``` + +# --before-all-- + +```js +window.fetch = () => Promise.resolve({json: () => Promise.resolve([{ author: 'Whoever', image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', url: "http://not-a-real-url.nowhere/", bio: 'words go here' }])}); +``` + +# --hints-- + +`initialFetch` should be a function. + +```js +assert.isFunction(initialFetch) +``` + +You should use `const` to create an `initialFetch` function. + +```js +assert.match(code, /const\s+initialFetch\s*=\s*/) +``` + +`initialFetch` should be an async function. + +```js +assert.isFunction(initialFetch) +assert.match(code, /const\s+initialFetch\s*=\s*async\s*/) +``` + +Your `initialFetch` function should not take any parameter. + +```js +assert.match(code, /const\s+initialFetch\s*=\s*.*\s*\(\s*\)\s*/) +``` + +Your `initialFetch` function should use arrow syntax. + +```js +assert.match(code, /const\s+initialFetch\s*=\s*.*\s*\(.*\)\s*=>\s*/) +``` + +Your `initialFetch` function should not be empty. + +```js +assert.isFunction(initialFetch) +assert.notMatch(code, /const\s+initialFetch\s*=\s*.*\s*\(\s*\)\s*=>\s*\{\s*\}/) +``` + +Your `initialFetch` function should contain the `fetch` statement and the chained methods. + +```js +const initialFetchBlockRegex = + /const\s+initialFetch\s*=\s*async\s*\(\s*\)\s*=>\s*\{([\s\S]*?)\}\s*$/m; + +const match = code.match(initialFetchBlockRegex); + +assert.isDefined(match); + +const initialFetchBlockContents = match[1].trim(); +const fetchStatementRegex = /^fetch\(\s*(['"`])https:\/\/cdn\.freecodecamp\.org\/curriculum\/news-author-page\/authors\.json\1\s*\)\s*\.then\(\s*(?:\(\s*res\s*\)|res)\s*=>\s*res\.json\(\s*\)\s*\)\s*\.then\(\s*(?:\(\s*data\s*\)|data)\s*=>\s*\{\s*authorDataArr\s*=\s*data;?\s*displayAuthors\(\s*authorDataArr\.slice\(\s*startingIndex\s*,\s*endingIndex\s*\)\s*\)\s*;?\s*\}\s*\)\s*\.catch\(\s*(?:\(\s*err\s*\)|err)\s*=>\s*\{?\s*authorContainer\.innerHTML\s*=\s*(['"`])\s*There\s+was\s+an\s+error\s+loading\s+the\s+authors\s*<\/p>\s*\2\s*;?\s*\}?\s*\)\s*;?/; + +assert.isTrue(fetchStatementRegex.test(initialFetchBlockContents)); + +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + freeCodeCamp News Author Page + + + +

freeCodeCamp News Author Page

+ +
+
+ +
+ + + + +``` + +```css +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --main-bg-color: #1b1b32; + --light-grey: #f5f6f7; + --dark-purple: #5a01a7; + --golden-yellow: #feac32; +} + +body { + background-color: var(--main-bg-color); + text-align: center; +} + +.title { + color: var(--light-grey); + margin: 20px 0; +} + +#author-container { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.user-card { + border-radius: 15px; + width: 300px; + height: 350px; + background-color: var(--light-grey); + margin: 20px; +} + +.user-img { + width: 150px; + height: 150px; + object-fit: cover; +} + +.purple-divider { + background-color: var(--dark-purple); + width: 100%; + height: 15px; +} + +.author-name { + margin: 10px; +} + +.bio { + margin: 20px; +} + +.error-msg { + color: var(--light-grey); +} + +.btn { + cursor: pointer; + width: 200px; + margin: 10px; + color: var(--main-bg-color); + font-size: 14px; + background-color: var(--golden-yellow); + background-image: linear-gradient(#fecc4c, #ffac33); + border-color: var(--golden-yellow); + border-width: 3px; +} +``` + +```js + +const authorContainer = document.getElementById('author-container'); +const loadMoreBtn = document.getElementById('load-more-btn'); + +let startingIndex = 0; +let endingIndex = 8; +let authorDataArr = []; + +--fcc-editable-region-- + +fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json') + .then((res) => res.json()) + .then((data) => { + authorDataArr = data; + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + }) + .catch((err) => { + authorContainer.innerHTML = '

There was an error loading the authors

'; + }); + +--fcc-editable-region-- + +const fetchMoreAuthors = () => { + startingIndex += 8; + endingIndex += 8; + + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + if (authorDataArr.length <= endingIndex) { + loadMoreBtn.disabled = true; + loadMoreBtn.style.cursor = "not-allowed" + loadMoreBtn.textContent = 'No more data to load'; + } +}; + +const displayAuthors = (authors) => { + authors.forEach(({ author, image, url, bio }, index) => { + authorContainer.innerHTML += ` +
+

${author}

+ ${author} avatar +
+

${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}

+ ${author} author page +
+ `; + }); +}; + +loadMoreBtn.addEventListener('click', fetchMoreAuthors); +``` diff --git a/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e14a2fd8ecf9210c0739ee.md b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e14a2fd8ecf9210c0739ee.md new file mode 100644 index 00000000000..04d1d2b78e9 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e14a2fd8ecf9210c0739ee.md @@ -0,0 +1,175 @@ +--- +id: 69e14a2fd8ecf9210c0739ee +title: Step 26 +challengeType: 0 +dashedName: step-26 +--- + +# --description-- + +When calling an asynchronous function, you can either await the function's return or you can let it run asynchronously. If you had any code that depends on the `initialFetch` data being returned, you would want to await the function call, but since you don't have any such dependency, you can go ahead and just call the function asynchronously. + +Add a function call to `initialFetch` in the indicated space in the editor. + +# --hints-- + +You should call `initialFetch` without any arguments. + +```js +assert.match(code, /^initialFetch\(\s*\)/m); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + freeCodeCamp News Author Page + + + +

freeCodeCamp News Author Page

+ +
+
+ +
+ + + + +``` + +```css +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --main-bg-color: #1b1b32; + --light-grey: #f5f6f7; + --dark-purple: #5a01a7; + --golden-yellow: #feac32; +} + +body { + background-color: var(--main-bg-color); + text-align: center; +} + +.title { + color: var(--light-grey); + margin: 20px 0; +} + +#author-container { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.user-card { + border-radius: 15px; + width: 300px; + height: 350px; + background-color: var(--light-grey); + margin: 20px; +} + +.user-img { + width: 150px; + height: 150px; + object-fit: cover; +} + +.purple-divider { + background-color: var(--dark-purple); + width: 100%; + height: 15px; +} + +.author-name { + margin: 10px; +} + +.bio { + margin: 20px; +} + +.error-msg { + color: var(--light-grey); +} + +.btn { + cursor: pointer; + width: 200px; + margin: 10px; + color: var(--main-bg-color); + font-size: 14px; + background-color: var(--golden-yellow); + background-image: linear-gradient(#fecc4c, #ffac33); + border-color: var(--golden-yellow); + border-width: 3px; +} +``` + +```js + +const authorContainer = document.getElementById('author-container'); +const loadMoreBtn = document.getElementById('load-more-btn'); + +let startingIndex = 0; +let endingIndex = 8; +let authorDataArr = []; + +const initialFetch = async () => { + fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json') + .then((res) => res.json()) + .then((data) => { + authorDataArr = data; + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + }) + .catch((err) => { + authorContainer.innerHTML = '

There was an error loading the authors

'; + }); +} + +const fetchMoreAuthors = () => { + startingIndex += 8; + endingIndex += 8; + + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + if (authorDataArr.length <= endingIndex) { + loadMoreBtn.disabled = true; + loadMoreBtn.style.cursor = "not-allowed" + loadMoreBtn.textContent = 'No more data to load'; + } +}; + +const displayAuthors = (authors) => { + authors.forEach(({ author, image, url, bio }, index) => { + authorContainer.innerHTML += ` +
+

${author}

+ ${author} avatar +
+

${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}

+ ${author} author page +
+ `; + }); +}; + +--fcc-editable-region-- + +--fcc-editable-region-- +loadMoreBtn.addEventListener('click', fetchMoreAuthors); +``` diff --git a/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e16a610b33e847d85be16e.md b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e16a610b33e847d85be16e.md new file mode 100644 index 00000000000..9d9a6fb9692 --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e16a610b33e847d85be16e.md @@ -0,0 +1,201 @@ +--- +id: 69e16a610b33e847d85be16e +title: Step 27 +challengeType: 0 +dashedName: step-27 +--- + +# --description-- + +At this point, the behaviour of the app should not have changed. The code still fetches a file from the CDN and then processes the retrieved data into `JSON`. Once that's done, it saves that data into the `authorDataArr` variable and then calls `displayAuthors`. + +Modify the call to `fetch` so that it uses the `await` operator. Save the result in a new `const` variable called `res`. The new variable should be locally defined within `initialFetch`. + +Given this code: + +```js + fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json') +``` + +You would write: + +```js + await fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json') +``` + +You may see a `SyntaxError`. This can be ignored for now. + +# --before-all-- + +```js +window.fetch = () => Promise.resolve({json: () => Promise.resolve([{ author: 'Whoever', image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', url: "http://not-a-real-url.nowhere/", bio: 'words go here' }])}); +``` + +# --hints-- + +You should use the `await` keyword to await the `fetch` call. + +```js +assert.match(code, /await\s+fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)/) +``` + +You should save the result of the awaited `fetch` call in a local `const` variable called `res`. + +```js +assert.match(code, /^\s*const\s+res\s*=\s*await\s+fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1\s*\)/m) +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + freeCodeCamp News Author Page + + + +

freeCodeCamp News Author Page

+ +
+
+ +
+ + + + +``` + +```css +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --main-bg-color: #1b1b32; + --light-grey: #f5f6f7; + --dark-purple: #5a01a7; + --golden-yellow: #feac32; +} + +body { + background-color: var(--main-bg-color); + text-align: center; +} + +.title { + color: var(--light-grey); + margin: 20px 0; +} + +#author-container { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.user-card { + border-radius: 15px; + width: 300px; + height: 350px; + background-color: var(--light-grey); + margin: 20px; +} + +.user-img { + width: 150px; + height: 150px; + object-fit: cover; +} + +.purple-divider { + background-color: var(--dark-purple); + width: 100%; + height: 15px; +} + +.author-name { + margin: 10px; +} + +.bio { + margin: 20px; +} + +.error-msg { + color: var(--light-grey); +} + +.btn { + cursor: pointer; + width: 200px; + margin: 10px; + color: var(--main-bg-color); + font-size: 14px; + background-color: var(--golden-yellow); + background-image: linear-gradient(#fecc4c, #ffac33); + border-color: var(--golden-yellow); + border-width: 3px; +} +``` + +```js + +const authorContainer = document.getElementById('author-container'); +const loadMoreBtn = document.getElementById('load-more-btn'); + +let startingIndex = 0; +let endingIndex = 8; +let authorDataArr = []; + +const initialFetch = async () => { +--fcc-editable-region-- + fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json') +--fcc-editable-region-- + .then((res) => res.json()) + .then((data) => { + authorDataArr = data; + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + }) + .catch((err) => { + authorContainer.innerHTML = '

There was an error loading the authors

'; + }); +} + +const fetchMoreAuthors = () => { + startingIndex += 8; + endingIndex += 8; + + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + if (authorDataArr.length <= endingIndex) { + loadMoreBtn.disabled = true; + loadMoreBtn.style.cursor = "not-allowed" + loadMoreBtn.textContent = 'No more data to load'; + } +}; + +const displayAuthors = (authors) => { + authors.forEach(({ author, image, url, bio }, index) => { + authorContainer.innerHTML += ` +
+

${author}

+ ${author} avatar +
+

${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}

+ ${author} author page +
+ `; + }); +}; + +initialFetch(); +loadMoreBtn.addEventListener('click', fetchMoreAuthors); +``` diff --git a/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e1e83ac6424325f77331e8.md b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e1e83ac6424325f77331e8.md new file mode 100644 index 00000000000..c0b1498356f --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e1e83ac6424325f77331e8.md @@ -0,0 +1,208 @@ +--- +id: 69e1e83ac6424325f77331e8 +title: Step 28 +challengeType: 0 +dashedName: step-28 +--- + +# --description-- + +Next, change the line of code that calls the `json` method to use the `await` operator instead. + +For example, given this code: + +```js + .then((res) => res.json()); +``` + +You would write: + +```js + await res.json(); +``` + +Then, store the result in your `authorDataArr`, and move the line of code that calls `displayAuthors` so it is immediately following your new `await` statement. +Finally, delete the remaining `then` statement and its code block as it no longer serves any purpose. + +You will notice a `SyntaxError` in the console at this point. The next step will deal with refactoring the `catch` method to resolve this error. + +# --before-all-- + +```js +window.fetch = () => Promise.resolve({json: () => Promise.resolve([{ author: 'Whoever', image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', url: "http://not-a-real-url.nowhere/", bio: 'words go here' }])}); +``` + +# --hints-- + +You should assign the awaited result of `res.json()` to the `authorDataArr` variable from the global scope + +```js +assert.match(code, /^\s*authorDataArr\s*=\s*await\s+res\.json\(\s*\)\s*;?/m) +``` + +`displayAuthors` should still be called as before + +```js +assert.match(code, /^\s*displayAuthors\(\s*authorDataArr\.slice\(\s*startingIndex\s*,\s*endingIndex\s*\)\s*\)\s*;?/m) +``` + +You should no longer have any chained `then` methods + +```js +assert.notMatch(code, /\.then\(/); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + freeCodeCamp News Author Page + + + +

freeCodeCamp News Author Page

+ +
+
+ +
+ + + + +``` + +```css +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --main-bg-color: #1b1b32; + --light-grey: #f5f6f7; + --dark-purple: #5a01a7; + --golden-yellow: #feac32; +} + +body { + background-color: var(--main-bg-color); + text-align: center; +} + +.title { + color: var(--light-grey); + margin: 20px 0; +} + +#author-container { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.user-card { + border-radius: 15px; + width: 300px; + height: 350px; + background-color: var(--light-grey); + margin: 20px; +} + +.user-img { + width: 150px; + height: 150px; + object-fit: cover; +} + +.purple-divider { + background-color: var(--dark-purple); + width: 100%; + height: 15px; +} + +.author-name { + margin: 10px; +} + +.bio { + margin: 20px; +} + +.error-msg { + color: var(--light-grey); +} + +.btn { + cursor: pointer; + width: 200px; + margin: 10px; + color: var(--main-bg-color); + font-size: 14px; + background-color: var(--golden-yellow); + background-image: linear-gradient(#fecc4c, #ffac33); + border-color: var(--golden-yellow); + border-width: 3px; +} +``` + +```js + +const authorContainer = document.getElementById('author-container'); +const loadMoreBtn = document.getElementById('load-more-btn'); + +let startingIndex = 0; +let endingIndex = 8; +let authorDataArr = []; + +const initialFetch = async () => { + const res = await fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json'); +--fcc-editable-region-- + .then((res) => res.json()) + .then((data) => { + authorDataArr = data; + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + }) +--fcc-editable-region-- + .catch((err) => { + authorContainer.innerHTML = '

There was an error loading the authors

'; + }); +} + +const fetchMoreAuthors = () => { + startingIndex += 8; + endingIndex += 8; + + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + if (authorDataArr.length <= endingIndex) { + loadMoreBtn.disabled = true; + loadMoreBtn.style.cursor = "not-allowed" + loadMoreBtn.textContent = 'No more data to load'; + } +}; + +const displayAuthors = (authors) => { + authors.forEach(({ author, image, url, bio }, index) => { + authorContainer.innerHTML += ` +
+

${author}

+ ${author} avatar +
+

${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}

+ ${author} author page +
+ `; + }); +}; + +initialFetch(); +loadMoreBtn.addEventListener('click', fetchMoreAuthors); +``` diff --git a/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e1f5fa037cd7c2c4d6b8eb.md b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e1f5fa037cd7c2c4d6b8eb.md new file mode 100644 index 00000000000..3d978e5caae --- /dev/null +++ b/curriculum/challenges/english/blocks/workshop-fcc-authors-page/69e1f5fa037cd7c2c4d6b8eb.md @@ -0,0 +1,383 @@ +--- +id: 69e1f5fa037cd7c2c4d6b8eb +title: Step 29 +challengeType: 0 +dashedName: step-29 +--- + +# --description-- + +Finally, you need to deal with the `catch` method. First, nest the `fetch` statement, the `json` statement, and the displayAuthors call into a `try` block within `initialFetch`. Then, modify the chained `catch` method to be a catch block instead as per the example shown below. + +```js +try { + // code that can cause or throw an error goes in this block +} catch (error) { + // error handling code here +} +``` + +If you've done this correctly, the `try` block will contain three lines of code in it and the `catch` block will contain just one line of code. Also, the `SyntaxError` will be resolved and the app should function normally. + +Give yourself a pat on the back! You have learned how to make your asynchronous code more readable using the `async` and `await` operators! Now your author page is complete. + +# --before-all-- + +```js +const testData = [{ author: 'Whoever', image: 'data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==', url: "http://not-a-real-url.nowhere/", bio: 'words go here' }]; +window.fetch = () => Promise.resolve({json: () => Promise.resolve(testData)}); +``` + +# --hints-- + +You should create a `try/catch` block + +```js +assert.match(code, /try\s*\{[\s\S]*?\}\s*catch\s*\([^)]*\)\s*\{/); +``` + +The `catch` should no longer be a chained method + +```js +assert.notMatch(code, /\.catch/); +``` + +The `try` block should contain the three statements that fetch the data, create the `json` and then display it. + +```js +const tryBlockRegex = + /try\s*\{([\s\S]*?)\}(?=\s*(catch|finally)\b|\s*$)/; + +const fetchLineRegex = + /const\s+res\s*=\s*await\s+fetch\(\s*('|"|`)https:\/\/cdn\.freecodecamp\.org\/curriculum\/news\-author\-page\/authors\.json\1,*\s*\)/; + +const jsonLineRegex = + /authorDataArr\s*=\s*await\s+res\.json\(\)/; + +const displayLineRegex = + /displayAuthors\(authorDataArr\.slice\(startingIndex,\s*endingIndex\)\)/; + +const match = code.match(tryBlockRegex); + +assert.isDefined(match); + +const tryBlockContents = match[1]; + +assert.isTrue(fetchLineRegex.test(tryBlockContents)); +assert.isTrue(jsonLineRegex.test(tryBlockContents)); +assert.isTrue(displayLineRegex.test(tryBlockContents)); +``` + +The fCC Authors Page should be rendering correctly + +```js +assert.equal(document.querySelector('.author-name').innerText, `Whoever`); +assert.equal(document.querySelector('img')?.getAttribute('alt'), `Whoever avatar`); +``` + +# --seed-- + +## --seed-contents-- + +```html + + + + + + + freeCodeCamp News Author Page + + + +

freeCodeCamp News Author Page

+ +
+
+ +
+ + + + +``` + +```css +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --main-bg-color: #1b1b32; + --light-grey: #f5f6f7; + --dark-purple: #5a01a7; + --golden-yellow: #feac32; +} + +body { + background-color: var(--main-bg-color); + text-align: center; +} + +.title { + color: var(--light-grey); + margin: 20px 0; +} + +#author-container { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.user-card { + border-radius: 15px; + width: 300px; + height: 350px; + background-color: var(--light-grey); + margin: 20px; +} + +.user-img { + width: 150px; + height: 150px; + object-fit: cover; +} + +.purple-divider { + background-color: var(--dark-purple); + width: 100%; + height: 15px; +} + +.author-name { + margin: 10px; +} + +.bio { + margin: 20px; +} + +.error-msg { + color: var(--light-grey); +} + +.btn { + cursor: pointer; + width: 200px; + margin: 10px; + color: var(--main-bg-color); + font-size: 14px; + background-color: var(--golden-yellow); + background-image: linear-gradient(#fecc4c, #ffac33); + border-color: var(--golden-yellow); + border-width: 3px; +} +``` + +```js + +const authorContainer = document.getElementById('author-container'); +const loadMoreBtn = document.getElementById('load-more-btn'); + +let startingIndex = 0; +let endingIndex = 8; +let authorDataArr = []; + +const initialFetch = async () => { +--fcc-editable-region-- + + const res = await fetch('https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json'); + authorDataArr = await res.json(); + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + + .catch((err) => { + authorContainer.innerHTML = '

There was an error loading the authors

'; + }); + +--fcc-editable-region-- +} + +const fetchMoreAuthors = () => { + startingIndex += 8; + endingIndex += 8; + + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + if (authorDataArr.length <= endingIndex) { + loadMoreBtn.disabled = true; + loadMoreBtn.style.cursor = "not-allowed" + loadMoreBtn.textContent = 'No more data to load'; + } +}; + +const displayAuthors = (authors) => { + authors.forEach(({ author, image, url, bio }, index) => { + authorContainer.innerHTML += ` +
+

${author}

+ ${author} avatar +
+

${bio.length > 50 ? bio.slice(0, 50) + '...' : bio}

+ ${author} author page +
+ `; + }); +}; + +initialFetch(); +loadMoreBtn.addEventListener('click', fetchMoreAuthors); +``` + + +# --solutions-- + +```html + + + + + + + freeCodeCamp News Author Page + + + +

freeCodeCamp News Author Page

+ +
+
+ +
+ + + + +``` + +```css +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +:root { + --main-bg-color: #1b1b32; + --light-grey: #f5f6f7; + --dark-purple: #5a01a7; + --golden-yellow: #feac32; +} + +body { + background-color: var(--main-bg-color); + text-align: center; +} + +.title { + color: var(--light-grey); + margin: 20px 0; +} + +#author-container { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + +.user-card { + border-radius: 15px; + width: 300px; + height: 350px; + background-color: var(--light-grey); + margin: 20px; +} + +.user-img { + width: 150px; + height: 150px; + object-fit: cover; +} + +.purple-divider { + background-color: var(--dark-purple); + width: 100%; + height: 15px; +} + +.author-name { + margin: 10px; +} + +.bio { + margin: 20px; +} + +.error-msg { + color: var(--light-grey); +} + +.btn { + cursor: pointer; + width: 200px; + margin: 10px; + color: var(--main-bg-color); + font-size: 14px; + background-color: var(--golden-yellow); + background-image: linear-gradient(#fecc4c, #ffac33); + border-color: var(--golden-yellow); + border-width: 3px; +} +``` + +```js +const authorContainer = document.getElementById("author-container"); +const loadMoreBtn = document.getElementById("load-more-btn"); + +let startingIndex = 0; +let endingIndex = 8; +let authorDataArr = []; + +const initialFetch = async () => { + try { + const res = await fetch( + "https://cdn.freecodecamp.org/curriculum/news-author-page/authors.json" + ); + authorDataArr = await res.json(); + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + } catch (err) { + authorContainer.innerHTML = + '

There was an error loading the authors

'; + } +} + +const fetchMoreAuthors = () => { + startingIndex += 8; + endingIndex += 8; + + displayAuthors(authorDataArr.slice(startingIndex, endingIndex)); + if (authorDataArr.length <= endingIndex) { + loadMoreBtn.disabled = true; + loadMoreBtn.style.cursor = "not-allowed"; + loadMoreBtn.textContent = "No more data to load"; + } +}; + +const displayAuthors = (authors) => { + authors.forEach(({ author, image, url, bio }, index) => { + authorContainer.innerHTML += ` +
+

${author}

+ ${author} avatar +
+

${bio.length > 50 ? bio.slice(0, 50) + "..." : bio}

+ ${author} author page +
+ `; + }); +}; + +initialFetch(); +loadMoreBtn.addEventListener("click", fetchMoreAuthors); +``` diff --git a/curriculum/structure/blocks/workshop-fcc-authors-page.json b/curriculum/structure/blocks/workshop-fcc-authors-page.json index f61cb75cb63..df83170b016 100644 --- a/curriculum/structure/blocks/workshop-fcc-authors-page.json +++ b/curriculum/structure/blocks/workshop-fcc-authors-page.json @@ -6,102 +6,35 @@ "hasEditableBoundaries": true, "dashedName": "workshop-fcc-authors-page", "challengeOrder": [ - { - "id": "641d9a19bff38d34d5a5edb8", - "title": "Step 1" - }, - { - "id": "641da3c6b6fbd742bff6ee40", - "title": "Step 2" - }, - { - "id": "641da42481d90c4314c99e94", - "title": "Step 3" - }, - { - "id": "641da465273051435d332b15", - "title": "Step 4" - }, - { - "id": "641da4b16937be43ba24c63d", - "title": "Step 5" - }, - { - "id": "641da51a9810e74411262fcc", - "title": "Step 6" - }, - { - "id": "641da5462576784453146ec2", - "title": "Step 7" - }, - { - "id": "641da5abaac81844a54adb03", - "title": "Step 8" - }, - { - "id": "641da73b09e7f046c758e0ed", - "title": "Step 9" - }, - { - "id": "641da791d0c34a472b8d15b6", - "title": "Step 10" - }, - { - "id": "641da7bfbc7f0f477438ad8a", - "title": "Step 11" - }, - { - "id": "641da803d9892447d059804e", - "title": "Step 12" - }, - { - "id": "641da836581c254815f785fe", - "title": "Step 13" - }, - { - "id": "641da86294fd9f485d3c2bf0", - "title": "Step 14" - }, - { - "id": "641da895fb7ec648a5bdf19c", - "title": "Step 15" - }, - { - "id": "641da8db2a036048ebe6999e", - "title": "Step 16" - }, - { - "id": "641da97c987a514959ada414", - "title": "Step 17" - }, - { - "id": "641da9aceb788e49a73ebcc9", - "title": "Step 18" - }, - { - "id": "641da9ea9b847a49fe6ee9b6", - "title": "Step 19" - }, - { - "id": "641daa5ea050f24a7cade6e6", - "title": "Step 20" - }, - { - "id": "641daa8c2c3e364ac3650b37", - "title": "Step 21`" - }, - { - "id": "641daabed8d0584b1150c953", - "title": "Step 22" - }, - { - "id": "641daae5e18eae4b562633e4", - "title": "Step 23" - }, - { - "id": "641dab13c1b6f14b9828e6b1", - "title": "Step 24" - } + { "id": "641d9a19bff38d34d5a5edb8", "title": "Step 1" }, + { "id": "641da3c6b6fbd742bff6ee40", "title": "Step 2" }, + { "id": "641da42481d90c4314c99e94", "title": "Step 3" }, + { "id": "641da465273051435d332b15", "title": "Step 4" }, + { "id": "641da4b16937be43ba24c63d", "title": "Step 5" }, + { "id": "641da51a9810e74411262fcc", "title": "Step 6" }, + { "id": "641da5462576784453146ec2", "title": "Step 7" }, + { "id": "641da5abaac81844a54adb03", "title": "Step 8" }, + { "id": "641da73b09e7f046c758e0ed", "title": "Step 9" }, + { "id": "641da791d0c34a472b8d15b6", "title": "Step 10" }, + { "id": "641da7bfbc7f0f477438ad8a", "title": "Step 11" }, + { "id": "641da803d9892447d059804e", "title": "Step 12" }, + { "id": "641da836581c254815f785fe", "title": "Step 13" }, + { "id": "641da86294fd9f485d3c2bf0", "title": "Step 14" }, + { "id": "641da895fb7ec648a5bdf19c", "title": "Step 15" }, + { "id": "641da8db2a036048ebe6999e", "title": "Step 16" }, + { "id": "641da97c987a514959ada414", "title": "Step 17" }, + { "id": "641da9aceb788e49a73ebcc9", "title": "Step 18" }, + { "id": "641da9ea9b847a49fe6ee9b6", "title": "Step 19" }, + { "id": "641daa5ea050f24a7cade6e6", "title": "Step 20" }, + { "id": "641daa8c2c3e364ac3650b37", "title": "Step 21" }, + { "id": "641daabed8d0584b1150c953", "title": "Step 22" }, + { "id": "641daae5e18eae4b562633e4", "title": "Step 23" }, + { "id": "641dab13c1b6f14b9828e6b1", "title": "Step 24" }, + { "id": "69df434b56d2e0faf1a7424c", "title": "Step 25" }, + { "id": "69e14a2fd8ecf9210c0739ee", "title": "Step 26" }, + { "id": "69e16a610b33e847d85be16e", "title": "Step 27" }, + { "id": "69e1e83ac6424325f77331e8", "title": "Step 28" }, + { "id": "69e1f5fa037cd7c2c4d6b8eb", "title": "Step 29" } ], "helpCategory": "JavaScript" }