Skip to content

Instantly share code, notes, and snippets.

@codecademydev
Created May 24, 2023 03:57
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save codecademydev/7ca40c1fa1d46ba2face90c66730d324 to your computer and use it in GitHub Desktop.
Save codecademydev/7ca40c1fa1d46ba2face90c66730d324 to your computer and use it in GitHub Desktop.
Codecademy export
import React, { useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
createCommentIsPending,
postCommentForArticleId
} from '../features/comments/commentsSlice';
export default function CommentForm({ articleId }) {
const dispatch = useDispatch();
const [comment, setComment] = useState('');
// Declare isCreatePending here.
const isCreatePending = useSelector(createCommentIsPending);
const handleSubmit = (e) => {
e.preventDefault();
// dispatch your asynchronous action here!
dispatch(postCommentForArticleId({articleId, comment}));
setComment('');
};
return (
<form onSubmit={handleSubmit}>
<label for='comment' className='label'>
Add Comment:
</label>
<div id='input-container'>
<input
id='comment'
value={comment}
onChange={(e) => setComment(e.currentTarget.value)}
type='text'
/>
<button
disabled={isCreatePending}
className='comment-button'
>
Submit
</button>
</div>
</form>
);
}
import React from 'react';
import Comment from './Comment';
export default function CommentList({ comments }) {
if (!comments) {
return null;
}
return (
<ul className='comments-list'>
{
comments.map(comment => {
return <li> <Comment comment={comment} /> </li>
})
}
</ul>
);
}
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
loadCommentsForArticleId,
selectComments,
isLoadingComments,
} from '../comments/commentsSlice';
import { selectCurrentArticle } from '../currentArticle/currentArticleSlice';
import CommentList from '../../components/CommentList';
import CommentForm from '../../components/CommentForm';
const Comments = () => {
const dispatch = useDispatch();
const article = useSelector(selectCurrentArticle);
// Declare additional selected data here.
const comments = useSelector(selectComments);
const commentsAreLoading = useSelector(isLoadingComments);
// Dispatch loadCommentsForArticleId with useEffect here.
useEffect(() => {
if ( article !== undefined ) {
dispatch(loadCommentsForArticleId(article.id))};
}, [dispatch, article]);
const commentsForArticleId = article === undefined ? [] : comments[article.id];
if (commentsAreLoading) return <div>Loading Comments</div>;
if (!article) return null;
return (
<div className='comments-container'>
<h3 className='comments-title'>Comments</h3>
<CommentList comments={commentsForArticleId} />
<CommentForm articleId={article.id} />
</div>
);
};
export default Comments;
// Import createAsyncThunk and createSlice here.
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
// Create loadCommentsForArticleId here.
export const loadCommentsForArticleId = createAsyncThunk (
'comments/loadCommentForArticleId',
async (id) => {
const response = await fetch (`api/articles/${id}/comments`);
const json = await response.json();
return json;
}
)
// Create postCommentForArticleId here.
export const postCommentForArticleId = createAsyncThunk (
'comments/postCommentForArticleId',
async ({articleId, comment}) => {
const requestBody = await JSON.stringify({comment: comment});
const response = await fetch(`api/articles/${articleId}/comments`, {
method:'POST',
body: requestBody}
);
const json = await response.json();
return json;
}
)
export const commentsSlice = createSlice({
name: 'comments',
initialState: {
// Add initial state properties here.
byArticleId: {},
isLoadingComments: false,
failedToLoadComments: false,
createCommentIsPending: false,
failedToCreateComment: false,
},
// Add extraReducers here.
extraReducers: {
[loadCommentsForArticleId.pending]: (state) => {
state.isLoadingComments = true;
state.failedToLoadComments = false;
},
[loadCommentsForArticleId.fulfilled]: (state, action) => {
state.isLoadingComments = false;
state.failedToLoadComments = false;
state.byArticleId[action.payload.articleId] = action.payload.comments;
},
[loadCommentsForArticleId.rejected]: (state) => {
state.isLoadingComments = false;
state.failedToLoadComments = false;
},
[postCommentForArticleId.pending]: (state) => {
state.createCommentIsPending = true;
state.failedToCreateComment = false;
},
[postCommentForArticleId.fulfilled]: (state, action) => {
state.createCommentIsPending = false;
state.failedToCreateComment = false;
state.byArticleId[action.payload.articleId].push(action.payload)
},
[postCommentForArticleId.rejected]: (state) => {
state.createCommentIsPending = false;
state.failedToCreateComment = true;
}
}
});
export const selectComments = (state) => state.comments.byArticleId;
export const isLoadingComments = (state) => state.comments.isLoadingComments;
export const createCommentIsPending = (state) => state.comments.createCommentIsPending;
export default commentsSlice.reducer;
import { rest } from 'msw';
import articlesData from './articles.json';
import commentsData from './comments.json';
const userComments = {};
function mockDelay(milliseconds) {
const date = Date.now();
let currentDate = null;
do {
currentDate = Date.now();
} while (currentDate - date < milliseconds);
}
export const handlers = [
rest.get('/api/articles', (req, res, ctx) => {
mockDelay(500);
return res(
ctx.status(200),
ctx.json(
articlesData.map((article) => ({
id: article.id,
title: article.title,
preview: article.preview,
image: article.image,
}))
)
);
}),
rest.get('/api/articles/:articleId', (req, res, ctx) => {
mockDelay(500);
const { articleId } = req.params;
return res(
ctx.status(200),
ctx.json(
articlesData.find((article) => article.id === parseInt(articleId))
)
);
}),
rest.get('/api/articles/:articleId/comments', (req, res, ctx) => {
mockDelay(500);
const { articleId } = req.params;
const userCommentsForArticle = userComments[articleId] || [];
return res(
ctx.status(200),
ctx.json({
articleId: parseInt(articleId),
comments: commentsData
.filter((comment) => comment.articleId === parseInt(articleId))
.concat(userCommentsForArticle),
})
);
}),
rest.post('/api/articles/:articleId/comments', (req, res, ctx) => {
mockDelay(500);
const { articleId } = req.params;
const commentResponse = {
id: commentsData.length,
articleId: parseInt(articleId),
text: JSON.parse(req.body).comment,
};
if (userComments[articleId]) {
userComments[articleId].push(commentResponse);
} else {
userComments[articleId] = [commentResponse];
}
return res(ctx.status(200), ctx.json(commentResponse));
}),
];
/**
* Mock Service Worker.
* @see https://github.com/mswjs/msw
* - Please do NOT modify this file.
* - Please do NOT serve this file on production.
*/
/* eslint-disable */
/* tslint:disable */
const INTEGRITY_CHECKSUM = '65d33ca82955e1c5928aed19d1bdf3f9'
const bypassHeaderName = 'x-msw-bypass'
let clients = {}
self.addEventListener('install', function () {
return self.skipWaiting()
})
self.addEventListener('activate', async function (event) {
return self.clients.claim()
})
self.addEventListener('message', async function (event) {
const clientId = event.source.id
const client = await event.currentTarget.clients.get(clientId)
const allClients = await self.clients.matchAll()
const allClientIds = allClients.map((client) => client.id)
switch (event.data) {
case 'KEEPALIVE_REQUEST': {
sendToClient(client, {
type: 'KEEPALIVE_RESPONSE',
})
break
}
case 'INTEGRITY_CHECK_REQUEST': {
sendToClient(client, {
type: 'INTEGRITY_CHECK_RESPONSE',
payload: INTEGRITY_CHECKSUM,
})
break
}
case 'MOCK_ACTIVATE': {
clients = ensureKeys(allClientIds, clients)
clients[clientId] = true
sendToClient(client, {
type: 'MOCKING_ENABLED',
payload: true,
})
break
}
case 'MOCK_DEACTIVATE': {
clients = ensureKeys(allClientIds, clients)
clients[clientId] = false
break
}
case 'CLIENT_CLOSED': {
const remainingClients = allClients.filter((client) => {
return client.id !== clientId
})
// Unregister itself when there are no more clients
if (remainingClients.length === 0) {
self.registration.unregister()
}
break
}
}
})
self.addEventListener('fetch', function (event) {
const { clientId, request } = event
const requestClone = request.clone()
const getOriginalResponse = () => fetch(requestClone)
// Bypass navigation requests.
if (request.mode === 'navigate') {
return
}
// Bypass mocking if the current client isn't present in the internal clients map
// (i.e. has the mocking disabled).
if (!clients[clientId]) {
return
}
// Opening the DevTools triggers the "only-if-cached" request
// that cannot be handled by the worker. Bypass such requests.
if (request.cache === 'only-if-cached' && request.mode !== 'same-origin') {
return
}
event.respondWith(
new Promise(async (resolve, reject) => {
const client = await event.target.clients.get(clientId)
// Bypass mocking when the request client is not active.
if (!client) {
return resolve(getOriginalResponse())
}
// Bypass requests with the explicit bypass header
if (requestClone.headers.get(bypassHeaderName) === 'true') {
const modifiedHeaders = serializeHeaders(requestClone.headers)
// Remove the bypass header to comply with the CORS preflight check
delete modifiedHeaders[bypassHeaderName]
const originalRequest = new Request(requestClone, {
headers: new Headers(modifiedHeaders),
})
return resolve(fetch(originalRequest))
}
const reqHeaders = serializeHeaders(request.headers)
const body = await request.text()
const rawClientMessage = await sendToClient(client, {
type: 'REQUEST',
payload: {
url: request.url,
method: request.method,
headers: reqHeaders,
cache: request.cache,
mode: request.mode,
credentials: request.credentials,
destination: request.destination,
integrity: request.integrity,
redirect: request.redirect,
referrer: request.referrer,
referrerPolicy: request.referrerPolicy,
body,
bodyUsed: request.bodyUsed,
keepalive: request.keepalive,
},
})
const clientMessage = rawClientMessage
switch (clientMessage.type) {
case 'MOCK_SUCCESS': {
setTimeout(
resolve.bind(this, createResponse(clientMessage)),
clientMessage.payload.delay,
)
break
}
case 'MOCK_NOT_FOUND': {
return resolve(getOriginalResponse())
}
case 'NETWORK_ERROR': {
const { name, message } = clientMessage.payload
const networkError = new Error(message)
networkError.name = name
// Rejecting a request Promise emulates a network error.
return reject(networkError)
}
case 'INTERNAL_ERROR': {
const parsedBody = JSON.parse(clientMessage.payload.body)
console.error(
`\
[MSW] Request handler function for "%s %s" has thrown the following exception:
${parsedBody.errorType}: ${parsedBody.message}
(see more detailed error stack trace in the mocked response body)
This exception has been gracefully handled as a 500 response, however, it's strongly recommended to resolve this error.
If you wish to mock an error response, please refer to this guide: https://mswjs.io/docs/recipes/mocking-error-responses\
`,
request.method,
request.url,
)
return resolve(createResponse(clientMessage))
}
}
}).catch((error) => {
console.error(
'[MSW] Failed to mock a "%s" request to "%s": %s',
request.method,
request.url,
error,
)
}),
)
})
function serializeHeaders(headers) {
const reqHeaders = {}
headers.forEach((value, name) => {
reqHeaders[name] = reqHeaders[name]
? [].concat(reqHeaders[name]).concat(value)
: value
})
return reqHeaders
}
function sendToClient(client, message) {
return new Promise((resolve, reject) => {
const channel = new MessageChannel()
channel.port1.onmessage = (event) => {
if (event.data && event.data.error) {
reject(event.data.error)
} else {
resolve(event.data)
}
}
client.postMessage(JSON.stringify(message), [channel.port2])
})
}
function createResponse(clientMessage) {
return new Response(clientMessage.payload.body, {
...clientMessage.payload,
headers: clientMessage.payload.headers,
})
}
function ensureKeys(keys, obj) {
return Object.keys(obj).reduce((acc, key) => {
if (keys.includes(key)) {
acc[key] = obj[key]
}
return acc
}, {})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment