- Yarn - package manager used for installing new libraries, and running other commands. Some helpful commands:
- React - Handles all DOM updates/manipulation. Replaced old Backbone and jQuery code.
- Redux - Handles application state management.
- redux-actions - added to make reducers more readable (mapping with an object instead of switch statements)
- Storybook - Provides component development environment outside of application
- Jest - Front end testing engine
- Enzyme - Airbnb's testing utilities
- redux-saga - Used for handling asynchronous side-effects caused by dispatching a redux action.
- styled-components - Used for styling, completely replacing the use of
.css
files.
All folders within app/javascript/jingleplayerReact
are structured in concurrence with the application state. Each folder represents a portion of the application, and contains all react components, sagas, reducers, and tests related to that section. So instead of being structured by file type, the player folders are structured by distinct application features/areas.
Every folder, with the exception of /util
, /dataModels
and /components
follows the same structure. This structure repeats itself indefinitely as features become nested. The basic pattern followed is that no folder may touch another that is not directly above it. If something is needed by multiple folders (such as a component, helper, or action) then it should be moved up until it is directly accessible by all files that need it by traversing vertically in one direction only.
Every folder contains the following:
reducer.js
- Updates the portion of the application state that the section is responsible for. If the reducer is a composition of multiple, more granular reducers, then it will instead be represented by a/reducer
folder containing all the nested reducers and the composed reducer in anindex.js
file. All reducer files are eventually composed into a single reducer found in thereducer.js
file found at the root level ofapp/javascript/jingleplayerReact
.actions.js
(when applicable) - These are the action creators used for dispatching actions to update the redux store. Action types only used by folder will be defined here. Action types needed by more than one folder are added to the array inapp/javascript/jingleplayerReact/util/createActionsAndTypes
. This makes them available to all action creators./components
(when applicable) - Contains all components used within the section and and folders nested within it.saga.js
(where applicable) - Each folder contains it's own saga that handles all asynchronous side-effects within the application section. This usually is used when a component triggers a request to load records on mount that then need to be added to the store (such as a cue search). If the saga is a composition of multiple nested sagas, then it will instead be represented by a/saga
folder composed inindex.js
. All saga files are eventually composed into a single root saga;saga.js
found at the root level ofapp/javascript/jingleplayerReact
./__tests__
(where applicable) - contains tests related to the files in the same directory as this folder- Additional files/folders containing logic used by the section (such as
/util
)
A description of these folders is as follows, where the first 4 represent actual UI sections of the player itself:
/pageDisplay
- Main page in the player that displays search results, playlists, etc/sidebar
- This holds folders containing playlists, links, and search suggestions/audioPlayer
- The actual audio player at the bottom of the page/search
- The search field and filter drawer that opens when field is engaged/cues
- contains the cues loaded on the front end from search or playlists. Used by multiple sections of player (Sidebar and Page Display)./theme
- contains the customizable theme used by the organization which flows through the entire player within Styled Components./translations
- this contains all thei18n
translations for text displayed within the player. No text is hard coded. Any viewable text is stored as a variable (inapp/locales/player
.yml
files) and then accessed through this store key. It is essentially a giant Object that is keyed into./user
- current user information/modal
- contains the modal type and data to be loaded if present
For each of these folders, there is a corresponding top level key in the Redux store with contains it's state:
/jingleplayerReact
/search ------------------> store key
/components
/SearchFilters
/components
/index.js
/dataModels
/fixtures
/reducer
/actions.js
/reducer.js
/reducer.js
/theme --------------------> store key
/reducer.js
/translations -------------> store key
/actions.js
/reducer.js
/util
...helpers
/index.js
/JinglePlayer.js
/reducer.js
/store.js
This structure was inspired by this article. It allows each feature to be more modular since each section is self contained.
While this format adds more nesting, it should also allow feature work to traverse less folders, keeping work that relates to each feature all in once place. Also, the structure shouldn't get more complicated as the app grows, but rather just adding a store key will add to the file structure accordingly.
All JP state is managed using Redux. Actions are dispatched to update the state using composed reducers.
The redux store follows the same structure as the folders mentioned above.
Since the state is a composition of reducers, and the folder structure follows the structure of the store, each top level folder within section of the Jingle Player has its own reducer. Each of these reducers then flows up to the top level reducer at the root of /jingleplayerReact
All changes to application state must be done through dispatching an action. This is the fundamental concept behind the flux pattern.
The player uses a specific type of action creator instead of passing a vanilla JS object into the dispatch()
method.
So instead of:
dispatch({ type: 'SOME_ACTION', payload: 'stuff' });
we do:
dispatch(actions.SOME_ACTION('stuff'));
Or with no payload:
dispatch(actions.SOME_ACTION());
// dispatch({ type: 'SOME_ACTION' })
Or other keys besides payload:
dispatch(actions.SOME_ACTION('stuff', { foo: 'bar' }));
// dispatch({ type: 'SOME_ACTION', payload: 'stuff', foo: 'bar' })
dispatch(actions.SOME_ACTION(undefined, { foo: 'bar' }));
// dispatch({ type: 'SOME_ACTION'', foo: 'bar' })
These actions are created by adding the string to array arg passed into the invoked function in app/actions/index.js
which can be accessed with
import actions from 'path/to/actions';
Constants for created actions can be accessed by:
import { actionTypes } from 'path/to/actions';
actionTypes.DO_SOMETHING // 'DO_SOMETHING'
This seemingly strange syntax (function constants?) has some benefits:
- Errors using string literals are avoided (since misspelling action types on dispatch or in the reducer will fail silently).
- Encourages the use/standardization of the
payload
key - The action creating function 'looks like' a constant. This is a helpful visual cue to know you are looking at an action type.
Note: The redux dev-tools chrome extension is helpful for debugging anything related to redux.
Any side-effects caused by dispatching an action are handled with redux-saga
Storybook allows react components to be developed outside the application. This encourages decoupling components from the rest of the player and provides a nice UI for seeing how components react to changes in props. These various states of components are called "stories".
All stories related to a component live in a subfolder in the same directory as a component with the following convention:
component:
folder/to/components/SomeComponent.js
story:
folder/to/components/__stories__/SomeComponent.stories.js
The react app jingle player is mounted in the DOM when this file is initialized
Using the command yarn test
, tests are run using the Jest engine
and Enzyme testing utilities.
All tests related to a file live in a subfolder in the same directory as the file with the following convention:
file to be tested:
folder/to/components/SomeComponent.js
test:
folder/to/components/__tests__/SomeComponent.test.js
Styles are made conistent using Prettier. Styles are to be automatically adjusted in editor (CTRL + OPTION + F in Atom editor) according to the config options found here.
All React component files are capitalized to signify that the file is exporting React. Components also are nested in a /components
folder in whichever directory they belong.
Stylesheets are not used at all in the Jingle Player. Instead styled-components are used to keep styles linked to the UI.