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 });
}
// ...
}
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 am creating a React application with Saga middleware. And I have few doubts to clarify.
On the first mount, why is it we are destructing an unknown prop const { onGetPhonesAndTablets } = props which is not passed from the Redux Store (State Container).
Why is it required to setPhoneAndTabletsList(phonesAndTablets) before dispatching an action onGetPhonesAndTablets . As the prop phonesAndTablets will have its value after dispatching an action i.e., onGetPhonesAndTablets
const Home = props => {
const {
phonesAndTablets
} = props
const [phonesAndTabletsList, setPhoneAndTabletsList] = useState([]);
useEffect(() => {
const {
onGetPhonesAndTablets
} = props
setPhoneAndTabletsList(phonesAndTablets)
onGetPhonesAndTablets()
}, [])
useEffect(() => {
if (!isEmpty(phonesAndTablets)) setPhoneAndTabletsList(phonesAndTablets)
}, [phonesAndTablets])
return (
<React.Fragment>
<ProductCategoryList list={phonesAndTabletsList}/>
</React.Fragment>
)
}
Home.propTypes = {
phonesAndTablets: PropTypes.array,
onGetPhonesAndTablets : PropTypes.func
}
const mapStateToProps = ({ home }) => ({
phonesAndTablets: home.phonesAndTablets
})
const mapDispatchToProps = dispatch => ({
onGetPhonesAndTablets: () => dispatch(getPhoneAndTablets())
})
export default connect(mapStateToProps, mapDispatchToProps)(Home)
I'm not entirely sure why this app uses a local state to store the redux state, which is passed as a prop. Seems redundant, not to mention bad for performance.
There is a selector hook, which lets components access individual pieces of redux state. useSelector(state => state.home.phonesAndTablets).
You can also pass a second argument, shallowEqual, which makes the
comparison function do a value comparison, as opposed to reference
comparison. This avoids unnecessary re-renders due to objects/arrays
whose contents are equal, but whose references point to different
objects. Remember that ['a'] !== ['a'] in JS.
There is also a dispatch hook to do the same for actions. useDispatch(). Then you can do
const list = useSelector(state => state.home.phonesAndTablets, shallowEqual)
const dispatch = useDispatch()
useEffect(() => {
dispatch(getPhoneAndTablets())
}, [])
return (
<>
<ProductCategoryList list={list ?? []} />
</>
)
I would personally add a check if the list is empty before fetching a new list, but maybe the app is supposed to fetch a new list whenever Home is mounted. I have no idea.
Since, the above code seems redundant as the state is already managed and handled by the State Container (Redux Store). So I tried removing redundant codes.
Personally I prefer mapStateToProps to selectors.
const Home = props => {
const {
phonesAndTablets,
onGetPhonesAndTablets
} = props
useEffect(() => {
onGetPhonesAndTablets()
}, [])
return (
<React.Fragment>
<ProductCategoryList list={phonesAndTablets ? phonesAndTablets : ''}/>
</React.Fragment>
)
}
Home.propTypes = {
phonesAndTablets: PropTypes.array,
onGetPhonesAndTablets : PropTypes.func
}
const mapStateToProps = ({ home }) => ({
phonesAndTablets: home.phonesAndTablets
})
const mapDispatchToProps = dispatch => ({
onGetPhonesAndTablets: () => dispatch(getPhoneAndTablets())
})
export default connect(mapStateToProps, mapDispatchToProps)(Home)
Here is an example of it.
My Code is:
const mapDispatchToProps = dispatch => ({
onChangeHandler: (name,value) =>
dispatch({ type: "REGISTRATION", key: name, value })
});
But I need something like this:
const mapDispatchToProps = dispatch => ({
onChangeHandler: (name,value) =>
dispatch({ type: "REGISTRATION", key: name, value }).then((nextProps)=>console.log(nextProps))
});
Please help us.
Redux data flow out of the box is synchronous, so it doesn't make sense to "then" a dispatch as it doesn't return anything. You can read more about that here:
https://redux.js.org/advanced/async-flow
I would recommend looking at redux-thunk which allows you to decide when to dispatch, whether it be sync or async, and will allow you to retrieve the updated state from the store. It does this by allowing you to dispatch a function instead of an action. The function provides you dispatch and getState.
Here is an example:
const onChangeHandler = (name, value) => (dispatch, getState) => {
// Dispatch event
dispatch({ type: "REGISTRATION", key: name, value });
// Get updated state
console.log("state :", getState());
};
export default connect(null, { onChangeHandler })(MyComponent);
If you are however looking at creating a side effect of the store updating from within your component, I would recommend to use the useEffect hook for this to respond to updates as a result of the redux store state updating your components props.
Here is an example:
const MyComponent = ({key, value}) => {
useEffect(() => {
console.log(`key or value updated: ${key} ${value}`);
}, [key, value]);
return (
<span>Current props are {key} and {value}</span>
)
};
const mapDispatchToProps=dispatch=>({
addToCartHandler:data=>dispatch(addToCart(data)),
removeToCartHandler:data=>dispatch(removeToCart(data))
})
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);
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();
};