{"id":15177643,"url":"https://github.com/william-zhan-bot/react_photo_website","last_synced_at":"2026-02-18T13:01:03.806Z","repository":{"id":255387078,"uuid":"849450268","full_name":"William-Zhan-bot/React_photo_website","owner":"William-Zhan-bot","description":"以前用React寫相片的網站，主要集中在api的調用以及用route的練習","archived":false,"fork":false,"pushed_at":"2024-08-29T16:09:55.000Z","size":354,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-21T16:47:24.066Z","etag":null,"topics":["css","frontend","html","javascript","pexels","pexels-api","react","reactjs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/William-Zhan-bot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-08-29T16:04:58.000Z","updated_at":"2024-08-29T16:10:53.000Z","dependencies_parsed_at":"2024-08-29T18:10:49.533Z","dependency_job_id":null,"html_url":"https://github.com/William-Zhan-bot/React_photo_website","commit_stats":null,"previous_names":["william-zhan-bot/react_photo_website"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/William-Zhan-bot/React_photo_website","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/William-Zhan-bot%2FReact_photo_website","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/William-Zhan-bot%2FReact_photo_website/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/William-Zhan-bot%2FReact_photo_website/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/William-Zhan-bot%2FReact_photo_website/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/William-Zhan-bot","download_url":"https://codeload.github.com/William-Zhan-bot/React_photo_website/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/William-Zhan-bot%2FReact_photo_website/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29580625,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-18T08:38:15.585Z","status":"ssl_error","status_checked_at":"2026-02-18T08:38:14.917Z","response_time":162,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["css","frontend","html","javascript","pexels","pexels-api","react","reactjs"],"created_at":"2024-09-27T14:41:32.005Z","updated_at":"2026-02-18T13:01:03.772Z","avatar_url":"https://github.com/William-Zhan-bot.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# React_photo_website\n\n製作以React為基底的相片網站 from Udemy Courses 2022網頁全端開發\n以下為之前學習的筆記\n\n技術總結:\n\n1. React模式製作\n2. Routing in React\n3. css + google fonts\n4. Fetch API\n5. 更多圖片\n\n5.2.3版 Route examples\n\n```jsx\nimport About from \"./pages/About\";\nimport { Switch, Route } from \"react-router-dom\";\n \nfunction App() {\n  return (\n    \u003cdiv className=\"App\"\u003e\n      \u003cNav /\u003e\n      \u003cSwitch\u003e\n        // exact 代表要完全符合 path 的值, 才會顯示\n        \u003cRoute path=\"/\" exact\u003e\n          \u003cHomepage /\u003e\n        \u003c/Route\u003e\n        \u003cRoute path=\"/about\" exact\u003e\n          \u003cAbout /\u003e\n        \u003c/Route\u003e\n      \u003c/Switch\u003e\n      \u003cFooter /\u003e\n    \u003c/div\u003e\n  );\n}\n```\n\n6.0.2 版Route examples\n\n```jsx\n\nimport About from \"./pages/About\";\nimport { Routes, Route } from \"react-router-dom\";\nfunction App() {\n   return (\n     \u003cdiv className=\"App\"\u003e\n       \u003cNav /\u003e\n       \u003cRoutes\u003e\n         \u003cRoute path=\"/\" element={\u003cHomepage /\u003e} /\u003e\n         \u003cRoute path=\"/about\" element={\u003cAbout /\u003e} /\u003e\n       \u003c/Routes\u003e\n       \u003cFooter /\u003e\n     \u003c/div\u003e\n   );\n }\n```\n\n## Router in React\n\n套件\n\n```jsx\nnpm install react-router-dom\n```\n\nindex設定Routes\n\n```jsx\nimport React from \"react\";\nimport ReactDOM from \"react-dom/client\";\nimport App from \"./App\";\n// routes設定在這裡\nimport { BrowserRouter } from \"react-router-dom\";\n\n// BrowserRouter =\u003e 包住tag 指定Router\nconst root = ReactDOM.createRoot(document.getElementById(\"root\"));\nroot.render(\n  \u003cReact.StrictMode\u003e\n    \u003cBrowserRouter\u003e\n      \u003cApp /\u003e\n    \u003c/BrowserRouter\u003e\n  \u003c/React.StrictMode\u003e\n);\n```\n\nin Apps.jsx\n\n```jsx\n// 導入各個頁面\nimport React from \"react\";\nimport Nav from \"./components/Nav\";\nimport Footer from \"./components/Footer\";\nimport Homepage from \"./pages/Homepage\";\nimport About from \"./pages/About\";\n\n// Route Set\n/**導入routes節點切換器以及route設定節點\n * 把節點放進去裡面\n * 新版本不會有route需要輸入exact的問題產生\n *\n * 另外就是要跟Nav後面的 href配合\n * 導入React當中的link to去實作\n */\nimport { Routes, Route } from \"react-router-dom\";\nfunction App() {\n  return (\n    \u003cdiv className=\"App\"\u003e\n      \u003cNav /\u003e\n\n      \u003cRoutes\u003e\n        \u003cRoute path=\"/\" element={\u003cHomepage /\u003e} /\u003e\n        \u003cRoute path=\"/about\" element={\u003cAbout /\u003e} /\u003e\n      \u003c/Routes\u003e\n\n      \u003cFooter /\u003e\n    \u003c/div\u003e\n  );\n}\n\nexport default App;\n```\n\nNav.jsx\n\n```jsx\nimport React from \"react\";\n// React router dom裡面希望用link\nimport { Link } from \"react-router-dom\";\n\nconst Nav = () =\u003e {\n  return (\n    \u003cdiv\u003e\n      \u003cul\u003e\n        \u003cli\u003e\n          \u003cLink to=\"/\"\u003eHomepage\u003c/Link\u003e\n        \u003c/li\u003e\n        \u003cli\u003e\n          \u003cLink to=\"/about\"\u003eAbout\u003c/Link\u003e\n        \u003c/li\u003e\n      \u003c/ul\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default Nav;\n```\n\n## CSS + Google fonts\n\n**css技巧**\n\n在App.jsx導入唯一的css檔\n\n```jsx\n*import* styles *from* \"./styles/style.css\";\n```\n\n之後後面從sass把各支sass串起來即可\n\n前面加斜線是方便導入可以接上\n\n```jsx\n// 直接這行串接css 導入一次即可\n// 必須用這樣的引入才可以連接libs\n@import \"./nav\";\n@import \"./search\";\n@import \"./footer\";\n```\n\n![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/432b441e-ecff-48c6-9b4e-027cb0d41894/Untitled.png)\n\n**設定footer壓底**\n\n在Pages當中，利用inline的css設定頁面長度，把footer壓到底下\n\n```jsx\n\u003cdiv style={{ minHeight: \"100vh\" }}\u003e\n      \u003cSearch /\u003e\n\u003c/div\u003e\n```\n\n**Google font複習**\n\n![Untitled](https://s3-us-west-2.amazonaws.com/secure.notion-static.com/cdc6459c-f4a0-410b-a54e-28aa5c64b48e/Untitled.png)\n\n字體會有link跟css\n\ncss丟掉sass裡面的font-family進行設定\n\nlink要丟到預設的Header\n\n也就是public中的index.html\n\n!!注意!!是.html裡面\n\n即可導入該字體使用\n\n## Fetch API + 更多圖片\n\n精選相片\n\n先參考pexels的api，裡面影一個自動的推薦15張\n\n搜尋相片\n\n連接pexel的api去查找\n\n**api fetch問題**\n\n每次進入頁面讓他載入照片，採用useEffect去製作即可。\n\n**搜尋問題**\n\n關於方格的抓取，有用一個inputHandler以onChange去及時記錄搜尋欄state的變化，之後已``的方式連接url即可。\n\n**更多照片問題**\n\n必須新增一個state為page並放入url，讓每次按下的more會回傳出不同的東西，之後以concact的方式根原本處理過的photo array相連，即可做到array連貫的部分。\n\n不須擔心data為空報錯，因為會是一開始載入過後，useEffect頁面更新後的data = [data.photos](http://data.photos)陣列，不會是空的arr。\n\n關於搜尋部分的state改變，因為採用即時抓取，可能因此會產生按下more，但自己執行了輸入框條件的問題(因為採即時更新state，不為空即被視為有搜尋條件)。所以必須多創造一個currentSearch，並放在search按鈕當中做state的改變，但卻不會因此改到真正的URL，因為再函式內改過的state會根據js的scope原則，只能在函式當中進行調用，若是運算的話會發生錯誤。\n\n**scope examples:**\n\n```jsx\nconst i = 5;\n\nfunction test(){\ni += 3; // 無法使用\nconsole.log(i);  // 無法使用\n}\ntest();\n```\n\n所以必須以全域的方式去讓他改變，也就是回到global的scope去做操作。\n\n方法為用useEffect，跟上面一開始的search融合，利用對input條件的判斷(剛載入時input必為空)，去調整search對應的網址，即可解決此問題，同時也結合前面currentSearch的state，解決了無輸入更新的問題。\n\n```jsx\n  useEffect(() =\u003e {\n    if (input === \"\") {\n      search(initialURL);\n    } else {\n      search(searchURL);\n    }\n  }, [currentSearch]);\n```\n\nHomepage.jsx\n\n```jsx\nimport React from \"react\";\nimport Search from \"../components/Search\";\n// 導入state配合Search\nimport { useState, useEffect } from \"react\";\nimport Photo from \"../components/Photo\";\n\nconst Homepage = () =\u003e {\n  // 頁面刷新，主要處理api中page的部分\n  const [page, setPage] = useState(1);\n  // 處理state小bug =\u003e不按下搜尋也出現相關資訊\n  // 把所有的input改成這個 多一層檢驗\n  // 之丟到search按鈕的click裡面去\n  const [currentSearch, setcurrentSearch] = useState(\"\");\n\n  // 搜尋列專用\n  const [input, setInput] = useState(\"\");\n  const searchURL = `https://api.pexels.com/v1/search?query=${currentSearch}\u0026per_page=15\u0026page=${page}`;\n\n  // 設定存放資料的state\n  let [data, setData] = useState(null);\n  const auth = \"563492ad6f91700001000001f5a17c93ac8d4005b0bc658071d73eac\";\n  const initialURL = \"https://api.pexels.com/v1/curated?page=1\u0026per_page=15\";\n\n  // 設定從api撈data\n  const search = async (url) =\u003e {\n    // 預設state是1 =\u003e 會先用他拿載入的data\n    // 之後頁面載入的話會從2開始往後+\n    // 這樣就不會重複\n    setPage(2);\n\n    // api連接\n    // 裡面的headers是依照參考的部分\n    const dataFetch = await fetch(url, {\n      method: \"Get\",\n      headers: {\n        Accept: \"application/json\",\n        Authorization: auth,\n      },\n    });\n    // api拿到之後轉乘json格式\n    let parseData = await dataFetch.json();\n    console.log(parseData.photos);\n    setData(parseData.photos);\n  };\n\n  // load more pic\n  /**\n   * 更多精選\n   * 更多搜尋相關\n   */\n  const morepic = async () =\u003e {\n    let newURL;\n    if (input === \"\") {\n      newURL = `https://api.pexels.com/v1/curated?page=${page}\u0026per_page=15`;\n    } else {\n      newURL = `https://api.pexels.com/v1/search?query=${currentSearch}\u0026per_page=15\u0026page=${page}`;\n    }\n    setPage(page + 1);\n    const dataFetch = await fetch(newURL, {\n      method: \"Get\",\n      headers: {\n        Accept: \"application/json\",\n        Authorization: auth,\n      },\n    });\n    let parseData = await dataFetch.json();\n    // 必須用concat串接上面的data\n    // 另外就是data因為前面刷新頁面執行過了 所以裡面一定有東西\n    setData(data.concat(parseData.photos));\n  };\n\n  // 頁面刷新自動上照片 useEffect\n  // 注意useEffect裡面要放arrow\n  // 同時也處理current Search的閉包問題\n  useEffect(() =\u003e {\n    if (input === \"\") {\n      search(initialURL);\n    } else {\n      search(searchURL);\n    }\n  }, [currentSearch]);\n\n  /**\n   * 將data以變數的形式丟回到photo使用\n   * 以維持state階級\n   * search setInput也是\n   */\n  return (\n    \u003cdiv style={{ minHeight: \"100vh\" }}\u003e\n      {/* 搜尋的變數必須由這裏去接，但由下一層啟動 \n          所以設定一個arrow去做    \n      */}\n      \u003cSearch\n        search={() =\u003e {\n          // 就算這裡做更改 searchURL依然會抓到最前面部分\n          // 因為JS的closure特性 他會先跳去上一層找searchURL，而非先執行新的URL，所以建議用useEffect\n          setcurrentSearch(input);\n          // search(searchURL);\n        }}\n        setInput={setInput}\n      /\u003e\n      \u003cdiv className=\"pictures\"\u003e\n        {\n          /* react的特殊技巧，因為下面的串接會因為data預設為null，\n        而不能用map抱錯。這裡可以用and運算的方式，把null出現的部分，\n        連同後面的map一起false掉，這樣就不會執行 */\n          data \u0026\u0026\n            data.map((d) =\u003e {\n              return \u003cPhoto data={d} key={Math.random() * 100} /\u003e;\n            })\n        }\n      \u003c/div\u003e\n      \u003cdiv className=\"morePicture\"\u003e\n        \u003cbutton onClick={morepic}\u003emore pictures\u003c/button\u003e\n      \u003c/div\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default Homepage;\n```\n\nSearch.jsx\n\n```jsx\nimport React, { useState } from \"react\";\n\nconst Search = ({ search, setInput }) =\u003e {\n  // 把輸入格的東西抓近來\n  // 透過js的節點去抓\n  // 必須以arrow function去做 以確保執行面\n  const inputHandler = (e) =\u003e {\n    setInput(e.target.value);\n  };\n\n  return (\n    \u003cdiv className=\"search\"\u003e\n      \u003cinput onChange={inputHandler} type=\"text\" /\u003e\n      \u003cbutton onClick={search}\u003eSearch\u003c/button\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default Search;\n```\n\nPhoto.jsx\n\n```jsx\nimport React from \"react\";\n\nconst Photo = ({ data }) =\u003e {\n  return (\n    \u003cdiv className=\"picture\"\u003e\n      \u003cp\u003e{data.photographer}\u003c/p\u003e\n      \u003cdiv className=\"ImageContainer\"\u003e\n        \u003cimg src={data.src.large} alt=\"\" /\u003e\n      \u003c/div\u003e\n      \u003cp\u003e\n        Download This:\n        {/* target blank是讓他開啟新分頁 */}\n        \u003ca target=\"_blank\" href={data.src.large}\u003e\n          Click Here\n        \u003c/a\u003e\n      \u003c/p\u003e\n    \u003c/div\u003e\n  );\n};\n\nexport default Photo;\n```\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwilliam-zhan-bot%2Freact_photo_website","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwilliam-zhan-bot%2Freact_photo_website","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwilliam-zhan-bot%2Freact_photo_website/lists"}