Redux: Cannot read property 'then' of undefined - reactjs

I'm trying to combine actions inside mapDispatchToProps. Trying to fetch data and after launch modal dialog. But I keep getting Cannot read property 'then' of undefined error.
Could somebody explain me, what am I doing wrong?
mapDispatchToProps:
const mapDispatchToProps = dispatch => ({
onClick: id => {
// console.log(fetchThing(id)) returns undefined
dispatch(fetchThing(id)).then(() => {
dispatch(showModal())
})
}
})
Redux action:
export const fetchThing = () => {
const request = axios.get(URL_API)
return dispatch => {
request.then(response => {
dispatch({ type: ACTION_FETCH, payload: response.data })
})
}
}

Why don't you use redux-thunk? With that you can write your action like that:
export const fetchThing = dispatch => axios.get(URL_API)
.then(response => dispatch({ type: ACTION_FETCH, payload: response.data }))
Redux thunk middleware will do all the stuff for you and you do not need to do that each time you need async actions within the mapDispatchToProps.

mapDispatchToProps should not have side effects. This should only do as the name suggests and hook up the actions to redux. This should be:
const mapDispatchToProps = {
fetchThing: yourAction.fetchThing,
showModal: yourAction.showModal,
};
export default connect(null, mapDispatchToProps)(YourComponent);
Then in your (probably) button:
<button type="button" onClick={this.handleClick} />
// this can be an asynchronous function, or you can just handle it in the action.
handleClick = async id => {
await this.props.fetchThing(id);
this.props.showModal();
};

Related

Uncaught TypeError: getState is not a function

I am working on an e-commerce shopping cart app. I am not able to use getState() method to access the store.
This is my code from actions/cartActions.js file that is giving me the error:
export const removeFromCart = (product) => (dispatch, getState) => {
const cartItems = getState()
.cart.cartItems.slice()
.filter((x) => x._id !== product._id);
dispatch({ type: REMOVE_FROM_CART, payload: { cartItems } });
localStorage.setItem("cartItems", JSON.stringify(cartItems));
};
From OP's comment I guess OP want to achieve something like this:
function Cart(props) {
const { cartItems, removeFromCart } = props
return (<div>
<h1>Cart</h1>
{cartItems.map(product =>
<div key={product._id}>
<div>{product.name}</div>
{/* how you'd invoke removeFromCart 👇 */}
<button onClick={() => removeFromCart(product)}>Delete</button>
</div>
)}
</div>)
}
And you want to achieve this through react-redux's connect(). It's feasible, but not in the way you currently write your code.
Let's revisit the doc first:
connect() Parameters​
connect accepts four different parameters, all optional. By convention, they are called:
mapStateToProps?: (state, ownProps?) => Object
mapDispatchToProps?: Object | (dispatch, ownProps?) => Object
mergeProps?: (stateProps, dispatchProps, ownProps) => Object
options?: Object
We need state and dispatch in one place in order to create removeFromCart. Reality is, in mapStateToProps we have access to state, in mapDispatchToProps we have access to dispatch, the only place we can access both is within the 3rd param, mergeProps function.
mergeProps should be specified with maximum of three parameters. They are the result of mapStateToProps(), mapDispatchToProps(), and the wrapper component's props, respectively.
This brings us to the solution:
export default connect(
state => ({ state }), // simply pass down `state` object
dispatch => ({ dispatch }), // simply pass down `dispatch` function
// here we do the real job:
({ state }, { dispatch }) => {
const removeFromCart = (product) => {
const cartItems = state.cart.cartItems.slice()
.filter((x) => x._id !== product._id);
dispatch({ type: REMOVE_FROM_CART, payload: { cartItems } });
localStorage.setItem("cartItems", JSON.stringify(cartItems));
};
return {
cartItems: state.cart.cartItems,
removeFromCart,
}
}
)(Cart)

React-TypeScript: Expected 0 arguments, but got 1

I'm not sure why I got this error. So basically I'm dispatching the id from Product component to getProduct redux action. Not sure why it isnt working.
// The product component
const Product: React.FC = () => {
const { id } = useParams();
const dispatch = useDispatch();
useEffect(() => {
dispatch(getProduct(id));
}, [dispatch, id]);
return (
<section className="product">
<div className="bd-container product-container"></div>
</section>
);
};
export default Product;
// getProduct redux action
interface productId {
id: string | undefined;
}
export const getProduct =
(id: productId) => async (dispatch: Dispatch<Actions>) => {
dispatch({
type: actionTypes.GET_PRODUCT_LOADING,
});
try {
const { data } = await axios.get(`${url}/api/products/${id}`);
dispatch({
type: actionTypes.GET_PRODUCT_SUCCESS,
payload: data,
});
} catch (error: any) {
dispatch({
type: actionTypes.GET_PRODUCT_FAIL,
payload: error,
});
}
};
Either an action object or action creator function could be passed to dispatch() function you got from useDispatch(). But here you are calling getProduct which is returning nothing, but dispatching actions.
Refer to more detail about useDispatch() hook here: https://react-redux.js.org/api/hooks#usedispatch
Redux has a dedicated article on how to deal with async logics here: https://redux.js.org/tutorials/fundamentals/part-6-async-logic

Difference between returning dispatch function or returning a

I'm learning redux and i'm reading a react/redux source-code. in actions/Video.js file it has this code:
export const startDownloading = url => dispatch => {
ipcRenderer.send("video:url", { url });
dispatch({
type: START_DOWNLOADING
});
};
export const getVideoInfo = info => {
return { type: GET_VIDEO_INFO, payload: info };
};
So what's the difference between:
export const startDownloading = url => dispatch => {
ipcRenderer.send("video:url", { url });
dispatch({
type: START_DOWNLOADING
});
};
and
export const startDownloading = url => {
ipcRenderer.send("video:url", { url });
return {
type: START_DOWNLOADING
};
};
I mean, when we should return an object and when we should call dispatch function in an action file?
the difference relies on how to fire a change in your store,
by default calling an action will not fire the event that is going to be handle by the reducer
so you call dispatch either and object or a function
function
export const someFunction = () => ({type: 'actioncreator'})
dispatch(someFunction())
object
dispatch({type: 'actioncreator'}
you can dispatch actions inside a component or inside a actions folder

Passing parameters from component to action creators

I'm really struggling with Redux. I have one component where my input field is, and user should type in a word so I use that as a query in fetch request (which is in actionCreators file). I set that word to state so I want to pass that term to action and use it in fetch URL.
Component:
<button className='search-btn'
onClick={() =>
this.props.getResults(this.state.searchTerm)}>
const mapDispatchToProps = dispatch => {
return {
getResults: (term) => dispatch(getData(term))
}
}
actionCreators.js:
export const getData = (term) => {
return (dispatch) => {
fetch(`...url...${term}`)
.then(response => response.json())
.then(data => {
console.log(data)
dispatch({type: 'GET_DATA', results: data})
})
}
}
In reducer, I set data to be action.results.
All I get is TypeError: dispatch is not a function
mapDispatchToProps is a second param to connect, the first is mapStateToProps:
default connect (null, mapDispatchToProps)(Search);
Official docs.
This is the correct usage of connect.
export default connect(mapStateToProps, mapDispatchToProps)(Search);

mapDispatchToProps to be explicit

In react/redux I'm trying to convert this mapDispatchToProps to be explicit:
const mapDispatchToProps = { createOrganization }
I tried this:
const mapDispatchToProps = (dispatch) => {
return {
createOrganization: (organization) => {
dispatch(createOrganization(organization))
}
}
}
And this is the action
export const createOrganization = (organization) => ({
type: ACTION_CREATE_ORGANIZATION,
payload: api.createOrganization(organization),
})
But It's nor working. What can I do? Am I missing something?
The error is "Cannot read property 'then' of undefined". What it's happening is that once I enter a code, it should create an organization and redirect me to the page /dashboard, but it's not working
handleClick = (e, formData) => {
e.preventDefault()
if (formData.betaCode && formData.organization && this.props.userData) {
this.props.createOrganization({
name: formData.organization,
owner: {
id: this.props.userData.id,
name: this.props.userData.login,
ownerAvatar: this.props.userData.avatar_url
},
beta_code: formData.betaCode
})
.then(() => {
this.props.history.push('/dashboard')
})
}
}
Based on your code, createOrganization action should be an async. Something similar to:
const = createOrganization = organization => dispatch =>
api.createOrganization(organization)
.then(
response => dispatch({ type: ACTION_CREATE_ORGANIZATION, payload: response}),
error => dispatch({ type: ACTION_CREATE_ORGANIZATION_ERROR, payload: error}),
)
But it's not enough, you should install redux-thunk && redux-promise to handle such kind of action.
Rest of your code shouldn't be changed. Then you will be able to use mapDispatchToProps as you want:
const mapDispatchToProps = { createOrganization }
Hope it make sense. Async flow in redux

Resources