Redux architecture for a non-ui application - javascript

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.

Related

React Redux - quick flash of previous state before dispatch issue

I'm building a React + Redux app with a Node.js backend, and one of the features is that a user can view profiles of other users. To do this, I have a section in the Redux state called users that looks like:
{
...,
users: {
user: {},
isLoading: true
}
}
Every time the /users/:id route is rendered, a getUser(id) action is dispatched and fills the state with the received information.
The main issue is when a user views user1's profile page (therefore redux state is now filled with user1's profile) and then views user2's profile page, the dispatch getUser(2) action is called right after the page is rendered. Since user1's info is still in the state, it will flash their info for a very short time, and then show the loading spinner until the new user is loaded.
I read about dispatching a resetUser(id) action on every unmount of the page, but I'm not sure if this is the right way to go. Also, one of the features is if a user is viewing their own page, they have an edit button which redirects them to /edit-profile. If I reset the state on every unmount, I'll have to fetch their profile again (in the edit page), even though I just did that when they viewed their page.. And that doesn't seem like it makes sense.
Any ideas how to solve this? Thanks!
The render phase runs after mounting. And you stated that previous data is being shown before new data. It seems that you have asynchronous mounting:
async componentDidMount() {}
It will continue rendering even before mounting phase is completed. To avoid issue, you may use synchronous nature of mounting:
componentDidMount(){}
Where you'll call api data.
Now, when you reach to rendering phase it will have new data available before rendering and won't flash you old data.
You now may be wondering how to call api asynchronously. You can create a asynchronous function and call that function inside the synchronous componentDidMount.
componentDidMount() {
yourAsyncFunc()
}
async yourAsyncFunc() {} // api call
How can I do this with React hooks?
While using useEffect, don't implement async:
useEffect(async () =>
Implement it simply:
useEffect(() => {
// you can still use asynchronous function here
async function callApi() {}
callApi()
}, []) // [] to run in similar to componentDidMount
If you miss second parameter to useEffect then you are not ensuring it to run on mounting phase. It will run before, after, and in the rendering phase depending on case.
Implementing something like resetUser(id) seems to be the right way here. I use this approach in my current project and this does solve the problem. The other approach of removing async keyword from useEffect callback as mentioned in another answer didn't work for me (I use hooks, redux-toolkit, Typescript).
After dispatching this action, your state should look something like
{
...,
users: {
user: null,
isLoading: false,
}
}
If you are using hooks, you can dispatch the action this way:
useEffect(() => {
const ac = new AbortController();
...
return () => {
dispatch(resetUser(null));
ac.abort();
};
}, []);
Action could look something like this:
resetListingDetails(state, action) {
// Immer-like mutable update
state.users = {
...state.users,
user: null,
};
}

Next steps in a react/redux sound dispatch app

I'm building a react/redux drum machine app for a freeCodeCamp challenge, and I've got the buttons done and the connections made for onClick to dispatch an action with the url link to the sound as a property, but I'm hung up on what I need to create as a reducer for this to work how I'm wanting.
You can view what I've got so far here: https://codesandbox.io/s/k29r3928z7 , and you can ignore the pieces for the Hello/Goodbye switch at the top as that was just for me to understand the flow of React/Redux to start. My main question is what should I be doing with state in this case? Pressing the button won't really change the state at all, so I'm not sure what I should be doing at the 'new state' step of creating a reducer.
I am not familiar with the specific goals of the freeCodeCamp challenge, however if you're wanting the app to play the correct sound when the user clicks any one of the buttons shown, you can achieve this by updating your drumClickHit action as follows:
export const drumClickHit = url => {
//Create an audio object from the url provided to the action
const sound = new Audio(url);
//Play the audio object immediatly
sound.play();
return {
type: "CLICKHIT",
url
};
};
To illustrate the relationship between action creators and reducers, you could achieve the same result as above, by adding the following "CLICKHIT" case to your reducer as follows:
case "CLICKHIT": {
//Create an audio object from the url provided to the action.
//Get the url from the action
const sound = new Audio(action.url);
//Play the audio object immediatly
sound.play();
return state;
}
In the case of your app however, logic for audio playback is better placed in the action, rather than reducer.

Callbacks using redux-thunk / redux-observable with redux

I am learning how redux works but its a lot of code to do simple things. For example, I want to load some data from the server before displaying. For editing reasons, I can't simply just use incoming props but I have to copy props data into the local state.
As far as I've learned, I have to send a Fetch_request action. If successful, a fetch_success action will update the store with new item. Then updated item will cause my component's render function to update.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id);
}
...
In actions
export function FETCH_REQUEST(id) {
api.get(...)
.then(d => FETCH_SUCCESS(d))
.catch(e => FETCH_FAILURE(e));
}
...
In reducer
export function FETCH_REDUCER(state = {}, action ={}) {
switch (action.type) {
case 'FETCH_SUCCESS':
return { ...state, [action.payload.id]: ...action.payload }
...
}
Back in component
this.props.FETCH_REDUCER
// extra code for state, getting desired item from...
Instead, can I call a react-thunk function and pass some callback functions? The react-thunk can update the store and callbacks can change the component's local state.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id, this.cbSuccess, this.cbFailure);
}
cbSuccess(data) {
// do something
}
cbFailure(error) {
// do something
}
...
In action
export function FETCH_REQUEST(id, cbSuccess, cbFailure) {
api.get(...)
.then(d => {
cbSuccess(d);
FETCH_SUCCESS(d);
}).catch(e => {
cbFailure(d);
FETCH_FAILURE(e);
});
}
...
Is this improper? Can I do the same thing with redux-observable?
UPDATE 1
I moved nearly everything to the redux store, even for edits (ie replaced this.setState with this.props.setState). It eases state management. However, every time any input's onChange fires, a new state is popping up. Can someone confirm whether this is okay? I'm worried about the app's memory management due to redux saving a ref to each state.
First of all, you should call your API in componentDidMount instead of componentWillMount. More on this at : what is right way to do API call in react js?
When you use a redux store, your components subscribe to state changes using the mapStateToProps function and they change state using the actions added a props through the mapDispatchToProps function (assuming you are using these functions in your connect call).
So you already are subscribing to state changes using your props. Using a callback would be similar to having the callback tell you of a change which your component already knows about because of a change in its props. And the change in props would trigger a re-render of the component to show the new state.
UPDATE:
The case you refer to, of an input field firing an onChange event at the change of every character, can cause a lot of updates to the store. As mentioned in my comments, you can use an api like _.debounce to throttle the updates to the store to reduce the number of state changes in such cases. More on handling this at Perform debounce in React.js.
The issue of memory management is a real issue in real world applications when using Redux. The way to reduce the effect of repeated updates to the state is to
Normalize the shape of state : http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Create memoized selectors using Reselect (https://github.com/reactjs/reselect)
Follow the advice provided in the articles regarding performance in Redux github pages (https://github.com/reactjs/redux/blob/master/docs/faq/Performance.md)
Also remember that although the whole state should be copied to prevent mutating, only the slice of state that changes needs to be updated. For example, if your state holds 10 objects and only one of them changes, you need to update the reference of the new object in the state, but the remaining 9 unchanged objects still point to the old references and the total number of objects in your memory is 11 and not 20 (excluding the encompassing state object.)

Using Redux to Manage a Video Player

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;
}
}

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

Categories

Resources