Can I dispatch an action in reducer? - reactjs

is it possible to dispatch an action in a reducer itself? I have a progressbar and an audio element. The goal is to update the progressbar when the time gets updated in the audio element. But I don't know where to place the ontimeupdate eventhandler, or how to dispatch an action in the callback of ontimeupdate, to update the progressbar. Here is my code:
//reducer
const initialState = {
audioElement: new AudioElement('test.mp3'),
progress: 0.0
}
initialState.audioElement.audio.ontimeupdate = () => {
console.log('progress', initialState.audioElement.currentTime/initialState.audioElement.duration);
//how to dispatch 'SET_PROGRESS_VALUE' now?
};
const audio = (state=initialState, action) => {
switch(action.type){
case 'SET_PROGRESS_VALUE':
return Object.assign({}, state, {progress: action.progress});
default: return state;
}
}
export default audio;

Starting another dispatch before your reducer is finished is an anti-pattern, because the state you received at the beginning of your reducer will not be the current application state anymore when your reducer finishes. But scheduling another dispatch from within a reducer is NOT an anti-pattern. In fact, that is what the Elm language does, and as you know Redux is an attempt to bring the Elm architecture to JavaScript.
Here is a middleware that will add the property asyncDispatch to all of your actions. When your reducer has finished and returned the new application state, asyncDispatch will trigger store.dispatch with whatever action you give to it.
// This middleware will just add the property "async dispatch" to all actions
const asyncDispatchMiddleware = store => next => action => {
let syncActivityFinished = false;
let actionQueue = [];
function flushQueue() {
actionQueue.forEach(a => store.dispatch(a)); // flush queue
actionQueue = [];
}
function asyncDispatch(asyncAction) {
actionQueue = actionQueue.concat([asyncAction]);
if (syncActivityFinished) {
flushQueue();
}
}
const actionWithAsyncDispatch =
Object.assign({}, action, { asyncDispatch });
const res = next(actionWithAsyncDispatch);
syncActivityFinished = true;
flushQueue();
return res;
};
Now your reducer can do this:
function reducer(state, action) {
switch (action.type) {
case "fetch-start":
fetch('wwww.example.com')
.then(r => r.json())
.then(r => action.asyncDispatch({ type: "fetch-response", value: r }))
return state;
case "fetch-response":
return Object.assign({}, state, { whatever: action.value });;
}
}

Dispatching an action within a reducer is an anti-pattern. Your reducer should be without side effects, simply digesting the action payload and returning a new state object. Adding listeners and dispatching actions within the reducer can lead to chained actions and other side effects.
Sounds like your initialized AudioElement class and the event listener belong within a component rather than in state. Within the event listener you can dispatch an action, which will update progress in state.
You can either initialize the AudioElement class object in a new React component or just convert that class to a React component.
class MyAudioPlayer extends React.Component {
constructor(props) {
super(props);
this.player = new AudioElement('test.mp3');
this.player.audio.ontimeupdate = this.updateProgress;
}
updateProgress () {
// Dispatch action to reducer with updated progress.
// You might want to actually send the current time and do the
// calculation from within the reducer.
this.props.updateProgressAction();
}
render () {
// Render the audio player controls, progress bar, whatever else
return <p>Progress: {this.props.progress}</p>;
}
}
class MyContainer extends React.Component {
render() {
return <MyAudioPlayer updateProgress={this.props.updateProgress} />
}
}
function mapStateToProps (state) { return {}; }
return connect(mapStateToProps, {
updateProgressAction
})(MyContainer);
Note that the updateProgressAction is automatically wrapped with dispatch so you don't need to call dispatch directly.

You might try using a library like redux-saga. It allows for a very clean way to sequence async functions, fire off actions, use delays and more. It is very powerful!

redux-loop takes a cue from Elm and provides this pattern.

Since anything is technically possible, you can do it. But you SHOULD NOT do it.
Here is a quote from Dan Abramov (the creator of Redux):
"Why would you want to dispatch inside a reducer? It's grossly
misusing the library. It's exactly the same as React doesn't allow you
to setState inside render."
From "Forbid dispatch from inside a reducer" Github ticket that he himself created

