{"id":24592395,"url":"https://github.com/alexbrillant/react-awesome","last_synced_at":"2026-05-22T14:06:04.459Z","repository":{"id":83437484,"uuid":"304157689","full_name":"alexbrillant/react-awesome","owner":"alexbrillant","description":"An awesome opinionated guide to react to build high quality testable react applications. ","archived":false,"fork":false,"pushed_at":"2020-10-17T03:07:38.000Z","size":61,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-27T02:07:28.330Z","etag":null,"topics":["react","react-hook-form","react-redux","react-router","react-saga"],"latest_commit_sha":null,"homepage":"","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/alexbrillant.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-10-14T23:17:28.000Z","updated_at":"2023-03-09T01:18:22.000Z","dependencies_parsed_at":"2024-01-13T01:32:16.353Z","dependency_job_id":"32e16af6-4988-4ba1-a5cc-73ec0ea2cae6","html_url":"https://github.com/alexbrillant/react-awesome","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/alexbrillant%2Freact-awesome","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexbrillant%2Freact-awesome/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexbrillant%2Freact-awesome/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/alexbrillant%2Freact-awesome/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/alexbrillant","download_url":"https://codeload.github.com/alexbrillant/react-awesome/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244110177,"owners_count":20399563,"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":["react","react-hook-form","react-redux","react-router","react-saga"],"created_at":"2025-01-24T10:14:22.850Z","updated_at":"2026-05-22T14:05:59.392Z","avatar_url":"https://github.com/alexbrillant.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-awesome 🐨\n\nReact can be really confusing if you are just starting out and learning from different sources all at the same time. So I decided to build my own opiniated guide to react ! This guide will contain a mix of good/standard practices and also my favorite ways of doing things and the reasons why.\n\n## Separation between container and display containers 👣\n\nLife is simpler when UI components are unaware of the network, business logic, or app state. Given the same props, always render the same data.\n\n- **Container components**: components that are connected to the data store or local state and may have side-effects.\n- **Presentation components**: mostly pure components, which, given the same props and context, always return the same JSX.\n\n## Component state for component state 👓\n\nWhy ? \n\n- Don't use redux if you don't need it\n- useState and useEffect hooks are often more then enough \n\n## Hooks\n\n[hooks](https://reactjs.org/docs/hooks-intro.html)\n\nWhy ? \n\n- Use state and hook in the components lifecycle\n- Replaces render prop pattern \n- Colocate related logic\n- Share reusable behaviors independent of component implementations\n\n## Testing  🦢\n\npreferred lib: [react-testing](https://testing-library.com/docs/react-testing-library/intro)\n\nWhy ? \n\n- Test behavior of components instead of implementation\n- Easily test with nodes using `data-testid`\n- Encourages best practices\n\n\n\n## State container for Application wide state 🕶\n\npreferred lib: [react-redux](https://react-redux.js.org)\n\nWhy ? \n\n- Deterministic state resolution (enabling deterministic view renders when combined with pure components)\n- Transactional state\n- Isolate state management from I/O and side-effects\n- Single source of truth for application state\n- Easily share state between different components\n- Transaction telemetry (auto-logging action objects)\n- Time travel debugging\n\n[react-redux](https://react-redux.js.org/api/hooks) with hooks\n\nWhy ? \n\n- Subscribe to the Redux store and dispatch actions, without having to wrap your components in connect()\n- Useful for using dispatch in deeply nested components \n\n```javascript\nimport React, { useCallback } from 'react'\nimport { useDispatch } from 'react-redux'\n\nexport const CounterComponent = ({ value }) =\u003e {\n  const dispatch = useDispatch()\n  const incrementCounter = useCallback(\n    () =\u003e dispatch({ type: 'increment-counter' }),\n    [dispatch]\n  )\n\n  return (\n    \u003cdiv\u003e\n      \u003cspan\u003e{value}\u003c/span\u003e\n      \u003cMyIncrementButton onIncrement={incrementCounter} /\u003e\n    \u003c/div\u003e\n  )\n}\n\nexport const MyIncrementButton = React.memo(({ onIncrement }) =\u003e (\n  \u003cbutton onClick={onIncrement}\u003eIncrement counter\u003c/button\u003e\n))\n```\n\n## Computing Derived Data\n\n[reselect](https://github.com/reduxjs/reselect)\n\nWhy ? \n\n- Selectors can compute derived data, allowing Redux to store the minimal possible state.\n- Selectors are efficient. A selector is not recomputed unless one of its arguments changes.\n- Selectors are composable. They can be used as input to other selectors.\n\n```javascript\nimport { createSelector } from 'reselect'\n\nconst shopItemsSelector = state =\u003e state.shop.items\nconst taxPercentSelector = state =\u003e state.shop.taxPercent\n\nconst subtotalSelector = createSelector(\n  shopItemsSelector,\n  items =\u003e items.reduce((acc, item) =\u003e acc + item.value, 0)\n)\n\nconst taxSelector = createSelector(\n  subtotalSelector,\n  taxPercentSelector,\n  (subtotal, taxPercent) =\u003e subtotal * (taxPercent / 100)\n)\n\nexport const totalSelector = createSelector(\n  subtotalSelector,\n  taxSelector,\n  (subtotal, tax) =\u003e ({ total: subtotal + tax })\n)\n\nlet exampleState = {\n  shop: {\n    taxPercent: 8,\n    items: [\n      { name: 'apple', value: 1.20 },\n      { name: 'orange', value: 0.95 },\n    ]\n  }\n}\n\nconsole.log(subtotalSelector(exampleState)) // 2.15\nconsole.log(taxSelector(exampleState))      // 0.172\nconsole.log(totalSelector(exampleState))    // { total: 2.322 }\n```\n\n\n## Composed Selectors\n\n### composedSelectors.js\n\n```javascript\nimport { createSelector } from 'reselect'\n\nconst getBar = (state) =\u003e state.foo.bar\n\nexport const getBarState = createSelector(\n    [getBar],\n    (bar) =\u003e bar\n)\n\nexport const firstSelector = (state) =\u003e state.a\nexport const secondSelector = (state) =\u003e state.b\nexport const thirdSelector = (state) =\u003e state.c\n\nexport const myComposedSelector = createSelector(\n  firstSelector,\n  secondSelector,\n  thirdSelector,\n  (a, b, c) =\u003e a * b * c\n)\n```\n\n### __tests__/composedSelectors.js\n\n```javascript\nimport { getBarState, firstSelector, secondSelector, thirdSelector, myComposedSelector } from \"../composedSelectors\";\n\ndescribe('selectors', () =\u003e {\n    it('should select bars state', () =\u003e {\n        expect(getBarState({ foo: { bar: [0] } })).toEqual([0])\n    })\n\n    it('should select a state', () =\u003e {\n        expect(firstSelector({ a: 1 })).toEqual(1)\n    })\n\n    it('should select b state', () =\u003e {\n        expect(secondSelector({ b: 1 })).toEqual(1)\n    })\n\n    it('should select c state', () =\u003e {\n        expect(thirdSelector({ c: 1 })).toEqual(1)\n    })\n\n    it('should calculate composed selector', () =\u003e {\n        expect(myComposedSelector.resultFunc(2,2,2)).toEqual(8)\n    })\n})\n```\n\n## Redux state normalization\n\nNested data means that the corresponding reducer logic has to be more nested or more complex. In particular, trying to update a deeply nested field can become very ugly very fast.\n\nSince immutable data updates require all ancestors in the state tree to be copied and updated as well, and new object references will cause connected UI components to re-render, an update to a deeply nested data object could force totally unrelated UI components to re-render even if the data they're displaying hasn't actually changed.\n\n[Normalizing state shape redux documentation](https://redux.js.org/recipes/structuring-reducers/normalizing-state-shape)\n\n```javascript\nimport { combineReducers } from \"redux\"\n\n\nconst rootReducer = combineReducers({\n  users: sectionReducer(\"USER\")(users),\n  articles: sectionReducer(\"ARTICLE\")(articles)\n})\n```\n\n\n\n## Component local side effects 🏄🏽\n\n[useEffect hook](https://reactjs.org/docs/hooks-effect.html)\n\nWhy ? \n\n- Remove duplicates between lifecycle methods (componentDidMount, componentDidUpdate, etc.)\n\n```javascript\nimport React, { useState, useEffect } from 'react';\n\nfunction FriendStatus(props) {\n  const [isOnline, setIsOnline] = useState(null);\n\n  useEffect(() =\u003e {\n    function handleStatusChange(status) {\n      setIsOnline(status.isOnline);\n    }\n    ChatAPI.subscribeToFriendStatus(props.friend.id, handleStatusChange);\n    // Specify how to clean up after this effect:\n    return function cleanup() {\n      ChatAPI.unsubscribeFromFriendStatus(props.friend.id, handleStatusChange);\n    };\n  });\n\n  if (isOnline === null) {\n    return 'Loading...';\n  }\n  return isOnline ? 'Online' : 'Offline';\n}\n```\n\n## Application wide side effects 🏋\n\npreferred lib: [redux-saga](https://redux-saga.js.org/)\n\nA redux middleware that uses generators to apply side effects after a redux action has been dispatched to the store. \n\nWhy ? \n\n- Complex async side effects (fork, race, channels, etc.)\n- Compose parallel tasks\n- Easily test generators\n- **Have full power over how \u0026 when effects are executed**\n\n## Forms 📝 \n\npreferred lib: [react-hook-form](https://react-hook-form.com/)\n\nWhy ? \n\n- Compact Code\n- Isolates Component Re-renders\n- Input change subscriptions\n- Faster mounting\n\n```javascript\nimport React from \"react\";\nimport { useForm } from \"react-hook-form\";\n\nconst Example = () =\u003e {\n  const { handleSubmit, register, errors } = useForm();\n  const onSubmit = values =\u003e console.log(values);\n\n  return (\n    \u003cform onSubmit={handleSubmit(onSubmit)}\u003e\n      \u003cinput\n        name=\"email\"\n        ref={register({\n          required: \"Required\",\n          pattern: {\n            value: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}$/i,\n            message: \"invalid email address\"\n          }\n        })}\n      /\u003e\n      {errors.email \u0026\u0026 errors.email.message}\n\n      \u003cinput\n        name=\"username\"\n        ref={register({\n          validate: value =\u003e value !== \"admin\" || \"Nice try!\"\n        })}\n      /\u003e\n      {errors.username \u0026\u0026 errors.username.message}\n\n      \u003cbutton type=\"submit\"\u003eSubmit\u003c/button\u003e\n    \u003c/form\u003e\n  );\n};\n```\n\n## Testing Forms\n\n### Form to test\n\n```javascript\nimport React from \"react\";\nimport { useForm } from \"./src\";\n\nimport \"./styles.css\";\n\nexport default function App() {\n  const { register, handleSubmit, watch, errors } = useForm();\n  const onSubmit = data =\u003e {\n    console.log(data);\n  };\n\n  const example = watch(\"example\");\n\n  return (\n    \u003cform onSubmit={handleSubmit(onSubmit)}\u003e\n      \u003clabel\u003eExample\u003c/label\u003e\n      \u003cinput\n        name=\"example\"\n        defaultValue=\"test\"\n        ref={register}\n        data-testid=\"example\"\n      /\u003e\n      \u003clabel\u003eExampleRequired\u003c/label\u003e\n      \u003cinput\n        name=\"exampleRequired\"\n        ref={register({ required: true, maxLength: 10 })}\n        data-testid=\"exampleRequired\"\n      /\u003e\n      {errors.exampleRequired \u0026\u0026 \u003cp\u003eThis field is required\u003c/p\u003e}\n\n      {example === \"test\" \u0026\u0026 \u003ci data-testid=\"message\"\u003eHidden message\u003c/i\u003e}\n      \u003cinput type=\"submit\" data-testid=\"submit\" /\u003e\n    \u003c/form\u003e\n  );\n}\n```\n\n### Tests\n\n```javascript\nimport React from \"react\";\nimport App from \"./App\";\nimport { render, fireEvent } from \"@testing-library/react\";\n\ndescribe.only(\"App\", () =\u003e {\n  test(\"should watch input correctly\", () =\u003e {\n    const { getByTestId } = render(\u003cApp /\u003e);\n\n    fireEvent.input(getByTestId(\"example\"), {\n      target: {\n        value: \"test\"\n      }\n    });\n\n    expect(getByTestId(\"message\").innerHTML).toEqual(\"Hidden message\");\n  });\n\n  test(\"should display correct error message\", () =\u003e {\n    const { getByTestId, findByText } = render(\u003cApp /\u003e);\n\n    getByTestId(\"submit\");\n\n    fireEvent.click(getByTestId(\"submit\"));\n\n    findByText(\"This field is required\");\n  });\n});\n```\n\n## Routing ⚒\n\npreferred lib: [react-router](https://reactrouter.com/web/guides/quick-start)\n\nWhy ? \n\n- Dynamic Routing \n- Nested Routes\n- Responsive Routes\n\n```javascript\nimport React, { Component } from 'react';\nimport { BrowserRouter as Router, Route } from 'react-router-dom'\nimport Footer from './components/Footer';\n\nclass App extends Component {\n  render() {\n    return (\n      \u003cdiv\u003e\n        \u003cRouter\u003e\n          \u003cdiv\u003e\n            \u003ch1\u003eReact router\u003c/h1\u003e\n            \u003cRoute path='/:filter?' render={({match}) =\u003e (\n              \u003ch2\u003e{match.params.filter}\u003c/h2\u003e\n            )} /\u003e\n            \u003cFooter/\u003e\n          \u003c/div\u003e\n        \u003c/Router\u003e\n      \u003c/div\u003e\n    )\n  }\n}\n```\n\n## Optimistic Updates\n\n```javascript\nimport React, {Component} from 'react';\n\nconst Items = ({ items, deleteItemOptimistic, loading }) =\u003e {\n    return (\n        \u003cul style={{ opacity: loading ? 0.6 : 1 }}\u003e\n            {items.map(item =\u003e (\n                \u003cli key={item.id}\u003e\n                    {item.title}{' '}\n                    \u003cbutton onClick={() =\u003e deleteItemOptimistic(item.id)}\u003e\n                        Delete item\n              \u003c/button\u003e\n                \u003c/li\u003e\n            ))}\n        \u003c/ul\u003e\n    )\n}\n\nfunction deleteItemRequest(id) {\n  return new Promise((resolve, reject) =\u003e {\n    setTimeout(id === 3 ? reject : resolve, 750);\n  });\n}\n\nclass App extends Component {\n  state = {\n    items: Array.from(Array(5), (_, i) =\u003e ({\n      id: i + 1,\n      title: `Item ${i + 1}`,\n    })),\n    loading: false,\n    error: null,\n  };\n\n  deleteItemOptimistic = id =\u003e {\n    const deletingItem = this.state.items.find(item =\u003e item.id === id);\n\n    this.setState(state =\u003e ({\n      items: state.items.filter(item =\u003e item.id !== id),\n    }));\n\n    deleteItemRequest(id)\n      .catch(() =\u003e\n        this.restoreItem(deletingItem, id)\n      );\n  };\n\n  restoreItem(deletingItem, id) {\n    return this.setState(state =\u003e ({\n      items: [...state.items, deletingItem].sort((a, b) =\u003e a.id - b.id),\n      error: `Request failed for item ${id}`,\n    }));\n  }\n\n  render() {\n    const {items, loading, error} = this.state;\n\n    return (\n      \u003cdiv\u003e\n        \u003ch4\u003eOptimistic UI updates in React using setState()\u003c/h4\u003e\n        \u003cItems loading={loading} deleteItemOptimistic={this.deleteItemOptimistic} items={items} /\u003e\n        {error \u0026\u0026 \u003cp\u003e{error}\u003c/p\u003e}\n      \u003c/div\u003e\n    );\n  }\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexbrillant%2Freact-awesome","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Falexbrillant%2Freact-awesome","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Falexbrillant%2Freact-awesome/lists"}