Let's say that I have state with elements that represent different data types of objects.
Each element can have a different action to dispatch
export default connect(
(state) => {
return {
events: getRegisteredEventsList(state).map(item => {
return {
title: item.get('name'),
actionButton: <a onClick={dispatch(someActionForThisSpecifiedItem(item))}>Custom Action</a>
}
})
},
(dispatch) => {
return {
}
}
)(Dashboard)
What is reliable way to achieve this kind of pattern ?
Should I put dispatch method to my container's props?
How do I achieve that at this point is:
export default connect(
(state) => {
return {
events: getRegisteredEventsList(state).map(item => {
return {
title: item.get('name'),
actionButton: ({dispatch}) => <a
className={"btn btn-success"}
onClick={() => dispatch(someActionForThisSpecifiedItem(item))}>Go To</a>
}
})
}
)(Dashboard)
adding method:
renderActionButtons() {
const { actionButtons, dispatch } = this.props
return actionButtons.map(button => renderComponent(button, {
dispatch
}));
}
into my dummy component - which is violation of separation of concerns because my view components now need to know and maintain dispatch property
I feel like that could be redux a feature request as well.
I would go for something like this, lets say for simplicity your state is something like this:
const items = [{
name: 'abc',
}, {
name: 'def',
}];
The link component which simply dispatches an action when it's clicked
const Link = ({name, onClick}) => <a onClick={() => onClick(name)}>{name}</a>;
The render links component which accepts the following props: a list of items and the onClick function which is capable of dispatching actions
const RenderLinks = ({ items, onClick }) => (
<div>
{items.map(a =>
<Link
name={a.name}
onClick={onClick}
/>)}
</div>
);
const mapStateToProps = (state) => ({
items,
});
The onClick function has the ability to dispatch the actions
const mapDispatchToProps = (dispatch) => ({
onClick: (name) => dispatch({type: `${name.toUpperCase()}_CLICKED`}),
});
export default connect(mapStateToProps, mapDispatchToProps)(RenderLinks);
Related
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)
I wonder what is the best approach to dry out common redux actions ('m using redux-thunk)
for example I have these two components:
const Main = ({list, onLike }) => (
<ul>
{list.map(item => (
<Comment item={item} onLike={onLike} />
))}
</ul>
)
const mapStateToProps = (state) => ({
list: state.main.comments
})
const mapStateToProps = (dispatch) => ({
onLike: (commentId) => dispatch(actions.mainOnLike(commentId))
})
export default connect(mapStateToProps, mapDispachToProps)(Main)
const Profile = ({ list, onLike }) => (
<ul>
{list.map(item => (
<Comment item={item} onLike={onLike} />
))}
</ul>
)
const mapStateToProps = (state) => ({
list: state.profile.comments
})
const mapStateToProps = (dispatch) => ({
onLike: (commentId) => dispatch(actions.profileOnLike(commentId))
})
export default connect(mapStateToProps, mapDispachToProps)(Profile)
actions.js
export const mainOnLike = commentId => dispatch => {
commentsService.like(commentId).then(() => {
dispatch(mainReloadComments())
})
}
export const profileOnLike = commentId => dispatch => {
commentsService.like(commentId).then(() => {
dispatch(profileReloadComments())
})
}
This is an example to show the problem. The point is that I'm calling commentsService.like multiple times and I will like to dry that out. The reason for that is that I need to call profileReloadComments and mainReloadComments separated to reload the comments in store for those containers/reducers.
I'm tempted to call commentsService.like inside the <Comment> component, so whenever I use it I have that functionality, but that does not seems the redux way.
any ideas how to dry this out?
Thanks!
Probably something like this:
const likeCommentWithAction = actionCreator => commentId => dispatch => {
commentsService.like(commentId).then(() => {
dispatch(actionCreator())
})
}
// Then
export const mainOnLike = likeCommentWithAction(mainReloadComments)
export const profileOnLike = likeCommentWithAction(profileReloadComments)
I am quite used to class components. There I can pass a function that is bound to this to other components without problems. I thought the same would hold true for functional components.
Yet, the following code simply does not work:
const Dropdown: React.FC<{onNewPost: any}> = (props) => {
return(
<div onClick={props.onNewPost}></div>
)
}
function AddMessage(props: IProps) {
const { conversationUUID } = props.match.params;
const navigateToNewPost = (postUUID: string) => {
props.history.push(`/app/messages/new/${postUUID}/`)
}
const onNewPost = () => {
// props.history.push('/example/') -> this works without problems
props.createPost(
conversationUUID,
navigateToNewPost
)
}
return(
<Dropdown onNewPost={onNewPost}/>}
)
}
const mapStateToProps = (state: AppState) => ({
})
const mapDispatchToProps = (dispatch: Dispatch) => bindActionCreators({
createPost
}, dispatch)
export default connect(mapStateToProps, mapDispatchToProps)(withRouter(AddMessage));
I think the problem arises because props.history.push is not bound to anything here but the global object? I think I am having trouble understanding how functions are bound in functional components ... Maybe some helpful soul can come up with an explanation. Thanks!
EDIT:
export const createPost = (conversationUUID: string, callback: any) => async (dispatch: Dispatch) => {
try {
const res: any = authAxios.post('/posts/', {
conversation: conversationUUID,
})
if (callback) {callback(res.data.uuid)}
} catch(e) {
}
}
Here is react/redux application.
This a basic stripped down version of what I am trying to accomplish. showFolder() produces a list of folders and a button to click where it calls the removeFolder action from FolderActions.js. The button works and will call the function in FolderActions.js however will not dispatch the type. The functions works as I can see the console.log message but will not dispatch the type using redux..
I have a strong feeling it's the way I'm calling the function however I am lost at the moment
import {
addFolder,
getFolder,
removeFolder,
} from "../../../actions/FolderActions";
class Folders extends Component {
onRemoveFolder = (e,id) => {
e.preventDefault();
console.log(id);
this.props.removeFolder(id);
};
showFolders = () => {
return (
<ul>
{this.props.folder.map((key, index) => (
<form onSubmit={(e) => this.onRemoveFolder(e,key._id)}>
<input type="submit"></input>
</form>
))}
</ul>
);
};
render() {
let { isShown } = this.state;
return (
<div>
<div className="folder_names">{this.showFolders()}</div>
</div>
);
}
}
const mapStateToProps = state => {
return {
userId: state.auth.user._id,
folder: state.folder.data
};
};
export default connect(mapStateToProps, {removeFolder,addFolder,getFolder})(
Folders
);
FolderActions.js
export const removeFolder = id => dispatch => {
console.log("called")
axios
.delete(`api/folders/${id}`)
.then(res =>
dispatch({
type: DELETE_FOLDER,
payload: id
})
)
.catch(err => {
console.log(err);
});
};
Your function call looks strange to me...
Can you try defining a proper mapDispatchToProps and calling dispatch within that instead of within your function?
const mapDispatchToProps = dispatch => ({
removeFolder: (id) => dispatch( removeFolder(id) ),
addFolder: (id) => dispatch( addFolder(id) ),
getFolder: (id) => dispatch( getFolder(id) ),
})
export default connect(mapStateToProps, mapDispatchToProps)(
Folders
);
export const removeFolder = id => {
// code block
};
I know that's more a rework that you probably were hoping for, but does it work?
Correct me if I'm wrong but my server never sent a response.
Because the server never sent a response, when I made a request, res is not true so cannot dispatch the type and payload.
Sorry for wasting peoples time!
I want to create a generic checkbox UI component wrapped in redux connect.
I'd like to pass the component a redux action as a prop so I can then use this action on the connect dispatch action:
const CheckBoxWithState = connect(
state => ({
xxxxxxxxxxxxx
}),
dispatch => (
dispatch(this.props.reduxAction)
),
)(CheckBox)
Is it possible to do this?
The second parameter of connect (mapDispatchToProps) gets two parameters: dispatch and the props of the component. So you could use props like this:
const CheckBoxWithState = connect(
state => ({
xxxxxxxxxxxxx
}),
(dispatch, props) => ({
action: actionPayload =>
dispatch({ type: props.reduxAction, payload: actionPayload })
})
)(CheckBox);
Then call the action in CheckBox:
const CheckBox = (props) => {
const { action } = props;
const onChange = (value) => {
action({ checked: value });
}
// ...
}