├── .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 Selecciona una categoría\r\n {categories.map(({ id, name }) => (\r\n \r\n {name}\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 |
21 | 28 |
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 | --------------------------------------------------------------------------------