From 9e1a3464eb6ada2df22e85c96ffacc7c139f28d6 Mon Sep 17 00:00:00 2001
From: stz184 <ivanov.bg@gmail.com>
Date: Fri, 30 Oct 2020 15:24:35 +0200
Subject: [PATCH 01/10] AlbumsPages is migrated to a functional component

---
 ui/src/Pages/AllAlbumsPage/AlbumsPage.js | 37 +++++++++++-------------
 1 file changed, 17 insertions(+), 20 deletions(-)

diff --git a/ui/src/Pages/AllAlbumsPage/AlbumsPage.js b/ui/src/Pages/AllAlbumsPage/AlbumsPage.js
index 249f5ab..bdb8ade 100644
--- a/ui/src/Pages/AllAlbumsPage/AlbumsPage.js
+++ b/ui/src/Pages/AllAlbumsPage/AlbumsPage.js
@@ -1,8 +1,7 @@
-import React, { Component } from 'react'
+import React from 'react'
 import AlbumBoxes from '../../components/albumGallery/AlbumBoxes'
 import Layout from '../../Layout'
-import { gql } from '@apollo/client'
-import { Query } from '@apollo/client/react/components'
+import { useQuery, gql } from '@apollo/client'
 
 const getAlbumsQuery = gql`
   query getMyAlbums {
@@ -18,23 +17,21 @@ const getAlbumsQuery = gql`
   }
 `
 
-class AlbumsPage extends Component {
-  render() {
-    return (
-      <Layout title="Albums">
-        <h1>Albums</h1>
-        <Query query={getAlbumsQuery}>
-          {({ loading, error, data }) => (
-            <AlbumBoxes
-              loading={loading}
-              error={error}
-              albums={data && data.myAlbums}
-            />
-          )}
-        </Query>
-      </Layout>
-    )
-  }
+const AlbumsPage = () => {
+  const { loading, error, data } = useQuery(getAlbumsQuery)
+
+  return (
+    <Layout title="Albums">
+      <h1>Albums</h1>
+      {!loading && (
+        <AlbumBoxes
+          loading={loading}
+          error={error}
+          albums={data && data.myAlbums}
+        />
+      )}
+    </Layout>
+  )
 }
 
 export default AlbumsPage

From f76c3e4e0dd1ddf979deac3edb42fee7d8a14df4 Mon Sep 17 00:00:00 2001
From: stz184 <ivanov.bg@gmail.com>
Date: Fri, 30 Oct 2020 15:54:09 +0200
Subject: [PATCH 02/10] AlbumPages is now using useQuery hook instead of
 <Query> component

---
 ui/src/Pages/AlbumPage/AlbumPage.js | 68 ++++++++++++-----------------
 1 file changed, 27 insertions(+), 41 deletions(-)

diff --git a/ui/src/Pages/AlbumPage/AlbumPage.js b/ui/src/Pages/AlbumPage/AlbumPage.js
index bcf8641..ea4c08c 100644
--- a/ui/src/Pages/AlbumPage/AlbumPage.js
+++ b/ui/src/Pages/AlbumPage/AlbumPage.js
@@ -1,8 +1,7 @@
 import React, { useCallback, useState, useEffect } from 'react'
 import ReactRouterPropTypes from 'react-router-prop-types'
 import { useLocation } from 'react-router-dom'
-import { gql } from '@apollo/client'
-import { Query } from '@apollo/client/react/components'
+import { useQuery, gql } from '@apollo/client'
 import AlbumGallery from '../../components/albumGallery/AlbumGallery'
 import PropTypes from 'prop-types'
 import Layout from '../../Layout'
@@ -80,7 +79,7 @@ function AlbumPage({ match }) {
   )
 
   const toggleFavorites = useCallback(
-    (onlyFavorites, refetch) => {
+    onlyFavorites => {
       if (
         (refetchNeededAll && !onlyFavorites) ||
         (refetchNeededFavorites && onlyFavorites)
@@ -97,7 +96,7 @@ function AlbumPage({ match }) {
         setOnlyFavorites(onlyFavorites)
       }
     },
-    [setOnlyFavorites]
+    [setOnlyFavorites, refetch]
   )
 
   useEffect(() => {
@@ -107,44 +106,31 @@ function AlbumPage({ match }) {
     history.replaceState({}, '', pathName + '?' + queryString)
   }, [onlyFavorites, ordering])
 
-  return (
-    <Query
-      query={albumQuery}
-      variables={{
-        id: albumId,
-        onlyFavorites,
-        mediaOrderBy: ordering.orderBy,
-        mediaOrderDirection: ordering.orderDirection,
-      }}
-    >
-      {({ loading, error, data, refetch }) => {
-        const setOnlyFavorites = useCallback(
-          checked => {
-            toggleFavorites(checked, refetch)
-          },
-          [toggleFavorites, refetch]
-        )
+  const { loading, error, data, refetch } = useQuery(albumQuery, {
+    variables: {
+      id: albumId,
+      onlyFavorites,
+      mediaOrderBy: ordering.orderBy,
+      mediaOrderDirection: ordering.orderDirection,
+    },
+  })
 
-        if (error) return <div>Error</div>
-        return (
-          <Layout title={data ? data.album.title : 'Loading album'}>
-            <AlbumGallery
-              album={data && data.album}
-              loading={loading}
-              showFavoritesToggle
-              setOnlyFavorites={setOnlyFavorites}
-              onlyFavorites={onlyFavorites}
-              onFavorite={() =>
-                (refetchNeededAll = refetchNeededFavorites = true)
-              }
-              showFilter
-              setOrdering={setOrderingCallback}
-              ordering={ordering}
-            />
-          </Layout>
-        )
-      }}
-    </Query>
+  if (error) return <div>Error</div>
+
+  return (
+    <Layout title={data ? data.album.title : 'Loading album'}>
+      <AlbumGallery
+        album={data && data.album}
+        loading={loading}
+        showFavoritesToggle
+        setOnlyFavorites={toggleFavorites}
+        onlyFavorites={onlyFavorites}
+        onFavorite={() => (refetchNeededAll = refetchNeededFavorites = true)}
+        showFilter
+        setOrdering={setOrderingCallback}
+        ordering={ordering}
+      />
+    </Layout>
   )
 }
 

From 4ccc50ec49aa1c76f7e9c6f68258b22793bbe39a Mon Sep 17 00:00:00 2001
From: stz184 <ivanov.bg@gmail.com>
Date: Sat, 31 Oct 2020 23:44:00 +0200
Subject: [PATCH 03/10] Fixed issue that caused error when no image state is
 available on history popstate

---
 ui/src/components/albumGallery/AlbumGallery.js | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/ui/src/components/albumGallery/AlbumGallery.js b/ui/src/components/albumGallery/AlbumGallery.js
index 4bed335..dc61a92 100644
--- a/ui/src/components/albumGallery/AlbumGallery.js
+++ b/ui/src/components/albumGallery/AlbumGallery.js
@@ -56,7 +56,9 @@ const AlbumGallery = ({
 
   useEffect(() => {
     const updateImageState = event => {
-      setImageState(event.state.imageState)
+      if (event.state.imageState) {
+        setImageState(event.state.imageState)
+      }
     }
     window.addEventListener('popstate', updateImageState)
 

From d03525d3157392408c3b26595c172b0f277a94a2 Mon Sep 17 00:00:00 2001
From: stz184 <ivanov.bg@gmail.com>
Date: Sat, 31 Oct 2020 23:50:08 +0200
Subject: [PATCH 04/10] AlbumSidebar, AuthorizedRoute, LoginPage are now using
 hooks instead of the deprecated Query component

---
 ui/src/Pages/LoginPage/LoginPage.js         | 177 ++++++++++----------
 ui/src/components/routes/AuthorizedRoute.js |  21 +--
 ui/src/components/sidebar/AlbumSidebar.js   |  27 ++-
 3 files changed, 104 insertions(+), 121 deletions(-)

diff --git a/ui/src/Pages/LoginPage/LoginPage.js b/ui/src/Pages/LoginPage/LoginPage.js
index b6c3dff..b7ba752 100644
--- a/ui/src/Pages/LoginPage/LoginPage.js
+++ b/ui/src/Pages/LoginPage/LoginPage.js
@@ -1,6 +1,5 @@
-import React, { Component } from 'react'
-import { gql } from '@apollo/client'
-import { Mutation, Query } from '@apollo/client/react/components'
+import React, { useState, useCallback } from 'react'
+import { useQuery, gql, useMutation } from '@apollo/client'
 import { Redirect } from 'react-router-dom'
 import styled from 'styled-components'
 import { Button, Form, Message, Header } from 'semantic-ui-react'
@@ -34,98 +33,92 @@ const LogoHeaderStyled = styled(LogoHeader)`
   margin-bottom: 72px !important;
 `
 
-class LoginPage extends Component {
-  constructor(props) {
-    super(props)
+const LoginPage = () => {
+  const [credentials, setCredentials] = useState({
+    username: '',
+    password: '',
+  })
 
-    this.state = {
-      username: '',
-      password: '',
-    }
+  const handleChange = useCallback(
+    (event, key) => {
+      const value = event.target.value
+      setCredentials(credentials => {
+        return {
+          ...credentials,
+          [key]: value,
+        }
+      })
+    },
+    [setCredentials]
+  )
+
+  const signIn = useCallback(
+    (event, authorize) => {
+      event.preventDefault()
+
+      authorize({
+        variables: {
+          username: credentials.username,
+          password: credentials.password,
+        },
+      })
+    },
+    [credentials]
+  )
+
+  const { initialSetupData } = useQuery(checkInitialSetupQuery)
+
+  const [authorize, { loading, data }] = useMutation(authorizeMutation, {
+    onCompleted: data => {
+      const { success, token } = data.authorizeUser
+
+      if (success) {
+        login(token)
+      }
+    },
+  })
+
+  const errorMessage =
+    data && !data.authorizeUser.success ? data.authorizeUser.status : null
+
+  if (authToken()) {
+    return <Redirect to="/" />
   }
 
-  handleChange(event, key) {
-    this.setState({ [key]: event.target.value })
-  }
-
-  signIn(event, authorize) {
-    event.preventDefault()
-
-    authorize({
-      variables: {
-        username: this.state.username,
-        password: this.state.password,
-      },
-    })
-  }
-
-  render() {
-    if (authToken()) {
-      return <Redirect to="/" />
-    }
-
-    return (
-      <div>
-        <Container>
-          <LogoHeaderStyled />
-          <Query query={checkInitialSetupQuery}>
-            {({ data }) => {
-              if (data && data.siteInfo && data.siteInfo.initialSetup) {
-                return <Redirect to="/initialSetup" />
-              }
-
-              return null
-            }}
-          </Query>
-          <Mutation
-            mutation={authorizeMutation}
-            onCompleted={data => {
-              const { success, token } = data.authorizeUser
-
-              if (success) {
-                login(token)
-              }
-            }}
-          >
-            {(authorize, { loading, data }) => {
-              let errorMessage = null
-              if (data) {
-                if (!data.authorizeUser.success)
-                  errorMessage = data.authorizeUser.status
-              }
-
-              return (
-                <Form
-                  style={{ width: 500, margin: 'auto' }}
-                  error={!!errorMessage}
-                  onSubmit={e => this.signIn(e, authorize)}
-                  loading={loading || (data && data.authorizeUser.success)}
-                >
-                  <Form.Field>
-                    <label htmlFor="username_field">Username</label>
-                    <input
-                      id="username_field"
-                      onChange={e => this.handleChange(e, 'username')}
-                    />
-                  </Form.Field>
-                  <Form.Field>
-                    <label htmlFor="password_field">Password</label>
-                    <input
-                      type="password"
-                      id="password_field"
-                      onChange={e => this.handleChange(e, 'password')}
-                    />
-                  </Form.Field>
-                  <Message error content={errorMessage} />
-                  <Button type="submit">Sign in</Button>
-                </Form>
-              )
-            }}
-          </Mutation>
-        </Container>
-      </div>
-    )
-  }
+  return (
+    <div>
+      <Container>
+        <LogoHeaderStyled />
+        {initialSetupData?.siteInfo?.initialSetup && (
+          <Redirect to="/initialSetup" />
+        )}
+        <Form
+          style={{ width: 500, margin: 'auto' }}
+          error={!!errorMessage}
+          onSubmit={e => signIn(e, authorize)}
+          loading={loading || (data && data.authorizeUser.success)}
+        >
+          <Form.Field>
+            <label htmlFor="username_field">Username</label>
+            <input
+              id="username_field"
+              onChange={e => handleChange(e, 'username')}
+            />
+          </Form.Field>
+          <Form.Field>
+            <label htmlFor="password_field">Password</label>
+            <input
+              type="password"
+              id="password_field"
+              onChange={e => handleChange(e, 'password')}
+            />
+          </Form.Field>
+          <Message error content={errorMessage} />
+          <Button type="submit">Sign in</Button>
+        </Form>
+      </Container>
+    </div>
+  )
 }
 
 export default LoginPage
diff --git a/ui/src/components/routes/AuthorizedRoute.js b/ui/src/components/routes/AuthorizedRoute.js
index 1eb55be..15581f6 100644
--- a/ui/src/components/routes/AuthorizedRoute.js
+++ b/ui/src/components/routes/AuthorizedRoute.js
@@ -1,8 +1,7 @@
 import React from 'react'
 import PropTypes from 'prop-types'
 import { Route, Redirect } from 'react-router-dom'
-import { gql } from '@apollo/client'
-import { Query } from '@apollo/client/react/components'
+import { useQuery, gql } from '@apollo/client'
 import { authToken } from '../../authentication'
 
 export const ADMIN_QUERY = gql`
