{"id":20355712,"url":"https://github.com/geekeast/complete-guide-to-react-useeffect","last_synced_at":"2025-07-09T09:16:27.541Z","repository":{"id":38982884,"uuid":"236164678","full_name":"GeekEast/complete-guide-to-react-useEffect","owner":"GeekEast","description":"A complete guide to react useEffect","archived":false,"fork":false,"pushed_at":"2023-01-27T00:38:31.000Z","size":4152,"stargazers_count":2,"open_issues_count":7,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-02T12:17:26.399Z","etag":null,"topics":["usecallback","useeffect"],"latest_commit_sha":null,"homepage":"https://codesandbox.io/s/complete-intro-to-useeffect-uwgz2","language":"TypeScript","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/GeekEast.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}},"created_at":"2020-01-25T12:03:34.000Z","updated_at":"2022-05-10T07:50:39.000Z","dependencies_parsed_at":"2023-02-04T06:31:43.416Z","dependency_job_id":null,"html_url":"https://github.com/GeekEast/complete-guide-to-react-useEffect","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeekEast%2Fcomplete-guide-to-react-useEffect","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeekEast%2Fcomplete-guide-to-react-useEffect/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeekEast%2Fcomplete-guide-to-react-useEffect/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GeekEast%2Fcomplete-guide-to-react-useEffect/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GeekEast","download_url":"https://codeload.github.com/GeekEast/complete-guide-to-react-useEffect/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241889415,"owners_count":20037506,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["usecallback","useeffect"],"created_at":"2024-11-14T23:13:49.870Z","updated_at":"2025-03-04T17:30:35.634Z","avatar_url":"https://github.com/GeekEast.png","language":"TypeScript","readme":"## Case 1: miss dependency in useEffect\n- 因为`没有依赖`，是无法触发页面的`unmount`和`componentDidUpdate`的, 只会在`componentDidMount`时运行一次\n- 同样的值传入`setState`多次，只有**前两次**会引起re-render;\n- 为什么`count`的值一直是`0`？\n  - 每次渲染都是一次`snapshot`，会有自己`state`的一个`version`\n  - 因为`没有依赖`，所以就没有`unmount`的发生，所以`clean`不会发生\n  - `clean`没有发生，全局就`只`有一次`setInterval`,会把count`闭包`闭进去\n  - 闭包是闭了`count`这个`变量`，而`不是`它的`值`\n  - 但是`count`这个变量`永远停留`在当时渲染的那个`状态`(Immutable)\n  - 所以`setInterval`中读到的`count`永远都是`0`.\n```javascript\nimport React, { useState, useEffect } from 'react';\n\nconst Counter = () =\u003e {\n  const [count, setCount] = useState(0);\n  console.log('render');\n\n  useEffect(() =\u003e {\n    console.log('componentDidUpdate')\n\n    const id = setInterval(() =\u003e {\n      console.log(count)\n      setCount(count + 1)\n    }, 1000);\n\n    return () =\u003e {\n      clearInterval(id); // 只会在页面退出时执行\n      console.log('clean');\n    }\n  }, []); // 是无法触发页面的unmount和componentDidUpdate的, 只会在componentDidMount时运行一次\n  return (\n    \u003ch1\u003e\n      {count}\n    \u003c/h1\u003e\n  )\n}\n```\n\n\n### Solution 1: append dependency\n- 如果传入`count`，会发生什么?\n  - 因为设置了`count`为`dependency`, 每次`count`的变动都会引起`unmount`, `re-render`和`useEffect`\n  - 即clean上一次`setInterval`, 重新渲染(更新`count`的值), 和重新`setInterval`(闭包闭了新的`count`)\n  - 因此`表现起来`会很正常\n- 传入useEffect的dependencies:\n  - state: 如果useEffect中又会引起state的变化，将会陷入无限循环当中\n  - function:\n    - 如果`function`定义在`useEffect`里面，则无需添加到依赖中\n    - 如果`function`定义在`component`里面，将会陷入无限循环的渲染当中, 因为`function`每次渲染-引用都会发生变化\n    - 如果`function`定义在`component`外面，则只会渲染一次\n    - 如果必须把`function`定义在`component`里面，但是又不想无限循环，则在使用`useCallback`或者`useMemo`\n```javascript\nimport React, { useState, useEffect } from 'react';\nconst Counter = () =\u003e {\n  const [count, setCount] = useState(0);\n  console.log('render');\n\n  useEffect(() =\u003e {\n    console.log('componentDidUpdate')\n\n    const id = setInterval(() =\u003e {\n      console.log(count)\n      setCount(count + 1)\n    }, 1000);\n\n    return () =\u003e {\n      clearInterval(id);\n      console.log('clean');\n    }\n  }, [count]); // 每次变化将会触发unmount和componentDidUpdate\n\n  return (\n    \u003ch1\u003e\n      {count}\n    \u003c/h1\u003e\n  )\n}\n\nexport default Counter;\n```\n\u003cdiv style=\"text-align:center; margin:auto\"\u003e\u003cimg src=\"img/2020-01-23-13-33-54.png\"\u003e\u003c/div\u003e\n\n### Solution 2: remove dependency\n- `Solution 1`有什么问题?\n  - 按照直觉，状态的更新引起页面的重新渲染我们可以理解\n  - 但是状态的更新却同时引起了页面的`unmount`和`componentDidUpdate`, 其实是没有必要的过程\n  - 我们希望只有一个`setInterval`，但是页面依然会根据状态变化而重新熏染\n#### Solution A: 使用setState的函数模式\n```javascript\nimport React, { useState, useEffect } from 'react';\n\nconst Counter = () =\u003e {\n  const [count, setCount] = useState(0);\n  console.log('render');\n\n  useEffect(() =\u003e {\n    console.log('componentDidUpdate')\n\n    const id = setInterval(() =\u003e {\n      setCount(count =\u003e count + 1); // 仅仅描述了一种行为，每次执行时都会自动读取当时最新count值\n    }, 1000);\n\n    return () =\u003e {\n      clearInterval(id);\n      console.log('clean');\n    }\n  }, []); // 是无法触发页面的unmount和componentDidUpdate的\n\n  return (\n    \u003ch1\u003e\n      {count}\n    \u003c/h1\u003e\n  )\n}\n\nexport default Counter;\n```\n\n#### Solution B: 使用`useReducer`\n- 这种情况适用于: state相互之间存在依赖，需要传入多个进入`useEffect`的`dependency`\n- 建议: 能把`initialState`和`reducer`都放在`component`外面的，就尽量放在外面\n- 因为每次渲染都会形成新的`initialState`和`reducer`, 是没有必要的\n- 你会发现这里`state`的变化也不会引起`unmount`的发生\n```javascript\nimport React, { useEffect, useReducer } from 'react';\n\nconst initialState = { count: 0, step: 1 };\nconst reducer = (state, action) =\u003e {\n  switch (action.type) {\n    case 'TICK': return { ...state, \n      count: state.count + state.step };\n    case 'STEP': return { ...state, \n      count: state.count + state.step + action.payload, \n      step: state.step + action.payload }\n    default: throw new Error();\n  }\n}\n\nconst Counter = () =\u003e {\n  const [state, dispatch] = useReducer(reducer, initialState);\n\n  console.log('render');\n  useEffect(() =\u003e {\n    console.log('componentDidUpdate')\n    const id = setInterval(() =\u003e {\n      dispatch({ type: 'TICK' }); // 读取上一次最新的state交给了userReducer去完成\n    }, 1000);\n    return () =\u003e {\n      clearInterval(id);\n      console.log('clean');\n    }\n  }, []); // 是无法触发页面的unmount和componentDidUpdate的\n\n  return (\n    \u003ch1\u003e\n      {state.count} \u003cbr/\u003e\n      \u003cbutton onClick={() =\u003e { dispatch({ type: 'STEP', payload: 1 }) }}\u003eStep Up\u003c/button\u003e\n    \u003c/h1\u003e\n  )\n}\n\nexport default Counter;\n```\n## case 2: compute state based on props\n### Solution 1: pass props to dispatch\n- 这种情形跟`case 1`类似: \n  - 如果**不将**`step`例如`dependency`, 那么不会触发`unmount`, `setInterval`全局只有**一**个，闭包闭的`step`的值一直停留在最初的值上面，因为`props`的变化并**不**能引起`count`的变化\n  - 如果**将**`step`列入`dependency`, 将会触发`unmount`, `setInterval`一直会`clean`和`重建`，读取到的props也是最新的，但是有`一定性能的损耗`.\n```javascript\nimport React, { useEffect, useReducer } from 'react';\n\nconst initialState = { count: 0 };\nconst reducer = (state, action) =\u003e {\n  switch (action.type) {\n    case 'TICK': return { ...state, count: state.count + action.step };\n    default: throw new Error();\n  }\n}\n\nconst Counter = (props: any) =\u003e {\n  const { step } = props;\n  const [state, dispatch] = useReducer(reducer, initialState);\n\n  console.log('render');\n  useEffect(() =\u003e {\n    console.log('componentDidUpdate')\n    const id = setInterval(() =\u003e {\n      dispatch({ type: 'TICK', step }); // 读取上一次最新的state交给了userReducer去完成\n    }, 1000);\n    return () =\u003e {\n      clearInterval(id);\n      console.log('clean');\n    }\n  }, [step]); // react 保证dispatch在每次渲染中都是一样的\n\n  return (\n    \u003ch1\u003e\n      {state.count} \u003cbr /\u003e\n      {/* \u003cbutton onClick={() =\u003e { dispatch({ type: 'STEP', payload: step }) }}\u003eStep Up\u003c/button\u003e */}\n    \u003c/h1\u003e\n  )\n}\n\nexport default Counter;\n```\n### Solution 2: move reducer inside component\n- 因为`dependency`为`[]`, 因此全局只有一个`setInterval`\n- `props`的更新本身就会引起`重新渲染`，因此`reducer`里面读到的永远是最新的`props`\n```javascript\nimport React, { useEffect, useReducer } from 'react';\n\nconst initialState = { count: 0 };\n\nconst Counter = (props: any) =\u003e {\n  const { step } = props;\n\n  const reducer = (state, action) =\u003e {\n    switch (action.type) {\n      case 'TICK': return { ...state, count: state.count + step };\n      default: throw new Error();\n    }\n  }\n  const [state, dispatch] = useReducer(reducer, initialState);\n\n  console.log('render');\n  useEffect(() =\u003e {\n    console.log('componentDidUpdate')\n    const id = setInterval(() =\u003e {\n      dispatch({ type: 'TICK' }); \n    }, 1000);\n    return () =\u003e {\n      clearInterval(id);\n      console.log('clean');\n    }\n  }, []); \n\n  return (\n    \u003ch1\u003e\n      {state.count} \u003cbr /\u003e\n    \u003c/h1\u003e\n  )\n}\n\nexport default Counter;\n```\n\n## case 3: put function outside of effect\n- 特征: \n  - `函数`本身的**值**不会发生变化(**stable**)\n  - 函数本身`未引用`任何外部**变量**,\n- `useEffect`中引用`外部`的**变量**(**仅仅是变量不包括函数**)会`被提醒`要加入到`dependency`中\n- 所以这里的`fetchData`并**未**被要求加入到`dep`s中\n- 因为`fetchData`定义是在`component`内部，`useEffect`的外部，如果加入到`dep`s中，一定会引起页面的**无限刷新**\n```javascript\nimport React, { useState, useEffect } from 'react';\nimport axios from 'axios';\n\nconst SearchResult = () =\u003e {\n\n  const [data, setData] = useState({ hits: [] });\n\n  const fetchData = async () =\u003e {\n    const result = await axios('https://hn.algolia.com/api/v1/search?query=react')\n    setData(result.data);\n  }\n\n  useEffect(() =\u003e {\n    // 将fetchData放到这里其实效果一样，性能也基本上没有差异\n    fetchData(); // 未引用任何变量，所以不会被提醒加入到deps中\n  }, []) // 如果把函数加入到deps中，反而会造成页面的无限循环\n  return (\n    \u003cdiv\u003e{data.hits.map((item: any) =\u003e (\n      \u003cdiv\u003e{item.title}\u003c/div\u003e\n    ))}\u003c/div\u003e\n  )\n}\n\nexport default SearchResult;\n```\n## case 4: function references any external variables\n### Solution : put function inside useEffect with variable as deps in useEffect\n```javascript\nimport React, { useState, useEffect } from 'react';\nimport axios from 'axios';\n\nconst SearchResult = () =\u003e {\n  const [query, setQuery] = useState('react');\n  const [data, setData] = useState({ hits: [] });\n\n  useEffect(() =\u003e {\n    const fetchData = async () =\u003e { // define inside useEffect\n      const result = await axios('https://hn.algolia.com/api/v1/search?query='+ query);\n      setData(result.data);\n    }\n    fetchData();\n  }, [query]) // add external variables here\n  return (\n\n    \u003cdiv\u003e\n      \u003cul\u003e\n        {data.hits.map((item: any) =\u003e (\n          \u003cli key={item.objectID}\u003e{item.title}\u003c/li\u003e\n        ))}\n      \u003c/ul\u003e\n      {query}\n    \u003c/div\u003e\n  )\n}\n\nexport default SearchResult;\n```\n## case 5: function references any external variables but is used by multiple useEffects\n### Solution: `useCallback`\n- 特征:\n  - 如果有一个函数必须放到useEffect之外，例如想要在多个useEffect中复用这个函数\n  - 该函数要依赖外部变量时\n- 解决:\n  - useCallback的使用能够使得函数在每次页面渲染时候保持稳定，因此不会引起页面的再次渲染\n  - useCallback的本质其实是将原本定义在useEffect中的函数分离出来的一层，最终还是依赖于变量的\n- 问题:\n  - 只是'缓存'了变量，并未缓存api请求的结果，可以进一步优化\n```javascript\nimport React, { useState, useEffect, useCallback } from 'react';\nimport axios from 'axios';\n\nconst SearchResult = () =\u003e {\n  const [query, setQuery] = useState('react');\n  const [data1, setData1] = useState({ hits: [] });\n  const [data2, setData2] = useState({ hits: [] });\n\n  const MemorizedGetFetchUrl = useCallback(() =\u003e 'https://hn.algolia.com/api/v1/search?query=' + query, [query]);\n\n  useEffect(() =\u003e {\n    const fetchData = async () =\u003e {\n      const result = await axios(MemorizedGetFetchUrl());\n      setData1(result.data);\n    }\n    fetchData();\n  }, [MemorizedGetFetchUrl])\n\n\n  useEffect(() =\u003e {\n    const fetchData = async () =\u003e {\n      const result = await axios(MemorizedGetFetchUrl())\n      setData2(result.data);\n    }\n    fetchData()\n  }, [MemorizedGetFetchUrl])\n\n  return (\n    \u003cdiv\u003e\n      \u003cul\u003e\n        {data1.hits.map((item: any) =\u003e (\n          \u003cli key={item.objectID}\u003e{item.title}\u003c/li\u003e\n        ))}\n      \u003c/ul\u003e\n\n      \u003cbr /\u003e\n      \u003cul\u003e\n        {data2.hits.map((item: any) =\u003e (\n          \u003cli key={item.objectID}\u003e{item.title}\u003c/li\u003e\n        ))}\n      \u003c/ul\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default SearchResult;\n```\n### Optimization: `useCallback`\n- 这里优化，本质上是因为deps的缘故，只有在`query`改变时才会进行api请求\n```javascript\nimport React, { useState, useEffect, useCallback } from 'react';\nimport axios from 'axios';\n\nconst SearchResult = () =\u003e {\n  const [query1, setQuery1] = useState('react');\n  const [query2, setQuery2] = useState('redux');\n  const [refresh, setRefresh] = useState(0)\n  const [data1, setData1] = useState({ hits: [] });\n  const [data2, setData2] = useState({ hits: [] });\n\n  const MemorizedGetFetchUrl = useCallback((query, id) =\u003e {\n    const fetchData = async () =\u003e {\n      const result = await axios('https://hn.algolia.com/api/v1/search?query=' + query);\n      if (id === 1) setData1(result.data);\n      if (id === 2) setData2(result.data);\n    }\n    fetchData()\n  }, []);\n\n\n  useEffect(() =\u003e {\n    MemorizedGetFetchUrl(query1, 1);\n  }, [MemorizedGetFetchUrl, query1])\n\n\n  useEffect(() =\u003e {\n    MemorizedGetFetchUrl(query2, 2);\n  }, [MemorizedGetFetchUrl, query2])\n\n  return (\n    \u003cdiv\u003e\n      \u003cul\u003e\n        {data1.hits.map((item: any) =\u003e (\n          \u003cli key={item.objectID}\u003e{item.title}\u003c/li\u003e\n        ))}\n      \u003c/ul\u003e\n\n      \u003cbr /\u003e\n      \u003cul\u003e\n        {data2.hits.map((item: any) =\u003e (\n          \u003cli key={item.objectID}\u003e{item.title}\u003c/li\u003e\n        ))}\n      \u003c/ul\u003e\n      \u003cbutton onClick={() =\u003e { setRefresh(refresh + 1) }}\u003eRe-render Page\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport default SearchResult;\n```\n\n## Case 6: clean up last api influence to avoid `Race Condition`\n- 何时需要`cleanup`函数?\n  - useEffect存在`deps`且`deps`会**变化**的时候\n  - 例如在`useEffect`中的api请求依赖于某个`变量`。\n    - **因为无法保证api请求的返回顺序**，我们需要在新的请求之前，**禁止上一次请求的结果对页面元素产生影响**！\n- 代码分析:\n  - 本质原因是js的`闭包`，闭包**闭的variable而不是值**\n  - 因此在第一次`useEffect`执行未完成，但是`unmount`已经触发的情况下，第一次·的值会被修改为·\n  - 这时候`setData`的操作就不会被出发\n```javascript\nconst SearchResult = () =\u003e {\n  const [query, setQuery] = useState('');\n  const [search, setSearch] = useState('')\n  const [data, setData] = useState({ hits: [] });\n\n  const MemorizedGetFetchUrl = useCallback((didCancel) =\u003e {\n    const fetchData = async () =\u003e {\n      const result = await axios('https://hn.algolia.com/api/v1/search?query=' + search);\n      !didCancel \u0026\u0026 setData(result.data); \n      !!didCancel \u0026\u0026 console.log('cancel setting result');\n    }\n    search !== '' \u0026\u0026 fetchData()\n  }, [search]);\n\n\n  useEffect(() =\u003e {\n    let didCancel = false;\n    !didCancel \u0026\u0026 MemorizedGetFetchUrl(didCancel);\n    return () =\u003e {\n      didCancel = true;\n    }\n  }, [MemorizedGetFetchUrl])\n\n  return (\n    \u003cdiv\u003e\n      \u003cul\u003e\n        {data.hits.map((item: any) =\u003e (\n          \u003cli key={item.objectID}\u003e{item.title}\u003c/li\u003e\n        ))}\n      \u003c/ul\u003e\n      \u003cinput type=\"text\" value={query} onChange={evt =\u003e setQuery(evt.target.value)} /\u003e\n      \u003cbutton onClick={() =\u003e setSearch(query)}\u003eSearch\u003c/button\u003e\n    \u003c/div\u003e\n  )\n}\nexport default SearchResult;\n```\n\n## Case 7: how to choose between useMemo and useCallback\n- `useCallback`可以直接传**参数**，而`useMemo`**不**能`直接`传**参数**, 但是二者都可以直接读取定义在文件内的`变量`\n- `useMemo`重点在与缓存了`昂贵计算`函数运行的**结果**，而`useCallback`的重点在于缓存了**函数本身**; **使用useCallback无法达到缓存昂贵计算的结果**；\n- `useCallback`中可以进行`api`请求，而`useMemo`内部传入函数时只适合做用在`渲染过程`中的**昂贵计算**上,比如**重交互的图表**和**动画**等\n- 结论: 单纯为了**持久化**函数或者**多**个`useEffect`函数复用一个函数，使用`useCallback`; 为了能够减少昂贵计算的次数，使用`useMemo`\n## Summary\n- `useEffect`依赖的变动会引起\n  - `unmount`\n  - `render`\n  - `componentDidUpdate`\n- 什么会引起`componentWillUnmount`?\n  - 页面`退出`\n  - `useEffect`中`依赖值`的变动\n- setState会引起什么？\n  - `render`\n  - `componentDidUpdate`\n  - **不会**引起`unmount`\n- props的改变会引起什么?\n  - 子组件的`render`，如果是不必要的，可以使用memo + shallowEqual的方式避免子组件的重新渲染\n  - 子组件的`componentDidUpdate`\n  - **不会**引起`unmount`\n  - **不会引起子组件state的reset**\n```javascript\nimport React, { useState, memo } from \"react\";\nimport shallowCompare from \"react-addons-shallow-compare\";\n\nconst App = () =\u003e {\n  const [a, setA] = useState(0);\n  console.log(\"app render\");\n  return (\n    \u003cdiv\u003e\n      \u003cdiv\u003eHello World\u003c/div\u003e\n      {a}\n      \u003cbutton onClick={() =\u003e setA(a =\u003e a + 1)}\u003echange app\u003c/button\u003e\n      \u003cCounter a={a} /\u003e\n    \u003c/div\u003e\n  );\n};\n\nconst Counter = memo(({ a }: any) =\u003e {\n  const [b, setB] = useState(a);\n  console.log(\"counter render\");\n  return (\n    \u003cdiv\u003e\n      {b}\n      \u003cbutton onClick={() =\u003e setB(b =\u003e b + 1)}\u003echange counter\u003c/button\u003e\n    \u003c/div\u003e\n  );\n}, shallowCompare);\n\nexport default App;\n```\n- 为什么要减少dependency?\n  - 为了减少dependency变化带来的`componentWillUnmount`的执行\n- useEffect会提醒你把什么东西加入到deps中?\n  - **外部变量**\n  - 包含**外部变量**的`函数`\n  - **不提醒**你把**不包含外部变量**的`函数`加入\n- 什么样的变量适合放入`state`中?\n  - 用来**沟通**`html`和`js`的变量\n- 定义在`hooks`之外，组件之内的变量和函数有什么特征?\n  - 在每次渲染后，`引用`都会发生**改变**\n- 经验之谈：\n  - 放在`deps`里面的不是`props`就是`state`, 共同特征: 会引起页面的重新渲染\n  - 因为`component`中的`hooks`之外的变量和函数都是不稳定的，会引起**无限渲染**\n  - 如果要放在`component`之外，说明不需要被复用，其实可以直接放入到`hooks`里面\n  - 如果没办法放在component之内，有必须在hooks之外的(复用)，则需要使用`useCallback`, `useMemo`来原始化\n- 本质: hooks的本质就是在immutable中使用mutable\n- useReducer vs useState: 前者具备了数据在上一次和此次沟通的能力，也便利了状态之间的沟通\n## References\n- [A Complete Guide to useEffect](https://overreacted.io/a-complete-guide-to-useeffect/)\n- [什么时候使用 useMemo 和 useCallback](https://jancat.github.io/post/2019/translation-usememo-and-usecallback/)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeekeast%2Fcomplete-guide-to-react-useeffect","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fgeekeast%2Fcomplete-guide-to-react-useeffect","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fgeekeast%2Fcomplete-guide-to-react-useeffect/lists"}