Added bonus chapter 8

02_lesson
gitdagray 2022-09-15 13:49:53 -05:00
parent 2aba59bf08
commit 4c4fe735c1
31 changed files with 29226 additions and 3 deletions

23
08_lesson/.gitignore vendored 100644
View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,15 @@
# "React Redux Toolkit for Beginners"
### Bonus Lesson - Chapter 8:
### Blog Project with full RTK Query Refactor
---
🚩 This lesson is not included in the Youtube course video or playlist.
🚀 Chapter 8 completes the modification of the Blog Project from the course to a full-integration of RTK Query:
- ✅ Relies on RTK Query hooks with identifiable cache keys
- ✅ Removes all instances of the Redux useSelector in favor of RTK useQuery hooks
- ✅ Demonstrates how to use multiple queries, loading states, etc. inside of one component.
- ✅ Demonstrates the use of selectFromResult in useQuery hooks with accompanying loading, success, and error states.

View File

@ -0,0 +1,896 @@
{
"users": [
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "Shanna@melissa.tv",
"address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
"geo": {
"lat": "-43.9509",
"lng": "-34.4618"
}
},
"phone": "010-692-6593 x09125",
"website": "anastasia.net",
"company": {
"name": "Deckow-Crist",
"catchPhrase": "Proactive didactic contingency",
"bs": "synergize scalable supply-chains"
}
},
{
"id": 3,
"name": "Clementine Bauch",
"username": "Samantha",
"email": "Nathan@yesenia.net",
"address": {
"street": "Douglas Extension",
"suite": "Suite 847",
"city": "McKenziehaven",
"zipcode": "59590-4157",
"geo": {
"lat": "-68.6102",
"lng": "-47.0653"
}
},
"phone": "1-463-123-4447",
"website": "ramiro.info",
"company": {
"name": "Romaguera-Jacobson",
"catchPhrase": "Face to face bifurcated interface",
"bs": "e-enable strategic applications"
}
},
{
"id": 4,
"name": "Patricia Lebsack",
"username": "Karianne",
"email": "Julianne.OConner@kory.org",
"address": {
"street": "Hoeger Mall",
"suite": "Apt. 692",
"city": "South Elvis",
"zipcode": "53919-4257",
"geo": {
"lat": "29.4572",
"lng": "-164.2990"
}
},
"phone": "493-170-9623 x156",
"website": "kale.biz",
"company": {
"name": "Robel-Corkery",
"catchPhrase": "Multi-tiered zero tolerance productivity",
"bs": "transition cutting-edge web services"
}
},
{
"id": 5,
"name": "Chelsey Dietrich",
"username": "Kamren",
"email": "Lucio_Hettinger@annie.ca",
"address": {
"street": "Skiles Walks",
"suite": "Suite 351",
"city": "Roscoeview",
"zipcode": "33263",
"geo": {
"lat": "-31.8129",
"lng": "62.5342"
}
},
"phone": "(254)954-1289",
"website": "demarco.info",
"company": {
"name": "Keebler LLC",
"catchPhrase": "User-centric fault-tolerant solution",
"bs": "revolutionize end-to-end systems"
}
},
{
"id": 6,
"name": "Mrs. Dennis Schulist",
"username": "Leopoldo_Corkery",
"email": "Karley_Dach@jasper.info",
"address": {
"street": "Norberto Crossing",
"suite": "Apt. 950",
"city": "South Christy",
"zipcode": "23505-1337",
"geo": {
"lat": "-71.4197",
"lng": "71.7478"
}
},
"phone": "1-477-935-8478 x6430",
"website": "ola.org",
"company": {
"name": "Considine-Lockman",
"catchPhrase": "Synchronised bottom-line interface",
"bs": "e-enable innovative applications"
}
},
{
"id": 7,
"name": "Kurtis Weissnat",
"username": "Elwyn.Skiles",
"email": "Telly.Hoeger@billy.biz",
"address": {
"street": "Rex Trail",
"suite": "Suite 280",
"city": "Howemouth",
"zipcode": "58804-1099",
"geo": {
"lat": "24.8918",
"lng": "21.8984"
}
},
"phone": "210.067.6132",
"website": "elvis.io",
"company": {
"name": "Johns Group",
"catchPhrase": "Configurable multimedia task-force",
"bs": "generate enterprise e-tailers"
}
},
{
"id": 8,
"name": "Nicholas Runolfsdottir V",
"username": "Maxime_Nienow",
"email": "Sherwood@rosamond.me",
"address": {
"street": "Ellsworth Summit",
"suite": "Suite 729",
"city": "Aliyaview",
"zipcode": "45169",
"geo": {
"lat": "-14.3990",
"lng": "-120.7677"
}
},
"phone": "586.493.6943 x140",
"website": "jacynthe.com",
"company": {
"name": "Abernathy Group",
"catchPhrase": "Implemented secondary concept",
"bs": "e-enable extensible e-tailers"
}
},
{
"id": 9,
"name": "Glenna Reichert",
"username": "Delphine",
"email": "Chaim_McDermott@dana.io",
"address": {
"street": "Dayna Park",
"suite": "Suite 449",
"city": "Bartholomebury",
"zipcode": "76495-3109",
"geo": {
"lat": "24.6463",
"lng": "-168.8889"
}
},
"phone": "(775)976-6794 x41206",
"website": "conrad.com",
"company": {
"name": "Yost and Sons",
"catchPhrase": "Switchable contextually-based project",
"bs": "aggregate real-time technologies"
}
},
{
"id": 10,
"name": "Clementina DuBuque",
"username": "Moriah.Stanton",
"email": "Rey.Padberg@karina.biz",
"address": {
"street": "Kattie Turnpike",
"suite": "Suite 198",
"city": "Lebsackbury",
"zipcode": "31428-2261",
"geo": {
"lat": "-38.2386",
"lng": "57.2232"
}
},
"phone": "024-648-3804",
"website": "ambrose.net",
"company": {
"name": "Hoeger LLC",
"catchPhrase": "Centralized empowering task-force",
"bs": "target end-to-end models"
}
}
],
"posts": [
{
"id": 2,
"title": "qui est esse",
"body": "hey there",
"userId": 1,
"date": "2022-04-13T23:02:19.248Z"
},
{
"id": 3,
"title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
"body": "hey there!",
"userId": 1,
"date": "2022-05-02T20:41:05.437Z"
},
{
"id": 5,
"title": "nesciunt quas odio",
"body": "Hello there!",
"userId": 1,
"date": "2022-04-14T17:46:43.450Z",
"reactions": {
"thumbsUp": 5,
"wow": 0,
"heart": 0,
"rocket": 0,
"coffee": 0
}
},
{
"id": 6,
"title": "dolorem eum magni eos aperiam quia",
"body": "Hi",
"userId": 1,
"date": "2022-04-14T17:58:28.955Z"
},
{
"id": 7,
"title": "magnam facilis autem",
"body": "testing!",
"userId": 1,
"date": "2022-04-27T16:59:02.116Z",
"reactions": {
"thumbsUp": 0,
"wow": 0,
"heart": 0,
"rocket": 0,
"coffee": 12
}
},
{
"id": 8,
"title": "dolorem dolore est ipsam",
"body": "dignissimos aperiam dolorem qui eum\nfacilis quibusdam animi sint suscipit qui sint possimus cum\nquaerat magni maiores excepturi\nipsam ut commodi dolor voluptatum modi aut vitae",
"userId": 1,
"date": "2022-09-15T03:26:02.899Z",
"reactions": {
"thumbsUp": 2,
"wow": 0,
"heart": 0,
"rocket": 0,
"coffee": 0
}
},
{
"id": 10,
"title": "optio molestias id quia eum!",
"body": "quo et expedita modi cum officia vel magni\ndoloribus qui repudiandae\nvero nisi sit\nquos veniam quod sed accusamus veritatis error",
"userId": 1,
"date": "2022-09-15T01:02:01.488Z",
"reactions": {
"thumbsUp": 7,
"wow": 0,
"heart": 0,
"rocket": 0,
"coffee": 0
}
},
{
"userId": 2,
"id": 11,
"title": "et ea vero quia laudantium autem",
"body": "delectus reiciendis molestiae occaecati non minima eveniet qui voluptatibus\naccusamus in eum beatae sit\nvel qui neque voluptates ut commodi qui incidunt\nut animi commodi",
"reactions": {
"thumbsUp": 8,
"wow": 0,
"heart": 0,
"rocket": 0,
"coffee": 1
}
},
{
"userId": 2,
"id": 12,
"title": "in quibusdam tempore odit est dolorem",
"body": "itaque id aut magnam\npraesentium quia et ea odit et ea voluptas et\nsapiente quia nihil amet occaecati quia id voluptatem\nincidunt ea est distinctio odio"
},
{
"userId": 2,
"id": 13,
"title": "dolorum ut in voluptas mollitia et saepe quo animi",
"body": "aut dicta possimus sint mollitia voluptas commodi quo doloremque\niste corrupti reiciendis voluptatem eius rerum\nsit cumque quod eligendi laborum minima\nperferendis recusandae assumenda consectetur porro architecto ipsum ipsam"
},
{
"userId": 2,
"id": 14,
"title": "voluptatem eligendi optio",
"body": "fuga et accusamus dolorum perferendis illo voluptas\nnon doloremque neque facere\nad qui dolorum molestiae beatae\nsed aut voluptas totam sit illum"
},
{
"userId": 2,
"id": 15,
"title": "eveniet quod temporibus",
"body": "reprehenderit quos placeat\nvelit minima officia dolores impedit repudiandae molestiae nam\nvoluptas recusandae quis delectus\nofficiis harum fugiat vitae"
},
{
"userId": 2,
"id": 16,
"title": "sint suscipit perspiciatis velit dolorum rerum ipsa laboriosam odio",
"body": "suscipit nam nisi quo aperiam aut\nasperiores eos fugit maiores voluptatibus quia\nvoluptatem quis ullam qui in alias quia est\nconsequatur magni mollitia accusamus ea nisi voluptate dicta"
},
{
"userId": 2,
"id": 17,
"title": "fugit voluptas sed molestias voluptatem provident",
"body": "eos voluptas et aut odit natus earum\naspernatur fuga molestiae ullam\ndeserunt ratione qui eos\nqui nihil ratione nemo velit ut aut id quo"
},
{
"userId": 2,
"id": 18,
"title": "voluptate et itaque vero tempora molestiae",
"body": "eveniet quo quis\nlaborum totam consequatur non dolor\nut et est repudiandae\nest voluptatem vel debitis et magnam"
},
{
"userId": 2,
"id": 19,
"title": "adipisci placeat illum aut reiciendis qui",
"body": "illum quis cupiditate provident sit magnam\nea sed aut omnis\nveniam maiores ullam consequatur atque\nadipisci quo iste expedita sit quos voluptas"
},
{
"userId": 2,
"id": 20,
"title": "doloribus ad provident suscipit at",
"body": "qui consequuntur ducimus possimus quisquam amet similique\nsuscipit porro ipsam amet\neos veritatis officiis exercitationem vel fugit aut necessitatibus totam\nomnis rerum consequatur expedita quidem cumque explicabo"
},
{
"userId": 3,
"id": 21,
"title": "asperiores ea ipsam voluptatibus modi minima quia sint",
"body": "repellat aliquid praesentium dolorem quo\nsed totam minus non itaque\nnihil labore molestiae sunt dolor eveniet hic recusandae veniam\ntempora et tenetur expedita sunt"
},
{
"userId": 3,
"id": 22,
"title": "dolor sint quo a velit explicabo quia nam",
"body": "eos qui et ipsum ipsam suscipit aut\nsed omnis non odio\nexpedita earum mollitia molestiae aut atque rem suscipit\nnam impedit esse"
},
{
"userId": 3,
"id": 23,
"title": "maxime id vitae nihil numquam",
"body": "veritatis unde neque eligendi\nquae quod architecto quo neque vitae\nest illo sit tempora doloremque fugit quod\net et vel beatae sequi ullam sed tenetur perspiciatis"
},
{
"userId": 3,
"id": 24,
"title": "autem hic labore sunt dolores incidunt",
"body": "enim et ex nulla\nomnis voluptas quia qui\nvoluptatem consequatur numquam aliquam sunt\ntotam recusandae id dignissimos aut sed asperiores deserunt"
},
{
"userId": 3,
"id": 25,
"title": "rem alias distinctio quo quis",
"body": "ullam consequatur ut\nomnis quis sit vel consequuntur\nipsa eligendi ipsum molestiae et omnis error nostrum\nmolestiae illo tempore quia et distinctio"
},
{
"userId": 3,
"id": 26,
"title": "est et quae odit qui non",
"body": "similique esse doloribus nihil accusamus\nomnis dolorem fuga consequuntur reprehenderit fugit recusandae temporibus\nperspiciatis cum ut laudantium\nomnis aut molestiae vel vero"
},
{
"userId": 3,
"id": 27,
"title": "quasi id et eos tenetur aut quo autem",
"body": "eum sed dolores ipsam sint possimus debitis occaecati\ndebitis qui qui et\nut placeat enim earum aut odit facilis\nconsequatur suscipit necessitatibus rerum sed inventore temporibus consequatur"
},
{
"userId": 3,
"id": 28,
"title": "delectus ullam et corporis nulla voluptas sequi",
"body": "non et quaerat ex quae ad maiores\nmaiores recusandae totam aut blanditiis mollitia quas illo\nut voluptatibus voluptatem\nsimilique nostrum eum"
},
{
"userId": 3,
"id": 30,
"title": "a quo magni similique perferendis",
"body": "alias dolor cumque\nimpedit blanditiis non eveniet odio maxime\nblanditiis amet eius quis tempora quia autem rem\na provident perspiciatis quia"
},
{
"userId": 4,
"id": 31,
"title": "ullam ut quidem id aut vel consequuntur",
"body": "debitis eius sed quibusdam non quis consectetur vitae\nimpedit ut qui consequatur sed aut in\nquidem sit nostrum et maiores adipisci atque\nquaerat voluptatem adipisci repudiandae"
},
{
"userId": 4,
"id": 32,
"title": "doloremque illum aliquid sunt",
"body": "deserunt eos nobis asperiores et hic\nest debitis repellat molestiae optio\nnihil ratione ut eos beatae quibusdam distinctio maiores\nearum voluptates et aut adipisci ea maiores voluptas maxime"
},
{
"userId": 4,
"id": 33,
"title": "qui explicabo molestiae dolorem",
"body": "rerum ut et numquam laborum odit est sit\nid qui sint in\nquasi tenetur tempore aperiam et quaerat qui in\nrerum officiis sequi cumque quod"
},
{
"userId": 4,
"id": 34,
"title": "magnam ut rerum iure",
"body": "ea velit perferendis earum ut voluptatem voluptate itaque iusto\ntotam pariatur in\nnemo voluptatem voluptatem autem magni tempora minima in\nest distinctio qui assumenda accusamus dignissimos officia nesciunt nobis"
},
{
"userId": 4,
"id": 35,
"title": "id nihil consequatur molestias animi provident",
"body": "nisi error delectus possimus ut eligendi vitae\nplaceat eos harum cupiditate facilis reprehenderit voluptatem beatae\nmodi ducimus quo illum voluptas eligendi\net nobis quia fugit"
},
{
"userId": 4,
"id": 36,
"title": "fuga nam accusamus voluptas reiciendis itaque",
"body": "ad mollitia et omnis minus architecto odit\nvoluptas doloremque maxime aut non ipsa qui alias veniam\nblanditiis culpa aut quia nihil cumque facere et occaecati\nqui aspernatur quia eaque ut aperiam inventore"
},
{
"userId": 4,
"id": 37,
"title": "provident vel ut sit ratione est",
"body": "debitis et eaque non officia sed nesciunt pariatur vel\nvoluptatem iste vero et ea\nnumquam aut expedita ipsum nulla in\nvoluptates omnis consequatur aut enim officiis in quam qui"
},
{
"userId": 4,
"id": 38,
"title": "explicabo et eos deleniti nostrum ab id repellendus",
"body": "animi esse sit aut sit nesciunt assumenda eum voluptas\nquia voluptatibus provident quia necessitatibus ea\nrerum repudiandae quia voluptatem delectus fugit aut id quia\nratione optio eos iusto veniam iure"
},
{
"userId": 4,
"id": 39,
"title": "eos dolorem iste accusantium est eaque quam",
"body": "corporis rerum ducimus vel eum accusantium\nmaxime aspernatur a porro possimus iste omnis\nest in deleniti asperiores fuga aut\nvoluptas sapiente vel dolore minus voluptatem incidunt ex"
},
{
"userId": 4,
"id": 40,
"title": "enim quo cumque",
"body": "ut voluptatum aliquid illo tenetur nemo sequi quo facilis\nipsum rem optio mollitia quas\nvoluptatem eum voluptas qui\nunde omnis voluptatem iure quasi maxime voluptas nam"
},
{
"userId": 5,
"id": 41,
"title": "non est facere",
"body": "molestias id nostrum\nexcepturi molestiae dolore omnis repellendus quaerat saepe\nconsectetur iste quaerat tenetur asperiores accusamus ex ut\nnam quidem est ducimus sunt debitis saepe"
},
{
"userId": 5,
"id": 42,
"title": "commodi ullam sint et excepturi error explicabo praesentium voluptas",
"body": "odio fugit voluptatum ducimus earum autem est incidunt voluptatem\nodit reiciendis aliquam sunt sequi nulla dolorem\nnon facere repellendus voluptates quia\nratione harum vitae ut"
},
{
"userId": 5,
"id": 43,
"title": "eligendi iste nostrum consequuntur adipisci praesentium sit beatae perferendis",
"body": "similique fugit est\nillum et dolorum harum et voluptate eaque quidem\nexercitationem quos nam commodi possimus cum odio nihil nulla\ndolorum exercitationem magnam ex et a et distinctio debitis"
},
{
"userId": 5,
"id": 44,
"title": "optio dolor molestias sit",
"body": "temporibus est consectetur dolore\net libero debitis vel velit laboriosam quia\nipsum quibusdam qui itaque fuga rem aut\nea et iure quam sed maxime ut distinctio quae"
},
{
"userId": 5,
"id": 45,
"title": "ut numquam possimus omnis eius suscipit laudantium iure",
"body": "est natus reiciendis nihil possimus aut provident\nex et dolor\nrepellat pariatur est\nnobis rerum repellendus dolorem autem"
},
{
"userId": 5,
"id": 46,
"title": "aut quo modi neque nostrum ducimus",
"body": "voluptatem quisquam iste\nvoluptatibus natus officiis facilis dolorem\nquis quas ipsam\nvel et voluptatum in aliquid"
},
{
"userId": 5,
"id": 47,
"title": "quibusdam cumque rem aut deserunt",
"body": "voluptatem assumenda ut qui ut cupiditate aut impedit veniam\noccaecati nemo illum voluptatem laudantium\nmolestiae beatae rerum ea iure soluta nostrum\neligendi et voluptate"
},
{
"userId": 5,
"id": 48,
"title": "ut voluptatem illum ea doloribus itaque eos",
"body": "voluptates quo voluptatem facilis iure occaecati\nvel assumenda rerum officia et\nillum perspiciatis ab deleniti\nlaudantium repellat ad ut et autem reprehenderit"
},
{
"userId": 5,
"id": 49,
"title": "laborum non sunt aut ut assumenda perspiciatis voluptas",
"body": "inventore ab sint\nnatus fugit id nulla sequi architecto nihil quaerat\neos tenetur in in eum veritatis non\nquibusdam officiis aspernatur cumque aut commodi aut"
},
{
"userId": 5,
"id": 50,
"title": "repellendus qui recusandae incidunt voluptates tenetur qui omnis exercitationem",
"body": "error suscipit maxime adipisci consequuntur recusandae\nvoluptas eligendi et est et voluptates\nquia distinctio ab amet quaerat molestiae et vitae\nadipisci impedit sequi nesciunt quis consectetur"
},
{
"userId": 6,
"id": 51,
"title": "soluta aliquam aperiam consequatur illo quis voluptas",
"body": "sunt dolores aut doloribus\ndolore doloribus voluptates tempora et\ndoloremque et quo\ncum asperiores sit consectetur dolorem"
},
{
"userId": 6,
"id": 52,
"title": "qui enim et consequuntur quia animi quis voluptate quibusdam",
"body": "iusto est quibusdam fuga quas quaerat molestias\na enim ut sit accusamus enim\ntemporibus iusto accusantium provident architecto\nsoluta esse reprehenderit qui laborum"
},
{
"userId": 6,
"id": 53,
"title": "ut quo aut ducimus alias",
"body": "minima harum praesentium eum rerum illo dolore\nquasi exercitationem rerum nam\nporro quis neque quo\nconsequatur minus dolor quidem veritatis sunt non explicabo similique"
},
{
"userId": 6,
"id": 54,
"title": "sit asperiores ipsam eveniet odio non quia",
"body": "totam corporis dignissimos\nvitae dolorem ut occaecati accusamus\nex velit deserunt\net exercitationem vero incidunt corrupti mollitia"
},
{
"userId": 6,
"id": 55,
"title": "sit vel voluptatem et non libero",
"body": "debitis excepturi ea perferendis harum libero optio\neos accusamus cum fuga ut sapiente repudiandae\net ut incidunt omnis molestiae\nnihil ut eum odit"
},
{
"userId": 6,
"id": 56,
"title": "qui et at rerum necessitatibus",
"body": "aut est omnis dolores\nneque rerum quod ea rerum velit pariatur beatae excepturi\net provident voluptas corrupti\ncorporis harum reprehenderit dolores eligendi"
},
{
"userId": 6,
"id": 57,
"title": "sed ab est est",
"body": "at pariatur consequuntur earum quidem\nquo est laudantium soluta voluptatem\nqui ullam et est\net cum voluptas voluptatum repellat est"
},
{
"userId": 6,
"id": 58,
"title": "voluptatum itaque dolores nisi et quasi",
"body": "veniam voluptatum quae adipisci id\net id quia eos ad et dolorem\naliquam quo nisi sunt eos impedit error\nad similique veniam"
},
{
"userId": 6,
"id": 59,
"title": "qui commodi dolor at maiores et quis id accusantium",
"body": "perspiciatis et quam ea autem temporibus non voluptatibus qui\nbeatae a earum officia nesciunt dolores suscipit voluptas et\nanimi doloribus cum rerum quas et magni\net hic ut ut commodi expedita sunt"
},
{
"userId": 6,
"id": 60,
"title": "consequatur placeat omnis quisquam quia reprehenderit fugit veritatis facere",
"body": "asperiores sunt ab assumenda cumque modi velit\nqui esse omnis\nvoluptate et fuga perferendis voluptas\nillo ratione amet aut et omnis"
},
{
"userId": 7,
"id": 61,
"title": "voluptatem doloribus consectetur est ut ducimus",
"body": "ab nemo optio odio\ndelectus tenetur corporis similique nobis repellendus rerum omnis facilis\nvero blanditiis debitis in nesciunt doloribus dicta dolores\nmagnam minus velit"
},
{
"userId": 7,
"id": 62,
"title": "beatae enim quia vel",
"body": "enim aspernatur illo distinctio quae praesentium\nbeatae alias amet delectus qui voluptate distinctio\nodit sint accusantium autem omnis\nquo molestiae omnis ea eveniet optio"
},
{
"userId": 7,
"id": 63,
"title": "voluptas blanditiis repellendus animi ducimus error sapiente et suscipit",
"body": "enim adipisci aspernatur nemo\nnumquam omnis facere dolorem dolor ex quis temporibus incidunt\nab delectus culpa quo reprehenderit blanditiis asperiores\naccusantium ut quam in voluptatibus voluptas ipsam dicta"
},
{
"userId": 7,
"id": 64,
"title": "et fugit quas eum in in aperiam quod",
"body": "id velit blanditiis\neum ea voluptatem\nmolestiae sint occaecati est eos perspiciatis\nincidunt a error provident eaque aut aut qui"
},
{
"userId": 7,
"id": 65,
"title": "consequatur id enim sunt et et",
"body": "voluptatibus ex esse\nsint explicabo est aliquid cumque adipisci fuga repellat labore\nmolestiae corrupti ex saepe at asperiores et perferendis\nnatus id esse incidunt pariatur"
},
{
"userId": 7,
"id": 66,
"title": "repudiandae ea animi iusto",
"body": "officia veritatis tenetur vero qui itaque\nsint non ratione\nsed et ut asperiores iusto eos molestiae nostrum\nveritatis quibusdam et nemo iusto saepe"
},
{
"userId": 7,
"id": 67,
"title": "aliquid eos sed fuga est maxime repellendus",
"body": "reprehenderit id nostrum\nvoluptas doloremque pariatur sint et accusantium quia quod aspernatur\net fugiat amet\nnon sapiente et consequatur necessitatibus molestiae"
},
{
"userId": 7,
"id": 68,
"title": "odio quis facere architecto reiciendis optio",
"body": "magnam molestiae perferendis quisquam\nqui cum reiciendis\nquaerat animi amet hic inventore\nea quia deleniti quidem saepe porro velit"
},
{
"userId": 7,
"id": 69,
"title": "fugiat quod pariatur odit minima",
"body": "officiis error culpa consequatur modi asperiores et\ndolorum assumenda voluptas et vel qui aut vel rerum\nvoluptatum quisquam perspiciatis quia rerum consequatur totam quas\nsequi commodi repudiandae asperiores et saepe a"
},
{
"userId": 7,
"id": 70,
"title": "voluptatem laborum magni",
"body": "sunt repellendus quae\nest asperiores aut deleniti esse accusamus repellendus quia aut\nquia dolorem unde\neum tempora esse dolore"
},
{
"userId": 8,
"id": 71,
"title": "et iusto veniam et illum aut fuga",
"body": "occaecati a doloribus\niste saepe consectetur placeat eum voluptate dolorem et\nqui quo quia voluptas\nrerum ut id enim velit est perferendis"
},
{
"userId": 8,
"id": 72,
"title": "sint hic doloribus consequatur eos non id",
"body": "quam occaecati qui deleniti consectetur\nconsequatur aut facere quas exercitationem aliquam hic voluptas\nneque id sunt ut aut accusamus\nsunt consectetur expedita inventore velit"
},
{
"userId": 8,
"id": 73,
"title": "consequuntur deleniti eos quia temporibus ab aliquid at",
"body": "voluptatem cumque tenetur consequatur expedita ipsum nemo quia explicabo\naut eum minima consequatur\ntempore cumque quae est et\net in consequuntur voluptatem voluptates aut"
},
{
"userId": 8,
"id": 74,
"title": "enim unde ratione doloribus quas enim ut sit sapiente",
"body": "odit qui et et necessitatibus sint veniam\nmollitia amet doloremque molestiae commodi similique magnam et quam\nblanditiis est itaque\nquo et tenetur ratione occaecati molestiae tempora"
},
{
"userId": 8,
"id": 75,
"title": "dignissimos eum dolor ut enim et delectus in",
"body": "commodi non non omnis et voluptas sit\nautem aut nobis magnam et sapiente voluptatem\net laborum repellat qui delectus facilis temporibus\nrerum amet et nemo voluptate expedita adipisci error dolorem"
},
{
"userId": 8,
"id": 76,
"title": "doloremque officiis ad et non perferendis",
"body": "ut animi facere\ntotam iusto tempore\nmolestiae eum aut et dolorem aperiam\nquaerat recusandae totam odio"
},
{
"userId": 8,
"id": 77,
"title": "necessitatibus quasi exercitationem odio",
"body": "modi ut in nulla repudiandae dolorum nostrum eos\naut consequatur omnis\nut incidunt est omnis iste et quam\nvoluptates sapiente aliquam asperiores nobis amet corrupti repudiandae provident"
},
{
"userId": 8,
"id": 78,
"title": "quam voluptatibus rerum veritatis",
"body": "nobis facilis odit tempore cupiditate quia\nassumenda doloribus rerum qui ea\nillum et qui totam\naut veniam repellendus"
},
{
"userId": 8,
"id": 79,
"title": "pariatur consequatur quia magnam autem omnis non amet",
"body": "libero accusantium et et facere incidunt sit dolorem\nnon excepturi qui quia sed laudantium\nquisquam molestiae ducimus est\nofficiis esse molestiae iste et quos"
},
{
"userId": 8,
"id": 80,
"title": "labore in ex et explicabo corporis aut quas",
"body": "ex quod dolorem ea eum iure qui provident amet\nquia qui facere excepturi et repudiandae\nasperiores molestias provident\nminus incidunt vero fugit rerum sint sunt excepturi provident"
},
{
"userId": 9,
"id": 81,
"title": "tempora rem veritatis voluptas quo dolores vero",
"body": "facere qui nesciunt est voluptatum voluptatem nisi\nsequi eligendi necessitatibus ea at rerum itaque\nharum non ratione velit laboriosam quis consequuntur\nex officiis minima doloremque voluptas ut aut",
"reactions": {
"thumbsUp": 1,
"wow": 0,
"heart": 0,
"rocket": 0,
"coffee": 0
}
},
{
"userId": 9,
"id": 82,
"title": "laudantium voluptate suscipit sunt enim enim",
"body": "ut libero sit aut totam inventore sunt\nporro sint qui sunt molestiae\nconsequatur cupiditate qui iste ducimus adipisci\ndolor enim assumenda soluta laboriosam amet iste delectus hic"
},
{
"userId": 9,
"id": 83,
"title": "odit et voluptates doloribus alias odio et",
"body": "est molestiae facilis quis tempora numquam nihil qui\nvoluptate sapiente consequatur est qui\nnecessitatibus autem aut ipsa aperiam modi dolore numquam\nreprehenderit eius rem quibusdam"
},
{
"userId": 9,
"id": 84,
"title": "optio ipsam molestias necessitatibus occaecati facilis veritatis dolores aut",
"body": "sint molestiae magni a et quos\neaque et quasi\nut rerum debitis similique veniam\nrecusandae dignissimos dolor incidunt consequatur odio"
},
{
"userId": 9,
"id": 85,
"title": "dolore veritatis porro provident adipisci blanditiis et sunt",
"body": "similique sed nisi voluptas iusto omnis\nmollitia et quo\nassumenda suscipit officia magnam sint sed tempora\nenim provident pariatur praesentium atque animi amet ratione"
},
{
"userId": 9,
"id": 86,
"title": "placeat quia et porro iste",
"body": "quasi excepturi consequatur iste autem temporibus sed molestiae beatae\net quaerat et esse ut\nvoluptatem occaecati et vel explicabo autem\nasperiores pariatur deserunt optio"
},
{
"userId": 9,
"id": 87,
"title": "nostrum quis quasi placeat",
"body": "eos et molestiae\nnesciunt ut a\ndolores perspiciatis repellendus repellat aliquid\nmagnam sint rem ipsum est"
},
{
"userId": 9,
"id": 88,
"title": "sapiente omnis fugit eos",
"body": "consequatur omnis est praesentium\nducimus non iste\nneque hic deserunt\nvoluptatibus veniam cum et rerum sed"
},
{
"userId": 9,
"id": 89,
"title": "sint soluta et vel magnam aut ut sed qui",
"body": "repellat aut aperiam totam temporibus autem et\narchitecto magnam ut\nconsequatur qui cupiditate rerum quia soluta dignissimos nihil iure\ntempore quas est"
},
{
"userId": 9,
"id": 90,
"title": "ad iusto omnis odit dolor voluptatibus",
"body": "minus omnis soluta quia\nqui sed adipisci voluptates illum ipsam voluptatem\neligendi officia ut in\neos soluta similique molestias praesentium blanditiis"
},
{
"userId": 10,
"id": 91,
"title": "aut amet sed",
"body": "libero voluptate eveniet aperiam sed\nsunt placeat suscipit molestias\nsimilique fugit nam natus\nexpedita consequatur consequatur dolores quia eos et placeat",
"reactions": {
"thumbsUp": 0,
"wow": 0,
"heart": 0,
"rocket": 5,
"coffee": 0
}
},
{
"userId": 10,
"id": 92,
"title": "ratione ex tenetur perferendis",
"body": "aut et excepturi dicta laudantium sint rerum nihil\nlaudantium et at\na neque minima officia et similique libero et\ncommodi voluptate qui"
},
{
"userId": 10,
"id": 93,
"title": "beatae soluta recusandae",
"body": "dolorem quibusdam ducimus consequuntur dicta aut quo laboriosam\nvoluptatem quis enim recusandae ut sed sunt\nnostrum est odit totam\nsit error sed sunt eveniet provident qui nulla"
},
{
"userId": 10,
"id": 94,
"title": "qui qui voluptates illo iste minima",
"body": "aspernatur expedita soluta quo ab ut similique\nexpedita dolores amet\nsed temporibus distinctio magnam saepe deleniti\nomnis facilis nam ipsum natus sint similique omnis"
},
{
"userId": 10,
"id": 95,
"title": "id minus libero illum nam ad officiis",
"body": "earum voluptatem facere provident blanditiis velit laboriosam\npariatur accusamus odio saepe\ncumque dolor qui a dicta ab doloribus consequatur omnis\ncorporis cupiditate eaque assumenda ad nesciunt"
},
{
"userId": 10,
"id": 96,
"title": "quaerat velit veniam amet cupiditate aut numquam ut sequi",
"body": "in non odio excepturi sint eum\nlabore voluptates vitae quia qui et\ninventore itaque rerum\nveniam non exercitationem delectus aut"
},
{
"userId": 10,
"id": 97,
"title": "quas fugiat ut perspiciatis vero provident",
"body": "eum non blanditiis soluta porro quibusdam voluptas\nvel voluptatem qui placeat dolores qui velit aut\nvel inventore aut cumque culpa explicabo aliquid at\nperspiciatis est et voluptatem dignissimos dolor itaque sit nam"
},
{
"userId": 10,
"id": 98,
"title": "laboriosam dolor voluptates",
"body": "doloremque ex facilis sit sint culpa\nsoluta assumenda eligendi non ut eius\nsequi ducimus vel quasi\nveritatis est dolores"
},
{
"userId": 10,
"id": 99,
"title": "temporibus sit alias delectus eligendi possimus magni",
"body": "quo deleniti praesentium dicta non quod\naut est molestias\nmolestias et officia quis nihil\nitaque dolorem quia"
},
{
"userId": 10,
"id": 100,
"title": "at nam consequatur ea labore ea harum",
"body": "cupiditate quo est a modi nesciunt soluta\nipsa voluptas error itaque dicta in\nautem qui minus magnam et distinctio eum\naccusamus ratione error aut"
},
{
"id": 101,
"title": "Hello!",
"body": "Hey",
"userId": 1,
"date": "2022-09-15T01:04:44.492Z"
},
{
"id": 102,
"title": "Checking",
"body": "check one two three",
"userId": 2,
"date": "2022-04-26T23:03:52.299Z"
},
{
"title": "Test Post",
"body": "Testing one two",
"userId": 3,
"date": "2022-09-15T01:27:01.642Z",
"reactions": {
"thumbsUp": 0,
"wow": 0,
"heart": 0,
"rocket": 0,
"coffee": 0
},
"id": 103
}
]
}

27309
08_lesson/package-lock.json generated 100644

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
{
"name": "07_lesson",
"version": "0.1.0",
"private": true,
"dependencies": {
"@reduxjs/toolkit": "^1.8.0",
"date-fns": "^2.28.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-redux": "^7.2.6",
"react-router-dom": "^6.3.0",
"react-scripts": "5.0.0"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -0,0 +1,36 @@
import PostsList from "./features/posts/PostsList";
import AddPostForm from "./features/posts/AddPostForm";
import SinglePostPage from "./features/posts/SinglePostPage";
import EditPostForm from "./features/posts/EditPostForm";
import UsersList from "./features/users/UsersList";
import UserPage from './features/users/UserPage';
import Layout from "./components/Layout";
import { Routes, Route, Navigate } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<PostsList />} />
<Route path="post">
<Route index element={<AddPostForm />} />
<Route path=":postId" element={<SinglePostPage />} />
<Route path="edit/:postId" element={<EditPostForm />} />
</Route>
<Route path="user">
<Route index element={<UsersList />} />
<Route path=":userId" element={<UserPage />} />
</Route>
{/* Catch all - replace with 404 component if you want */}
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
);
}
export default App;

View File

@ -0,0 +1,11 @@
import { configureStore } from "@reduxjs/toolkit";
import { apiSlice } from '../features/api/apiSlice';
export const store = configureStore({
reducer: {
[apiSlice.reducerPath]: apiSlice.reducer
},
middleware: getDefaultMiddleware =>
getDefaultMiddleware().concat(apiSlice.middleware),
devTools: true
})

View File

@ -0,0 +1,19 @@
import { Link } from "react-router-dom"
const Header = () => {
return (
<header className="Header">
<h1>Redux Blog</h1>
<nav>
<ul>
<li><Link to="/">Home</Link></li>
<li><Link to="post">Post</Link></li>
<li><Link to="user">Users</Link></li>
</ul>
</nav>
</header>
)
}
export default Header

View File

@ -0,0 +1,15 @@
import { Outlet } from 'react-router-dom';
import Header from './Header';
const Layout = () => {
return (
<>
<Header />
<main className="App">
<Outlet />
</main>
</>
)
}
export default Layout

View File

@ -0,0 +1,8 @@
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'
export const apiSlice = createApi({
reducerPath: 'api', // optional
baseQuery: fetchBaseQuery({ baseUrl: 'http://localhost:3500' }),
tagTypes: ['Post', 'User'],
endpoints: builder => ({})
})

View File

@ -0,0 +1,81 @@
import { useState } from "react";
import { useNavigate } from "react-router-dom";
import { useAddNewPostMutation } from "./postsSlice";
import { useGetUsersQuery } from "../users/usersSlice";
const AddPostForm = () => {
const [addNewPost, { isLoading }] = useAddNewPostMutation()
const navigate = useNavigate()
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [userId, setUserId] = useState('')
const { data: users, isSuccess } = useGetUsersQuery('getUsers')
const onTitleChanged = e => setTitle(e.target.value)
const onContentChanged = e => setContent(e.target.value)
const onAuthorChanged = e => setUserId(e.target.value)
const canSave = [title, content, userId].every(Boolean) && !isLoading;
const onSavePostClicked = async () => {
if (canSave) {
try {
await addNewPost({ title, body: content, userId }).unwrap()
setTitle('')
setContent('')
setUserId('')
navigate('/')
} catch (err) {
console.error('Failed to save the post', err)
}
}
}
let usersOptions
if (isSuccess) {
usersOptions = users.ids.map(id => (
<option key={id} value={id}>
{users.entities[id].name}
</option>
))
}
return (
<section>
<h2>Add a New Post</h2>
<form>
<label htmlFor="postTitle">Post Title:</label>
<input
type="text"
id="postTitle"
name="postTitle"
value={title}
onChange={onTitleChanged}
/>
<label htmlFor="postAuthor">Author:</label>
<select id="postAuthor" value={userId} onChange={onAuthorChanged}>
<option value=""></option>
{usersOptions}
</select>
<label htmlFor="postContent">Content:</label>
<textarea
id="postContent"
name="postContent"
value={content}
onChange={onContentChanged}
/>
<button
type="button"
onClick={onSavePostClicked}
disabled={!canSave}
>Save Post</button>
</form>
</section>
)
}
export default AddPostForm

