Using Redux to Manage a Video Player - javascript

What would be the ideal way to manage a video player using Redux, primarily in terms of dispatching actions to play/pause a video?
I'm working on building a video player in a React application, and I have event listeners on the video player dispatching relevant events to update the state. What would be the best way to have the parent component act on other components dispatching a PLAY or PAUSE action?
One use case that I would want to account for, for example, is one video being played and all other videos making sure to pause their playback.
Two ways that I've thought of would be:
1) Have the parent component check for changes in componentWillReceiveProps, and checking for something like
if (this.props.paused && !nextProps.paused) {
this.refs.video.play()
}
2) Store a reference to the underlying video element in the state tree, and using middleware to act on certain actions (such as a PLAY action), such as
if (action.type === PLAY) {
let state = getState();
state.players[action.payload.playerId].play();
}
Would one strategy be more "correct" than the other, or is there another way that would make more sense?

1) is the way to go.
Your video is simply a view component. In React you always want the component's props to dictate the output.
The problem with 2) is that the video object doesn't belong in the state. You're telling Redux too much about the implementation detail. Redux doesn't care about the implementation detail; it's just a state container.
UPDATE
On further reflection, I recommend componentDidUpdate() is the best place to place this logic. i.e.
componentDidUpdate(prevProps)
if (prevProps.paused && !this.props.paused) {
this.refs.video.play();
}
}
The advantage being that componentDidUpdate() is called after re-rendering. It may not make a difference for your purposes, but it's possible that triggering a DOM event before React has had a chance to update the DOM may cause some unfortunate race conditions.
Of course this doesn't change the gist of my advice that output should always be props (or state) driven.

I don't think that you need (2), (1) is pretty good. Now you handle only play action, but you can add pause there, like this:
if (this.props.paused && !nextProps.paused) {
this.refs.video.play()
}
if (!this.props.paused && nextProps.paused) {
this.refs.video.pause()
}
In this case your html player state will be synchronized with redux player state.
One use case that I would want to account for, for example, is one video being played and all other videos making sure to pause their playback.
In this case you can handle this situation in your reducer. I'd store all players by ids with paused state, like:
{
1: {
paused: true,
},
2: {
paused: false,
},
...
}
You need to add player id in your action and pause other players, when you receive PLAY action:
function players(state = {}, action) {
switch (action.type) {
case PAUSE:
return {
...state,
[action.id]: {
paused: true
}
}
case PLAY:
const nowPlaying = Object.keys(state).find(id => !state[id].paused);
const newState = {
...state,
[action.id]: {
paused: false
}
};
if (nowPlaying) {
newState[nowPlaying].paused = true;
}
return newState;
}
}

Related

Electron.js + React/Redux - best practices

