How to access redux store from action creator? - reactjs

I'm looking at trying to chain actions together. In my current issue, when the SET_CURRENT_USER action occurs, I'd like it to modify the state (set the current user) and then fire off a bunch of other side-effect tasks (go fetch data, rebuild the UI, etc). My first thought was "well, I'll set a listener on the store"... which resulted in this previous question: How to access 'store' in react redux? (or how to chain actions) There, I was basically that setting listeners is an anti-pattern.
The solution suggested was to 'dispatch' multiple actions chained together. I didn't follow how to do that (my "mapDispatchToProps" is based on the redux tutorials and looks nothing like the suggested mapDispatchToProps) so I did some additional googling about how to chain side-effect actions together and got to this page: https://goshakkk.name/redux-side-effect-approaches/
Trying the first example, I went to my action creator file, which looks like this:
(actionCreators.js)
export function setCurrentUser(username) {
return { type: SET_CURRENT_USER, payload: username }
}
export function actionTwo(username) {
return { type: ACTION_TWO, payload: username }
}
export function actionThree(username) {
return { type: ACTION_THREE, payload: username }
}
and I tried to change the 'setCurrentUser' action creator to something resembling what was in the demo but without the async parts for simplicity - just looking to see if the actions fire:
export function setCurrentUser(username) {
return dispatch => {
dispatch( { type: SET_CURRENT_USER, payload: username } );
dispatch( { type: ACTION_TWO, payload: username } );
dispatch( { type: ACTION_THREE, payload: username } );
}
}
In my app, my mapDispatchToProps looks like this:
const mapDispatchToProps = {
setCurrentUser: setCurrentUser,
}
and I call this in the switchUser handler, like this: this.props.setCurrentUser(this.state.newUsername);
... and I get an error saying that actions must be plain objects.
How can I chain actions together?
Perhaps a deeper issue is that I don't know how to access the store in order to be able to call store.dispatch. (which was my previous question noted above)

i cannot leave a comment so a question: why dont you just set mapState and pass the state as an argument to dispatched action?
Here are Component stuff
class AComponent extends Component{
...
... onClick={()=>this.props.anAction(this.props.state)}
}
const mapStateToProps = state => {
return { state: state.YourReducer }
}
const mapDispatchToProps = dispatch => {
return { anAction: () => dispatch(actions.doAnyStaffWith(state)) }
}
export default connect(mapStateToProps, mapDispatchToProps)(BeerReviewer)
actions file:
export function actionThree(state) {
return { type: ACTION_TYPE, state }
}
That's what you're looking for?

You'll need thunk in order to enhance your action creators. the reason why you're getting the "must be plain objects" error is because your action creator is returning a function(), not an object. Thunk allows you to return functions in your action creators and with it, the code you wrote should work.
import { createStore, combineReducers, applyMiddleware } from "redux"
import thunk from "redux-thunk"
const store = createStore(combineReducers({
data1: reducer1,
data2: reducer2
}), {}, applyMiddleware(thunk))
Hope that works.

You should be able to access your store like this (illustrating with a fetch request):
import { someAPICALL } from '../api'
export function setCurrentUser(username) {
return async (dispatch, getState) => {
const { yourStateVariable } = getState().YourReducer
, data = await someAPICall(yourStateVariable)
.catch(e => dispatch({ type: FETCH_ERROR, payload: e }))
if (data) {
dispatch( { type: SET_CURRENT_USER, payload: username } );
dispatch( { type: ACTION_TWO, payload: username } );
dispatch( { type: ACTION_THREE, payload: username } );
} else {
dispatch( { type: SOME_OTHER_ACTION, payload: 'whatever state update you like' } )
}
}
}

Redux thunk would be the ideal way to do it but you can still chain actions by returning promises as your payload.(If you do not want to use thunk)
export function setCurrentUser(username) {
return {
type: SET_CURRENT_USER,
payload: new Promise(resolve => resolve(username))
}
}
export function actionTwo(username) {
return {
type: ACTION_TWO,
payload: new Promise(resolve => resolve(username))
}
}
export function actionThree(username) {
return {
type: ACTION_THREE,
payload: new Promise(resolve => resolve(username))
}
}
And then you can call all three like:
this.props.setCurrentUser(this.state.newUsername)
.then(newUsername => this.props.actionTwo(newUsername))
.then(newUsername => this.props.actionThree(newUsername))