View File

@ -0,0 +1,132 @@
import { useState, useEffect } from 'react'
import { useParams, useNavigate } from 'react-router-dom'
import { useGetPostsQuery } from './postsSlice';
import { useUpdatePostMutation, useDeletePostMutation } from "./postsSlice";
import { useGetUsersQuery } from '../users/usersSlice';
const EditPostForm = () => {
const { postId } = useParams()
const navigate = useNavigate()
const [updatePost, { isLoading }] = useUpdatePostMutation()
const [deletePost] = useDeletePostMutation()
const { post, isLoading: isLoadingPosts, isSuccess } = useGetPostsQuery('getPosts', {
selectFromResult: ({ data, isLoading, isSuccess }) => ({
post: data?.entities[postId],
isLoading,
isSuccess
}),
})
const { data: users, isSuccess: isSuccessUsers } = useGetUsersQuery('getUsers')
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [userId, setUserId] = useState('')
useEffect(() => {
if (isSuccess) {
setTitle(post.title)
setContent(post.body)
setUserId(post.userId)
}
}, [isSuccess, post?.title, post?.body, post?.userId])
if (isLoadingPosts) return <p>Loading...</p>
if (!post) {
return (
<section>
<h2>Post not found!</h2>
</section>
)
}
const onTitleChanged = e => setTitle(e.target.value)
const onContentChanged = e => setContent(e.target.value)
const onAuthorChanged = e => setUserId(Number(e.target.value))
const canSave = [title, content, userId].every(Boolean) && !isLoading;
const onSavePostClicked = async () => {
if (canSave) {
try {
await updatePost({ id: post?.id, title, body: content, userId }).unwrap()
setTitle('')
setContent('')
setUserId('')
navigate(`/post/${postId}`)
} catch (err) {
console.error('Failed to save the post', err)
}
}
}
let usersOptions
if (isSuccessUsers) {
usersOptions = users.ids.map(id => (
<option
key={id}
value={id}
>{users.entities[id].name}</option>
))
}
const onDeletePostClicked = async () => {
try {
await deletePost({ id: post?.id }).unwrap()
setTitle('')
setContent('')
setUserId('')
navigate('/')
} catch (err) {
console.error('Failed to delete the post', err)
}
}
return (
<section>
<h2>Edit Post</h2>
<form>
<label htmlFor="postTitle">Post Title:</label>
<input
type="text"
id="postTitle"
name="postTitle"
value={title}
onChange={onTitleChanged}
/>
<label htmlFor="postAuthor">Author:</label>
<select id="postAuthor" value={userId} onChange={onAuthorChanged}>
<option value=""></option>
{usersOptions}
</select>
<label htmlFor="postContent">Content:</label>
<textarea
id="postContent"
name="postContent"
value={content}
onChange={onContentChanged}
/>
<button
type="button"
onClick={onSavePostClicked}
disabled={!canSave}
>
Save Post
</button>
<button className="deleteButton"
type="button"
onClick={onDeletePostClicked}
>
Delete Post
</button>
</form>
</section>
)
}
export default EditPostForm

