{"id":28789657,"url":"https://github.com/lemoncode/react-from-classes-to-hooks-typescript","last_synced_at":"2025-10-26T20:01:43.676Z","repository":{"id":81411390,"uuid":"170309481","full_name":"Lemoncode/react-from-classes-to-hooks-typescript","owner":"Lemoncode","description":"Sample simple applications migrations from class based components to hooks","archived":false,"fork":false,"pushed_at":"2019-02-13T09:06:17.000Z","size":39,"stargazers_count":13,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-17T22:11:45.780Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Lemoncode.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,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2019-02-12T11:51:23.000Z","updated_at":"2023-02-23T08:47:51.000Z","dependencies_parsed_at":null,"dependency_job_id":"c3220a12-12bd-424c-921c-c5f79a5a07d6","html_url":"https://github.com/Lemoncode/react-from-classes-to-hooks-typescript","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/Lemoncode/react-from-classes-to-hooks-typescript","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lemoncode%2Freact-from-classes-to-hooks-typescript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lemoncode%2Freact-from-classes-to-hooks-typescript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lemoncode%2Freact-from-classes-to-hooks-typescript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lemoncode%2Freact-from-classes-to-hooks-typescript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Lemoncode","download_url":"https://codeload.github.com/Lemoncode/react-from-classes-to-hooks-typescript/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Lemoncode%2Freact-from-classes-to-hooks-typescript/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":260635773,"owners_count":23039760,"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":[],"created_at":"2025-06-17T22:11:21.316Z","updated_at":"2025-10-26T20:01:42.871Z","avatar_url":"https://github.com/Lemoncode.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# react-from-classes-to-hooks-typescript\n\nSimple applications examples: migrations from class based components to hooks.\n\n## Examples\n\nUnder each example folder you will find two subfolders:\n\n- 00_start: Starting point (using state + classes).\n- 01_migrated: Application fully migrated to hooks.\n\n## 00_login-page\n\nThis application is composed by a login page (class based + state) and a second page that shows the logged in user (making use of context + hoc to keep the login name as a field available globally).\n\nMigration process:\n\n- Migrate the login page from class component to function compon ent using hooks and access the context using the _useContext_ effect.\n\n- Migrate page B (just displays the name of the user logged in), remove the usage of an HOC to inject the login context and use the effect _useContext_.\n\n### LoginPage\n\nFor the login page:\n\n- Use the _useContext_ effect to store in the context the user name (once the user has successfully logged in).\n- We have created two custom hooks:\n  - One to store the login information that the user is entering.\n  - Another to store the form error information.\n\n\u003e About why creating two custom hooks splitting the state instead of having one (like we use to do in _class components_), you can read the entry\n\u003e _Should I use one or many state variable_ from the [reactjs hooks-faq](https://reactjs.org/docs/hooks-faq.html#should-i-use-one-or-many-state-variables)\n\n**Original LoginPage class based component (extract)**\n\n_./00_login/00_start/src/pages/login/loginPage.tsx_\n\n```typescript\ninterface State {\n  loginInfo: LoginEntity;\n  showLoginFailedMsg: boolean;\n  loginFormErrors: LoginFormErrors;\n}\n\ninterface Props extends RouteComponentProps, WithStyles\u003ctypeof styles\u003e {\n  updateLogin: (value) =\u003e void;\n}\n\nclass LoginPageInner extends React.Component\u003cProps, State\u003e {\n  constructor(props) {\n    super(props);\n\n    this.state = {\n      loginInfo: createEmptyLogin(),\n      showLoginFailedMsg: false,\n      loginFormErrors: createDefaultLoginFormErrors()\n    };\n  }\n\n  onLogin = () =\u003e {\n    loginFormValidation\n      .validateForm(this.state.loginInfo)\n      .then(formValidatinResult =\u003e {\n        if (formValidatinResult.succeeded) {\n          if (isValidLogin(this.state.loginInfo)) {\n            this.props.updateLogin(this.state.loginInfo.login);\n            this.props.history.push(\"/pageB\");\n          } else {\n            this.setState({ showLoginFailedMsg: true });\n          }\n        } else {\n          alert(\"error, review the fields\");\n        }\n      });\n  };\n\n  onUpdateLoginField = (name: string, value) =\u003e {\n    this.setState({\n      loginInfo: {\n        ...this.state.loginInfo,\n        [name]: value\n      }\n    });\n\n    loginFormValidation\n      .validateField(this.state.loginInfo, name, value)\n      .then(fieldValidationResult =\u003e {\n        this.setState({\n          loginFormErrors: {\n            ...this.state.loginFormErrors,\n            [name]: fieldValidationResult\n          }\n        });\n      });\n  };\n\n  render() {\n    const { classes } = this.props;\n    return (\n      \u003c\u003e\n        \u003cCard className={classes.card}\u003e\n          \u003cCardHeader title=\"Login\" /\u003e\n          \u003cCardContent\u003e\n            \u003cLoginForm\n              onLogin={this.onLogin}\n              onUpdateField={this.onUpdateLoginField}\n              loginInfo={this.state.loginInfo}\n              loginFormErrors={this.state.loginFormErrors}\n            /\u003e\n          \u003c/CardContent\u003e\n        \u003c/Card\u003e\n        \u003cNotificationComponent\n          message=\"Invalid login or password, please type again\"\n          show={this.state.showLoginFailedMsg}\n          onClose={() =\u003e this.setState({ showLoginFailedMsg: false })}\n        /\u003e\n      \u003c/\u003e\n    );\n  }\n}\n\nexport const LoginPage = withSessionContext(\n  withStyles(styles)(withRouter\u003cProps\u003e(LoginPageInner))\n);\n```\n\n**Migrated LoginPage class based component (extract)**\n\n_./00_login/01_migrated/src/pages/login/loginPage.tsx_\n\n```typescript\nfunction useLogin() {\n  const [loginInfo, setLoginInfo] = React.useState(createEmptyLogin());\n\n  return {\n    loginInfo,\n    setLoginInfo\n  };\n}\n\nfunction useErrorHandling() {\n  const [showLoginFailedMessage, setShowLoginFailedMessage] = React.useState(\n    false\n  );\n  const [loginFormErrors, setLoginFormErrors] = React.useState(\n    createDefaultLoginFormErrors()\n  );\n\n  return {\n    showLoginFailedMessage,\n    setShowLoginFailedMessage,\n    loginFormErrors,\n    setLoginFormErrors\n  };\n}\n\ninterface Props extends RouteComponentProps, WithStyles\u003ctypeof styles\u003e {}\n\nconst LoginPageInner = (props: Props) =\u003e {\n  const { loginInfo, setLoginInfo } = useLogin();\n\n  const {\n    showLoginFailedMessage,\n    setShowLoginFailedMessage,\n    loginFormErrors,\n    setLoginFormErrors\n  } = useErrorHandling();\n\n  const loginContext = React.useContext(SessionContext);\n\n  const onLogin = () =\u003e {\n    loginFormValidation.validateForm(loginInfo).then(formValidationResult =\u003e {\n      if (formValidationResult.succeeded) {\n        if (isValidLogin(loginInfo)) {\n          loginContext.updateLogin(loginInfo.login);\n          props.history.push(\"/pageB\");\n        } else {\n          setShowLoginFailedMessage(true);\n        }\n      } else {\n        alert(\"error, review the fields\");\n      }\n    });\n  };\n\n  const onUpdateLoginField = (name: string, value) =\u003e {\n    setLoginInfo({\n      ...loginInfo,\n      [name]: value\n    });\n\n    loginFormValidation\n      .validateField(loginInfo, name, value)\n      .then(fieldValidationResult =\u003e {\n        setLoginFormErrors({\n          ...loginFormErrors,\n          [name]: fieldValidationResult\n        });\n      });\n  };\n\n  const { classes } = props;\n\n  return (\n    \u003c\u003e\n      \u003cCard className={classes.card}\u003e\n        \u003cCardHeader title=\"Login\" /\u003e\n        \u003cCardContent\u003e\n          \u003cLoginForm\n            onLogin={onLogin}\n            onUpdateField={onUpdateLoginField}\n            loginInfo={loginInfo}\n            loginFormErrors={loginFormErrors}\n          /\u003e\n        \u003c/CardContent\u003e\n      \u003c/Card\u003e\n      \u003cNotificationComponent\n        message=\"Invalid login or password, please type again\"\n        show={showLoginFailedMessage}\n        onClose={() =\u003e setShowLoginFailedMessage(false)}\n      /\u003e\n    \u003c/\u003e\n  );\n};\n\nexport const LoginPage = withStyles(styles)(withRouter\u003cProps\u003e(LoginPageInner));\n```\n\n### Page B\n\nInstead of using an HOC to inject the propery username from the _context_, we use the _useContext_ effect.\n\n**Original code\\***\n_./00_login/00_start/src/pages/b/pageB.tsx_\n\n```typescript\nimport * as React from \"react\";\nimport { Link } from \"react-router-dom\";\nimport { SessionContext, withSessionContext } from \"../../common/\";\n\ninterface Props {\n  login: string;\n}\n\nconst PageBInner = (props: Props) =\u003e (\n  \u003c\u003e\n    \u003ch2\u003eHello from page B\u003c/h2\u003e\n    \u003cbr /\u003e\n    \u003cbr /\u003e\n    \u003ch3\u003eLogin: {props.login}\u003c/h3\u003e\n\n    \u003cLink to=\"/\"\u003eNavigate to Login\u003c/Link\u003e\n  \u003c/\u003e\n);\n\nexport const PageB = withSessionContext(PageBInner);\n```\n\n**Migrated code**\n\n_./00_login/01_migrated/src/pages/b/pageB.tsx_\n\n```typescript\nimport * as React from \"react\";\nimport { Link } from \"react-router-dom\";\nimport { SessionContext } from \"../../common/\";\n\ninterface Props {}\n\nexport const PageB = (props: Props) =\u003e {\n  const loginContext = React.useContext(SessionContext);\n  return (\n    \u003c\u003e\n      \u003ch2\u003eHello from page B\u003c/h2\u003e\n      \u003cbr /\u003e\n      \u003cbr /\u003e\n      \u003ch3\u003eLogin: {loginContext.login}\u003c/h3\u003e\n\n      \u003cLink to=\"/\"\u003eNavigate to Login\u003c/Link\u003e\n    \u003c/\u003e\n  );\n};\n```\n\n\u003e you can find an additioanl sample called _02_migrated_reducer_ that uses the _userReducer_ effect to store\n\u003e the login form errors.\n\n## 01_fetch\n\nIn this application we just fetch from the Github api the list of members\nbelonging to a given organization (lemoncode).\n\nThe original sample uses a class component that keeps in it's state\nthe list of the members (the fetch list is triggered in the _componentDidMount_ event from the class component).\n\nMigration process:\n\n- Refactor _MemberTable_ component to be a function component.\n- Create a custom hook to hold the member list and expose the\n  _loadmemberlist_ function.\n- Call _useEffect_ passing as a second argument an empty array (This tells React that your effect doesn’t depend on any values from props or state, so it never needs to re-run).\n\n**Original class based component**\n\n_./01_fetch/00_start/component/memberTable.tsx_\n\n```typescript\ninterface Props {}\n\n// We define members as a state (the compoment holding this will be a container\n// component)\ninterface State {\n  members: MemberEntity[];\n}\n\nexport class MemberTableComponent extends React.Component\u003cProps, State\u003e {\n  constructor(props: Props) {\n    super(props);\n    this.state = { members: [] };\n  }\n\n  public componentDidMount() {\n    memberAPI.getAllMembers().then(members =\u003e this.setState({ members }));\n  }\n\n  public render() {\n    return (\n      \u003ctable className=\"table\"\u003e\n        \u003cthead\u003e\n          \u003cMemberHead /\u003e\n        \u003c/thead\u003e\n        \u003ctbody\u003e\n          {this.state.members.map((member: MemberEntity) =\u003e (\n            \u003cMemberRow key={member.id} member={member} /\u003e\n          ))}\n        \u003c/tbody\u003e\n      \u003c/table\u003e\n    );\n  }\n}\n```\n\n**Migrated to hooks + function component**\n\n_./01_fetch/01_migrated/component/memberTable.tsx_\n\n```typescript\nfunction useMembers() {\n  const [members, setMembers] = React.useState\u003cMemberEntity[]\u003e([]);\n\n  const loadMembers = () =\u003e {\n    memberAPI.getAllMembers().then(members =\u003e setMembers(members));\n  };\n\n  return { members, loadMembers };\n}\n\nexport const MemberTableComponent = () =\u003e {\n  const { members, loadMembers } = useMembers();\n\n  React.useEffect(() =\u003e {\n    loadMembers();\n  }, []);\n\n  return (\n    \u003ctable className=\"table\"\u003e\n      \u003cthead\u003e\n        \u003cMemberHead /\u003e\n      \u003c/thead\u003e\n      \u003ctbody\u003e\n        {members.map((member: MemberEntity) =\u003e (\n          \u003cMemberRow key={member.id} member={member} /\u003e\n        ))}\n      \u003c/tbody\u003e\n    \u003c/table\u003e\n  );\n};\n```\n\n\u003e Passing an empty array as a second argument, tells React that your effect doesn't depende on any\n\u003e values from props or state, so it never needs to re-run (you get a similar behavior like in _componentDidMount_),\n\u003e more info: https://reactjs.org/docs/hooks-effect.html.\n\n# About Basefactor + Lemoncode\n\nWe are an innovating team of Javascript experts, passionate about turning your ideas into robust products.\n\n[Basefactor, consultancy by Lemoncode](http://www.basefactor.com) provides consultancy and coaching services.\n\n[Lemoncode](http://lemoncode.net/services/en/#en-home) provides training services.\n\nFor the LATAM/Spanish audience we are running an Online Front End Master degree, more info: http://lemoncode.net/master-frontend\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flemoncode%2Freact-from-classes-to-hooks-typescript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Flemoncode%2Freact-from-classes-to-hooks-typescript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Flemoncode%2Freact-from-classes-to-hooks-typescript/lists"}