So, here the thing.
We have main process and renderer and they`re is isolated from each other and only bridge is IPC messages(to main process) or BrowserWindow webcontents send(to main process).
I'm handling my events in componentDidMount like so:
ipcRenderer.on('deviceWasPlugged', this.deviceWasPluggedListener);
And ofc I'll remove this listener with componentWillUnount for preventing memory leaking in case the component was destroyed:
ipcRenderer.removeListener('deviceWasPlugged', this.deviceWasPluggedListener);
Obviously I have a such method in my Class/Component
deviceWasPluggedListener = (e, somedata) => {
// boring stuff goes there...
}
All is peachy, except one fact - It doesn't seems right as for me.
I still think, there should be a better place to keep my events listeners.
I think, I cant keep it in redux middleware, coz event based model won't allow me to unsubscribe of this events in redux middleware and multiple events will be created when I'll be trying to dispatch smth, even if I'll switch it by action.type:
const someMiddleware = store => next => action => {
if (action.type === 'SOME_TYPE') {
// ...
}
next(action);
};
export default someMiddleware;
So, guys, please tell me is there any better place to keep my backend events? It should be triggered every time I want it, and it shouldn't cause memory leaking problem with maxEventListeners.

Redux architecture for a non-ui application

I am working on an html5 audio player and I am trying to figure out if Redux would be a good solution even without a UI for the state (data) to flow in.
I will have multiple modules:
Playback Module: Encapsulate all callbacks from the Audio Element and dispatch an action after each callback to update the state.
Streaming Module: All the logic to figure out which segment is need at a given time, which segment to prefetch, ...
MediaSource Module: The media source module is wrapper of all actions of the media source and the source buffer.
Question 1:
the Audio Element has it's own state, playing, paused, seeking, currentTime, ... and the Redux state reflects the state of the Audio Element.
Is that a good practice? I feel a bit concerned about having 2 states in different places and out of sync state...
Question 2:
Who is updated first?
Let's imagine I want to Pause my player:
//Implementation 1:
function pause() {
dispatch({type:'PLAYBACK_PAUSED'}).then(()=> {
this.audio.paused = true;
});
}
// Implementation 2:
function pause() {
dispatch({type:'PLAYBACK_PAUSED'});
this.audio.paused = true;
}
// Implementation 3:
function pause() {
dispatch({type:'PLAYBACK_PAUSED'});
onPause();
}
function onPause() {
const state = getState();
if (this.audio.paused != state.paused) {
this.audio.paused = state.paused;
}
}
Question 3:
Every 0.5 second, the Audio element triggers a callback with a new current time. When this callback is trigger, I dispatch an action to update the current time in the state.
Now, the Streaming Module needs to be aware of that change in order to figure out which segment is needed at this given time and needed to know if we need to prefetch future segments.
How is my Streaming Module supposed to be aware of that state change?
Like that?:
currentTime = 0;
...
const state = getState();
if (state.currentTime !== currentTime){
// Do Something...
// Do Something Else...
}
Thank you
Let me try to tackle your questions one by one:
Question 1
Assuming that you are using the HTML5 audio component: there is nothing wrong about a redux state augmenting/syncing with a state of another component such as your audio player. In all cases, though, I suggest that the redux state follows the audio components state (should there be a delay for example)
If, however, you built the audio component yourself, maybe you can wire it into the redux ecosystem by dispatching events from it directly.
Question 2
Like I said in the previous answer, I would always go with pausing the actual component first, THEN update the state once the component acknowledges the pause. Code wise, this would mean triggering a dispatch when the audio component has fully paused (on an onPause callback for example). The reason for following the audio element in terms of state rather than expecting it to follow our state is because the element might not pause synchronously/immediately which could bring our states out of sync.
Question 3
When you dispatch the event to update the redux state every 0.5s, you could dispatch another event to your streaming module. For this, I suggest using an asynchronous action creator like so:
import StreamingModule from 'your-streaming-module';
export function updateTime (timeInMs) {
return (dispatch) => {
dispatch({
type: YOUR_UPDATE_ACTION
time: timeInMs
});
// pseudo
StreamingModule.fetchSegment(timeInMs);
};
}
An alternative would be to subscribe to the store state directly. This all depends on how your streaming module is structured/instantiated:
import { createStore } from 'redux';
import StreamingModule from 'your-streaming-module';
const store = createStore(...);
store.subscribe(() => {
StreamingModule.fetchSegment(store.getState().time.ms);
});
The latter might not work well unless you save the current time somewhere before you fetch the segment.
Hope this helps.

Redux - Dispatching Actions Correctly

In my app, I had small action creator functions which returns an action for a small section of the app. My rational being that I wanted to be as 'precise & specific' with my logic, yet keeping the code reusable.
To give an example, I have React 'tab' components which are reused on everywhere. In one scenario, clicking the 'tab' would -1- Make the tab active, -2- do something else that is required. In someother scenario, clicking the 'tab' would -1- Make the tab active, -2- do something else, possibly different to the first scenario above.
The logic I thought of was that I would dispatch two action creators in each scenario above. The first is obviously the common one, the second one is unique to each scenario.
// rough code for illustrative purposes
activateTab : () => {
dispatch( actionCreator1 );
dispatch( actionCreator2 );
},
render: () => {
<Tab onclick = { activateTab }
}
PROBLEM(?):
I occured to me that each action creator being dispatched would call reducer functions & then make React run its 'differing' algorithm. I.e. In the above Tab, React is recalculating all the DOM changes twice? Is this correct? Can someone please confirm?
And how should these scenarios be handled?
Should I be making an action type (& thereby action object) unique to each scenario? Which would mean, there should be only one dispatch function.
Thanks,
Should I be making an action type (& thereby action object) unique to each scenario?
Presumably you are already doing this, if you are dispatching two different actions correct? I think you do not need the SET_TAB action, if you never use it on its own, the only thing you need is to listen to more actions in your reducer.
In any case, your assumption is correct, and it's totally normal to have one reducer listen to many different actions. Consider your active tab reducer to look something like:
let initial = {
fooActiveTab: 0, // first foo tab is open
barActiveTab: 2, // third bar tab is open
}
function activeTab (state, action) {
switch (action.type) {
case 'SOMETHING_RELEVANT_TO_FOO':
case 'FOO_AJAX_SUCCESS':
return {
...state,
fooActiveTab: action.payload // set current tab
}
default:
return state
}
}
This makes your reducer composition very clear, as the tabs state listens to a variety of things that may change what the active tab is. This way you do not need to dispatch two actions on every function call.. just one is enough as long as you send along the id of the tab you want to be active.
I think you should do the above, but not to prevent React from running the diff algo.. but to be more organized. React's diff / patch will run A LOT and your app will still be very fast. Any slowness you perceive is due to components actually updating because the DOM needs to change, and not because of reconciliation.

How to show a loading indicator in React Redux app while fetching the data? [closed]

Closed. This question needs to be more focused. It is not currently accepting answers.
Want to improve this question? Update the question so it focuses on one problem only by editing this post.
Closed 5 years ago.
Improve this question
I'm new to React/Redux. I use a fetch api middleware in Redux app to process the APIs. It's (redux-api-middleware). I think it's the good way to process async api actions. But I find some cases which can't be resolve by myself.
As the homepage (Lifecycle) say, a fetch API lifecycle begins with dispatching a CALL_API action ends with dispatching a FSA action.
So my first case is showing/hiding a preloader when fetching APIs. The middleware will dispatch a FSA action at the beginning and dispatch a FSA action at the end. Both the actions are received by reducers which should be only doing some normal data processing. No UI operations, no more operations. Maybe I should save the processing status in state then render them when store updating.
But how to do this? A react component flow over the whole page? what happen with store updating from other actions? I mean they are more like events than state!
Even a worse case, what should I do when I have to use the native confirm dialog or alert dialog in redux/react apps? Where should they be put, actions or reducers?
Best wishes! Wish for replying.
I mean they are more like events than state!
I would not say so. I think loading indicators are a great case of UI that is easily described as a function of state: in this case, of a boolean variable. While this answer is correct, I would like to provide some code to go along with it.
In the async example in Redux repo, reducer updates a field called isFetching:
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
})
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
The component uses connect() from React Redux to subscribe to the store’s state and returns isFetching as part of the mapStateToProps() return value so it is available in the connected component’s props:
function mapStateToProps(state) {
const { selectedReddit, postsByReddit } = state
const {
isFetching,
lastUpdated,
items: posts
} = postsByReddit[selectedReddit] || {
isFetching: true,
items: []
}
return {
selectedReddit,
posts,
isFetching,
lastUpdated
}
}
Finally, the component uses isFetching prop in the render() function to render a “Loading...” label (which could conceivably be a spinner instead):
{isEmpty
? (isFetching ? <h2>Loading...</h2> : <h2>Empty.</h2>)
: <div style={{ opacity: isFetching ? 0.5 : 1 }}>
<Posts posts={posts} />
</div>
}
Even a worse case, what should I do when I have to use the native confirm dialog or alert dialog in redux/react apps? Where should they be put, actions or reducers?
Any side effects (and showing a dialog is most certainly a side effect) do not belong in reducers. Think of reducers as passive “builders of state”. They don’t really “do” things.
If you wish to show an alert, either do this from a component before dispatching an action, or do this from an action creator. By the time an action is dispatched, it is too late to perform side effects in response to it.
For every rule, there is an exception. Sometimes your side effect logic is so complicated you actually want to couple them either to specific action types or to specific reducers. In this case check out advanced projects like Redux Saga and Redux Loop. Only do this when you are comfortable with vanilla Redux and have a real problem of scattered side effects you’d like to make more manageable.
Great answer Dan Abramov!
Just want to add that I was doing more or less exactly that in one of my apps (keeping isFetching as a boolean) and ended up having to make it an integer (which ends up reading as the number of outstanding requests) to support multiple simultaneous requests.
with boolean:
request 1 starts -> spinner on -> request 2 starts -> request 1 ends -> spinner off -> request 2 ends
with integer:
request 1 starts -> spinner on -> request 2 starts -> request 1 ends -> request 2 ends -> spinner off
case REQUEST_POSTS:
return Object.assign({}, state, {
isFetching: state.isFetching + 1,
didInvalidate: false
})
case RECEIVE_POSTS:
return Object.assign({}, state, {
isFetching: state.isFetching - 1,
didInvalidate: false,
items: action.posts,
lastUpdated: action.receivedAt
I'd like to add something. The real world example uses a field isFetching in the store to represent when a collection of items is being fetched. Any collection is generalized to a pagination reducer that can be connected to your components to track the state and show if a collection is loading.
It happened to me that I wanted to fetch details for an specific entity that doesn't fit in the pagination pattern. I wanted to have a state representing if the details are being fetched from the server but also I didn't want to have a reducer just for that.
To solve this I added another generic reducer called fetching. It works in a similar fashion to the pagination reducer and it's responsibility is just to watch a set of actions and generate new state with pairs [entity, isFetching]. That allows to connect the reducer to any component and to know if the app is currently fetching data not just for a collection but for an specific entity.
I didn't happen upon this question until now, but since no answer is accepted I'll throw in my hat. I wrote a tool for this very job: react-loader-factory. It's got slightly more going on than Abramov's solution, but is more modular and convenient, since I didn't want to have to think after I wrote it.
There are four big pieces:
Factory pattern: This allows you to quickly call the same function to set up which states mean "Loading" for your component, and which actions to dispatch. (This assumes that the component is responsible for starting the actions it waits on.) const loaderWrapper = loaderFactory(actionsList, monitoredStates);
Wrapper: The component the Factory produces is a "higher order component" (like what connect() returns in Redux), so that you can just bolt it onto your existing stuff. const LoadingChild = loaderWrapper(ChildComponent);
Action/Reducer interaction: The wrapper checks to see if a reducer it's plugged into contains keywords that tell it not to pass through to the component that needs data. The actions dispatched by the wrapper are expected to produce the associated keywords (the way redux-api-middleware dispatches ACTION_SUCCESS and ACTION_REQUEST, for example). (You could dispatch actions elsewhere and just monitor from the wrapper if you wanted, of course.)
Throbber: The component you want to appear while the data your component depends on isn't ready. I added a little div in there so you can test it out without having to rig it up.
The module itself is independent of redux-api-middleware, but that's what I use it with, so here's some sample code from the README:
A component with a Loader wrapping it:
import React from 'react';
import { myAsyncAction } from '../actions';
import loaderFactory from 'react-loader-factory';
import ChildComponent from './ChildComponent';
const actionsList = [myAsyncAction()];
const monitoredStates = ['ASYNC_REQUEST'];
const loaderWrapper = loaderFactory(actionsList, monitoredStates);
const LoadingChild = loaderWrapper(ChildComponent);
const containingComponent = props => {
// Do whatever you need to do with your usual containing component
const childProps = { someProps: 'props' };
return <LoadingChild { ...childProps } />;
}
A reducer for the Loader to monitor (although you can wire it differently if you want):
export function activeRequests(state = [], action) {
const newState = state.slice();
// regex that tests for an API action string ending with _REQUEST
const reqReg = new RegExp(/^[A-Z]+\_REQUEST$/g);
// regex that tests for a API action string ending with _SUCCESS
const sucReg = new RegExp(/^[A-Z]+\_SUCCESS$/g);
// if a _REQUEST comes in, add it to the activeRequests list
if (reqReg.test(action.type)) {
newState.push(action.type);
}
// if a _SUCCESS comes in, delete its corresponding _REQUEST
if (sucReg.test(action.type)) {
const reqType = action.type.split('_')[0].concat('_REQUEST');
const deleteInd = state.indexOf(reqType);
if (deleteInd !== -1) {
newState.splice(deleteInd, 1);
}
}
return newState;
}
I expect in the near future I'll add things like timeout and error to the module, but the pattern's not going to be very different.
The short answer to your question is:
Tie rendering to rendering code--use a wrapper around the component you need to render with the data like the one I showed above.
Add a reducer that makes the status of requests around the app you might care about easily digestible, so you don't have to think too hard about what is happening.
Events and state aren't really different.
The rest of your intuitions seem correct to me.
Am I the only one thinking that loading indicators don't belong in a Redux store? I mean, I don't think it's part of an application's state per se..
Now, I work with Angular2, and what I do is that I have a "Loading" service which exposes different loading indicators via RxJS BehaviourSubjects.. I guess the mechanism is the same, I just don't store the information in Redux.
Users of the LoadingService just subscribe to those events they want to listen to..
My Redux action creators call the LoadingService whenever things need to change. UX components subscribe to the exposed observables...
You can add change listeners to your stores, using either connect() from React Redux or the low-level store.subscribe() method. You should have the loading indicator in your store, which the store change handler can then check and update the component state. The component then renders the preloader if needed, based on the state.
alert and confirm shouldn't be a problem. They are blocking and alert doesn't even take any input from the user. With confirm, you can set state based on what the user has clicked if the user choice should affect component rendering. If not, you can store the choice as component member variable for later use.
We have three types of notifications in our app, all of which are designed as aspects:
Loading indicator (modal or non-modal based on prop)
Error Popup (modal)
Notification snackbar (non-modal, self closing)
All three of these are at the top level of our app (Main), and wired through Redux as shown in the below code snippet. These props control display of their corresponding aspects.
I designed a proxy that handles all our API calls, thus all isFetching and (api) errors are mediated with actionCreators I import in the proxy. (As an aside, I also use webpack to inject a mock of the backing service for dev so we can work without server dependencies.)
Any other place in the app that needs to provide any type of notification simply imports the appropriate action. Snackbar & Error have params for messages to be displayed.
#connect(
// map state to props
state => ({
isFetching :state.main.get('isFetching'), // ProgressIndicator
notification :state.main.get('notification'), // Snackbar
error :state.main.get('error') // ErrorPopup
}),
// mapDispatchToProps
(dispatch) => { return {
actions: bindActionCreators(actionCreators, dispatch)
}}
)
export default class Main extends React.Component{
I'm saving the urls such as::
isFetching: {
/api/posts/1: true,
api/posts/3: false,
api/search?q=322: true,
}
And then I have a memorised selector (via reselect).
const getIsFetching = createSelector(
state => state.isFetching,
items => items => Object.keys(items).filter(item => items[item] === true).length > 0 ? true : false
);
To make the url unique in case of POST, I pass some variable as query.
And where I want to show an indicator, I simply use the getFetchCount variable

Emitting events from Redux reducers

I'm using redux as a state container in a simple shooter game. State is completely deterministic, the only input the system receives is user input (eg. a weapon was fired, etc).
My problem is that I have to track (and process) certain events, that happen during the game (eg. something was destroyed, etc), and I'm not quite sure how to do that.
My current solution is that the reducer maintains an events array in the current state, and every reducer just appends events to it.
FIRE_WEAPON+-+ FIRE_WEAPON+-+
| |
| |
+-v--------+--------------v------------->
|
|
+->PLAYER_DESTROYED
Here the reduces receives two FIRE_WEAPON action, and should "emit" a PLAYER_DESTROYED event (right now, it is used to render an explosion there).
The project is open source, the reducer looks something like this (it's just pseudocode, but here is the relevant game logic):
// combine is just (f, g) => ((s, a) => g(f(s, a), a))
const reducer = combine(
// simulate world
(state, action) => {
while (state.time < action.time) {
state = evolve(state, delta); // evolve appends the happened in-game events to the state
}
return state;
},
// handle actual user input
(state, action) => {
return handleUserInput(state, action);
}
);
const evolve = (state, delta) => {
const events = [];
// some game logic that does some `events.push(...)`
return {
...state,
time: state.time + delta,
events: state.events.concat(events),
};
}
We can assume, that handleUserInput is a simple x => x identity function (it doesn't touch the events array).
During evolve, I'd like to "emit" events, but since that would make evolve impure, I cannot do that.
As I said, right now I'm doing this by storing the happened events in the state, but there might be a better way. Any suggestions?
These events are used during rendering, which looks likes this:
let sprites = [];
// `subscribe`d to store
function onStateChange() {
// `obsolete` removes old sprites that should not be displayed anymore
// `toSprite` converts events to sprites, you can assume that they are just simple objects
sprites = sprites.filter(obsolete).concat(store.getState().events.map(toSprite));
}
function render(state) {
renderState(state);
renderSprites(sprites);
}
But later on I'd like use events on the server (the reducer described above runs on the server too), for calculating various stats (eg. enemies destroyed, etc.).
Ps.: these "emitted" events have no influence on the state (they are totally unrelated), so I'm sure that they shouldn't be actions (because they would leave the state unchanged). They are processed after the reducer has finished, after that they can be dropped (the reducer always receives an empty events array).
I'm pretty sure that you can divide it in three parts:
-Actions:
const fireWeapon = ()=>({
type: FIRE_WEAPON
})
You can launch actions like fireWeapon okey, as you said reducers are pure functions so you can store in the state how much times you have launched that action.
-Reducer Fires
initialState: { fireWeapon: 0, fireShotgun:0}
CASE FIRE_WEAPON:
return {...state, fireWeapon: state.fireWeapon+1}
And finally, the key part, a libary called redux-observable, it's based on rxjs, reactive programming. You can suscribe to stream of actions and emit a new ones.
A really easy example is :
export const clearDeletedSiteEpic = (action$,state$) =>
action$.pipe(
ofType(FIRE_WEAPON),
map(() => {
if (state$.value.fires.fireWeapon % 2 === 0){
playerDestroyed() // action is launched
}
}
);

Categories

Resources