View File

@ -0,0 +1,16 @@
import { Link } from "react-router-dom";
import { useGetUsersQuery } from "../users/usersSlice";
const PostAuthor = ({ userId }) => {
const { user: author } = useGetUsersQuery('getUsers', {
selectFromResult: ({ data, isLoading }) => ({
user: data?.entities[userId]
}),
})
return <span>by {author
? <Link to={`/user/${userId}`}>{author.name}</Link>
: 'Unknown author'}</span>
}
export default PostAuthor

View File

@ -0,0 +1,29 @@
import PostAuthor from "./PostAuthor";
import TimeAgo from "./TimeAgo";
import ReactionButtons from "./ReactionButtons";
import { Link } from 'react-router-dom';
import { useGetPostsQuery } from './postsSlice';
const PostsExcerpt = ({ postId }) => {
const { post } = useGetPostsQuery('getPosts', {
selectFromResult: ({ data }) => ({
post: data?.entities[postId]
}),
})
return (
<article>
<h2>{post.title}</h2>
<p className="excerpt">{post.body.substring(0, 75)}...</p>
<p className="postCredit">
<Link to={`post/${post.id}`}>View Post</Link>
<PostAuthor userId={post.userId} />
<TimeAgo timestamp={post.date} />
</p>
<ReactionButtons post={post} />
</article>
)
}
export default PostsExcerpt

