Skip to content

Instantly share code, notes, and snippets.

@codecademydev
Created January 13, 2023 21:58
Show Gist options
  • Save codecademydev/501a1665076701102676b78b66da5ecd to your computer and use it in GitHub Desktop.
Save codecademydev/501a1665076701102676b78b66da5ecd to your computer and use it in GitHub Desktop.
Codecademy export
@import url('https://fonts.googleapis.com/css2?family=Oxygen:wght@400;700&display=swap');
html,
body {
margin: 0;
font-family: 'Oxygen', sans-serif;
}
main {
display: flex;
flex-direction: column;
}
.articles-container {
width: 100%;
display: grid;
grid-template-columns: repeat(2, 215px);
grid-gap: 16px;
justify-content: center;
background-color: #f2f2f2;
padding-bottom: 48px;
}
.current-article-container {
padding: 24px 16px 32px 16px;
}
.comments-container {
padding: 0 16px 0 16px;
}
.article-container {
cursor: pointer;
border-radius: 6px;
border: solid 1px #141c3a;
background-color: #ffffff;
color: #141c3a;
padding: 0;
text-align: start;
width: 215px;
height: 100%;
display: flex;
flex-direction: column;
}
.article-title {
font-weight: bold;
margin: 0px;
margin-bottom: 8px;
font-size: 20px;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
}
.article-container:hover {
background-color: #141c3a;
color: #ffffff;
}
.article-preview {
font-style: italic;
margin: 0;
font-size: 16px;
font-weight: normal;
font-stretch: normal;
font-style: italic;
line-height: 1.31;
letter-spacing: normal;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
}
.article-content-container {
padding: 9px 16px 31px 16px;
}
.section-title {
font-size: 32px;
font-weight: bold;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
grid-column-end: 3;
grid-column-start: 1;
margin: 0;
margin-top: 32px;
margin-bottom: 8px;
}
.article-image {
width: 100%;
display: block;
}
.current-article {
padding-bottom: 32px;
}
.article-full-image-container {
width: 100%;
height: 220px;
}
.article-full-image-container > img {
object-fit: cover;
height: 100%;
width: 100%;
}
.current-article-title {
margin-top: 0;
margin-bottom: 16px;
font-size: 20px;
font-weight: bold;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
color: #141c3a;
}
.comments-title {
margin-top: 0;
padding-bottom: 8px;
border-bottom: solid 1px #141c3a;
}
.comments-list {
list-style: none;
padding-left: 0;
}
.comment-container {
padding: 10px;
border-radius: 6px;
border: solid 1px #e5e5e5;
background-color: #ffffff;
font-size: 14px;
font-weight: normal;
font-stretch: normal;
font-style: normal;
line-height: 1.43;
letter-spacing: normal;
color: #141c3a;
overflow-wrap: anywhere;
}
.comment-container:not(:last-of-type) {
margin-bottom: 8px;
}
.label {
font-size: 15px;
font-weight: bold;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
color: #141c3a;
display: block;
margin-bottom: 16px;
}
#comment {
padding: 7px 13px 7px 13px;
border-radius: 6px;
border: solid 1px #141c3a;
background-color: #ffffff;
flex-grow: 1;
margin-right: 8px;
}
.comment-button {
padding: 6px 12px;
border-radius: 6px;
border: solid 1px #141c3a;
background-color: #141c3a;
font-size: 18px;
font-weight: bold;
font-stretch: normal;
font-style: normal;
line-height: normal;
letter-spacing: normal;
color: #ffffff;
margin-left: auto;
cursor: pointer;
}
.comment-button:hover {
border: solid 1px #6400e4;
background-color: #6400e4;
}
.comment-button:disabled {
border: solid 1px #b9bbc4;
background-color: #b9bbc4;
}
#input-container {
display: flex;
}
// import './App.css';
import React from 'react';
import ArticlePreviews from '../features/articlePreviews/ArticlePreviews';
import CurrentArticle from '../features/currentArticle/CurrentArticle';
import Comments from '../features/comments/Comments';
function App() {
return (
<div className='App'>
<header className='App-header' />
<main>
<div className='current-article'>
<CurrentArticle />
<Comments />
</div>
<ArticlePreviews />
</main>
</div>
);
}
export default App;
import React from 'react';
export default function ArticleListItem({ article }) {
return (
<button key={article.id} className='article-container'>
<img src={article.image} alt='' className='article-image' />
<div className='article-content-container'>
<h3 className='article-title'>{article.title}</h3>
<p className='article-preview'>{article.preview}</p>
</div>
</button>
);
}
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
loadAllPreviews,
selectAllPreviews,
isLoading,
} from './articlePreviewsSlice';
import { loadCurrentArticle } from '../currentArticle/currentArticleSlice';
import ArticleListItem from '../../components/ArticleListItem';
const ArticlePreviews = () => {
const dispatch = useDispatch();
const articlePreviews = useSelector(selectAllPreviews);
const isLoadingPreviews = useSelector(isLoading);
useEffect(() => {
dispatch(loadAllPreviews());
}, [dispatch]);
if (isLoadingPreviews) {
return <div>loading state</div>;
}
return (
<>
<section className='articles-container'>
<h2 className='section-title'>All Articles</h2>
{articlePreviews.map((article) => (
<div key={article.id} onClick={(e) => dispatch(loadCurrentArticle(article.id))}>
<ArticleListItem article={article} />
</div>
))}
</section>
</>
);
};
export default ArticlePreviews;
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
export const loadAllPreviews = createAsyncThunk(
'articlePreviews/loadAllPreviews',
async () => {
const data = await fetch('api/articles');
const json = await data.json();
return json;
}
);
export const articlePreviewsSlice = createSlice({
name: 'articlePreviews',
initialState: {
articles: [],
isLoadingArticlePreviews: false,
hasError: false
},
extraReducers: (builder) => {
builder
.addCase(loadAllPreviews.pending, (state) => {
state.isLoadingArticlePreviews = true;
state.hasError = false;
})
.addCase(loadAllPreviews.fulfilled, (state, action) => {
state.isLoadingArticlePreviews = false;
state.articles = action.payload;
})
.addCase(loadAllPreviews.rejected, (state, action) => {
state.isLoadingArticlePreviews = false;
state.hasError = true;
state.articles = [];
})
},
});
export const selectAllPreviews = (state) => state.articlePreviews.articles;
export const isLoading = (state) => state.articlePreviews.isLoading;
export default articlePreviewsSlice.reducer;
[
{
"id": 1,
"title": "Biden Inaugurated as 46th President - The New York Times",
"preview": "Joseph Robinette Biden Jr. and Kamala Devi Harris took the oath of office at a Capitol still reeling from the attack of a violent mob at a time when a deadly pandemic is still ravaging the country.",
"fullText": "Joseph Robinette Biden Jr. and Kamala Devi Harris took the oath of office at a Capitol still reeling from the attack of a violent mob at a time when a deadly pandemic is still ravaging the country.",
"image": "https://static01.nyt.com/images/2021/01/20/us/politics/20dc-biden1-sub3/20dc-biden1-sub3-facebookJumbo.jpg"
},
{
"id": 2,
"title": "LG says it might quit the smartphone market",
"preview": "LG says it needs to make \"a cold judgment\" about its only money-losing division.",
"fullText": "LG says it needs to make \"a cold judgment\" about its only money-losing division.",
"image": "https://cdn.arstechnica.net/wp-content/uploads/2021/01/37-760x380.jpg"
},
{
"id": 3,
"title": "VW CEO teases Tesla’s Elon Musk in Twitter debut",
"preview": "VW CEO Herbert Diess is poking fun at his friendly rivalry with Tesla CEO Elon Musk in his Twitter debut as he tries to position Volkswagen as a leader in electrification.",
"fullText": "TVW CEO Herbert Diess is poking fun at his friendly rivalry with Tesla CEO Elon Musk in his Twitter debut as he tries to position Volkswagen as a leader in electrification.",
"image": "https://i1.wp.com/electrek.co/wp-content/uploads/sites/3/2020/09/VW-CEO-Hebert-Diess-Tesla-CEO-Elon-Musk-selfie-hero.jpg?resize=1200%2C628&quality=82&strip=all&ssl=1"
},
{
"id": 4,
"title": "QAnon believers struggle with inauguration.",
"preview": "As President Biden took office, some QAnon believers tried to rejigger their theories to accommodate a transfer of power.",
"fullText": "As President Biden took office, some QAnon believers tried to rejigger their theories to accommodate a transfer of power.",
"image": "https://static01.nyt.com/images/2021/01/20/business/20distortions-qanon/20distortions-qanon-facebookJumbo.jpg"
},
{
"id": 5,
"title": "Kamala Harris sworn into history",
"preview": "Harris becomes the first woman, Black woman and Asian American to serve as vice president.",
"fullText": "Harris becomes the first woman, Black woman and Asian American to serve as vice president.",
"image": "https://www.washingtonpost.com/wp-apps/imrs.php?src=https://arc-anglerfish-washpost-prod-washpost.s3.amazonaws.com/public/H4TGAGS3IEI6XKCJN6KCHJ277U.jpg&w=1440"
},
{
"id": 6,
"title": "SpaceX expands public beta test of Starlink satellite internet to Canada and the UK",
"preview": "Elon Musk's company is now offering early public access to its Starlink satellite internet service in Canada and the U.K.",
"fullText": "Elon Musk's company is now offering early public access to its Starlink satellite internet service in Canada and the U.K.",
"image": "https://image.cnbcfm.com/api/v1/image/106758975-1603465968180-EkzQ0UbXgAAGYVe-orig.jpg?v=1603466027"
},
{
"id": 7,
"title": "Scientists have finally worked out how butterflies fly",
"preview": "Experts, long puzzled by how butterflies fly, have found that the insects \"clap\" their wings together -- and their wings are perfectly evolved for better propulsion.",
"fullText": "Experts, long puzzled by how butterflies fly, have found that the insects \"clap\" their wings together -- and their wings are perfectly evolved for better propulsion.",
"image": "https://cdn.cnn.com/cnnnext/dam/assets/210120064324-restricted-butterflies-clap-intl-scli-super-tease.jpg"
},
{
"id": 8,
"title": "Navalny releases investigation into decadent billion-dollar 'Putin palace'",
"preview": "Even locked up in a detention center on the outskirts of Moscow, Kremlin critic Alexey Navalny continues to be a thorn in Russian President Vladimir Putin's side.",
"fullText": "Even locked up in a detention center on the outskirts of Moscow, Kremlin critic Alexey Navalny continues to be a thorn in Russian President Vladimir Putin's side.",
"image": "https://cdn.cnn.com/cnnnext/dam/assets/210120111237-restricted-05-putin-palace-navalny-russia-intl-super-tease.jpeg"
}
]
import { setupWorker } from 'msw';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
import React from 'react';
export default function Comment({ comment }) {
const { id, text } = comment
return (
<li key={id} className='comment-container'>
<span>{text}</span>
</li>
);
}
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 <Comment comment={comment} />;
})}
</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.
const commentsForArticleId =
article === undefined ? [] : comments[article.id];
useEffect(() => {
if (article !== undefined) {
dispatch(loadCommentsForArticleId(article.id));
}
}, [dispatch, article]);
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;
[
{
"id": 1,
"articleId": 5,
"text": "Congratulations Kamala."
},
{
"id": 2,
"articleId": 5,
"text": "Wow, very cool."
},
{
"id": 2,
"articleId": 7,
"text": "Butterflies are so awesome!!!"
},
{
"id": 2,
"articleId": 2,
"text": "Sad, I love my LG phone :("
}
]
// Import createAsyncThunk and createSlice here.
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
// Create loadCommentsForArticleId here.
export const loadCommentsForArticleId = createAsyncThunk(
"comments/loadCommentsForArticleId",
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 = 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, action) => {
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, action) => {
state.isLoadingComments = false;
state.failedToLoadComments = true;
},
[postCommentForArticleId.pending]: (state, action) => {
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, action) => {
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 React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import {
selectCurrentArticle,
isLoadingCurrentArticle,
} from '../currentArticle/currentArticleSlice';
import FullArticle from '../../components/FullArticle';
const CurrentArticle = () => {
const dispatch = useDispatch();
const article = useSelector(selectCurrentArticle);
const currentArticleIsLoading = useSelector(isLoadingCurrentArticle);
if (currentArticleIsLoading) {
return <div>Loading</div>;
} else if (!article) {
return null;
}
return <FullArticle article={article} />;
};
export default CurrentArticle;
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
export const loadCurrentArticle = createAsyncThunk(
'currentArticle/loadCurrentArticle',
async (articleId) => {
const data = await fetch(`api/articles/${articleId}`);
const json = await data.json();
return json;
}
);
export const currentArticleSlice = createSlice({
name: 'currentArticle',
initialState: {
article: undefined,
isLoadingCurrentArticle: false,
hasError: false
},
extraReducers: (builder) => {
builder
.addCase(loadCurrentArticle.pending, (state) => {
state.isLoadingCurrentArticle = true;
state.hasError = false;
})
.addCase(loadCurrentArticle.fulfilled, (state, action) => {
state.isLoadingCurrentArticle = false;
state.hasError = false;
state.article = action.payload;
})
.addCase(loadCurrentArticle.rejected, (state) => {
state.isLoadingCurrentArticle = false;
state.hasError = true;
state.article = {};
})
},
});
export const selectCurrentArticle = (state) => state.currentArticle.article;
export const isLoadingCurrentArticle = (state) => state.currentArticle.isLoadingCurrentArticle;
export default currentArticleSlice.reducer;
import React from 'react';
export default function FullArticle({ article }) {
return (
<>
<div className='article-full-image-container'>
<img src={article.image} alt='' />
</div>
<div key={article.id} className='current-article-container'>
<h1 className='current-article-title'>{article.title}</h1>
<div className='article-full-text'>{article.fullText}</div>
</div>
</>
);
}
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));
}),
];
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="./app/App.css">
<title>Learn ReactJS</title>
</head>
<body>
<main id="root">
</main>
<script src="https://content.codecademy.com/courses/React/react-16-full.min.js"></script>
<script src="./index.compiled.js"></script>
</body>
</html>
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/App';
import store from './app/store';
import { Provider } from 'react-redux';
const { worker } = require('./mocks/browser');
worker.start();
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
/**
* 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
}, {})
}
import { configureStore } from '@reduxjs/toolkit';
import articlePreviewsReducer from '../features/articlePreviews/articlePreviewsSlice';
import currentArticleReducer from '../features/currentArticle/currentArticleSlice';
import commentsReducer from '../features/comments/commentsSlice';
export default configureStore({
reducer: {
articlePreviews: articlePreviewsReducer,
currentArticle: currentArticleReducer,
comments: commentsReducer
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment