I am working on a project and still learning React. So currently, I have the following in my function. I was told that having numerous dispatches like so can cause problems, apart from looking messy and was suggested to create a single dispatch. How would I go about doing that?
dispatch({
type: 'UPDATE_ARRAY',
orderItemsArray: newitemsArray,
});
dispatch({
type: 'UPDATE_NUMBER',
tickNumber: tickNumber,
});
dispatch({
type: 'UPDATE_MESSAGE',
message: orderMessage,
})
Use redux-batched-actions.
const mapDispatchToProps = (dispatch) => ({
action2: id => dispatch(Actions.action2(id)),
action3: id => dispatch(Actions.action3(id)),
action1: (dateId, attrId) =>
dispatch(batchActions([
Actions.action2(dateId),
Actions.action3(attrId)
]))
});
When used out of the box without any performance optimisations, there are two primary concerns:
React will re-render multiple times
React Redux will re-evaluate selectors multiple times
Calling dispatch on forEach should work.
const actions = [{
type: 'UPDATE_ARRAY',
orderItemsArray: newitemsArray,
},{
type: 'UPDATE_NUMBER',
tickNumber: tickNumber,
},{
type: 'UPDATE_MESSAGE',
message: orderMessage,
}]
actions.forEach(action => dispatch(action));
Related
I am using mui-datatable and based on the official example of this codesandbox, you can setState on the tableState. https://codesandbox.io/s/trusting-jackson-k6t7ot?file=/examples/on-table-init/index.js
handleTableInit = (action, tableState) => {
console.log("handleTableInit: ", tableState);
this.setState({ table: tableState });
};
handleTableChange = (action, tableState) => {
console.log("handleTableChange: ", tableState);
this.setState({ table: tableState });
};
I wanted to get the the tableState.displayData hence, I added this, however, this will result to an error that says:
Maximum update depth exceeded. This can happen when a component
repeatedly calls setState inside componentWillUpdate or
componentDidUpdate. React limits the number of nested updates to
prevent infinite loops.
const handleChange = (action, tableState) => {
console.log(tableState.displayData);
setDisplayedData(tableState.displayData);
};
const options = {
enableNestedDataAccess: ".",
print: false,
filterType: "multiselect",
selectableRows: "none",
downloadOptions: { filename: "Data.csv", separator: "," },
expandableRows: true,
onTableChange: handleChange,
onTableInit: handleTableChange,
I wanted to store the data of the tableState.displayData to the setDisplayedData. How can I fix this error?
I recreated this error on codesandbox: https://codesandbox.io/s/mui-datatable-reports-mqrbb3?file=/src/App.js:4130-4415
This is keep rendering because you have used setDisplayedData in the handleChange function. so whenever table change you update the state and it again changing the state. So it is going to an infinite loop.
you should put condition to check if data you are getting is different from the prev one or not. you can try isEqualwith & isEqual functions from lodash library to check if you new data is different from old or not.
const handleChange = (action, tableState) => {
console.log(tableState.displayData);
if(!isEqualwith(displayedData, tableState.displayData, isEqual)) {
setDisplayedData([...tableState.displayData]);}
};
Note: add lodash to you dependencies and import isEqualwith & isEqual functions.
8.4 of react-admin. I've been trying to implement a custom action that connects with the custom reducer but so far nothing has worked.
I've Implemented this part of the guide in the official documentation for the action side https://marmelab.com/react-admin/doc/3.8/Actions.html#querying-the-api-with-fetch and this for the reducer https://marmelab.com/react-admin/doc/3.8/Admin.html#customreducers. The problem stems from that I can only use useUpdate method which sends update request, instead of a get without connecting to the reducer and there is no clear explanation of how I can chain those two things together. I also tried using an older way of dispatching actions, but still didn't work. Please help I've been trying this for 2 weeks now. Nothing gets updates and the redux store stays the same.
component
const { data, loading, error } = useQueryWithStore({
type: 'getList',
resource: 'goals',
action: "GET_USER_GOALS",
payload: { pagination: { page: 1, perPage: 10 }, sort: { field: "a-z", order: "ABC" }, filter: {} }
});
reducer
export default (previousState = 0, { type, payload }) => {
console.log(type)
if (type === 'GET_USER_GOALS') {
return payload.rate;
}
return previousState;
}
I even wrote a custom action
but it says that "Cannot read property 'update' of undefined" which isn't supported in the newer version I guess.
import { UPDATE } from 'react-admin';
export const UPDATE_PAGE = 'GET_USER_GOALS';
export const setGoals = (id, data) => {
return {
type: UPDATE_PAGE,
payload: { id, data: { ...data, is_updated: true } },
meta: { fetch: UPDATE, resource: 'goals' },
}
};
admin
<Admin
locale="en"
customReducers={{ userGoals: userGaolsReducer }}
loginPage={LoginPage}
authProvider={authProvider}
dataProvider={testProvider}
i18nProvider={i18nProvider}
history={history}
dashboard={Dashboard}
customSagas={[userGoalsSaga]}
>
I had to include it in the store.js as well
const reducer = combineReducers({
admin: adminReducer,
router: connectRouter(history),
userDashboardSettings: userGaolsReducer
});
I'm using React v 16.3.2 and am building an app to communicate orders with a DB on Firebase, also using axios 0.18.0.
I'm expecting that once I hit a certain button (continue) it will create this spinner effect on the screen as it loads, so I have the following code:
purchaseContinueHandler = () => {
this.setState = ({loading: true});
const order = {
ingredients: this.state.ingredients,
price: this.state.totalPrice,
customer: {
name: 'Name',
address: {
street: 'teststreet 1',
zipCode: '86753',
country: 'U.S.'
},
email: 'example#example.com'
},
deliveryMethod: 'fastest'
}
axios.post('/orders.json', order)
.then(response => { this.setState( { loading: false } );
})
.catch(error => {
this.setState( { loading: false } ); //**<--This is where i get the error**
});
}
I run it and hit Continue on the app and get the error:
Unhandled Rejection (TypeError): _this.setState is not a function
and it points to the line listed above.
Ran into this documentation and tried to implement the solution that worked for him (create a constructor() and bind this to this) but that didn't work.
I was reading that this was no longer automatically added to the function but not sure what to do with that information, I'm pretty new to React still.
Any advice?
You are assigning an object to setState in the first line of code, so when you try to call it later it no longer is a function...
this.setState = ({loading: true});
should be
this.setState({loading: true})
The setState is a function, you change it to an JavaScript Object, I guess it's a tiny mistake, you have to write:
this.setState({loading: true})
Use it not change it to other things.
I have a list of invites in my app, each one with corresponding delete button. When a user clicks delete a DELETE_INVITE action is dispatched and an epic fire:
const deleteInvite = (action$: any, store: Store<ReduxState, *>) =>
action$.pipe(
ofType(DELETE_INVITE),
mergeMap(({ payload }) =>
ajax(api.deleteInvite(payload.inviteId)).pipe(
map((response: Object) => ({
type: DELETE_INVITE + SUCCESS,
payload: {
data: response.response,
status: response.status,
},
})),
catchError((error: Object) => of({
type: DELETE_INVITE + FAILURE,
error: {
response: {
data: error.xhr.response,
status: error.xhr.status,
},
},
})),
),
),
);
Now I would like to ensure that only one request is fired at the time and wait until last one finished. In other words, I want to protect myself from a situation where user rapidly clicks all over the buttons and fires few request simultaneously.
switchMap is kind of something I'm looking for because it would handle only most recent click... but the request would be already fired and UI left with outdated data. So I need something that will let call mergeMap again only when inner chain completes.
Based on your comments below, it sounds like what you want is exhaustMap.
Projects each source value to an Observable which is merged in the output Observable only if the previous projected Observable has completed.
const deleteInvite = (action$: any, store: Store<ReduxState, *>) =>
action$.pipe(
ofType(DELETE_INVITE),
exhaustMap(({ payload }) =>
ajax(api.deleteInvite(payload.inviteId)).pipe(
map((response: Object) => ({
type: DELETE_INVITE + SUCCESS,
payload: {
data: response.response,
status: response.status,
},
})),
catchError((error: Object) => of({
type: DELETE_INVITE + FAILURE,
error: {
response: {
data: error.xhr.response,
status: error.xhr.status,
},
},
})),
),
),
);
I guess I would ask why you need to use redux-observable to achieve this in the first place. Can't you just set some state variable in your redux store at the start of your request (like deleteInProgress = true), and use this state to disable the delete button. And when your request finishes (either successfully or erroroneously), set the deleteInProgress flag back to false, which will re-enable the button.
I'm trying to create an epic that will take an action, and then dispatch two different actions, with the second one delayed by two seconds. After a bunch of attempts, this was the best I could do:
const succeedEpic = action$ =>
action$.filter(action => action.type === 'FETCH_WILL_SUCCEED')
.mapTo({ type: 'FETCH_REQUEST' })
.merge(Observable.of({ type: 'FETCH_SUCCESS' }).delay(2000))
Unfortunately, it seems that:
Observable.of({ type: 'FETCH_SUCCESS' }).delay(2000)
Is run immediately upon my app being loaded (rather than when an event comes down the parent stream). I noticed this because the FETCH_SUCCESS action is received by the reducer exactly two seconds after my app is loaded. I even attached a console.log to confirm this:
const succeedEpic = action$ =>
action$.filter(action => action.type === 'FETCH_WILL_SUCCEED')
.mapTo({ type: 'FETCH_REQUEST' })
.merge(Observable.of({ type: 'FETCH_SUCCESS' })
.do(() => console.log('this has begun'))
.delay(2000)
)
"this has begun" is logged to the console the moment the app is started.
I suspect this has something to do with how Redux-Observable automatically subscribes for you.
The desired behaviour is that I will:
Click a button that dispatches the FETCH_WILL_SUCCEED event.
Immediately, a FETCH_REQUEST event is dispatched.
Two seconds after that, a FETCH_SUCCESS event is dispatched.
It turns out I needed to wrap both of my events inside a mergeMap. Thanks to #dorus on the RxJS Gitter channel for this answer.
This is my working result:
const succeedEpic = action$ =>
action$.filter(action => action.type === 'FETCH_WILL_SUCCEED')
.mergeMapTo(Observable.of({ type: 'FETCH_REQUEST' })
.concat(Observable.of({ type: 'FETCH_SUCCESS' })
.delay(1000)))
merge should work as well in place of concat, but I thought concat makes better semantic sense.
There might be a more elegant solution, but why not just use 2 epics and combine them?
The first one dispatches the fetch request:
const onFetchWillSucceed = action$ => action$.ofType('FETCH_WILL_SUCCEED')
.mapTo({ type: 'FETCH_REQUEST' })
The second one waits 2 secs and dispatches the success:
const onFetchRequest = action$ => action$.ofType('FETCH_REQUEST')
.delay(2000)
.mapTo({ type: 'FETCH_SUCCESS' })
And in the end they are just combined into 1 epic
const both = combineEpics(onFetchWillSucceed, onFetchRequest)