View File

@ -0,0 +1,28 @@
import PostsExcerpt from "./PostsExcerpt";
import { useGetPostsQuery } from './postsSlice';
const PostsList = () => {
const {
data: posts,
isLoading,
isSuccess,
isError,
error
} = useGetPostsQuery('getPosts')
let content;
if (isLoading) {
content = <p>"Loading..."</p>;
} else if (isSuccess) {
content = posts.ids.map(postId => <PostsExcerpt key={postId} postId={postId} />)
} else if (isError) {
content = <p>{error}</p>;
}
return (
<section>
{content}
</section>
)
}
export default PostsList

View File

@ -0,0 +1,32 @@
import { useAddReactionMutation } from './postsSlice'
const reactionEmoji = {
thumbsUp: '👍',
wow: '😮',
heart: '❤️',
rocket: '🚀',
coffee: '☕'
}
const ReactionButtons = ({ post }) => {
const [addReaction] = useAddReactionMutation()
const reactionButtons = Object.entries(reactionEmoji).map(([name, emoji]) => {
return (
<button
key={name}
type="button"
className="reactionButton"
onClick={() => {
const newValue = post.reactions[name] + 1;
addReaction({ postId: post.id, reactions: { ...post.reactions, [name]: newValue } })
}}
>
{emoji} {post.reactions[name]}
</button>
)
})
return <div>{reactionButtons}</div>
}
export default ReactionButtons

