├── .env
├── .eslintcache
├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── App.js
├── client-api
├── clientApi.js
└── index.js
├── components
├── card
│ ├── card.js
│ ├── content.js
│ ├── header.js
│ └── index.js
├── home
│ ├── home.css
│ ├── home.js
│ └── index.js
├── playlists
│ ├── index.js
│ ├── playlists.css
│ └── playlists.js
└── selectCategories
│ ├── index.js
│ ├── selectCategories.css
│ └── selectCategories.js
├── context
└── dataProvider.js
├── index.css
├── index.js
└── services
├── getCategories.js
├── getPlaylists.js
└── index.js
/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_API_TOKEN=BQCCZDwHM6ApJBRSiSrnApq65T0JKR_AMOkvfrgf5Ft5D6G2HBoZ45uiK4PNOXvhKLEZ3kAqnqG7fh2aPis3nPFlBkk5LQqpv-CB7rcbWYNNJJpSMrW5UMIybre6tSlCNDIBC8BljsSQTAE
--------------------------------------------------------------------------------
/.eslintcache:
--------------------------------------------------------------------------------
1 | [{"D:\\dev\\react-architecture-patterns\\src\\index.js":"1","D:\\dev\\react-architecture-patterns\\src\\App.js":"2","D:\\dev\\react-architecture-patterns\\src\\context\\dataProvider.js":"3","D:\\dev\\react-architecture-patterns\\src\\components\\home\\index.js":"4","D:\\dev\\react-architecture-patterns\\src\\services\\index.js":"5","D:\\dev\\react-architecture-patterns\\src\\components\\home\\home.js":"6","D:\\dev\\react-architecture-patterns\\src\\services\\getPlaylists.js":"7","D:\\dev\\react-architecture-patterns\\src\\services\\getCategories.js":"8","D:\\dev\\react-architecture-patterns\\src\\components\\selectCategories\\index.js":"9","D:\\dev\\react-architecture-patterns\\src\\components\\playlists\\index.js":"10","D:\\dev\\react-architecture-patterns\\src\\components\\selectCategories\\selectCategories.js":"11","D:\\dev\\react-architecture-patterns\\src\\client-api\\index.js":"12","D:\\dev\\react-architecture-patterns\\src\\components\\playlists\\playlists.js":"13","D:\\dev\\react-architecture-patterns\\src\\client-api\\clientApi.js":"14","D:\\dev\\react-architecture-patterns\\src\\components\\card\\index.js":"15","D:\\dev\\react-architecture-patterns\\src\\components\\card\\header.js":"16","D:\\dev\\react-architecture-patterns\\src\\components\\card\\card.js":"17","D:\\dev\\react-architecture-patterns\\src\\components\\card\\content.js":"18"},{"size":219,"mtime":1607877517128,"results":"19","hashOfConfig":"20"},{"size":224,"mtime":1607877603255,"results":"21","hashOfConfig":"20"},{"size":2073,"mtime":1607877562694,"results":"22","hashOfConfig":"20"},{"size":52,"mtime":1607877562672,"results":"23","hashOfConfig":"20"},{"size":133,"mtime":1607877562714,"results":"24","hashOfConfig":"20"},{"size":582,"mtime":1607877562670,"results":"25","hashOfConfig":"20"},{"size":329,"mtime":1607877562711,"results":"26","hashOfConfig":"20"},{"size":318,"mtime":1607877562711,"results":"27","hashOfConfig":"20"},{"size":88,"mtime":1607877562675,"results":"28","hashOfConfig":"20"},{"size":67,"mtime":1607877562673,"results":"29","hashOfConfig":"20"},{"size":1091,"mtime":1607877562676,"results":"30","hashOfConfig":"20"},{"size":67,"mtime":1607877562646,"results":"31","hashOfConfig":"20"},{"size":1408,"mtime":1607877562674,"results":"32","hashOfConfig":"20"},{"size":786,"mtime":1607878551461,"results":"33","hashOfConfig":"20"},{"size":149,"mtime":1607877562668,"results":"34","hashOfConfig":"20"},{"size":137,"mtime":1607877562668,"results":"35","hashOfConfig":"20"},{"size":195,"mtime":1607877562666,"results":"36","hashOfConfig":"20"},{"size":189,"mtime":1607877562667,"results":"37","hashOfConfig":"20"},{"filePath":"38","messages":"39","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},"12ikqyf",{"filePath":"41","messages":"42","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"43","messages":"44","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"45","messages":"46","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"47","messages":"48","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"49","messages":"50","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"51","messages":"52","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"53","messages":"54","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"55","messages":"56","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"57","messages":"58","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"59","messages":"60","errorCount":0,"warningCount":1,"fixableErrorCount":0,"fixableWarningCount":0,"source":"61","usedDeprecatedRules":"40"},{"filePath":"62","messages":"63","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"64","messages":"65","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"66","messages":"67","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"68","messages":"69","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"70","messages":"71","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"72","messages":"73","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"40"},{"filePath":"74","messages":"75","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0,"usedDeprecatedRules":"76"},"D:\\dev\\react-architecture-patterns\\src\\index.js",[],["77","78"],"D:\\dev\\react-architecture-patterns\\src\\App.js",[],"D:\\dev\\react-architecture-patterns\\src\\context\\dataProvider.js",[],"D:\\dev\\react-architecture-patterns\\src\\components\\home\\index.js",[],"D:\\dev\\react-architecture-patterns\\src\\services\\index.js",[],"D:\\dev\\react-architecture-patterns\\src\\components\\home\\home.js",[],"D:\\dev\\react-architecture-patterns\\src\\services\\getPlaylists.js",[],"D:\\dev\\react-architecture-patterns\\src\\services\\getCategories.js",[],"D:\\dev\\react-architecture-patterns\\src\\components\\selectCategories\\index.js",[],"D:\\dev\\react-architecture-patterns\\src\\components\\playlists\\index.js",[],"D:\\dev\\react-architecture-patterns\\src\\components\\selectCategories\\selectCategories.js",["79"],"import React, { useEffect } from \"react\";\r\nimport { Select, MenuItem, FormControl } from \"@material-ui/core\";\r\nimport { useDataActions, useDataState } from \"../../context/dataProvider\";\r\nimport \"./selectCategories.css\";\r\n\r\nconst SelectCategories = () => {\r\n const { categories, selectedCategory } = useDataState();\r\n const { fetchCategories, selectCategory } = useDataActions();\r\n\r\n useEffect(() => {\r\n fetchCategories();\r\n }, []);\r\n\r\n const handleOnChange = (event) => {\r\n const categoryId = event.target.value;\r\n selectCategory(categoryId);\r\n };\r\n\r\n return (\r\n
\r\n \r\n \r\n \r\n
\r\n );\r\n};\r\n\r\nexport default SelectCategories;\r\n","D:\\dev\\react-architecture-patterns\\src\\client-api\\index.js",[],"D:\\dev\\react-architecture-patterns\\src\\components\\playlists\\playlists.js",[],"D:\\dev\\react-architecture-patterns\\src\\client-api\\clientApi.js",[],"D:\\dev\\react-architecture-patterns\\src\\components\\card\\index.js",[],"D:\\dev\\react-architecture-patterns\\src\\components\\card\\header.js",[],"D:\\dev\\react-architecture-patterns\\src\\components\\card\\card.js",[],"D:\\dev\\react-architecture-patterns\\src\\components\\card\\content.js",[],["80","81"],{"ruleId":"82","replacedBy":"83"},{"ruleId":"84","replacedBy":"85"},{"ruleId":"86","severity":1,"message":"87","line":12,"column":6,"nodeType":"88","endLine":12,"endColumn":8,"suggestions":"89"},{"ruleId":"82","replacedBy":"90"},{"ruleId":"84","replacedBy":"91"},"no-native-reassign",["92"],"no-negated-in-lhs",["93"],"react-hooks/exhaustive-deps","React Hook useEffect has a missing dependency: 'fetchCategories'. Either include it or remove the dependency array.","ArrayExpression",["94"],["92"],["93"],"no-global-assign","no-unsafe-negation",{"desc":"95","fix":"96"},"Update the dependencies array to be: [fetchCategories]",{"range":"97","text":"98"},[434,436],"[fetchCategories]"]
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env.local
17 | .env.development.local
18 | .env.test.local
19 | .env.production.local
20 |
21 | npm-debug.log*
22 | yarn-debug.log*
23 | yarn-error.log*
24 |
25 | .env
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ⚛️ React Architecture Patterns
2 |
3 | A very simple showcase of different patterns applied to React.
4 |
5 | - [Xstate state machines Pattern](https://github.com/mikelpmc/react-architecture-patterns/tree/xstate)
6 | - [Context Provider Pattern](https://github.com/mikelpmc/react-architecture-patterns/tree/context-provider)
7 | - [Query hook Pattern](https://github.com/mikelpmc/react-architecture-patterns/tree/query-hook)
8 | - [PubSub Pattern](https://github.com/mikelpmc/react-architecture-patterns/tree/pub-sub)
9 |
10 | To access each pattern go to the desired branch.
11 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-architecture-patterns",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@material-ui/core": "^4.11.2",
7 | "@testing-library/jest-dom": "^5.11.6",
8 | "@testing-library/react": "^11.2.2",
9 | "@testing-library/user-event": "^12.5.0",
10 | "react": "^17.0.1",
11 | "react-dom": "^17.0.1",
12 | "react-scripts": "4.0.1",
13 | "request-promise": "^4.2.6"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test",
19 | "eject": "react-scripts eject"
20 | },
21 | "eslintConfig": {
22 | "extends": [
23 | "react-app",
24 | "react-app/jest"
25 | ]
26 | },
27 | "browserslist": {
28 | "production": [
29 | ">0.2%",
30 | "not dead",
31 | "not op_mini all"
32 | ],
33 | "development": [
34 | "last 1 chrome version",
35 | "last 1 firefox version",
36 | "last 1 safari version"
37 | ]
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikelpmc/react-architecture-patterns/e2aed5ba426941ebb1142ba55e5c697fc61ffcbf/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
13 |
17 |
18 |
27 | React App
28 |
29 |
30 |
31 |
32 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikelpmc/react-architecture-patterns/e2aed5ba426941ebb1142ba55e5c697fc61ffcbf/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mikelpmc/react-architecture-patterns/e2aed5ba426941ebb1142ba55e5c697fc61ffcbf/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "React App",
3 | "name": "Create React App Sample",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Home from "./components/home";
3 | import { DataProvider } from "./context/dataProvider";
4 |
5 | const App = () => (
6 |
7 |
8 |
9 | );
10 |
11 | export default App;
12 |
--------------------------------------------------------------------------------
/src/client-api/clientApi.js:
--------------------------------------------------------------------------------
1 | const rp = require("request-promise");
2 | const token = process.env.REACT_APP_API_TOKEN;
3 |
4 | const clientApi = {
5 | baseUrl() {
6 | return `https://api.spotify.com/v1`;
7 | },
8 |
9 | call(method, path, body, token) {
10 | const options = {
11 | method,
12 | url: `${this.baseUrl()}/${path}`,
13 | json: true,
14 | };
15 |
16 | if (body) options.body = body;
17 | if (token) options.headers = { authorization: `Bearer ${token}` };
18 |
19 | return rp(options);
20 | },
21 |
22 | getCategories() {
23 | return this.call("GET", `browse/categories?country=ES`, undefined, token);
24 | },
25 |
26 | getPlaylists(category) {
27 | return this.call(
28 | "GET",
29 | `browse/categories/${category}/playlists?country=ES&limit=10`,
30 | undefined,
31 | token
32 | );
33 | },
34 | };
35 |
36 | export default clientApi;
37 |
--------------------------------------------------------------------------------
/src/client-api/index.js:
--------------------------------------------------------------------------------
1 | import clientApi from "./clientApi";
2 |
3 | export default clientApi;
4 |
--------------------------------------------------------------------------------
/src/components/card/card.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import MaterialCard from "@material-ui/core/Card";
3 |
4 | const Card = ({ children }) => {
5 | return {children};
6 | };
7 |
8 | export default Card;
9 |
--------------------------------------------------------------------------------
/src/components/card/content.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import CardContent from "@material-ui/core/CardContent";
3 |
4 | const Content = ({ children }) => {children};
5 |
6 | export default Content;
7 |
--------------------------------------------------------------------------------
/src/components/card/header.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from "react";
2 |
3 | const Header = ({ children }) => {children};
4 |
5 | export default Header;
6 |
--------------------------------------------------------------------------------
/src/components/card/index.js:
--------------------------------------------------------------------------------
1 | import Card from "./card";
2 | import Content from "./content";
3 | import Header from "./header";
4 |
5 | export { Content, Header };
6 |
7 | export default Card;
8 |
--------------------------------------------------------------------------------
/src/components/home/home.css:
--------------------------------------------------------------------------------
1 | .container {
2 | display: grid;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/home/home.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import SelectCategories from "../selectCategories/";
3 | import Typography from "@material-ui/core/Typography";
4 | import Playlists from "../playlists";
5 | import "./home.css";
6 |
7 | const Home = () => {
8 | return (
9 |
10 |
17 | Spotify App - React Architecture Patterns
18 |
19 |
20 | Context Provider Pattern
21 |
22 |
23 |
24 |
25 | );
26 | };
27 |
28 | export default Home;
29 |
--------------------------------------------------------------------------------
/src/components/home/index.js:
--------------------------------------------------------------------------------
1 | import Home from "./home";
2 |
3 | export default Home;
4 |
--------------------------------------------------------------------------------
/src/components/playlists/index.js:
--------------------------------------------------------------------------------
1 | import Playlists from "./playlists";
2 |
3 | export default Playlists;
4 |
--------------------------------------------------------------------------------
/src/components/playlists/playlists.css:
--------------------------------------------------------------------------------
1 | .playlists-container {
2 | margin-top: 2rem;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/playlists/playlists.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Card, { Content, Header } from "../card";
3 | import { Grid, Typography, CardMedia } from "@material-ui/core";
4 | import { useDataState } from "../../context/dataProvider";
5 | import "./playlists.css";
6 |
7 | const Playlists = () => {
8 | const { playlists } = useDataState();
9 |
10 | return (
11 |
12 |
13 | {playlists.map((playlist) => {
14 | const image =
15 | playlist.images && playlist.images[0] && playlist.images[0].url;
16 |
17 | return (
18 |
19 |
20 |
29 |
30 |
36 | {playlist.name}
37 |
38 |
39 |
40 |
41 | );
42 | })}
43 |
44 |
45 | );
46 | };
47 |
48 | export default Playlists;
49 |
--------------------------------------------------------------------------------
/src/components/selectCategories/index.js:
--------------------------------------------------------------------------------
1 | import SelectCategories from "./selectCategories";
2 |
3 | export default SelectCategories;
4 |
--------------------------------------------------------------------------------
/src/components/selectCategories/selectCategories.css:
--------------------------------------------------------------------------------
1 | .select-container {
2 | text-align: center;
3 | }
4 |
--------------------------------------------------------------------------------
/src/components/selectCategories/selectCategories.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect } from "react";
2 | import { Select, MenuItem, FormControl } from "@material-ui/core";
3 | import { useDataActions, useDataState } from "../../context/dataProvider";
4 | import "./selectCategories.css";
5 |
6 | const SelectCategories = () => {
7 | const { categories, selectedCategory } = useDataState();
8 | const { fetchCategories, selectCategory } = useDataActions();
9 |
10 | useEffect(() => {
11 | fetchCategories();
12 | }, []);
13 |
14 | const handleOnChange = (event) => {
15 | const categoryId = event.target.value;
16 | selectCategory(categoryId);
17 | };
18 |
19 | return (
20 |
21 |
22 |
34 |
35 |
36 | );
37 | };
38 |
39 | export default SelectCategories;
40 |
--------------------------------------------------------------------------------
/src/context/dataProvider.js:
--------------------------------------------------------------------------------
1 | import React, { createContext, useContext, useEffect, useState } from "react";
2 | import { getCategories, getPlaylists } from "../services";
3 |
4 | const DataStateContext = createContext();
5 | const DataActionsContext = createContext();
6 |
7 | const DataProvider = ({ children }) => {
8 | const [categories, setCategories] = useState([]);
9 | const [playlists, setPlaylists] = useState([]);
10 | const [selectedCategory, setSelectedCategory] = useState(-1);
11 |
12 | const handleFetchCategories = async () => {
13 | try {
14 | const categories = await getCategories();
15 | setCategories(categories);
16 | } catch (error) {
17 | console.error(error);
18 | }
19 | };
20 |
21 | const handleFetchCategoryPlaylists = async (category) => {
22 | try {
23 | const playlists = await getPlaylists(category);
24 | setPlaylists(playlists);
25 | } catch (error) {
26 | console.error(error);
27 | }
28 | };
29 |
30 | const handleCategorySelect = (category) => {
31 | setSelectedCategory(category);
32 | };
33 |
34 | useEffect(() => {
35 | if (selectedCategory === -1) {
36 | setPlaylists([]);
37 | } else {
38 | handleFetchCategoryPlaylists(selectedCategory);
39 | }
40 | }, [selectedCategory]);
41 |
42 | const store = {
43 | categories,
44 | playlists,
45 | selectedCategory,
46 | };
47 |
48 | const actions = {
49 | fetchCategories: handleFetchCategories,
50 | selectCategory: handleCategorySelect,
51 | };
52 |
53 | return (
54 |
55 |
56 | {children}
57 |
58 |
59 | );
60 | };
61 |
62 | const useDataState = () => {
63 | const context = useContext(DataStateContext);
64 |
65 | if (context === undefined) {
66 | throw new Error("useDataState must be used within a DataProvider");
67 | }
68 |
69 | return context;
70 | };
71 |
72 | const useDataActions = () => {
73 | const context = useContext(DataActionsContext);
74 |
75 | if (context === undefined) {
76 | throw new Error("useDataActions must be used within a DataProvider");
77 | }
78 |
79 | return context;
80 | };
81 |
82 | export { DataProvider, useDataState, useDataActions };
83 |
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
5 | sans-serif;
6 | -webkit-font-smoothing: antialiased;
7 | -moz-osx-font-smoothing: grayscale;
8 | }
9 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import "./index.css";
4 | import App from "./App";
5 |
6 | ReactDOM.render(
7 |
8 |
9 | ,
10 | document.getElementById("root")
11 | );
12 |
--------------------------------------------------------------------------------
/src/services/getCategories.js:
--------------------------------------------------------------------------------
1 | import clientApi from "../client-api/";
2 |
3 | const getCategories = () => {
4 | return clientApi.getCategories().then((res) => {
5 | const categories = res.categories && res.categories.items;
6 | if (!categories) throw Error("No categories found");
7 |
8 | return categories;
9 | });
10 | };
11 |
12 | export default getCategories;
13 |
--------------------------------------------------------------------------------
/src/services/getPlaylists.js:
--------------------------------------------------------------------------------
1 | import clientApi from "../client-api/";
2 |
3 | const getPlaylists = (categoryId) => {
4 | return clientApi.getPlaylists(categoryId).then((res) => {
5 | const playlists = res.playlists && res.playlists.items;
6 | if (!playlists) throw Error("No playlists found");
7 |
8 | return playlists;
9 | });
10 | };
11 |
12 | export default getPlaylists;
13 |
--------------------------------------------------------------------------------
/src/services/index.js:
--------------------------------------------------------------------------------
1 | import getPlaylists from "./getPlaylists";
2 | import getCategories from "./getCategories";
3 |
4 | export { getPlaylists, getCategories };
5 |
--------------------------------------------------------------------------------