I think you mapDispatchToProps function should look like this: const mapDispatchToProps = {
setCurrentUser: (data) => dispatch(setCurrentUser(data)),
}. Regarding accessing store from your action creators, you need to export it from where you declared it(index.js). import it to your actionCreators file and then use it there. It goes as follows:
in index.js file: export const store = createStore(...) and in your actionCreators file: import { store } from "your index.js file's path" and you are good to go. Hope that solves your problem.

Related

Why the error "Actions must be plain objects. Use custom middleware for async actions." appears, when there are no async actions?

I was trying to figure out React deeper and stuck on this error.
It didn't allow me to dispatch any actions. However, I'm not using async at all.
Here you can find codesandbox of the full app.
I've added thunkMiddleware to the store, so the app will work.
However I can't understand what is going on?
Here are the action creators, inside which I cloudn't dispatch.
I've searched for different similar answers, and all those were connected to incorrect usage of
async actions. Mine are sync:
import CART_ACTIONS from "../action_types/cartActionTypes";
function addToCart(item) {
return dispatch => dispatch({ type: CART_ACTIONS.ADD_ITEM, item: item });
}
function removeFromCart(item) {
return dispatch => {
dispatch({ type: CART_ACTIONS.REMOVE_ITEM, item });
};
}
function clearCart(item) {
return dispatch => {
dispatch({ type: CART_ACTIONS.CLEAR_CART });
};
}
export const cartActions = { addToCart, removeFromCart, clearCart };
You need to update your cartActions like this, if you don't want to use thunkMiddleware:
import CART_ACTIONS from "../action_types/cartActionTypes";
function addToCart(item) {
return({ type: CART_ACTIONS.ADD_ITEM, item: item });
}
function removeFromCart(item) {
return({ type: CART_ACTIONS.REMOVE_ITEM, item });
}
function clearCart(item) {
return({ type: CART_ACTIONS.CLEAR_CART });
}
export const cartActions = { addToCart, removeFromCart, clearCart };
Simply, you just need to return an action which must be an object with type property and optionally a payload.
codesandbox

Storing Normalized data into Redux

When making my API request, I get a deeply nested object back from the server, from reading up on this it's bad practice for Redux to store nested object data, therefore, I am using Normalizr on that object which returns me many different entities.
The issue I am facing is when making that request inside my Action. How I can store each entity into a different reducer effectively.
action.ts
export const fetchClaim = (claimId: string) => async (dispatch: any) => {
dispatch({ type: REQUEST_CLAIM, payload: true });
try {
const response = await req.get(`claim/${claimId}`);
const normalizedData = normalize(response.data, Claim);
dispatch({ type: SET_LIABILITY, payload: normalizedData.entities.LiabilityType });
dispatch({ type: SET_CLAIM_STATUS, payload: normalizedData.entities.ClaimStatus });
dispatch({ type: SET_CLAIM, payload: normalizedData.entities.Claim });
dispatch({ type: SET_ACTIONS, payload: normalizedData.entities.Actions });
} catch (e) {}
}
I then have an index.ts file inside my reducer folder which combines the reducers.
import { combineReducers } from 'redux';
import claimReducer from './claimReducer';
import actionReducer from './actionReducer';
import liabilityReducer from './liabilityReducer';
import claimStatusReducer from './claimStatusReducer';
export default combineReducers({
claim: claimReducer,
actions: actionReducer,
liability: liabilityReducer,
claimStatus: claimStatusReducer
});
Finally, each reducer looks identical in terms of storing its data.
import { LiabilityActionTypes, LiabilityState } from "../types/Liability";
const INITIAL_STATE: LiabilityState = {
liability: null
}
// State - Returns what the state was when this reducer was previously ran.
// Action - This is what the action creator dispatched.
export default (state = INITIAL_STATE, action: LiabilityActionTypes): LiabilityState => {
switch (action.type) {
case 'SET_LIABILITY':
return { ...state, liability: action.payload };
default:
return state;
}
}
My question is, it doesn't seem right that if i have around 40 entities, that I need to call dispatch 40 times inside the fetchClaim action. Is there a best practice to achieve this?

Pass parameters to mapDispatchToProps()

