Skip to content

Commit

Permalink
implement event emmiter to use it for selected change handling
Browse files Browse the repository at this point in the history
  • Loading branch information
usavkov-epam committed Dec 6, 2023
1 parent c51131d commit b260d13
Show file tree
Hide file tree
Showing 13 changed files with 151 additions and 20 deletions.
18 changes: 16 additions & 2 deletions lib/FindLocation/FindLocation.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import PropTypes from 'prop-types';
import { useCallback, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { FormattedMessage } from 'react-intl';

import { ColumnManager } from '@folio/stripes/smart-components';

import { EVENT_EMMITER_EVENTS } from '../constants';4

Check failure on line 7 in lib/FindLocation/FindLocation.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

Expected 1 empty line after import statement not followed by another import

Check failure on line 7 in lib/FindLocation/FindLocation.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

Missing whitespace after semicolon

Check failure on line 7 in lib/FindLocation/FindLocation.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

Expected an assignment or function call and instead saw an expression

Check failure on line 7 in lib/FindLocation/FindLocation.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

Missing semicolon
import { FindRecords } from '../FindRecords';

Check failure on line 8 in lib/FindLocation/FindLocation.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

Import in body of module; reorder to top
import { useEventEmitter } from '../hooks';

Check failure on line 9 in lib/FindLocation/FindLocation.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

Import in body of module; reorder to top
import {

Check failure on line 10 in lib/FindLocation/FindLocation.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

Import in body of module; reorder to top
COLUMN_MAPPING,
COLUMN_NAMES,
Expand All @@ -25,8 +27,21 @@ export const FindLocation = (props) => {
...rest
} = props;

const eventEmitter = useEventEmitter();

const [selectedLocations, setSelectedLocations] = useState({});

useEffect(() => {
const eventType = EVENT_EMMITER_EVENTS.FIND_RECORDS_SELECTED_CHANGED;
const callback = ({ detail: assignment }) => setSelectedLocations(assignment);

eventEmitter.on(eventType, callback);

return () => {
eventEmitter.off(eventType, callback);
};
}, [eventEmitter]);

const columnMapping = useMemo(() => ({
...COLUMN_MAPPING,
...(
Expand Down Expand Up @@ -83,7 +98,6 @@ export const FindLocation = (props) => {
isMultiSelect={isMultiSelect}
isLoading={isLoading}
selectRecords={onRecordsSelect}
onSelectedChange={setSelectedLocations}
stickyFirstColumn={isMultiSelect}
renderActionMenu={() => renderColumnsMenu}
renderFilters={renderFilters}
Expand Down
20 changes: 6 additions & 14 deletions lib/FindRecords/FindRecordsModal.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React, { useEffect } from 'react';
import { pickBy, noop } from 'lodash';
import PropTypes from 'prop-types';
import { useEffect } from 'react';
import { FormattedMessage, useIntl } from 'react-intl';
import { pickBy, noop } from 'lodash';

import {
Button,
Checkbox,
Modal,
Paneset,
MultiColumnList,
Checkbox,
Paneset,
} from '@folio/stripes/components';

import {
Expand All @@ -23,10 +23,7 @@ import {
getFiltersCount,
PrevNextPagination,
} from '../AcqList';

import {
useRecordsSelect,
} from './hooks';
import { useRecordsSelect } from './hooks';

import css from './FindRecordsModal.css';

Expand Down Expand Up @@ -58,7 +55,6 @@ export const FindRecordsModal = ({
isLoading,
onNeedMoreData,
onSaveMultiple,
onSelectedChange,
onSelectRow,
records,
refreshRecords,
Expand Down Expand Up @@ -102,10 +98,6 @@ export const FindRecordsModal = ({
});
}, [refreshRecords, filters, sortingField, sortingDirection]);

useEffect(() => {
onSelectedChange?.(selectedRecordsMap);
}, [onSelectedChange, selectedRecordsMap]);

const saveMultiple = () => {
const selectedRecords = Object.values(pickBy(selectedRecordsMap));

Expand Down Expand Up @@ -328,7 +320,7 @@ FindRecordsModal.propTypes = {
initialFilters: PropTypes.object,
// records props
isLoading: PropTypes.bool,
onNeedMoreData: PropTypes.func.isRequired,
onNeedMoreData: PropTypes.func,
onSaveMultiple: PropTypes.func,
onSelectRow: PropTypes.func,
records: PropTypes.arrayOf(PropTypes.object).isRequired,
Expand Down
26 changes: 22 additions & 4 deletions lib/FindRecords/hooks/useRecordsSelect/useRecordsSelect.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { useState, useCallback, useEffect } from 'react';
import { differenceBy, keyBy } from 'lodash';

import { EVENT_EMMITER_EVENTS } from '../../../constants';
import { useEventEmitter } from '../../../hooks';

const reduceCheckedRecords = (records, isChecked = false) => {
const recordsReducer = (accumulator, record) => {
if (isChecked) {
Expand All @@ -22,6 +25,7 @@ const selectedFromOtherPages = (selectedRecordsMap, pageRecords) => {
};

export const useRecordsSelect = ({ records }) => {
const eventEmitter = useEventEmitter();
const [allRecordsSelected, setAllRecordsSelected] = useState(false);
const [selectedRecordsMap, setSelectedRecordsMap] = useState({});
const selectedRecordsLength = Object.keys(selectedRecordsMap).length;
Expand All @@ -31,10 +35,19 @@ export const useRecordsSelect = ({ records }) => {
}, [records, selectedRecordsMap]);

const toggleSelectAll = useCallback(() => {
setSelectedRecordsMap(() => ({
...selectedFromOtherPages(selectedRecordsMap, records),
...reduceCheckedRecords(records, !allRecordsSelected),
}));
setSelectedRecordsMap(() => {
const newMap = {
...selectedFromOtherPages(selectedRecordsMap, records),
...reduceCheckedRecords(records, !allRecordsSelected),
}

eventEmitter.emit(
EVENT_EMMITER_EVENTS.FIND_RECORDS_SELECTED_CHANGED,
newMap,
);

return newMap;
});
}, [allRecordsSelected, records, selectedRecordsMap]);

Check warning on line 51 in lib/FindRecords/hooks/useRecordsSelect/useRecordsSelect.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

React Hook useCallback has a missing dependency: 'eventEmitter'. Either include it or remove the dependency array

const selectRecord = useCallback(record => {
Expand All @@ -50,6 +63,11 @@ export const useRecordsSelect = ({ records }) => {
newMap[id] = record;
}

eventEmitter.emit(
EVENT_EMMITER_EVENTS.FIND_RECORDS_SELECTED_CHANGED,
newMap,
);

return newMap;
});
}, []);

Check warning on line 73 in lib/FindRecords/hooks/useRecordsSelect/useRecordsSelect.js

View workflow job for this annotation

GitHub Actions / github-actions-ci

React Hook useCallback has a missing dependency: 'eventEmitter'. Either include it or remove the dependency array
Expand Down
3 changes: 3 additions & 0 deletions lib/constants/events.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const EVENT_EMMITER_EVENTS = {
FIND_RECORDS_SELECTED_CHANGED: 'FIND_RECORDS_SELECTED_CHANGED',
};
1 change: 1 addition & 0 deletions lib/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export * from './acqCommands';
export * from './api';
export * from './constants';
export * from './countries';
export * from './events';
export * from './invoice';
export * from './item';
export * from './languages';
Expand Down
1 change: 1 addition & 0 deletions lib/hooks/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
export * from './useAccordionToggle';
export * from './useAcqMethodsOptions';
export * from './useAsyncDebounce';
export * from './useEventEmitter';
export * from './useFunds';
export * from './useIntegrationConfigs';
export * from './useLineHoldings';
Expand Down
1 change: 1 addition & 0 deletions lib/hooks/useEventEmitter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { useEventEmitter } from './useEventEmitter';
5 changes: 5 additions & 0 deletions lib/hooks/useEventEmitter/useEventEmitter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { EventEmitter } from '../../utils';

export const useEventEmitter = ({ singleton = true } = {}) => {
return new EventEmitter({ singleton });
};
12 changes: 12 additions & 0 deletions lib/hooks/useEventEmitter/useEventEmitter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { renderHook } from '@folio/jest-config-stripes/testing-library/react';

import { EventEmitter } from '../../utils';
import { useEventEmitter } from './useEventEmitter';

describe('useEventEmitter', () => {
it('should return event emitter instance', async () => {
const { result } = renderHook(() => useEventEmitter());

expect(result.current).toBeInstanceOf(EventEmitter);
});
});
30 changes: 30 additions & 0 deletions lib/utils/EventEmitter/EventEmitter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
export class EventEmitter {
constructor(config = {}) {
if (config.singleton && EventEmitter.sharedEventTarget) {
// If `singleton` is set and `sharedEventTarget` already exists, use it for all instances
this.eventTarget = EventEmitter.sharedEventTarget;
} else {
// Otherwise, create a new `EventTarget`
this.eventTarget = new EventTarget();

// If `singleton` is set, save the created `EventTarget` in `sharedEventTarget`
if (config.singleton) {
EventEmitter.sharedEventTarget = this.eventTarget;
}
}
}

on(eventName, callback) {
this.eventTarget.addEventListener(eventName, callback);
}

off(eventName, callback) {
this.eventTarget.removeEventListener(eventName, callback);
}

emit(eventName, data) {
const event = new CustomEvent(eventName, { detail: data });

this.eventTarget.dispatchEvent(event);
}
}
52 changes: 52 additions & 0 deletions lib/utils/EventEmitter/EventEmitter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { EventEmitter } from './EventEmitter';

const EVENT_TYPE = 'test-event-type';
const callback = jest.fn();
const payload = 'Test payload';

describe('EventEmitter', () => {
let emitter;

beforeEach(() => {
emitter = new EventEmitter();
callback.mockClear();
});

it('should add and invoke event listeners', () => {
emitter.on(EVENT_TYPE, callback);
emitter.emit(EVENT_TYPE, payload);

expect(callback).toHaveBeenCalledWith(expect.objectContaining({ detail: payload }));
});

it('should remove event listeners', () => {
emitter.on(EVENT_TYPE, callback);
emitter.off(EVENT_TYPE, callback);
emitter.emit(EVENT_TYPE, payload);

expect(callback).not.toHaveBeenCalled();
});

it('should emit events with the correct data', () => {
emitter.on(EVENT_TYPE, callback);
emitter.emit(EVENT_TYPE, payload);

expect(callback).toHaveBeenCalledWith(expect.objectContaining({ detail: payload }));
});

describe('with configuration', () => {
it('should create a shared EventTarget for instances with singleton set to true', () => {
const emitter1 = new EventEmitter({ singleton: true });
const emitter2 = new EventEmitter({ singleton: true });

expect(emitter1.eventTarget).toBe(emitter2.eventTarget);
});

it('should create separate EventTargets for instances with singleton set to false', () => {
const emitter3 = new EventEmitter();
const emitter4 = new EventEmitter();

expect(emitter3.eventTarget).not.toBe(emitter4.eventTarget);
});
});
});
1 change: 1 addition & 0 deletions lib/utils/EventEmitter/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { EventEmitter } from './EventEmitter';
1 change: 1 addition & 0 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export * from './batchFetch';
export * from './calculateFundAmount';
export * from './createClearFilterHandler';
export * from './downloadBase64';
export * from './EventEmitter';
export * from './fetchAllRecords';
export * from './fetchExportDataByIds';
export * from './filterArrayValues';
Expand Down

0 comments on commit b260d13

Please sign in to comment.