diff --git a/.eslintignore b/.eslintignore
index a03688c9bde..9859bdd0fc7 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -766,6 +766,7 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/dropbox-login.js
packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/status.js
+packages/app-mobile/components/screens/tags.js
packages/app-mobile/components/side-menu-content.js
packages/app-mobile/components/testing/TestProviderStack.js
packages/app-mobile/components/voiceTyping/VoiceTypingDialog.js
diff --git a/.gitignore b/.gitignore
index 17a5d0be4c2..e7ab6ab717e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -742,6 +742,7 @@ packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/dropbox-login.js
packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/status.js
+packages/app-mobile/components/screens/tags.js
packages/app-mobile/components/side-menu-content.js
packages/app-mobile/components/testing/TestProviderStack.js
packages/app-mobile/components/voiceTyping/VoiceTypingDialog.js
diff --git a/packages/app-mobile/components/screens/tags.js b/packages/app-mobile/components/screens/tags.js
deleted file mode 100644
index d3550daa54c..00000000000
--- a/packages/app-mobile/components/screens/tags.js
+++ /dev/null
@@ -1,114 +0,0 @@
-const React = require('react');
-
-const { View, Text, FlatList, StyleSheet, TouchableOpacity } = require('react-native');
-const { connect } = require('react-redux');
-const Tag = require('@joplin/lib/models/Tag').default;
-const { themeStyle } = require('../global-style');
-const { ScreenHeader } = require('../ScreenHeader');
-const { _ } = require('@joplin/lib/locale');
-const { BaseScreenComponent } = require('../base-screen');
-
-class TagsScreenComponent extends BaseScreenComponent {
- static navigationOptions() {
- return { header: null };
- }
-
- constructor() {
- super();
-
- this.state = {
- tags: [],
- };
-
- this.tagList_renderItem = this.tagList_renderItem.bind(this);
- this.tagList_keyExtractor = this.tagList_keyExtractor.bind(this);
- this.tagItem_press = this.tagItem_press.bind(this);
- }
-
- styles() {
- if (this.styles_) return this.styles_;
-
- const theme = themeStyle(this.props.themeId);
-
- this.styles_ = StyleSheet.create({
- listItem: {
- flexDirection: 'row',
- borderBottomWidth: 1,
- borderBottomColor: theme.dividerColor,
- alignItems: 'flex-start',
- paddingLeft: theme.marginLeft,
- paddingRight: theme.marginRight,
- paddingTop: theme.itemMarginTop,
- paddingBottom: theme.itemMarginBottom,
- },
- listItemText: {
- flex: 1,
- color: theme.color,
- fontSize: theme.fontSize,
- },
- });
-
- return this.styles_;
- }
-
- tagItem_press(event) {
- this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
-
- this.props.dispatch({
- type: 'NAV_GO',
- routeName: 'Notes',
- tagId: event.id,
- });
- }
-
- tagList_renderItem(event) {
- const tag = event.item;
- return (
- {
- this.tagItem_press({ id: tag.id });
- }}
- >
-
- {tag.title}
-
-
- );
- }
-
- tagList_keyExtractor(item) {
- return item.id;
- }
-
- async componentDidMount() {
- const tags = await Tag.allWithNotes();
- tags.sort((a, b) => {
- return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : +1;
- });
- this.setState({ tags: tags });
- }
-
- render() {
- const theme = themeStyle(this.props.themeId);
-
- const rootStyle = {
- flex: 1,
- backgroundColor: theme.backgroundColor,
- };
-
- return (
-
-
-
-
- );
- }
-}
-
-const TagsScreen = connect(state => {
- return {
- themeId: state.settings.theme,
- };
-})(TagsScreenComponent);
-
-module.exports = { TagsScreen };
diff --git a/packages/app-mobile/components/screens/tags.tsx b/packages/app-mobile/components/screens/tags.tsx
new file mode 100644
index 00000000000..36eb5a1f66b
--- /dev/null
+++ b/packages/app-mobile/components/screens/tags.tsx
@@ -0,0 +1,100 @@
+import * as React from 'react';
+
+import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
+import { connect } from 'react-redux';
+import Tag from '@joplin/lib/models/Tag';
+import { themeStyle } from '../global-style';
+import { ScreenHeader } from '../ScreenHeader';
+import { _ } from '@joplin/lib/locale';
+import { AppState } from '../../utils/types';
+import { TagEntity } from '@joplin/lib/services/database/types';
+import { useCallback, useMemo, useState } from 'react';
+import { Dispatch } from 'redux';
+import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect';
+
+interface Props {
+ dispatch: Dispatch;
+ themeId: number;
+}
+
+const useStyles = (themeId: number) => {
+ return useMemo(() => {
+ const theme = themeStyle(themeId);
+
+ return StyleSheet.create({
+ listItem: {
+ flexDirection: 'row',
+ borderBottomWidth: 1,
+ borderBottomColor: theme.dividerColor,
+ alignItems: 'flex-start',
+ paddingLeft: theme.marginLeft,
+ paddingRight: theme.marginRight,
+ paddingTop: theme.itemMarginTop,
+ paddingBottom: theme.itemMarginBottom,
+ },
+ listItemText: {
+ flex: 1,
+ color: theme.color,
+ fontSize: theme.fontSize,
+ },
+ rootStyle: theme.rootStyle,
+ });
+ }, [themeId]);
+};
+
+
+const TagsScreenComponent: React.FC = props => {
+ const [tags, setTags] = useState([]);
+ const styles = useStyles(props.themeId);
+
+ type TagItemPressEvent = { id: string };
+
+ useAsyncEffect(async () => {
+ const tags = await Tag.allWithNotes();
+ tags.sort((a, b) => {
+ return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : +1;
+ });
+ setTags(tags);
+ }, []);
+
+ const onTagItemPress = useCallback((event: TagItemPressEvent) => {
+ props.dispatch({ type: 'SIDE_MENU_CLOSE' });
+
+ props.dispatch({
+ type: 'NAV_GO',
+ routeName: 'Notes',
+ tagId: event.id,
+ });
+ }, [props.dispatch]);
+
+ type RenderItemEvent = { item: TagEntity };
+ const onRenderItem = useCallback(({ item }: RenderItemEvent) => {
+ return (
+ onTagItemPress({ id: item.id })}
+ accessibilityRole='button'
+ accessibilityHint={_('Shows notes for tag')}
+ >
+
+ {item.title}
+
+
+ );
+ }, [onTagItemPress, styles]);
+
+ return (
+
+
+ tag.id} />
+
+ );
+};
+
+
+const TagsScreen = connect((state: AppState) => {
+ return {
+ themeId: state.settings.theme,
+ };
+})(TagsScreenComponent);
+
+export default TagsScreen;
diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx
index ea396278008..09435fa4372 100644
--- a/packages/app-mobile/root.tsx
+++ b/packages/app-mobile/root.tsx
@@ -56,7 +56,7 @@ import RevisionService from '@joplin/lib/services/RevisionService';
import JoplinDatabase from '@joplin/lib/JoplinDatabase';
import Database from '@joplin/lib/database';
import NotesScreen from './components/screens/Notes';
-const { TagsScreen } = require('./components/screens/tags.js');
+import TagsScreen from './components/screens/tags';
import ConfigScreen from './components/screens/ConfigScreen/ConfigScreen';
const { FolderScreen } = require('./components/screens/folder.js');
import LogScreen from './components/screens/LogScreen';