@@ -29,19 +28,13 @@ const AuthorizedRoute = ({ component: Component, admin = false, ...props }) => {
 
   let adminRedirect = null
   if (token && admin) {
-    adminRedirect = (
-      <Query query={ADMIN_QUERY}>
-        {({ error, data }) => {
-          if (error) alert(error)
+    const { error, data } = useQuery(ADMIN_QUERY)
 
-          if (data && data.myUser && !data.myUser.admin) {
-            return <Redirect to="/" />
-          }
+    if (error) alert(error)
 
-          return null
-        }}
-      </Query>
-    )
+    if (data && data.myUser && !data.myUser.admin) {
+      adminRedirect = <Redirect to="/" />
+    }
   }
 
   return (
@@ -54,7 +47,7 @@ const AuthorizedRoute = ({ component: Component, admin = false, ...props }) => {
           <Component {...routeProps} />
         </>
       )}
-    ></Route>
+    />
   )
 }
 
diff --git a/ui/src/components/sidebar/AlbumSidebar.js b/ui/src/components/sidebar/AlbumSidebar.js
index 8ead908..361445a 100644
--- a/ui/src/components/sidebar/AlbumSidebar.js
+++ b/ui/src/components/sidebar/AlbumSidebar.js
@@ -1,7 +1,6 @@
 import React from 'react'
 import PropTypes from 'prop-types'
