Skip to content

Commit

Permalink
♻️ Use context for ticker and settings
Browse files Browse the repository at this point in the history
  • Loading branch information
batabana committed Nov 20, 2022
1 parent bb6a838 commit c6337bc
Show file tree
Hide file tree
Showing 10 changed files with 202 additions and 133 deletions.
90 changes: 8 additions & 82 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,87 +1,13 @@
import { FC, useEffect, useState } from 'react'
import { Container, Dimmer, Loader } from 'semantic-ui-react'
import { Settings, Ticker } from './lib/types'
import { getInit } from './lib/api'
import ErrorView from './views/ErrorView'
import ActiveView from './views/ActiveView'
import InactiveView from './views/InactiveView'
import { FC } from 'react'
import { TickerProvider } from './components/useTicker'
import ViewRenderer from './ViewRenderer'

const App: FC = () => {
const [ticker, setTicker] = useState<Ticker | null>(null)
const [settings, setSettings] = useState<Settings>()
const [isLoading, setIsLoading] = useState<boolean>(true)
const [isOffline, setIsOffline] = useState<boolean>(false)
const [gotError, setGotError] = useState<boolean>(false)

const fetchInit = () => {
getInit()
.then(response => {
if (response.data.settings) {
setSettings(response.data.settings)
}

if (response.data.ticker?.active) {
setTicker(response.data.ticker)
}

setIsLoading(false)
})
.catch(error => {
if (error instanceof TypeError) {
setIsOffline(true)
} else {
setGotError(true)
}
setIsLoading(false)
})
}

useEffect(() => {
fetchInit()
// This should only be executed once on load (~ componentDidMount)
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [])

useEffect(() => {
if (ticker?.title) {
document.title = ticker.title
}
}, [ticker])

if (isLoading) {
return (
<Container>
<Dimmer active>
<Loader content="Loading" size="large" />
</Dimmer>
</Container>
)
}

if (gotError) {
return (
<ErrorView message="There seems to be a problem connecting to the server." />
)
}

if (isOffline) {
return <ErrorView message="It seems that you are offline." />
}

if (ticker?.active) {
return (
<ActiveView
refreshInterval={settings?.refresh_interval || 0}
ticker={ticker}
/>
)
}

if (ticker === null && settings?.inactive_settings !== undefined) {
return <InactiveView settings={settings.inactive_settings} />
}

return <div>...</div>
return (
<TickerProvider>
<ViewRenderer />
</TickerProvider>
)
}

export default App
21 changes: 15 additions & 6 deletions src/App.test.tsx → src/ViewRenderer.test.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import App from './App'
import ViewRenderer from './ViewRenderer'
import { render, screen } from '@testing-library/react'
import * as api from './lib/api'
import { Settings, Ticker } from './lib/types'
import { TickerProvider } from './components/useTicker'

describe('App', function () {
describe('ViewRenderer', function () {
const initSettings = {
refresh_interval: 1000,
inactive_settings: {
Expand Down Expand Up @@ -32,9 +33,17 @@ describe('App', function () {
},
} as Ticker

const renderViewRenderer = () => {
return render(
<TickerProvider>
<ViewRenderer />
</TickerProvider>
)
}

test('renders OfflineView', async function () {
jest.spyOn(api, 'getInit').mockRejectedValue(new TypeError())
render(<App />)
renderViewRenderer()

expect(screen.getByText('Loading')).toBeInTheDocument()

Expand All @@ -51,7 +60,7 @@ describe('App', function () {
'The server responses with an error: Internal Server Error (500)'
)
)
render(<App />)
renderViewRenderer()

expect(screen.getByText('Loading')).toBeInTheDocument()

Expand All @@ -69,7 +78,7 @@ describe('App', function () {
ticker: null,
},
})
render(<App />)
renderViewRenderer()

expect(screen.getByText('Loading')).toBeInTheDocument()

Expand All @@ -96,7 +105,7 @@ describe('App', function () {
window.IntersectionObserver = jest
.fn()
.mockImplementation(intersectionObserverMock)
render(<App />)
renderViewRenderer()

expect(screen.getByText('Loading')).toBeInTheDocument()

Expand Down
48 changes: 48 additions & 0 deletions src/ViewRenderer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { FC, useEffect } from 'react'
import { Container, Dimmer, Loader } from 'semantic-ui-react'
import ErrorView from './views/ErrorView'
import ActiveView from './views/ActiveView'
import InactiveView from './views/InactiveView'
import useTicker from './components/useTicker'

const ViewRenderer: FC = () => {
const { ticker, settings, isLoading, isOffline, hasError } = useTicker()

useEffect(() => {
if (ticker?.title) {
document.title = ticker.title
}
}, [ticker])

if (isLoading) {
return (
<Container>
<Dimmer active>
<Loader content="Loading" size="large" />
</Dimmer>
</Container>
)
}

if (hasError) {
return (
<ErrorView message="There seems to be a problem connecting to the server." />
)
}

if (isOffline) {
return <ErrorView message="It seems that you are offline." />
}

if (ticker?.active) {
return <ActiveView />
}

if (ticker === null && settings?.inactive_settings !== undefined) {
return <InactiveView settings={settings.inactive_settings} />
}

return <div>...</div>
}

export default ViewRenderer
42 changes: 24 additions & 18 deletions src/components/About.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,50 +3,56 @@ import ReactMarkdown from 'react-markdown'
import { Button, Card, Icon, List, Modal } from 'semantic-ui-react'
import Credits from './Credits'
import DescriptionItem from './DescriptionItem'
import { DescriptionTypes, Ticker } from '../lib/types'
import { DescriptionTypes } from '../lib/types'
import { getAtomFeedUrl, getRssFeedUrl } from '../lib/api'
import useTicker from './useTicker'

interface Props {
ticker: Ticker
isModal?: boolean
}

const About: FC<Props> = props => {
const About: FC<Props> = ({ isModal }) => {
const { ticker } = useTicker()

if (!ticker) {
return null
}

const renderDescriptionList = () => (
<List>
{props.ticker.information.author && (
{ticker.information.author && (
<DescriptionItem
info={props.ticker.information.author}
info={ticker.information.author}
type={DescriptionTypes.Author}
/>
)}
{props.ticker.information.email && (
{ticker.information.email && (
<DescriptionItem
info={props.ticker.information.email}
info={ticker.information.email}
type={DescriptionTypes.Email}
/>
)}
{props.ticker.information.url && (
{ticker.information.url && (
<DescriptionItem
info={props.ticker.information.url}
info={ticker.information.url}
type={DescriptionTypes.Homepage}
/>
)}
{props.ticker.information.twitter && (
{ticker.information.twitter && (
<DescriptionItem
info={props.ticker.information.twitter}
info={ticker.information.twitter}
type={DescriptionTypes.Twitter}
/>
)}
{props.ticker.information.facebook && (
{ticker.information.facebook && (
<DescriptionItem
info={props.ticker.information.facebook}
info={ticker.information.facebook}
type={DescriptionTypes.Facebook}
/>
)}
{props.ticker.information.telegram && (
{ticker.information.telegram && (
<DescriptionItem
info={props.ticker.information.telegram}
info={ticker.information.telegram}
type={DescriptionTypes.Telegram}
/>
)}
Expand All @@ -60,7 +66,7 @@ const About: FC<Props> = props => {
</List>
)

if (props.isModal) {
if (isModal) {
return (
<Modal
closeIcon
Expand All @@ -73,7 +79,7 @@ const About: FC<Props> = props => {
>
<Modal.Header>About</Modal.Header>
<Modal.Content>
<ReactMarkdown>{props.ticker.description}</ReactMarkdown>
<ReactMarkdown>{ticker.description || ''}</ReactMarkdown>
</Modal.Content>
<Modal.Content>
{renderDescriptionList()}
Expand All @@ -90,7 +96,7 @@ const About: FC<Props> = props => {
<Card.Header>About</Card.Header>
</Card.Content>
<Card.Content>
<ReactMarkdown>{props.ticker.description}</ReactMarkdown>
<ReactMarkdown>{ticker.description || ''}</ReactMarkdown>
</Card.Content>
<Card.Content>{renderDescriptionList()}</Card.Content>
</Card>
Expand Down
12 changes: 7 additions & 5 deletions src/components/DynamicMetaTags.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { FC } from 'react'
import { Helmet } from 'react-helmet'
import { Ticker } from '../lib/types'
import useTicker from './useTicker'

interface Props {
ticker: Ticker
}
const DynamicMetaTags: FC = () => {
const { ticker } = useTicker()

if (!ticker) {
return null
}

const DynamicMetaTags: FC<Props> = ({ ticker }) => {
return (
<Helmet>
<meta content={ticker.description} name="description" />
Expand Down
2 changes: 1 addition & 1 deletion src/components/Map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const Map: FC<Props> = props => {

if (
features.length === 1 &&
// FIXME: Type is currently not defined by DefinitelyTyped?
// Type is currently not defined by DefinitelyTyped
// @ts-ignore
features[0].feature.geometry.type === 'Point'
) {
Expand Down
2 changes: 1 addition & 1 deletion src/components/MessageList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe('MessageList', function () {
.fn()
.mockImplementation(intersectionObserverMock)

render(<MessageList refreshInterval={10} />)
render(<MessageList />)

expect(screen.getByText('Loading messages')).toBeInTheDocument()
expect(
Expand Down
16 changes: 9 additions & 7 deletions src/components/MessageList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,15 @@ import { Dimmer, Header, Icon, Loader, Segment } from 'semantic-ui-react'
import { Message as MessageType } from '../lib/types'
import Message from './Message'
import { getTimeline } from '../lib/api'
import useTicker from './useTicker'

interface Props {
refreshInterval: number
}

const MessageList: FC<Props> = props => {
const MessageList: FC = () => {
const [isLoading, setIsLoading] = useState<boolean>(true)
const [messages, setMessages] = useState<MessageType[]>([])
const [lastMessageReceived, setLastMessageReceived] = useState<boolean>(false)

const { settings } = useTicker()

const loadMoreSpinnerRef = useRef<HTMLDivElement>(null)

const fetchMessages = useCallback(() => {
Expand Down Expand Up @@ -98,10 +97,13 @@ const MessageList: FC<Props> = props => {

// periodically fetch new messages
useEffect(() => {
const interval = setInterval(() => fetchMessages(), props.refreshInterval)
const interval = setInterval(
() => fetchMessages(),
settings?.refresh_interval || 0
)

return () => clearInterval(interval)
}, [fetchMessages, messages, props.refreshInterval])
}, [fetchMessages, messages, settings?.refresh_interval])

const renderPlaceholder = () => (
<Segment placeholder>
Expand Down
Loading

0 comments on commit c6337bc

Please sign in to comment.