diff --git a/src/containers/App/App.tsx b/src/containers/App/App.tsx
index c2a1ef70e..1cb1fe479 100644
--- a/src/containers/App/App.tsx
+++ b/src/containers/App/App.tsx
@@ -10,7 +10,7 @@ import { updateViewportDimensions, onScroll } from './actions'
import Ledgers from '../Ledgers'
import Header from '../Header'
import './app.scss'
-import ledger from '../Ledger'
+import { Ledger } from '../Ledger'
import transactions from '../Transactions'
import { Network } from '../Network'
import { Validator } from '../Validators'
@@ -105,7 +105,7 @@ const App = ({ actions }: AppProps) => {
-
+
{
const messages = [
- ['ledgerError', state.ledger.error],
['transactionError', state.transaction.error],
['balanceError', state.accountHeader.error],
['payStringError', state.payStringData.error],
diff --git a/src/containers/Header/test/Banner.test.js b/src/containers/Header/test/Banner.test.js
index 0d5a29902..0875e7b5a 100644
--- a/src/containers/Header/test/Banner.test.js
+++ b/src/containers/Header/test/Banner.test.js
@@ -29,16 +29,13 @@ describe('Banner component', () => {
it('renders with messages', () => {
const state = {
...initialState,
- ledger: {
- error: 'ledger_error',
- },
transaction: {
error: 'transaction_error',
},
}
const wrapper = createWrapper(state)
- expect(wrapper.find('.notification').length).toEqual(2)
+ expect(wrapper.find('.notification').length).toEqual(1)
wrapper.unmount()
})
})
diff --git a/src/containers/Ledger/actionTypes.js b/src/containers/Ledger/actionTypes.js
deleted file mode 100644
index 53d1df2e8..000000000
--- a/src/containers/Ledger/actionTypes.js
+++ /dev/null
@@ -1,4 +0,0 @@
-export const START_LOADING_FULL_LEDGER = 'START_LOADING_FULL_LEDGER'
-export const FINISH_LOADING_FULL_LEDGER = 'FINISH_LOADING_FULL_LEDGER'
-export const LOADING_FULL_LEDGER_SUCCESS = 'LOADING_FULL_LEDGER_SUCCESS'
-export const LOADING_FULL_LEDGER_FAIL = 'LOADING_FULL_LEDGER_FAIL'
diff --git a/src/containers/Ledger/actions.js b/src/containers/Ledger/actions.js
deleted file mode 100644
index 4ca20f2b1..000000000
--- a/src/containers/Ledger/actions.js
+++ /dev/null
@@ -1,45 +0,0 @@
-import {
- analytics,
- ANALYTIC_TYPES,
- BAD_REQUEST,
- DECIMAL_REGEX,
- HASH_REGEX,
-} from '../shared/utils'
-import * as actionTypes from './actionTypes'
-import { getLedger } from '../../rippled'
-
-export const loadLedger = (identifier, rippledSocket) => (dispatch) => {
- if (!DECIMAL_REGEX.test(identifier) && !HASH_REGEX.test(identifier)) {
- dispatch({
- type: actionTypes.LOADING_FULL_LEDGER_FAIL,
- data: { error: BAD_REQUEST },
- })
- return undefined
- }
-
- dispatch({
- type: actionTypes.START_LOADING_FULL_LEDGER,
- data: { id: identifier },
- })
-
- return getLedger(identifier, rippledSocket)
- .then((data) => {
- dispatch({ type: actionTypes.FINISH_LOADING_FULL_LEDGER })
- dispatch({ type: actionTypes.LOADING_FULL_LEDGER_SUCCESS, data })
- })
- .catch((error) => {
- const status = error.code
- analytics(ANALYTIC_TYPES.exception, {
- exDescription: `ledger ${identifier} --- ${JSON.stringify(error)}`,
- })
- dispatch({ type: actionTypes.FINISH_LOADING_FULL_LEDGER })
-
- dispatch({
- type: actionTypes.LOADING_FULL_LEDGER_FAIL,
- data: { error: status, id: identifier },
- error: status === 500 ? 'get_ledger_failed' : '',
- })
- })
-}
-
-export { loadLedger as default }
diff --git a/src/containers/Ledger/index.tsx b/src/containers/Ledger/index.tsx
index 30f747e81..cebab7af9 100644
--- a/src/containers/Ledger/index.tsx
+++ b/src/containers/Ledger/index.tsx
@@ -1,9 +1,8 @@
import { useContext, useEffect } from 'react'
import { useTranslation } from 'react-i18next'
-import { connect } from 'react-redux'
-import { bindActionCreators } from 'redux'
-import { Link } from 'react-router-dom'
import { useParams } from 'react-router'
+import { Link } from 'react-router-dom'
+import { useQuery } from 'react-query'
import NoMatch from '../NoMatch'
import Loader from '../shared/components/Loader'
import SocketContext from '../shared/SocketContext'
@@ -16,14 +15,16 @@ import {
BAD_REQUEST,
analytics,
ANALYTIC_TYPES,
+ DECIMAL_REGEX,
+ HASH_REGEX,
} from '../shared/utils'
import LeftArrow from '../shared/images/ic_left_arrow.svg'
import RightArrow from '../shared/images/ic_right_arrow.svg'
-import { loadLedger } from './actions'
import { LedgerTransactionTable } from './LedgerTransactionTable'
import './ledger.scss'
+import { getLedger } from '../../rippled'
const TIME_ZONE = 'UTC'
const DATE_OPTIONS = {
@@ -54,15 +55,7 @@ ERROR_MESSAGES.default = {
const getErrorMessage = (error) =>
ERROR_MESSAGES[error] || ERROR_MESSAGES.default
-export interface LedgerProps {
- actions: {
- loadLedger: Function
- }
- data: any
- loading: boolean
-}
-
-const Ledger = ({ actions, data, loading }: LedgerProps) => {
+export const Ledger = () => {
const rippledSocket = useContext(SocketContext)
const { identifier = '' } = useParams<{ identifier: string }>()
const { t } = useTranslation()
@@ -74,10 +67,29 @@ const Ledger = ({ actions, data, loading }: LedgerProps) => {
title: 'Ledger',
path: '/ledgers/:id',
})
- actions.loadLedger(identifier, rippledSocket)
- }, [actions, identifier, rippledSocket, t])
+ }, [identifier, t])
+
+ const {
+ data: ledgerData,
+ error,
+ isLoading,
+ } = useQuery(['ledger', identifier], () => {
+ if (!DECIMAL_REGEX.test(identifier) && !HASH_REGEX.test(identifier)) {
+ return Promise.reject(BAD_REQUEST)
+ }
+
+ return getLedger(identifier, rippledSocket).catch(
+ (transactionRequestError) => {
+ const status = transactionRequestError.code
+ analytics(ANALYTIC_TYPES.exception, {
+ exDescription: `ledger ${identifier} --- ${JSON.stringify(error)}`,
+ })
+ return Promise.reject(status)
+ },
+ )
+ })
- const renderNav = () => {
+ const renderNav = (data: any) => {
const { ledger_index: LedgerIndex, ledger_hash: LedgerHash } = data
const previousIndex = LedgerIndex - 1
const nextIndex = LedgerIndex + 1
@@ -133,45 +145,30 @@ const Ledger = ({ actions, data, loading }: LedgerProps) => {
}
const renderLedger = () =>
- data.ledger_hash ? (
+ ledgerData?.ledger_hash ? (
<>
- {renderNav()}
+ {renderNav(ledgerData)}
>
) : null
const renderError = () => {
- if (!data.error) {
+ if (!error) {
return null
}
- const message = getErrorMessage(data.error)
+ const message = getErrorMessage(error)
return
}
return (
- {loading && }
+ {isLoading && }
{renderLedger()}
{renderError()}
)
}
-
-export default connect(
- (state: any) => ({
- loading: state.ledger.loading,
- data: state.ledger.data,
- }),
- (dispatch) => ({
- actions: bindActionCreators(
- {
- loadLedger,
- },
- dispatch,
- ),
- }),
-)(Ledger)
diff --git a/src/containers/Ledger/reducer.js b/src/containers/Ledger/reducer.js
deleted file mode 100644
index 6a79631c6..000000000
--- a/src/containers/Ledger/reducer.js
+++ /dev/null
@@ -1,33 +0,0 @@
-import * as actionTypes from './actionTypes'
-
-export const initialState = {
- loading: false,
- error: '',
- data: {},
-}
-
-const rehydrate = (action) => {
- const payload =
- action.payload && action.payload.ledger ? action.payload.ledger : {}
- const ledgerData = payload.data && !payload.data.error ? payload.data : {}
- return { ...payload, loading: false, error: '', data: ledgerData }
-}
-
-const ledgerReducer = (state = initialState, action) => {
- switch (action.type) {
- case actionTypes.START_LOADING_FULL_LEDGER:
- return { ...state, loading: true, data: action.data }
- case actionTypes.FINISH_LOADING_FULL_LEDGER:
- return { ...state, loading: false }
- case actionTypes.LOADING_FULL_LEDGER_SUCCESS:
- return { ...state, error: '', data: action.data }
- case actionTypes.LOADING_FULL_LEDGER_FAIL:
- return { ...state, data: action.data, error: action.error }
- case 'persist/REHYDRATE':
- return { ...state, ...rehydrate(action) }
- default:
- return state
- }
-}
-
-export default ledgerReducer
diff --git a/src/containers/Ledger/test/Ledger.test.js b/src/containers/Ledger/test/Ledger.test.js
index 2b63b1c5e..0a60cdbf9 100644
--- a/src/containers/Ledger/test/Ledger.test.js
+++ b/src/containers/Ledger/test/Ledger.test.js
@@ -1,30 +1,45 @@
import { mount } from 'enzyme'
import { I18nextProvider } from 'react-i18next'
-import configureMockStore from 'redux-mock-store'
-import thunk from 'redux-thunk'
-import { Provider } from 'react-redux'
-import { BrowserRouter as Router } from 'react-router-dom'
+import { MemoryRouter as Router, Route } from 'react-router-dom'
+import { QueryClientProvider } from 'react-query'
import mockLedger from './storedLedger.json'
import i18n from '../../../i18n/testConfig'
-import { initialState } from '../../../rootReducer'
-import { NOT_FOUND, BAD_REQUEST, SERVER_ERROR } from '../../shared/utils'
-import Ledger from '../index'
+import { Ledger } from '../index'
+import { getLedger } from '../../../rippled'
+import { testQueryClient } from '../../test/QueryClient'
+import { Error as RippledError } from '../../../rippled/lib/utils'
+
+jest.mock('../../../rippled', () => {
+ const originalModule = jest.requireActual('../../../rippled')
+
+ return {
+ __esModule: true,
+ ...originalModule,
+ getLedger: jest.fn(),
+ }
+})
+
+const mockedGetLedger = getLedger
+
+export function flushPromises() {
+ return new Promise((resolve) => setImmediate(resolve))
+}
describe('Ledger container', () => {
- const middlewares = [thunk]
- const mockStore = configureMockStore(middlewares)
- const createWrapper = (state = {}) => {
- const store = mockStore({ ...initialState, ...state })
- return mount(
+ const createWrapper = (identifier = 38079857) =>
+ mount(
-
-
-
+
+
+
-
+
,
)
- }
+
+ afterEach(() => {
+ mockedGetLedger.mockReset()
+ })
it('renders without crashing', () => {
const wrapper = createWrapper()
@@ -32,20 +47,18 @@ describe('Ledger container', () => {
})
it('renders loading', () => {
- const state = { ...initialState }
- state.ledger.data = {}
- state.ledger.loading = true
- const wrapper = createWrapper(state)
+ const wrapper = createWrapper()
expect(wrapper.find('.loader').length).toBe(1)
wrapper.unmount()
})
- it('renders ledger navbar', () => {
- const state = { ...initialState }
- state.ledger.data = mockLedger
- state.ledger.loading = false
- state.ledger.error = false
- const wrapper = createWrapper(state)
+ it('renders ledger navbar', async () => {
+ mockedGetLedger.mockImplementation(() => Promise.resolve(mockLedger))
+
+ const wrapper = createWrapper()
+ await flushPromises()
+ wrapper.update()
+
const header = wrapper.find('.ledger-header')
expect(header.length).toBe(1)
expect(header.find('.ledger-nav').length).toBe(1)
@@ -53,12 +66,13 @@ describe('Ledger container', () => {
wrapper.unmount()
})
- it('renders ledger summary', () => {
- const state = { ...initialState }
- state.ledger.data = mockLedger
- state.ledger.loading = false
- state.ledger.error = false
- const wrapper = createWrapper(state)
+ it('renders ledger summary', async () => {
+ mockedGetLedger.mockImplementation(() => Promise.resolve(mockLedger))
+
+ const wrapper = createWrapper()
+ await flushPromises()
+ wrapper.update()
+
const summary = wrapper.find('.ledger-header .ledger-info')
expect(summary.length).toBe(1)
@@ -71,12 +85,13 @@ describe('Ledger container', () => {
wrapper.unmount()
})
- it('renders transaction table header', () => {
- const state = { ...initialState }
- state.ledger.data = mockLedger
- state.ledger.loading = false
- state.ledger.error = false
- const wrapper = createWrapper(state)
+ it('renders transaction table header', async () => {
+ mockedGetLedger.mockImplementation(() => Promise.resolve(mockLedger))
+
+ const wrapper = createWrapper()
+ await flushPromises()
+ wrapper.update()
+
const table = wrapper.find('.transaction-table')
expect(table.length).toBe(1)
expect(table.find('.transaction-li-header').length).toBe(1)
@@ -85,12 +100,13 @@ describe('Ledger container', () => {
wrapper.unmount()
})
- it('renders all transactions', () => {
- const state = { ...initialState }
- state.ledger.data = mockLedger
- state.ledger.loading = false
- state.ledger.error = false
- const wrapper = createWrapper(state)
+ it('renders all transactions', async () => {
+ mockedGetLedger.mockImplementation(() => Promise.resolve(mockLedger))
+
+ const wrapper = createWrapper()
+ await flushPromises()
+ wrapper.update()
+
const table = wrapper.find('.transaction-table')
expect(table.length).toBe(1)
expect(table.find('.transaction-li').length).toBe(
@@ -99,34 +115,41 @@ describe('Ledger container', () => {
wrapper.unmount()
})
- it('renders 404 page on no match', () => {
- const state = { ...initialState }
- state.ledger.data = { error: NOT_FOUND, id: 1 }
- state.ledger.loading = false
- state.ledger.error = true
+ it('renders 404 page on no match', async () => {
+ mockedGetLedger.mockImplementation(() =>
+ Promise.reject(new RippledError('ledger not found', 404)),
+ )
+
+ const wrapper = createWrapper()
+ await flushPromises()
+ wrapper.update()
- const wrapper = createWrapper(state)
expect(wrapper.find('.no-match .title').text()).toEqual('ledger_not_found')
wrapper.unmount()
})
- it('renders server error', () => {
- const state = { ...initialState }
- state.ledger.data = { error: SERVER_ERROR, id: 1 }
- state.ledger.loading = false
- state.ledger.error = true
+ it('renders server error', async () => {
+ mockedGetLedger.mockImplementation(() =>
+ Promise.reject(new RippledError('ledger failed', 500)),
+ )
+
+ const wrapper = createWrapper()
+ await flushPromises()
+ wrapper.update()
- const wrapper = createWrapper(state)
expect(wrapper.find('.no-match .title').text()).toEqual('generic_error')
wrapper.unmount()
})
- it('renders invalid id error', () => {
- const state = { ...initialState }
- state.ledger.data = { error: BAD_REQUEST, id: 'zzzz' }
- state.ledger.loading = false
+ it('renders invalid id error', async () => {
+ mockedGetLedger.mockImplementation(() =>
+ Promise.reject(new RippledError('invalid ledger index/hash', 400)),
+ )
+
+ const wrapper = createWrapper('aaaa')
+ await flushPromises()
+ wrapper.update()
- const wrapper = createWrapper(state)
expect(wrapper.find('.no-match .title').text()).toEqual('invalid_ledger_id')
wrapper.unmount()
})
diff --git a/src/containers/Ledger/test/actions.test.js b/src/containers/Ledger/test/actions.test.js
deleted file mode 100644
index 64b88a775..000000000
--- a/src/containers/Ledger/test/actions.test.js
+++ /dev/null
@@ -1,120 +0,0 @@
-import configureMockStore from 'redux-mock-store'
-import thunk from 'redux-thunk'
-import mockLedger from './mockLedger.json'
-import { NOT_FOUND, BAD_REQUEST, SERVER_ERROR } from '../../shared/utils'
-import { initialState } from '../reducer'
-import * as actions from '../actions'
-import * as actionTypes from '../actionTypes'
-import { summarizeLedger } from '../../../rippled/lib/summarizeLedger'
-import ledgerNotFound from './ledgerNotFound.json'
-import MockWsClient from '../../test/mockWsClient'
-
-describe('Ledger actions', () => {
- const middlewares = [thunk]
- const mockStore = configureMockStore(middlewares)
- let store
- let client
- beforeEach(() => {
- store = mockStore({ ledger: initialState })
- client = new MockWsClient()
- })
-
- afterEach(() => {
- store = null
- client.close()
- })
-
- it('should dispatch correct actions on success for loadLedger', async () => {
- const expectedActions = [
- {
- type: actionTypes.START_LOADING_FULL_LEDGER,
- data: { id: mockLedger.result.ledger.ledger_index },
- },
- { type: actionTypes.FINISH_LOADING_FULL_LEDGER },
- {
- type: actionTypes.LOADING_FULL_LEDGER_SUCCESS,
- data: summarizeLedger(mockLedger.result.ledger, true),
- },
- ]
- client.addResponse('ledger', mockLedger)
-
- await store.dispatch(
- actions.loadLedger(mockLedger.result.ledger.ledger_index, client),
- )
- expect(store.getActions()).toEqual(expectedActions)
- })
-
- it('should dispatch correct actions on success for loadLedger (ledger hash)', async () => {
- const expectedActions = [
- {
- type: actionTypes.START_LOADING_FULL_LEDGER,
- data: { id: mockLedger.result.ledger.ledger_hash },
- },
- { type: actionTypes.FINISH_LOADING_FULL_LEDGER },
- {
- type: actionTypes.LOADING_FULL_LEDGER_SUCCESS,
- data: summarizeLedger(mockLedger.result.ledger, true),
- },
- ]
- client.addResponse('ledger', mockLedger)
-
- await store.dispatch(
- actions.loadLedger(mockLedger.result.ledger.ledger_hash, client),
- )
- expect(store.getActions()).toEqual(expectedActions)
- })
-
- it('should dispatch correct actions on fail for loadLedger with invalid id', () => {
- const expectedActions = [
- {
- type: actionTypes.LOADING_FULL_LEDGER_FAIL,
- data: { error: BAD_REQUEST },
- },
- ]
- store.dispatch(actions.loadLedger('zzz', null))
- expect(store.getActions()).toEqual(expectedActions)
- })
-
- it('should dispatch correct actions on fail for loadLedger 404', async () => {
- const LEDGER_INDEX = 1234
- const expectedActions = [
- {
- type: actionTypes.START_LOADING_FULL_LEDGER,
- data: { id: LEDGER_INDEX },
- },
- { type: actionTypes.FINISH_LOADING_FULL_LEDGER },
- {
- type: actionTypes.LOADING_FULL_LEDGER_FAIL,
- data: {
- error: NOT_FOUND,
- id: LEDGER_INDEX,
- },
- error: '',
- },
- ]
- client.addResponse('ledger', ledgerNotFound)
-
- await store.dispatch(actions.loadLedger(LEDGER_INDEX, client))
- expect(store.getActions()).toEqual(expectedActions)
- })
-
- it('should dispatch correct actions on fail for loadLedger 500', async () => {
- const expectedActions = [
- { type: actionTypes.START_LOADING_FULL_LEDGER, data: { id: 1 } },
- { type: actionTypes.FINISH_LOADING_FULL_LEDGER },
- {
- type: actionTypes.LOADING_FULL_LEDGER_FAIL,
- error: 'get_ledger_failed',
- data: {
- error: SERVER_ERROR,
- id: 1,
- },
- },
- ]
- client.setReturnError()
- await store.dispatch(actions.loadLedger(1, client))
-
- const receivedActions = store.getActions()
- expect(receivedActions).toEqual(expectedActions)
- })
-})
diff --git a/src/containers/Ledger/test/reducer.test.js b/src/containers/Ledger/test/reducer.test.js
deleted file mode 100644
index dedf65fea..000000000
--- a/src/containers/Ledger/test/reducer.test.js
+++ /dev/null
@@ -1,85 +0,0 @@
-import * as actionTypes from '../actionTypes'
-import reducer, { initialState } from '../reducer'
-import mockLedger from './mockLedger.json'
-
-describe.only('Ledger reducers', () => {
- it('should return the initial state', () => {
- expect(reducer(undefined, {})).toEqual(initialState)
- })
-
- it('should handle START_LOADING_FULL_LEDGER', () => {
- const nextState = { ...initialState, data: { id: 1 }, loading: true }
- expect(
- reducer(initialState, {
- type: actionTypes.START_LOADING_FULL_LEDGER,
- data: { id: 1 },
- }),
- ).toEqual(nextState)
- })
-
- it('should handle FINISH_LOADING_FULL_LEDGER', () => {
- const nextState = { ...initialState, loading: false }
- expect(
- reducer(initialState, { type: actionTypes.FINISH_LOADING_FULL_LEDGER }),
- ).toEqual(nextState)
- })
-
- it('should handle LOADING_FULL_LEDGER_SUCCESS', () => {
- const nextState = { ...initialState, data: mockLedger }
- expect(
- reducer(initialState, {
- type: actionTypes.LOADING_FULL_LEDGER_SUCCESS,
- data: mockLedger,
- }),
- ).toEqual(nextState)
- })
-
- it('should handle LOADING_FULL_LEDGER_FAIL', () => {
- const error = 'get_ledger_failed'
- const nextState = { ...initialState, error }
- expect(
- reducer(initialState, {
- type: actionTypes.LOADING_FULL_LEDGER_FAIL,
- data: {},
- error,
- }),
- ).toEqual(nextState)
- })
-
- it('should clear data on rehydration (error)', () => {
- const nextState = {
- ...initialState,
- loading: false,
- error: 'get_ledger_failed',
- data: { error: 'not found' },
- }
- expect(
- reducer(initialState, {
- type: actionTypes.LOADING_FULL_LEDGER_FAIL,
- data: { error: 'not found' },
- error: 'get_ledger_failed',
- }),
- ).toEqual(nextState)
- expect(reducer(nextState, { type: 'persist/REHYDRATE' })).toEqual(
- initialState,
- )
- })
-
- it('should clear data on rehydration (ledger)', () => {
- const nextState = {
- ...initialState,
- loading: false,
- error: '',
- data: mockLedger,
- }
- expect(
- reducer(initialState, {
- type: actionTypes.LOADING_FULL_LEDGER_SUCCESS,
- data: mockLedger,
- }),
- ).toEqual(nextState)
- expect(reducer(nextState, { type: 'persist/REHYDRATE' })).toEqual(
- initialState,
- )
- })
-})
diff --git a/src/rootReducer.js b/src/rootReducer.js
index 606f7cff8..0c10781e3 100644
--- a/src/rootReducer.js
+++ b/src/rootReducer.js
@@ -1,8 +1,5 @@
import { combineReducers } from 'redux'
import appReducer, { initialState as appState } from './containers/App/reducer'
-import ledgerReducer, {
- initialState as ledgerState,
-} from './containers/Ledger/reducer'
import accountHeaderReducer, {
initialState as accountHeaderState,
} from './containers/Accounts/AccountHeader/reducer'
@@ -19,7 +16,6 @@ import tokenHeaderReducer, {
export const initialState = {
app: appState,
accountHeader: accountHeaderState,
- ledger: ledgerState,
transaction: transactionState,
payStringData: payStringState,
tokenHeader: tokenHeaderState,
@@ -28,7 +24,6 @@ export const initialState = {
const rootReducer = combineReducers({
app: appReducer,
accountHeader: accountHeaderReducer,
- ledger: ledgerReducer,
transaction: transactionReducer,
payStringData: payStringReducer,
tokenHeader: tokenHeaderReducer,