Dispatching and action inside of reducer seems occurs bug.
I made a simple counter example using useReducer which "INCREASE" is dispatched then "SUB" also does.
In the example I expected "INCREASE" is dispatched then also "SUB" does and, set cnt to -1 and then
continue "INCREASE" action to set cnt to 0, but it was -1 ("INCREASE" was ignored)
See this:
https://codesandbox.io/s/simple-react-context-example-forked-p7po7?file=/src/index.js:144-154
let listener = () => {
console.log("test");
};
const middleware = (action) => {
console.log(action);
if (action.type === "INCREASE") {
listener();
}
};
const counterReducer = (state, action) => {
middleware(action);
switch (action.type) {
case "INCREASE":
return {
...state,
cnt: state.cnt + action.payload
};
case "SUB":
return {
...state,
cnt: state.cnt - action.payload
};
default:
return state;
}
};
const Test = () => {
const { cnt, increase, substract } = useContext(CounterContext);
useEffect(() => {
listener = substract;
});
return (
<button
onClick={() => {
increase();
}}
>
{cnt}
</button>
);
};
{type: "INCREASE", payload: 1}
{type: "SUB", payload: 1}
// expected: cnt: 0
// cnt = -1

Related

useEffect infinite loop even though I've provided a dependency list

I'm trying to create a query page using redux, axios and an API. In useEffect(), if the variable from the useSelector is empty and the isLoading state is false, then I do the API request. However even without any changes to these variables (already in the dependency list), the useEffect keeps firing the API request indefinitely).
And while inspecting the state, I see integrationStatusLoading changing from true to false all the time. It seems that the useEffect is fired several times even before having completed the previous run.
My reducer:
import * as spreadsheetActions from './spreadsheetActions';
export const INITIAL_STATE = {
messagesLog: [],
loading: false,
modalIsOpen: false,
integration: {
rows: [],
loading: false
}
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case spreadsheetActions.SET_INTEGRATION_STATUS:
return { ...state, integration: {
...state.integration,
rows: action.payload
}};
case spreadsheetActions.SET_LOADING_INTEGRATION_STATUS:
return { ...state, integration: {
...state.integration,
loading: action.payload
}};
default:
return state;
}
};
The jsx:
const IntegrationStatusContainer = function() {
const classes = useStyles();
const dispatch = useDispatch();
const integrationStatusData = useSelector(state => state.spreadsheet.integration.rows);
const integrationStatusLoading = useSelector(state => state.spreadsheet.integration.loading);
useEffect(() => {
if (isEmpty(integrationStatusData) && !integrationStatusLoading) {
dispatch(spreadsheetOperations.getIntegrationStatus());
}
}, [dispatch, integrationStatusData, integrationStatusLoading]);
return (
<IntegrationStatusTable items={integrationStatusData} isLoading={integrationStatusLoading} />
);
};
export default IntegrationStatusContainer;
spreadsheetOperations.js
export const getIntegrationStatus = () => async dispatch => {
dispatch(spreadsheetActions.setLoadingIntegrationStatus(true));
let response = await spreadsheetManager.getIntegrationStatus();
batch(() => {
dispatch(spreadsheetActions.setLoadingIntegrationStatus(false));
dispatch(spreadsheetActions.setIntegrationStatus(response));
});
};
spreadsheetManager.js
getIntegrationStatus = async () => {
const getIntegrationStatusResult = await spreadsheetService.getIntegrationStatus();
return getIntegrationStatusResult;
};
spreadsheetService.js
getIntegrationStatus = async () => {
const integrationStatusResponse = await axios.get(
`${BASE_URL}/api/integration_status`
);
return integrationStatusResponse.data;
};
}
spreadsheetActions.js
export const setIntegrationStatus = rows => ({
type: SET_INTEGRATION_STATUS,
payload: rows
});
export const setLoadingIntegrationStatus = status => ({
type: SET_LOADING_INTEGRATION_STATUS,
payload: status
});
What is wrong with my code? How can I stop the infinite loop?
Problem solved by creating a new fetchStatus state with three possible values (pending, fulfilled and rejected), then in my if I've added a condition checking for fetchStatus === 'pending'.
I see you are replying on isEmpty to check whether API should be called. Can you check if isEmpty is working as expected or the API is always returning empty response?
What is the expected functionality of this component? That the API call only fires once?
Your store updates to the isLoading flag will cause a render every time it changes. At the moment your cycle looks like it would be:
Initial render - fire API call
Render from flag changing to true
Render from flag changing to false - fire API call
Repeat step 2 and 3 indefinitely
Have you console logged the variables inside the effect as well as the redux state? If the Redux state is changing then it will fire a render on the component.
I think the simplest solution is to remove the isLoading flag from the dependancies, unless you have a retry scenario you're trying to achieve but I'd be looking at redux-saga for a solution to that.

Property doesn't update after firing redux dispatch

After hours I have to ask for help, my useEffect doesn't always work on reduxs dispatching action.
const ScreenA = { currentItem, updatedItem } => {
useEffect(() => {
if (currentItem.id === updatedItem.id) { // Do stuff }
}, [updatedItem]) // XX Is not changing
... // do stuf
const mapStateToProps = (state) => {
console.log("AAABB ScreenA.mapStateToProps updatedItem: ", state.updatedItem) // XX I receive new updated id!! but it doesn't change the prop.updatedItem for ScreenA and useEffect is not calling.
return {
updatedItem: state.updatedItem,
}
}
export default connect(mapStateToProps)(FeedScreen)
}
My Reducer
Update
const initialState = {
updatedItem: undefined,
}
export default (state = initialState, action) => {
switch (action.type) {
case ITEM_CHANGED:
console.log("AAABB reducer.addChangedItem.ITEM_CHANGED: " + action.t) // Is printing after every dispatch call
return {
...state,
updatedItem: action.updatedItem,
}
// I tried also with
return {
updatedItem: action.updatedItem,
}
default:
return state
}
}
Strange was that it works if I dispatch change action from ScreenB to ScreenA but not between ScreenC to ScreenA. (It was the same way how I dispatch the action.
Spread out your updated item as well
Since your updated item is an object perhaps that causes a mutation. Seems unlikely but this are the issues you face when not using flat data. also generally you have a type and a payload (not updated item) on your action in redux.
return {
...state,
updatedItem: {...action.updatedItem},
}
If this doesn't work then probably something in your useEffect is broken, you need to log inside there and show results.

React / Redux / Meteor: method that dispatches an action in the callback, is called twice

I have a React / Redux / Meteor app in which I dispatch an action, that calls a method to get a value from the server, and the method has a callback in which I dispatch an action to save the returned value in the Redux store.
I'm also using Redux thunk.
Although my original action is only dispatched once, it runs twice. It seems that dispatching an action from inside a method callback, is causing the original action to be dispatched again.
In my React component:
class MyComponent extends Component {
....
render() {
...
}
}
function mapStateToProps(state, ownProps) {
return { value: state.myPartialState.value }
}
const Tracker = withTracker(({dispatch}) => {
const state = store.getState();
const isLoading = getIsLoading(state);
...
const handle = Meteor.subscribe('myData'), {
onReady: () => {
'onReady': () => {
secondaryPatternSubscriptions(patterns);
},
});
if (isLoading && handle.ready()) {
console.log('about to dispatch original action');
dispatch(getValue());
dispatch(setIsLoading(false));
} else if (!isLoading && !handle.ready()) {
dispatch(setIsLoading(true));
}
return { ... }
)(MyComponent);
export default connect(mapStateToProps)(Tracker);
In my actions file:
export const SET_VALUE = 'SET_VALUE';
export function setValue(value) {
return {
'type': 'SET_VALUE',
'payload': value,
};
}
export const getValue = () => (dispatch, getState) => {
console.log('about to call');
Meteor.call('getValue', (error, result) => {
console.log('about to dispatch second action');
dispatch(setValue(result)); // this causes the action to be dispatched again
});
// dispatch(setValue(10)); // this only runs once
};
const initialState = {
value: 0,
}
export default function myPartialState(state = initialState, action) {
switch (action.type) {
case SET_VALUE: {
return updeep({ 'value': action.payload }, state);
}
}
}
On the server, the method is like this:
Meteor.methods({
'getValue': function () {
...
return value;
},
})
I can see from the console logs that getValue is only dispatched once, but runs twice. I have checked this again and again, and I'm pretty near 100% sure that getValue is not dispatched twice.
I think it's something to do with calling an action from inside the method callback; if I comment out dispatch(setValue(result)); and replace it with a dispatch outside the method call, then getValue only runs once.
If I dispatch a different action instead of setValue, or change the setValue action so that it doesn't alter the 'value' property in the store, then again getValue only runs once. But I can't see why changing 'value' would cause the action to be run twice, when it is only dispatched once...
I've searched online and haven't found anything about this issue.
Can anybody think why my action is running twice, and a way to have it run only once? Thanks!

Save data in localstorage after call proper action. React & Redux

I'm going to save in localstorage some data, but only after call UPDATE_POST action. Now i'm apply localstorage in index.js via:
store.subscribe(throttle(() => {
post: saveState(store.getState().post);
}, 1000))
and it save data in localstorage for every second. But my goal is to save it only after updatePost action. Can I achieve it using middleware, and how to write it?
My reducer:
const Post = (state = {}, action) => {
switch (action.type) {
case 'INIT_POST':
..... some code
case 'UPDATE_POST':
... some code
default:
return state
}
};
My action:
export const updatePost = (...items) => ({
type: 'UPDATE_POST',
items
});
I use Redux-thunk for this (https://github.com/gaearon/redux-thunk) - it lets you write action creators that return a function instead of an action - allowing you to perform async tasks in the action, then hit the reducer.
With redux-thunk you can call an async function (performSomeAsyncFunction() in example below), get the response, deal with it (such as the saveDataToLocalStorage() dummy function below), then hit the reducer to update your state:
export const startUpdatePost = (...items) => {
return (dispatch) => {
return performSomeAsyncFunction(...items).then((response) => {
dispatch(updatePost(...items));
saveDataToLocalStorage()
});
};
};
Don't forget to also handle the failure of the async function above

React Redux infinite loop

I have the intention to perform a fetch to my rest api when ever the user updates an input bar. The problem I am having is that the componentDidUpdate method calls the action creator which dispatches the json to the reducer, in turn updating the state and calling componentDidUpdate again. Any ideas or best practices to end the endless cycle? Thanks!
Action Creator:
export const fetchFoods = (dispatch, searchText) => {
return fetch(`/nutrition/${searchText}`)
.then(res => res.json())
.then(json => dispatch({type: 'RECEIVE_FOODS', json}))
}
Reducer:
const foods = (state = [], action) => {
switch (action.type) {
case 'RECEIVE_FOODS':
return action.json
default:
return state
}
}
React Container:
const FoodList = React.createClass({
componentDidUpdate: function() {
fetchFoods(this.props.dispatch, this.props.searchText)
},
componentWillMount: function() {
fetchFoods(this.props.dispatch, this.props.searchText)
},
render: function() {
...
}
})
export default connect(
(state) => {
return {searchText: state.searchText, foods: state.foods}
}
)(FoodList)
You should remove the fetchFoods function from the componentDidUpdate. Calling the function on an update will result in an infinite loop like you describe.
Once the component has mounted the first time and retrieved the original set of data, you should only call the action when it's explicitly required. If the user changes it, another function changes it, etc.
Attach an onChange() handler to your input (or whatever you want to trigger an update), so that whenever input changes, it calls a function that explicitly dispatches that action.
https://jsfiddle.net/julianljk/69z2wepo/49105/
Function:
handleChange: function (e) {
var myInput = e.target.value;
this.setState({
value: myInput
});
fetchFoods(this.props.dispatch, myInput) //sends it to the store,
},
Render:
render: function (){
return(
<div>
<input type="text" value={this.state.value} onChange={this.handleChange}/>
</div>
)}
Putting action dispatches in lifecycle methods can generally cause this sort of behavior.

Resources