I can't lie, i'm a bit confused about react-redux. I think lot of the actions require parameters (for an example deleting items from the store), but even if i'm still reading about how to dispatch from component in that way to pass a parameter, about 2 hours now, i didn't get any answers. I was tried with this.props.dispatch and with mapDispatchToProps, i always get the this.props... is not a function message. Here's what i'm trying to do:
const getTheArray = (array) => {
return {
type: 'GET_ARRAY',
array
}
}
class Example extends......
componentDidUpdate(){
//i have a button, and when it clicked, turns the status: 'deleted'
if (this.state.status === 'deleted'){
fetch('http://localhost:3001/deleteitem', {
method: 'post',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({
id: this.props.id
})
})
.then(res => res.json())
.then(data => this.props.deleteFromArray(data))
.catch(err => console.log(err))
}
}
function mapStateToProps(state) {
return {
array: state.array
};
}
function mapDispatchToProps(dispatch) {
return({
deleteFromArray: (array) => {dispatch(getTheArray(array))}
})
}
export default connect(mapStateToProps, mapDispatchToProps)(Example);
It isn't the only place where i need to dispatch with an action, where the action's object's properties depending on another property passed to the function, so i really want to do, what's the best way to pass property to action, and dispatch it within a react component.
import { bindActionCreators } from "redux"
function mapDispatchToProps(dispatch) {
return(bindActionCreators({
deleteFromArray: (array) => {getTheArray(array)}
}, dispatch))
}
In your dumbComponents, just call e.g this.props.deleteFromArray([1,2,3,4])
This should solve the issue. you are not binding with dispatch
use of react-redux
you need to import action first
import { storeProfiles } from 'actions/profiles';
define props
const {
storeProfiles
} = props;
get data use of userEffect
useEffect(() => {
fetchProfiles().then(storeProfiles);
}, [ fetchProfiles, storeProfiles ]);
map to props
const mapDispatchToProps = dispatch => ({
storeProfiles: x => dispatch(storeProfiles(x))
});
export default connect(mapStateToProps, mapDispatchToProps)(Component);
Read official documentation
Using es6:
const mapDispatchToProps = (dispatch) => ({ addToCart: (params) => dispatch(actions.addToCart(params)), });
Actually, you don't have to pass params to actions like that, in my opinion.
I'm using React Hooks with TypeScript and Redux and thunk. The way I delete a post using an action creator looks like this:
PostDetails.tsx component:
import {deletePost, getPost, LanguageActionTypeEnum} from "../../../../app/store/aspian-core/actions";
interface IPostDetailsProps {
getPost: Function;
// action for deleting a post
deletePost: Function;
loadingInitial: boolean;
submitting: boolean;
post: IPost | undefined;
lang: LanguageActionTypeEnum;
}
const PostDetails: FC<PostDetailsProps> = ({
match, getPost,
lang, loadingInitial,
submitting, deletePost, post
}) => {
}
// To delete a post
const onDeleteBtnClick = async (id: string) => {
try {
/// using deletePost action and passing id to it
await deletePost(id);
message.success(t('messages.post-deleting-success'));
} catch (error) {
message.error(t('messages.post-deleting-error'));
}
};
return (
// omitted for brevity
<Popconfirm
key={uuidv4()}
title={t('popconfirm.title')}
/// calling onDeleteBtnClick() here
onConfirm={() => onDeleteBtnClick(post!.id)}
okText={t('popconfirm.ok-text')}
cancelText={t('popconfirm.cancel-text')}
placement={lang === LanguageActionTypeEnum.en ? 'left' : 'right'}
okButtonProps={{
danger: true,
}}
>
<Button
loading={submitting}
size="small"
type="primary"
icon={<DeleteFilled/>}
danger
>
{t('buttons.delete')}
</Button>
</Popconfirm>
// omitted for brevity
)
}
// Redux State To Map
const mapStateToProps = ({post, locale}: IStoreState): { post: IPost | undefined, submitting: boolean, loadingInitial: boolean, lang: LanguageActionTypeEnum } => {
return {
post: post.post,
submitting: post.submitting,
loadingInitial: post.loadingInitial,
lang: locale.lang
}
}
// Redux Dispatch To Map
const mapDispatchToProps = {
getPost,
// mapped deletePost action to props here
// and I will be able to pass id to its argument later
deletePost
}
export default connect(mapStateToProps, mapDispatchToProps)(PostDetails);
and my the action creator looks like this:
postActions.ts:
export const deletePost = (id: string) => async (dispatch: Dispatch) => {
try {
dispatch(setPostSubmitting(true));
await agent.Posts.delete([id]);
dispatch<IDeletePostAction>({
type: PostActionTypes.DELETE_SINGLE_POST,
payload: {
id,
submitting: false
}
})
} catch (error) {
console.log(error);
dispatch(setPostSubmitting(false));
throw error;
}
}
And it works fine. Hopefully it could help you with your situation.
If you use actions as arrow function so there would be no need to bindActionsCreators, let me do it with a very simple way.
your action should be of arrow function like so:
export const formHandler=({key,value})=>{
/*for checking that you get the values on function call or not*/
console.log(key,value)
return {
type:'formHandler',
payload:{key,value},
}
}
the below action function takes to arguments so you have to pass likewise
const mapDispatchToProps = dispatch=> {
/*this will add the functions as a props to the components
which can be trigger on order to change the state*/
return {
inputHandler: ({key,value})=>dispatch(formHandler({key,value}))
}
}
at the end you can use connect on yourComponent
export default connect(mapStatesToProps,mapDispatchToProps)(yourComponent)
then in your component
you can call the function using
class component
this.props.inputHandler({key:'k',value:2})
functional component
props.inputHandler({key:'k',value:2})