View File

@ -0,0 +1,42 @@
import PostAuthor from "./PostAuthor";
import TimeAgo from "./TimeAgo";
import ReactionButtons from "./ReactionButtons";
import { useParams } from 'react-router-dom';
import { Link } from 'react-router-dom';
import { useGetPostsQuery } from "./postsSlice";
const SinglePostPage = () => {
const { postId } = useParams()
const { post, isLoading } = useGetPostsQuery('getPosts', {
selectFromResult: ({ data, isLoading }) => ({
post: data?.entities[postId],
isLoading
}),
})
if (isLoading) return <p>Loading...</p>
if (!post) {
return (
<section>
<h2>Post not found!</h2>
</section>
)
}
return (
<article>
<h2>{post.title}</h2>
<p>{post.body}</p>
<p className="postCredit">
<Link to={`/post/edit/${post.id}`}>Edit Post</Link>
<PostAuthor userId={post.userId} />
<TimeAgo timestamp={post.date} />
</p>
<ReactionButtons post={post} />
</article>
)
}
export default SinglePostPage

View File

@ -0,0 +1,17 @@
import { parseISO, formatDistanceToNow } from 'date-fns';
const TimeAgo = ({ timestamp }) => {
let timeAgo = ''
if (timestamp) {
const date = parseISO(timestamp)
const timePeriod = formatDistanceToNow(date)
timeAgo = `${timePeriod} ago`
}
return (
<span title={timestamp}>
&nbsp; <i>{timeAgo}</i>
</span>
)
}
export default TimeAgo

View File

@ -0,0 +1,136 @@
import { createEntityAdapter } from "@reduxjs/toolkit";
import { sub } from 'date-fns';
import { apiSlice } from "../api/apiSlice";
const postsAdapter = createEntityAdapter({
sortComparer: (a, b) => b.date.localeCompare(a.date)
})
const initialState = postsAdapter.getInitialState()
export const extendedApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
getPosts: builder.query({
query: () => '/posts',
transformResponse: responseData => {
let min = 1;
const loadedPosts = responseData.map(post => {
if (!post?.date) post.date = sub(new Date(), { minutes: min++ }).toISOString();
if (!post?.reactions) post.reactions = {
thumbsUp: 0,
wow: 0,
heart: 0,
rocket: 0,
coffee: 0
}
return post;
});
return postsAdapter.setAll(initialState, loadedPosts)
},
providesTags: (result, error, arg) => [
{ type: 'Post', id: "LIST" },
...result.ids.map(id => ({ type: 'Post', id }))
]
}),
getPostsByUserId: builder.query({
query: id => `/posts/?userId=${id}`,
transformResponse: responseData => {
let min = 1;
const loadedPosts = responseData.map(post => {
if (!post?.date) post.date = sub(new Date(), { minutes: min++ }).toISOString();
if (!post?.reactions) post.reactions = {
thumbsUp: 0,
wow: 0,
heart: 0,
rocket: 0,
coffee: 0
}
return post;
});
return postsAdapter.setAll(initialState, loadedPosts)
},
providesTags: (result, error, arg) => [
...result.ids.map(id => ({ type: 'Post', id }))
]
}),
addNewPost: builder.mutation({
query: initialPost => ({
url: '/posts',
method: 'POST',
body: {
...initialPost,
userId: Number(initialPost.userId),
date: new Date().toISOString(),
reactions: {
thumbsUp: 0,
wow: 0,
heart: 0,
rocket: 0,
coffee: 0
}
}
}),
invalidatesTags: [
{ type: 'Post', id: "LIST" }
]
}),
updatePost: builder.mutation({
query: initialPost => ({
url: `/posts/${initialPost.id}`,
method: 'PUT',
body: {
...initialPost,
date: new Date().toISOString()
}
}),
invalidatesTags: (result, error, arg) => [
{ type: 'Post', id: arg.id }
]
}),
deletePost: builder.mutation({
query: ({ id }) => ({
url: `/posts/${id}`,
method: 'DELETE',
body: { id }
}),
invalidatesTags: (result, error, arg) => [
{ type: 'Post', id: arg.id }
]
}),
addReaction: builder.mutation({
query: ({ postId, reactions }) => ({
url: `posts/${postId}`,
method: 'PATCH',
// In a real app, we'd probably need to base this on user ID somehow
// so that a user can't do the same reaction more than once
body: { reactions }
}),
async onQueryStarted({ postId, reactions }, { dispatch, queryFulfilled }) {
// `updateQueryData` requires the endpoint name and cache key arguments,
// so it knows which piece of cache state to update
const patchResult = dispatch(
// updateQueryData takes three arguments: the name of the endpoint to update, the same cache key value used to identify the specific cached data, and a callback that updates the cached data.
extendedApiSlice.util.updateQueryData('getPosts', 'getPosts', draft => {
// The `draft` is Immer-wrapped and can be "mutated" like in createSlice
const post = draft.entities[postId]
if (post) post.reactions = reactions
})
)
try {
await queryFulfilled
} catch {
patchResult.undo()
}
}
})
})
})
export const {
useGetPostsQuery,
useGetPostsByUserIdQuery,
useAddNewPostMutation,
useUpdatePostMutation,
useDeletePostMutation,
useAddReactionMutation
} = extendedApiSlice