-import { Query } from '@apollo/client/react/components'
-import { gql } from '@apollo/client'
+import { useQuery, gql } from '@apollo/client'
 import SidebarShare from './Sharing'
 
 const albumQuery = gql`
@@ -14,22 +13,20 @@ const albumQuery = gql`
 `
 
 const AlbumSidebar = ({ albumId }) => {
+  const { loading, error, data } = useQuery(albumQuery, {
+    variables: { id: albumId },
+  })
+
+  if (loading) return <div>Loading...</div>
+  if (error) return <div>{error.message}</div>
+
   return (
     <div>
       <p>Album options</p>
-      <Query query={albumQuery} variables={{ id: albumId }}>
-        {({ loading, error, data }) => {
-          if (loading) return <div>Loading...</div>
-          if (error) return <div>{error.message}</div>
-
-          return (
-            <div>
-              <h1>{data.album.title}</h1>
-              <SidebarShare album={data.album} />
-            </div>
-          )
-        }}
-      </Query>
+      <div>
+        <h1>{data.album.title}</h1>
+        <SidebarShare album={data.album} />
+      </div>
     </div>
   )
 }

From dd6fc65b620cab8ca3420d689af39e9459931494 Mon Sep 17 00:00:00 2001
From: stz184 <ivanov.bg@gmail.com>
Date: Sat, 31 Oct 2020 23:52:05 +0200
Subject: [PATCH 05/10] Fixed issue that caused warnings on logout page

---
 ui/src/Layout.js | 11 +++++------
 1 file changed, 5 insertions(+), 6 deletions(-)

diff --git a/ui/src/Layout.js b/ui/src/Layout.js
index 6a49b11..c5c6fd8 100644
--- a/ui/src/Layout.js
+++ b/ui/src/Layout.js
@@ -8,6 +8,7 @@ import { useQuery, gql } from '@apollo/client'
 import { Authorized } from './components/routes/AuthorizedRoute'
 import { Helmet } from 'react-helmet'
 import Header from './components/header/Header'
+import { authToken } from './authentication'
 
 export const ADMIN_QUERY = gql`
   query adminQuery {
@@ -94,13 +95,11 @@ const SideButtonLabel = styled.div`
 `
 
 export const SideMenu = () => {
-  const adminQuery = useQuery(ADMIN_QUERY)
-  const mapboxQuery = useQuery(MAPBOX_QUERY)
+  const adminQuery = authToken() ? useQuery(ADMIN_QUERY) : null
+  const mapboxQuery = authToken() ? useQuery(MAPBOX_QUERY) : null
 
-  const isAdmin =
-    adminQuery.data && adminQuery.data.myUser && adminQuery.data.myUser.admin
-
-  const mapboxEnabled = mapboxQuery.data && mapboxQuery.data.mapboxToken != null
+  const isAdmin = adminQuery?.data?.myUser?.admin
+  const mapboxEnabled = !!mapboxQuery?.data?.mapboxToken
 
   return (
     <SideMenuContainer>

From 432f8adff783ccad53ea13fe8b4d450b64598949 Mon Sep 17 00:00:00 2001
From: stz184 <ivanov.bg@gmail.com>
Date: Fri, 6 Nov 2020 09:53:04 +0200
Subject: [PATCH 06/10] Fixed Layout.test.js

---
 ui/src/Layout.test.js | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/ui/src/Layout.test.js b/ui/src/Layout.test.js
index dbf3903..4262974 100644
--- a/ui/src/Layout.test.js
+++ b/ui/src/Layout.test.js
@@ -7,6 +7,10 @@ import { render, screen } from '@testing-library/react'
 import Layout, { ADMIN_QUERY, MAPBOX_QUERY, SideMenu } from './Layout'
 import { MemoryRouter } from 'react-router-dom'
 
+import * as authentication from './authentication'
+
+jest.mock('./authentication.js')
+
 test('Layout component', async () => {
   render(
     <Layout>
@@ -18,7 +22,13 @@ test('Layout component', async () => {
   expect(screen.getByText('layout_content')).toBeInTheDocument()
 })
 
+afterEach(() => {
+  authentication.authToken.mockClear()
+})
+
 test('Layout sidebar component', async () => {
+  authentication.authToken.mockImplementation(() => true)
+
   const mockedGraphql = [
     {
       request: {

From d5e21f6a5e6d2927278b8ab599472e2f9412a4d0 Mon Sep 17 00:00:00 2001
From: stz184 <ivanov.bg@gmail.com>
Date: Fri, 6 Nov 2020 09:56:27 +0200
Subject: [PATCH 07/10] Moved refetch declaration before its use

---
 ui/src/Pages/AlbumPage/AlbumPage.js | 18 +++++++++---------
 1 file changed, 9 insertions(+), 9 deletions(-)

diff --git a/ui/src/Pages/AlbumPage/AlbumPage.js b/ui/src/Pages/AlbumPage/AlbumPage.js
index ea4c08c..9d69bf9 100644
--- a/ui/src/Pages/AlbumPage/AlbumPage.js
+++ b/ui/src/Pages/AlbumPage/AlbumPage.js
@@ -66,6 +66,15 @@ function AlbumPage({ match }) {
     orderDirection: urlParams.get('orderDirection') || 'ASC',
   })
 
+  const { loading, error, data, refetch } = useQuery(albumQuery, {
+    variables: {
+      id: albumId,
+      onlyFavorites,
+      mediaOrderBy: ordering.orderBy,
+      mediaOrderDirection: ordering.orderDirection,
+    },
+  })
+
   const setOrderingCallback = useCallback(
     ordering => {
       setOrdering(prevState => {
@@ -106,15 +115,6 @@ function AlbumPage({ match }) {
     history.replaceState({}, '', pathName + '?' + queryString)
   }, [onlyFavorites, ordering])
 
-  const { loading, error, data, refetch } = useQuery(albumQuery, {
-    variables: {
-      id: albumId,
-      onlyFavorites,
-      mediaOrderBy: ordering.orderBy,
-      mediaOrderDirection: ordering.orderDirection,
-    },
-  })
-
   if (error) return <div>Error</div>
 
   return (

From a0f7b4b1353e34725a695e9ee90910329ce5228d Mon Sep 17 00:00:00 2001
From: stz184 <ivanov.bg@gmail.com>
Date: Sun, 15 Nov 2020 01:25:21 +0200
Subject: [PATCH 08/10] The following components are migrated to apollo hooks
 instead of deprecated <Query> and <Mutation> components: - AddUserRow.js -
 AlbumGallery.js - UserRow.js

LazyPhoto is migrated to functional component
---
 ui/src/Pages/SettingsPage/AddUserRow.js       | 124 +++---
 ui/src/Pages/SettingsPage/UserRow.js          | 391 +++++++++---------
 .../components/albumGallery/AlbumGallery.js   |   4 +-
 .../components/photoGallery/MediaThumbnail.js |  15 +-
 4 files changed, 259 insertions(+), 275 deletions(-)

diff --git a/ui/src/Pages/SettingsPage/AddUserRow.js b/ui/src/Pages/SettingsPage/AddUserRow.js
index 9a33be6..63cb877 100644
--- a/ui/src/Pages/SettingsPage/AddUserRow.js
+++ b/ui/src/Pages/SettingsPage/AddUserRow.js
@@ -1,7 +1,6 @@
-import { gql } from '@apollo/client'
+import { gql, useMutation } from '@apollo/client'
 import PropTypes from 'prop-types'
 import React, { useState } from 'react'
-import { Mutation } from '@apollo/client'
 import { Button, Checkbox, Input, Table } from 'semantic-ui-react'
 
 const createUserMutation = gql`
@@ -28,6 +27,12 @@ const initialState = {
 const AddUserRow = ({ setShow, show, onUserAdded }) => {
   const [state, setState] = useState(initialState)
 
+  const [createUser, { loading }] = useMutation(createUserMutation, {
+    onCompleted: () => {
+      onUserAdded()
+    },
+  })
+
   function updateInput(event, key) {
     setState({
       ...state,
@@ -40,68 +45,59 @@ const AddUserRow = ({ setShow, show, onUserAdded }) => {
   }
 
   return (
-    <Mutation
-      mutation={createUserMutation}
-      onCompleted={() => {
-        onUserAdded()
-      }}
-    >
-      {(createUser, { loading }) => (
-        <Table.Row>
-          <Table.Cell>
-            <Input
-              placeholder="Username"
-              value={state.username}
-              onChange={e => updateInput(e, 'username')}
-            />
-          </Table.Cell>
-          <Table.Cell>
-            <Input
-              placeholder="/path/to/photos"
-              value={state.rootPath}
-              onChange={e => updateInput(e, 'rootPath')}
-            />
-          </Table.Cell>
-          <Table.Cell>
-            <Checkbox
-              toggle
-              checked={state.admin}
-              onChange={(e, data) => {
-                setState({
-                  ...state,
-                  admin: data.checked,
-                })
-              }}
-            />
-          </Table.Cell>
-          <Table.Cell>
-            <Button.Group>
-              <Button negative onClick={() => setShow(false)}>
-                Cancel
-              </Button>
-              <Button
-                type="submit"
-                loading={loading}
-                disabled={loading}
-                positive
-                onClick={() => {
-                  createUser({
-                    variables: {
-                      username: state.username,
-                      rootPath: state.rootPath,
-                      admin: state.admin,
-                    },
-                  })
-                  setState(initialState)
-                }}
-              >
-                Add User
-              </Button>
-            </Button.Group>
-          </Table.Cell>
-        </Table.Row>
-      )}
-    </Mutation>
+    <Table.Row>
+      <Table.Cell>
+        <Input
+          placeholder="Username"
+          value={state.username}
+          onChange={e => updateInput(e, 'username')}
+        />
+      </Table.Cell>
+      <Table.Cell>
+        <Input
+          placeholder="/path/to/photos"
+          value={state.rootPath}
+          onChange={e => updateInput(e, 'rootPath')}
+        />
+      </Table.Cell>
+      <Table.Cell>
+        <Checkbox
+          toggle
+          checked={state.admin}
+          onChange={(e, data) => {
+            setState({
+              ...state,
+              admin: data.checked,
+            })
+          }}
+        />
+      </Table.Cell>
+      <Table.Cell>
+        <Button.Group>
+          <Button negative onClick={() => setShow(false)}>
+            Cancel
+          </Button>
+          <Button
+            type="submit"
+            loading={loading}
+            disabled={loading}
+            positive
+            onClick={() => {
+              createUser({
+                variables: {
+                  username: state.username,
+                  rootPath: state.rootPath,
+                  admin: state.admin,
+                },
+              })
+              setState(initialState)
+            }}
+          >
+            Add User
+          </Button>
+        </Button.Group>
+      </Table.Cell>
+    </Table.Row>
   )
 }
 
diff --git a/ui/src/Pages/SettingsPage/UserRow.js b/ui/src/Pages/SettingsPage/UserRow.js
index c1b0a18..23762ef 100644
--- a/ui/src/Pages/SettingsPage/UserRow.js
+++ b/ui/src/Pages/SettingsPage/UserRow.js
@@ -1,7 +1,6 @@
 import PropTypes from 'prop-types'
 import React, { useState } from 'react'
-import { Mutation } from '@apollo/client/react/components'
-import { gql } from '@apollo/client'
+import { gql, useMutation } from '@apollo/client'
 import {
   Button,
   Checkbox,
@@ -61,50 +60,47 @@ const scanUserMutation = gql`
 const ChangePasswordModal = ({ onClose, user, ...props }) => {
   const [passwordInput, setPasswordInput] = useState('')
 
+  const [changePassword] = useMutation(changeUserPasswordMutation, {
+    onCompleted: () => {
+      onClose && onClose()
+    },
+  })
+
   return (
-    <Mutation
-      mutation={changeUserPasswordMutation}
-      onCompleted={() => {
-        onClose && onClose()
-      }}
-    >
-      {changePassword => (
-        <Modal {...props}>
-          <Modal.Header>Change password</Modal.Header>
-          <Modal.Content>
-            <p>
-              Change password for <b>{user.username}</b>
-            </p>
-            <Form>
-              <Form.Field>
-                <label>New password</label>
-                <Input
-                  placeholder="password"
-                  onChange={e => setPasswordInput(e.target.value)}
-                  type="password"
-                />
-              </Form.Field>
-            </Form>
-          </Modal.Content>
-          <Modal.Actions>
-            <Button onClick={() => onClose && onClose()}>Cancel</Button>
-            <Button
-              positive
-              onClick={() => {
-                changePassword({
-                  variables: {
-                    userId: user.id,
-                    password: passwordInput,
-                  },
-                })
-              }}
-            >
-              Change password
-            </Button>
-          </Modal.Actions>
-        </Modal>
-      )}
-    </Mutation>
+    <Modal {...props}>
+      <Modal.Header>Change password</Modal.Header>
+      <Modal.Content>
+        <p>
+          Change password for <b>{user.username}</b>
+        </p>
+        <Form>
+          <Form.Field>
+            <label>New password</label>
+            <Input
+              placeholder="password"
+              onChange={e => setPasswordInput(e.target.value)}
+              type="password"
+            />
+          </Form.Field>
+        </Form>
+      </Modal.Content>
+      <Modal.Actions>
+        <Button onClick={() => onClose && onClose()}>Cancel</Button>
+        <Button
+          positive
+          onClick={() => {
+            changePassword({
+              variables: {
+                userId: user.id,
+                password: passwordInput,
+              },
+            })
+          }}
+        >
+          Change password
+        </Button>
+      </Modal.Actions>
+    </Modal>
   )
 }
 
@@ -119,7 +115,7 @@ const UserRow = ({ user, refetchUsers }) => {
     editing: false,
   })
 
-  const [showComfirmDelete, setConfirmDelete] = useState(false)
+  const [showConfirmDelete, setConfirmDelete] = useState(false)
   const [showChangePassword, setChangePassword] = useState(false)
 
   function updateInput(event, key) {
@@ -129,171 +125,168 @@ const UserRow = ({ user, refetchUsers }) => {
     })
   }
 
+  const [updateUser, { loading: updateUserLoading }] = useMutation(
+    updateUserMutation,
+    {
+      onCompleted: data => {
+        setState({
+          ...data.updateUser,
+          editing: false,
+        })
+        refetchUsers()
+      },
+    }
+  )
+
+  const [deleteUser] = useMutation(deleteUserMutation, {
+    onCompleted: () => {
+      refetchUsers()
+    },
+  })
+
+  const [scanUser, { called: scanUserCalled }] = useMutation(scanUserMutation, {
+    onCompleted: () => {
+      refetchUsers()
+    },
+  })
+
   if (state.editing) {
     return (
-      <Mutation
-        mutation={updateUserMutation}
-        onCompleted={data => {
-          setState({
-            ...data.updateUser,
-            editing: false,
-          })
-          refetchUsers()
-        }}
-      >
-        {(updateUser, { loading }) => (
-          <Table.Row>
-            <Table.Cell>
-              <Input
-                style={{ width: '100%' }}
-                placeholder={user.username}
-                value={state.username}
-                onChange={e => updateInput(e, 'username')}
-              />
-            </Table.Cell>
-            <Table.Cell>
-              <Input
-                style={{ width: '100%' }}
-                placeholder={user.rootPath}
-                value={state.rootPath}
-                onChange={e => updateInput(e, 'rootPath')}
-              />
-            </Table.Cell>
-            <Table.Cell>
-              <Checkbox
-                toggle
-                checked={state.admin}
-                onChange={(_, data) => {
-                  setState({
-                    ...state,
-                    admin: data.checked,
-                  })
-                }}
-              />
-            </Table.Cell>
-            <Table.Cell>
-              <Button.Group>
-                <Button
-                  negative
-                  onClick={() =>
-                    setState({
-                      ...state.oldState,
-                    })
-                  }
-                >
-                  Cancel
-                </Button>
-                <Button
-                  loading={loading}
-                  disabled={loading}
-                  positive
-                  onClick={() =>
-                    updateUser({
-                      variables: {
-                        id: user.id,
-                        username: state.username,
-                        rootPath: state.rootPath,
-                        admin: state.admin,
-                      },
-                    })
-                  }
-                >
-                  Save
-                </Button>
-              </Button.Group>
-            </Table.Cell>
-          </Table.Row>
-        )}
-      </Mutation>
+      <Table.Row>
+        <Table.Cell>
+          <Input
+            style={{ width: '100%' }}
+            placeholder={user.username}
+            value={state.username}
+            onChange={e => updateInput(e, 'username')}
+          />
+        </Table.Cell>
+        <Table.Cell>
+          <Input
+            style={{ width: '100%' }}
+            placeholder={user.rootPath}
+            value={state.rootPath}
+            onChange={e => updateInput(e, 'rootPath')}
+          />
+        </Table.Cell>
+        <Table.Cell>
+          <Checkbox
+            toggle
+            checked={state.admin}
+            onChange={(_, data) => {
+              setState({
+                ...state,
+                admin: data.checked,
+              })
+            }}
+          />
+        </Table.Cell>
+        <Table.Cell>
+          <Button.Group>
+            <Button
+              negative
+              onClick={() =>
+                setState({
+                  ...state.oldState,
+                })
+              }
+            >
+              Cancel
+            </Button>
+            <Button
+              loading={updateUserLoading}
+              disabled={updateUserLoading}
+              positive
+              onClick={() =>
+                updateUser({
+                  variables: {
+                    id: user.id,
+                    username: state.username,
+                    rootPath: state.rootPath,
+                    admin: state.admin,
+                  },
+                })
+              }
+            >
+              Save
+            </Button>
+          </Button.Group>
+        </Table.Cell>
+      </Table.Row>
     )
   }
 
   return (
-    <Mutation
-      mutation={deleteUserMutation}
-      onCompleted={() => {
-        refetchUsers()
-      }}
-    >
-      {deleteUser => (
-        <Table.Row>
-          <Table.Cell>{user.username}</Table.Cell>
-          <Table.Cell>{user.rootPath}</Table.Cell>
-          <Table.Cell>
-            {user.admin ? <Icon name="checkmark" size="large" /> : null}
-          </Table.Cell>
-          <Table.Cell>
-            <Button.Group>
-              <Button
-                onClick={() => {
-                  setState({ ...state, editing: true, oldState: state })
-                }}
-              >
-                <Icon name="edit" />
-                Edit
-              </Button>
-              <Mutation mutation={scanUserMutation}>
-                {(scanUser, { called }) => (
-                  <Button
-                    disabled={called}
-                    onClick={() => scanUser({ variables: { userId: user.id } })}
-                  >
-                    <Icon name="sync" />
-                    Scan
-                  </Button>
-                )}
-              </Mutation>
-              <Button onClick={() => setChangePassword(true)}>
-                <Icon name="key" />
-                Change password
-              </Button>
-              <ChangePasswordModal
-                user={user}
-                open={showChangePassword}
-                onClose={() => setChangePassword(false)}
-              />
+    <Table.Row>
+      <Table.Cell>{user.username}</Table.Cell>
+      <Table.Cell>{user.rootPath}</Table.Cell>
+      <Table.Cell>
+        {user.admin ? <Icon name="checkmark" size="large" /> : null}
+      </Table.Cell>
+      <Table.Cell>
+        <Button.Group>
+          <Button
+            onClick={() => {
+              setState({ ...state, editing: true, oldState: state })
+            }}
+          >
+            <Icon name="edit" />
+            Edit
+          </Button>
+          <Button
+            disabled={scanUserCalled}
+            onClick={() => scanUser({ variables: { userId: user.id } })}
+          >
+            <Icon name="sync" />
+            Scan
+          </Button>
+          <Button onClick={() => setChangePassword(true)}>
+            <Icon name="key" />
+            Change password
+          </Button>
+          <ChangePasswordModal
+            user={user}
+            open={showChangePassword}
+            onClose={() => setChangePassword(false)}
+          />
+          <Button
+            negative
+            onClick={() => {
+              setConfirmDelete(true)
+            }}
+          >
+            <Icon name="delete" />
+            Delete
+          </Button>
+          <Modal open={showConfirmDelete}>
+            <Modal.Header>Delete user</Modal.Header>
+            <Modal.Content>
+              <p>
+                {`Are you sure, you want to delete `}
+                <b>{user.username}</b>?
+              </p>
+              <p>{`This action cannot be undone`}</p>
+            </Modal.Content>
+            <Modal.Actions>
+              <Button onClick={() => setConfirmDelete(false)}>Cancel</Button>
               <Button
                 negative
                 onClick={() => {
-                  setConfirmDelete(true)
+                  setConfirmDelete(false)
+                  deleteUser({
+                    variables: {
+                      id: user.id,
+                    },
+                  })
                 }}
               >
-                <Icon name="delete" />
-                Delete
+                Delete {user.username}
               </Button>
-              <Modal open={showComfirmDelete}>
-                <Modal.Header>Delete user</Modal.Header>
-                <Modal.Content>
-                  <p>
-                    {`Are you sure, you want to delete `}
-                    <b>{user.username}</b>?
-                  </p>
-                  <p>{`This action cannot be undone`}</p>
-                </Modal.Content>
-                <Modal.Actions>
-                  <Button onClick={() => setConfirmDelete(false)}>
-                    Cancel
-                  </Button>
-                  <Button
-                    negative
-                    onClick={() => {
-                      setConfirmDelete(false)
-                      deleteUser({
-                        variables: {
-                          id: user.id,
-                        },
-                      })
-                    }}
-                  >
-                    Delete {user.username}
-                  </Button>
-                </Modal.Actions>
-              </Modal>
-            </Button.Group>
-          </Table.Cell>
-        </Table.Row>
-      )}
-    </Mutation>
+            </Modal.Actions>
+          </Modal>
+        </Button.Group>
+      </Table.Cell>
+    </Table.Row>
   )
 }
 
diff --git a/ui/src/components/albumGallery/AlbumGallery.js b/ui/src/components/albumGallery/AlbumGallery.js
index dc61a92..4bed335 100644
--- a/ui/src/components/albumGallery/AlbumGallery.js
+++ b/ui/src/components/albumGallery/AlbumGallery.js
@@ -56,9 +56,7 @@ const AlbumGallery = ({
 
   useEffect(() => {
     const updateImageState = event => {
-      if (event.state.imageState) {
-        setImageState(event.state.imageState)
-      }
+      setImageState(event.state.imageState)
     }
     window.addEventListener('popstate', updateImageState)
 
diff --git a/ui/src/components/photoGallery/MediaThumbnail.js b/ui/src/components/photoGallery/MediaThumbnail.js
index dad95f1..11c05fa 100644
--- a/ui/src/components/photoGallery/MediaThumbnail.js
+++ b/ui/src/components/photoGallery/MediaThumbnail.js
@@ -49,19 +49,16 @@ const PhotoImg = photoProps => {
   )
 }
 
-class LazyPhoto extends React.Component {
-  shouldComponentUpdate(nextProps) {
-    return nextProps.src != this.props.src
-  }
-
-  render() {
+const LazyPhoto = React.memo(
+  props => {
     return (
       <LazyLoad scrollContainer="#layout-content">
-        <PhotoImg {...this.props} />
+        <PhotoImg {...props} />
       </LazyLoad>
     )
-  }
-}
+  },
+  (prevProps, nextProps) => prevProps.src === nextProps.src
+)
 
 LazyPhoto.propTypes = {
   src: PropTypes.string,

From 9d25d3d89d42cd235bd734811ac2d915cdc21800 Mon Sep 17 00:00:00 2001
From: stz184 <ivanov.bg@gmail.com>
Date: Mon, 16 Nov 2020 22:46:42 +0200
Subject: [PATCH 09/10] Migrated InitialSetupPage.js to functional component
 with hooks Fixed issue checking the initial setup status on LoginPage

---
 ui/src/Pages/LoginPage/InitialSetupPage.js | 176 ++++++++++-----------
 ui/src/Pages/LoginPage/LoginPage.js        |   2 +-
 2 files changed, 86 insertions(+), 92 deletions(-)

diff --git a/ui/src/Pages/LoginPage/InitialSetupPage.js b/ui/src/Pages/LoginPage/InitialSetupPage.js
index 5b33bd8..8489447 100644
--- a/ui/src/Pages/LoginPage/InitialSetupPage.js
+++ b/ui/src/Pages/LoginPage/InitialSetupPage.js
@@ -1,6 +1,5 @@
-import React, { Component } from 'react'
-import { gql } from '@apollo/client'
-import { Mutation, Query } from '@apollo/client/react/components'
+import React, { useState } from 'react'
+import { gql, useQuery, useMutation } from '@apollo/client'
 import { Redirect } from 'react-router-dom'
 import { Button, Form, Message, Header } from 'semantic-ui-react'
 import { Container } from './loginUtilities'
@@ -26,106 +25,101 @@ const initialSetupMutation = gql`
   }
 `
 
-class InitialSetupPage extends Component {
-  constructor(props) {
-    super(props)
+const InitialSetupPage = () => {
+  const [state, setState] = useState({
+    username: '',
+    password: '',
+    rootPath: '',
+  })
 
-    this.state = {
-      username: '',
-      password: '',
-      rootPath: '',
-    }
+  const handleChange = (event, key) => {
+    const value = event.target.value
+    setState(prevState => ({
+      ...prevState,
+      [key]: value,
+    }))
   }
 
-  handleChange(event, key) {
-    this.setState({ [key]: event.target.value })
-  }
-
-  signIn(event, authorize) {
+  const signIn = (event, authorize) => {
     event.preventDefault()
 
     authorize({
       variables: {
-        username: this.state.username,
-        password: this.state.password,
-        rootPath: this.state.rootPath,
+        username: state.username,
+        password: state.password,
+        rootPath: state.rootPath,
       },
     })
   }
 
-  render() {
-    if (authToken()) {
-      return <Redirect to="/" />
-    }
-
-    return (
-      <div>
-        <Container>
-          <Header as="h1" textAlign="center">
-            Initial Setup
-          </Header>
-          <Query query={checkInitialSetupQuery}>
-            {({ data }) => {
-              if (data && data.siteInfo && data.siteInfo.initialSetup) {
-                return null
-              }
-
-              return <Redirect to="/" />
-            }}
-          </Query>
-          <Mutation
-            mutation={initialSetupMutation}
-            onCompleted={data => {
-              const { success, token } = data.initialSetupWizard
-
-              if (success) {
-                login(token)
-              }
-            }}
-          >
-            {(authorize, { loading, data }) => {
-              let errorMessage = null
-              if (data) {
-                if (!data.initialSetupWizard.success)
-                  errorMessage = data.initialSetupWizard.status
-              }
-
-              return (
-                <Form
-                  style={{ width: 500, margin: 'auto' }}
-                  error={!!errorMessage}
-                  onSubmit={e => this.signIn(e, authorize)}
-                  loading={loading || (data && data.initialSetupWizard.success)}
-                >
-                  <Form.Field>
-                    <label>Username</label>
-                    <input onChange={e => this.handleChange(e, 'username')} />
-                  </Form.Field>
-                  <Form.Field>
-                    <label>Password</label>
-                    <input
-                      type="password"
-                      onChange={e => this.handleChange(e, 'password')}
-                    />
-                  </Form.Field>
-                  <Form.Field>
-                    <label>Photo Path</label>
-                    <input
-                      placeholder="/path/to/photos"
-                      type="text"
-                      onChange={e => this.handleChange(e, 'rootPath')}
-                    />
-                  </Form.Field>
-                  <Message error content={errorMessage} />
-                  <Button type="submit">Setup Photoview</Button>
-                </Form>
-              )
-            }}
-          </Mutation>
-        </Container>
-      </div>
-    )
+  if (authToken()) {
+    return <Redirect to="/" />
   }
+
+  const { data: initialSetupData } = useQuery(checkInitialSetupQuery)
+  const initialSetupRedirect = initialSetupData?.siteInfo
+    ?.initialSetup ? null : (
+    <Redirect to="/" />
+  )
+
+  const [
+    authorize,
+    { loading: authorizeLoading, data: authorizationData },
+  ] = useMutation(initialSetupMutation, {
+    onCompleted: data => {
+      const { success, token } = data.initialSetupWizard
+
+      if (success) {
+        login(token)
+      }
+    },
+  })
+
+  let errorMessage = null
+  if (authorizationData && !authorizationData.initialSetupWizard.success) {
+    errorMessage = authorizationData.initialSetupWizard.status
+  }
+
+  return (
+    <div>
+      {initialSetupRedirect}
+      <Container>
+        <Header as="h1" textAlign="center">
+          Initial Setup
+        </Header>
+        <Form
+          style={{ width: 500, margin: 'auto' }}
+          error={!!errorMessage}
+          onSubmit={e => signIn(e, authorize)}
+          loading={
+            authorizeLoading || authorizationData?.initialSetupWizard?.success
+          }
+        >
+          <Form.Field>
+            <label>Username</label>
+            <input onChange={e => handleChange(e, 'username')} />
+          </Form.Field>
+          <Form.Field>
+            <label>Password</label>
+            <input
+              type="password"
+              onChange={e => handleChange(e, 'password')}
+            />
+          </Form.Field>
+          <Form.Field>
+            <label>Photo Path</label>
+            <input
+              placeholder="/path/to/photos"
+              type="text"
+              onChange={e => handleChange(e, 'rootPath')}
+            />
+          </Form.Field>
+          <Message error content={errorMessage} />
+          <Button type="submit">Setup Photoview</Button>
+        </Form>
+      </Container>
+    </div>
+  )
 }
 
 export default InitialSetupPage
diff --git a/ui/src/Pages/LoginPage/LoginPage.js b/ui/src/Pages/LoginPage/LoginPage.js
index b7ba752..5c4f6ff 100644
--- a/ui/src/Pages/LoginPage/LoginPage.js
+++ b/ui/src/Pages/LoginPage/LoginPage.js
@@ -66,7 +66,7 @@ const LoginPage = () => {
     [credentials]
   )
 
-  const { initialSetupData } = useQuery(checkInitialSetupQuery)
+  const { data: initialSetupData } = useQuery(checkInitialSetupQuery)
 
   const [authorize, { loading, data }] = useMutation(authorizeMutation, {
     onCompleted: data => {

From 18997a0d3e85e33fe5af86c63b5793bb03e3aa15 Mon Sep 17 00:00:00 2001
From: stz184 <ivanov.bg@gmail.com>
Date: Mon, 16 Nov 2020 23:26:38 +0200
Subject: [PATCH 10/10] App, Routes and Sidebar components are migrated to
 functional ones

---
 ui/src/App.js                        | 32 +++++++-------
 ui/src/components/routes/Routes.js   | 60 +++++++++++++--------------
 ui/src/components/sidebar/Sidebar.js | 62 +++++++++++++---------------
 3 files changed, 72 insertions(+), 82 deletions(-)

diff --git a/ui/src/App.js b/ui/src/App.js
index 59fb942..ffd77ad 100644
--- a/ui/src/App.js
+++ b/ui/src/App.js
@@ -1,4 +1,4 @@
-import React, { Component } from 'react'
+import React from 'react'
 import { createGlobalStyle } from 'styled-components'
 import { Helmet } from 'react-helmet'
 import Routes from './components/routes/Routes'
@@ -23,22 +23,20 @@ const GlobalStyle = createGlobalStyle`
 
 import 'semantic-ui-css/semantic.min.css'
 
-class App extends Component {
-  render() {
-    return (
-      <>
-        <Helmet>
-          <meta
-            name="description"
-            content="Simple and User-friendly Photo Gallery for Personal Servers"
-          />
-        </Helmet>
-        <GlobalStyle />
-        <Routes />
-        <Messages />
-      </>
-    )
-  }
+const App = () => {
+  return (
+    <>
+      <Helmet>
+        <meta
+          name="description"
+          content="Simple and User-friendly Photo Gallery for Personal Servers"
+        />
+      </Helmet>
+      <GlobalStyle />
+      <Routes />
+      <Messages />
+    </>
+  )
 }
 
 export default App
diff --git a/ui/src/components/routes/Routes.js b/ui/src/components/routes/Routes.js
index 954951e..e6a7cf0 100644
--- a/ui/src/components/routes/Routes.js
+++ b/ui/src/components/routes/Routes.js
@@ -24,37 +24,35 @@ const SettingsPage = React.lazy(() =>
   import('../../Pages/SettingsPage/SettingsPage')
 )
 
-class Routes extends React.Component {
-  render() {
-    return (
-      <React.Suspense
-        fallback={
-          <Layout>
-            <Loader active>Loading page</Loader>
-          </Layout>
-        }
-      >
-        <Switch>
-          <Route path="/login" component={LoginPage} />
-          <Route path="/logout">
-            {() => {
-              clearTokenCookie()
-              location.href = '/'
-            }}
-          </Route>
-          <Route path="/initialSetup" component={InitialSetupPage} />
-          <Route path="/share" component={SharePage} />
-          <AuthorizedRoute exact path="/albums" component={AlbumsPage} />
-          <AuthorizedRoute path="/album/:id/:subPage?" component={AlbumPage} />
-          <AuthorizedRoute path="/photos/:subPage?" component={PhotosPage} />
-          <AuthorizedRoute path="/places" component={PlacesPage} />
-          <AuthorizedRoute admin path="/settings" component={SettingsPage} />
-          <Route path="/" exact render={() => <Redirect to="/photos" />} />
-          <Route render={() => <div>Page not found</div>} />
-        </Switch>
-      </React.Suspense>
-    )
-  }
+const Routes = () => {
+  return (
+    <React.Suspense
+      fallback={
+        <Layout>
+          <Loader active>Loading page</Loader>
+        </Layout>
+      }
+    >
+      <Switch>
+        <Route path="/login" component={LoginPage} />
+        <Route path="/logout">
+          {() => {
+            clearTokenCookie()
+            location.href = '/'
+          }}
+        </Route>
+        <Route path="/initialSetup" component={InitialSetupPage} />
+        <Route path="/share" component={SharePage} />
+        <AuthorizedRoute exact path="/albums" component={AlbumsPage} />
+        <AuthorizedRoute path="/album/:id/:subPage?" component={AlbumPage} />
+        <AuthorizedRoute path="/photos/:subPage?" component={PhotosPage} />
+        <AuthorizedRoute path="/places" component={PlacesPage} />
+        <AuthorizedRoute admin path="/settings" component={SettingsPage} />
+        <Route path="/" exact render={() => <Redirect to="/photos" />} />
+        <Route render={() => <div>Page not found</div>} />
+      </Switch>
+    </React.Suspense>
+  )
 }
 
 export default Routes
diff --git a/ui/src/components/sidebar/Sidebar.js b/ui/src/components/sidebar/Sidebar.js
index cf30449..508d263 100644
--- a/ui/src/components/sidebar/Sidebar.js
+++ b/ui/src/components/sidebar/Sidebar.js
@@ -1,4 +1,4 @@
-import React, { createContext } from 'react'
+import React, { createContext, useState } from 'react'
 import PropTypes from 'prop-types'
 import styled from 'styled-components'
 import { Icon } from 'semantic-ui-react'
@@ -41,42 +41,36 @@ const SidebarDismissButton = styled(Icon)`
 export const SidebarContext = createContext()
 SidebarContext.displayName = 'SidebarContext'
 
-class Sidebar extends React.Component {
-  constructor(props) {
-    super(props)
+const Sidebar = ({ children }) => {
+  const [state, setState] = useState({
+    content: null,
+  })
 
-    this.state = {
-      content: null,
-    }
-
-    this.update = content => {
-      this.setState({ content })
-    }
+  const update = content => {
+    setState({ content })
   }
 
-  render() {
-    return (
-      <SidebarContext.Provider
-        value={{ updateSidebar: this.update, content: this.state.content }}
-      >
-        {this.props.children}
-        <SidebarContext.Consumer>
-          {value => (
-            <SidebarContainer highlighted={value.content != null}>
-              {value.content}
-              <SidebarDismissButton
-                name="angle double right"
-                size="big"
-                link
-                onClick={() => this.setState({ content: null })}
-              />
-              <div style={{ height: 100 }}></div>
-            </SidebarContainer>
-          )}
-        </SidebarContext.Consumer>
-      </SidebarContext.Provider>
-    )
-  }
+  return (
+    <SidebarContext.Provider
+      value={{ updateSidebar: update, content: state.content }}
+    >
+      {children}
+      <SidebarContext.Consumer>
+        {value => (
+          <SidebarContainer highlighted={value.content != null}>
+            {value.content}
+            <SidebarDismissButton
+              name="angle double right"
+              size="big"
+              link
+              onClick={() => setState({ content: null })}
+            />
+            <div style={{ height: 100 }}></div>
+          </SidebarContainer>
+        )}
+      </SidebarContext.Consumer>
+    </SidebarContext.Provider>
+  )
 }
 
 Sidebar.propTypes = {