Making Axios calls with Redux

Basically what I wanted to do was to stop making axios calls inside of my component. So I thought; “Why not just create an action for that?”
I googled around to find a good “guide” to use Redux and this is what I’m using:
Add a constant to the constants file. Something like const GREAT_COURSE = GREAT_COURSE
Add an action creator to the actions folder. Return an action JavaScript object with a type of the constant you created.
Add a reducer to the reducers folder that handles this action creator.
So I began to create my action creator:
import axios from 'axios'
import { CUSTOMER_FETCH } from './types'
import settings from '../settings'
axios.defaults.baseURL = settings.hostname
export const customers = () => {
return dispatch => {
return axios.get('http://hejhej/customers').then(res => {
dispatch({
type: CUSTOMER_FETCH,
data: res.data
})
})
}
}
And later to add a reducer that handles my action creator:
import { CUSTOMER_FETCH } from '../actions/types'
const initial = []
const customer = action => {
return {
data: action.data
}
}
const customers = (state = initial, action) => {
switch (action.type) {
case CUSTOMER_FETCH:
customers = [...state, customer(action)]
console.log('customers as state', customers)
return customers
default:
return state
}
}
export default customers
And inside of my component I'm importing it:
import { customers } from '../../actions/customersAction'
And later using connect: export default connect(null, { customers })(Events)
And finally I'm using it inside of my component:
customers() {
this.props.customers(this.state.data)
}
So I'm wondering what I'm doing wrong, because I can't see my console.log in my dev tools. Thanks a lot for reading!
Inside of my component atm:
axios.get('http://hejhej/customers').then(res => {
this.setState({
res,
customer: res.data
})
})

Reroute on specific state of Redux store

I'm using React.js with Redux, React-Router and React-Router-Redux.
I would like to to manually reroute (or call an action) when the store reaches a specific state. How do I listen if the store has a specific state? Where do I reroute or call the action?
If these calls are made async then using an async action creator and the promise would handle this quite well.
// Action creator
function doTheStuff(someData, router) {
return dispatch => {
dispatch(somePendingAction());
return myRequestLib.post('/the-endpoint/', someData)
.then((response) => {
if (response.isTransactionDone) {
dispatch(transactionDoneAction(response));
router.transitionTo('/theUrl');
}
});
}
}
I wouldn't put this into the store since I believe the stores only job is to contain/handle the data.
To initiate a page change when a series of other actions have completed and been persisted to the store, dispatch the following redux-thunk. The key here is that you have a chain of thunks which return promises, and then once they all complete, you can update the router with another action.
In a component:
import React from 'react';
import { push } from 'react-router-redux';
import { connect } from 'react-redux';
import { doStuff, doMoreStuff } from './actions';
class SomeComponent extends React.Component {
...
onClick = e => {
const { doStuff, doMoreStuff, push } = this.props; // these are already wrapped by dispatch because we use connect
...
doStuff()
.then(r => doMoreStuff())
.then(r => push('/newPath')) // if actions succeeded, change route
.catch(e => console.warn('doStuff() or doMoreStuff failed'));
...
}
...
}
export default connect(null, { doStuff, doMoreStuff, push })(SomeComponent);
some actions in ./actions.js:
export function doStuff() {
return (dispatch, getState) => {
return API.getSomeInfo()
.then(info => dispatch({ type: 'GET_SOME_INFO', payload: info }))
}
}
export function doMoreStuff() {
return (dispatch, getState) => {
return API.getSomeMoreInfo()
.then(info => dispatch({ type: 'GET_SOME_MORE_INFO', payload: info }));
}
}
Alternatively, you could make a single promise that looks like this, where the redirect happens:
export function doAllStuff() {
return dispatch => {
return Promise.all(doStuff(), doAllStuff())
.then(results => dispatch(push('/newPath')))
.catch(e => console.warn('Not all actions were successful'))
}
}
the component code that intiates it would just be
onClick = e => {
...
doAllStuff(); // Since we caught the promise in the thunk, this will always resolve
...
}

Resources