View File

@ -0,0 +1,55 @@
import { Link, useParams } from 'react-router-dom'
import { useGetPostsByUserIdQuery } from '../posts/postsSlice'
import { useGetUsersQuery } from '../users/usersSlice'
const UserPage = () => {
const { userId } = useParams()
const { user,
isLoading: isLoadingUser,
isSuccess: isSuccessUser,
isError: isErrorUser,
error: errorUser
} = useGetUsersQuery('getUsers', {
selectFromResult: ({ data, isLoading, isSuccess, isError, error }) => ({
user: data?.entities[userId],
isLoading,
isSuccess,
isError,
error
}),
})
const {
data: postsForUser,
isLoading,
isSuccess,
isError,
error
} = useGetPostsByUserIdQuery(userId);
let content;
if (isLoading || isLoadingUser) {
content = <p>Loading...</p>
} else if (isSuccess && isSuccessUser) {
const { ids, entities } = postsForUser
content = (
<section>
<h2>{user?.name}</h2>
<ol>
{ids.map(id => (
<li key={id}>
<Link to={`/post/${id}`}>{entities[id].title}</Link>
</li>
))}
</ol>
</section>
)
} else if (isError || isErrorUser) {
content = <p>{error || errorUser}</p>;
}
return content
}
export default UserPage

View File

@ -0,0 +1,39 @@
import { Link } from 'react-router-dom'
import { useGetUsersQuery } from './usersSlice'
const UsersList = () => {
const {
data: users,
isLoading,
isSuccess,
isError,
error
} = useGetUsersQuery('getUsers')
let content;
if (isLoading) {
content = <p>"Loading..."</p>;
} else if (isSuccess) {
const renderedUsers = users.ids.map(userId => (
<li key={userId}>
<Link to={`/user/${userId}`}>{users.entities[userId].name}</Link>
</li>
))
content = (
<section>
<h2>Users</h2>
<ul>{renderedUsers}</ul>
</section>
)
} else if (isError) {
content = <p>{error}</p>;
}
return content
}
export default UsersList

View File

@ -0,0 +1,25 @@
import { createEntityAdapter } from "@reduxjs/toolkit";
import { apiSlice } from "../api/apiSlice";
const usersAdapter = createEntityAdapter()
const initialState = usersAdapter.getInitialState()
export const usersApiSlice = apiSlice.injectEndpoints({
endpoints: builder => ({
getUsers: builder.query({
query: () => '/users',
transformResponse: responseData => {
return usersAdapter.setAll(initialState, responseData)
},
providesTags: (result, error, arg) => [
{ type: 'User', id: "LIST" },
...result.ids.map(id => ({ type: 'User', id }))
]
})
})
})
export const {
useGetUsersQuery
} = usersApiSlice

View File

@ -0,0 +1,130 @@
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
font-family: Cambria, Cochin, Georgia, Times, "Times New Roman", serif;
background-color: white;
color: #000;
}
body {
min-height: 100vh;
font-size: 1.5rem;
}
input,
textarea,
button,
select {
font: inherit;
margin-bottom: 1em;
}
header {
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: flex-start;
background-color: purple;
color: whitesmoke;
position: sticky;
top: 0;
}
nav {
display: flex;
justify-content: flex-end;
}
nav ul {
list-style-type: none;
}
nav ul li {
display: inline-block;
margin-right: 1rem;
}
nav a, nav a:visited {
color: #fff;
text-decoration: none;
}
nav a:hover, nav a:focus {
text-decoration: underline;
}
main {
max-width: 500px;
margin: auto;
}
section {
margin-top: 1em;
}
article {
margin: 0.5em;
border: 1px solid #000;
border-radius: 10px;
padding: 1em;
}
h1 {
font-size: 3.5rem;
}
h2 {
margin-bottom: 1rem;
}
p {
font-family: Arial, Helvetica, sans-serif;
line-height: 1.4;
font-size: 1.2rem;
margin: 0.5em 0;
}
form {
display: flex;
flex-direction: column;
}
textarea {
height: 200px;
}
.postCredit {
font-size: 1rem;
}
.postCredit a,
.postCredit a:visited {
margin-right: 0.5rem;
color: black;
}
.postCredit a:hover,
.postCredit a:focus {
color: hsla(0, 0%, 0%, 0.75);
}
.excerpt {
font-style: italic;
}
.reactionButton {
margin: 0 0.25em 0 0;
background: transparent;
border: none;
color: #000;
font-size: 1rem;
}
.deleteButton {
background-color: palevioletred;
color: white;
}

View File

@ -0,0 +1,20 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { store } from './app/store';
import { Provider } from 'react-redux';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<Router>
<Routes>
<Route path="/*" element={<App />} />
</Routes>
</Router>
</Provider>
</React.StrictMode>,
document.getElementById('root')
);

View File

@ -1,6 +1,6 @@
# "React Redux Toolkit for Beginners" # "React Redux Toolkit for Beginners"
### 7 Chapters ### ~~7~~ 8 Chapters
--- ---
@ -77,4 +77,5 @@
- 🔗 [Chapter 7 Starter Code - Advanced Redux Blog](https://github.com/gitdagray/react_redux_toolkit/tree/main/07_lesson_starter) - 🔗 [Chapter 7 Starter Code - Advanced Redux Blog](https://github.com/gitdagray/react_redux_toolkit/tree/main/07_lesson_starter)
- 🔗 [Chapter 7 Completed Code - Advanced Redux Blog](https://github.com/gitdagray/react_redux_toolkit/tree/main/07_lesson) - 🔗 [Chapter 7 Completed Code - Advanced Redux Blog](https://github.com/gitdagray/react_redux_toolkit/tree/main/07_lesson)
🚩🚩🚩
- 🔗 [Chapter 8 - Blog Project with full RTK Query Refactor](https://github.com/gitdagray/react_redux_toolkit/tree/main/08_lesson)