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!
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)
"myGroups" is a context variable. In my context store, I am fetching data from a database and populating this "myGroups" variable. So initially it contains only an empty array, after some time it contains an array of objects. e.g. [{id: "", data: ""}]
I want to render these groups. So I am mapping through the myGroups variable, and trying to render them.
But the problem is that even after context updating, my component does not re-render. I have console logged and seen that the fetching of data works absolutely fine, though it takes some time to do so.
Does changing the context does not rerender it's consumer? Why is the component not rerendering? It would be of great help if you can provide some solution. Thanks in Advance.
Here is my code.
import React, { useContext, useEffect, useState } from 'react';
import "../css/MyGroups.css";
import GroupCard from './GroupCard';
import { GlobalContext } from '../context/GlobalState';
const MyGroups = () => {
const { myGroups } = useContext(GlobalContext);
useEffect(() => console.log(myGroups), [myGroups]); // Debugging
return (
<div className="my__groups">
<h1 className="my__groups__heading">My Groups</h1>
<div className="my__groups__underline"></div>
<div className="my__groups__grid__container">
{
myGroups.map(({id, data}) => (
<GroupCard
key={id}
name={data.name}
image={data.image}
/>
))
}
</div>
</div>
)
}
export default MyGroups
This is what I get on the console when the context changes:
Console log image
My Global Provider:
<GlobalProvider>
<BrowserRouter>
<Main />
</BrowserRouter>
</GlobalProvider>
The MyGroups Component is a descendant of the Main Component.
Edit 1: Fetch Function of my Store
function fetchGroupsFromDatabase(id) {
let myGroups = [];
db.collection("users").doc(id).get() // Fetch user details with given id
.then(doc => {
doc.data().groupIDs.map(groupID => { // Fetch all group IDs of the user
db.collection("groups").doc(groupID).get() // Fetch all the groups
.then(doc => {
myGroups.push({id: doc.id, data: doc.data()})
})
})
})
.then(() => {
const action = {
type: FETCH_GROUPS_FROM_DATABASE,
payload: myGroups
};
dispatch(action);
})
}
Edit 2: Reducer
const Reducer = (state, action) => {
switch(action.type) {
case FETCH_GROUPS_FROM_DATABASE:
return {
...state,
myGroups: action.payload
};
default:
return state;
}
}
export default Reducer;
As Yousaf said, not need to using useState and useEffect. You can use context in two different way
first:
const MyGroups = () => (
<GlobalContext.Consumer>
{({ myGroups }) => (
<div className="my__groups">
<h1 className="my__groups__heading">My Groups</h1>
<div className="my__groups__underline"></div>
<div className="my__groups__grid__container">
{myGroups.map(({id, data}) => (
<GroupCard
key={id}
name={data.name}
image={data.image}
/>
))
}
</div>
</div>
)}
</GlobalContext.Consumer>
);
second:
const MyGroups = () => {
const { myGroups } = useContext(GlobalContext);
return (
<div className="my__groups">
<h1 className="my__groups__heading">My Groups</h1>
<div className="my__groups__underline"></div>
<div className="my__groups__grid__container">
{myGroups.map(({ id, data }) => (
<GroupCard key={id} name={data.name} image={data.image} />
))}
</div>
</div>
);
};
Edit:
the problem comes from the Fetch Function it based on this answer should be like this:
async function fetchGroupsFromDatabase(id) {
const doc = await db.collection("users").doc(id).get() // Fetch user details with given id
const myGroups = await Promise.all(
doc.data().groupIDs.map(groupID => // Fetch all group IDs of the user
db.collection("groups").doc(groupID).get() // Fetch all the groups
.then(doc => ({ id: doc.id, data: doc.data() }))
)
);
const action = {
type: FETCH_GROUPS_FROM_DATABASE,
payload: myGroups
};
dispatch(action);
}
I'm trying to pass some data through useContext() that I'm getting from a Contentful API but I can't figure out how. Can someone tell me what I'm doing wrong?
First, I get the data and save it in a state:
const [products, setProducts] = useState([]);
function getProducts() {
Client.getEntries("products")
.then((entry) => {
entry.items.map((item) => {
setProducts(products.push(item));
});
})
.catch((err) => console.log(err));
}
useEffect(() => {
getProducts();
}, []);
Then I pass the state to the Provider:
<ProductContext.Provider value={{ products }}>
//children
</ProductContext.Provider>
When I log 'products' inside the getProducts() function, I get an array with a bunch of objects, but when I try to map it somewhere else in my app, I get a products.map is not a function.
import { ProductContext } from "../../../Context";
export default function ProductList() {
const { products } = useContext(ProductContext);
return (
<Container>
{products.map(product => {
//do something
})}
</Container>
);
}
.then((entry) => {
entry.items.map((item) => {
setProducts(products.push(item));
});
could be simplified to
.then((entry) => setProducts(entry.items));
Maybe that's not the cause, but could be that the multiple calls to setProducts() cause some delay (set of state is asynchronous)
A child component has the following button code:
// SelectDonation.js
<button
onClick={(e) => {
e.preventDefault();
this.props.testThunk();
console.log(store.getState());
}}
>Test thunks</button>
this.props.testThunk() does not update the state object. I connected Redux Thunk like so:
// reducer.js
import ReduxThunk from "redux-thunk";
const starting_state = {
log_to_console : 0,
donation_amount : 12,
checkoutStep : 'selectDonation',
};
const reducer = (previous_state = starting_state, action) => {
switch (action.type) {
case 'thunkTest':
return {
...previous_state,
redux_thunk_test_var : action.payload
};
default:
return previous_state;
}
};
export default createStore(reducer, starting_state, applyMiddleware(ReduxThunk));
I expect a new state property redux_thunk_test_var to display in state but it does not onClick. I do see the state variables with initial states in the console though.
Am I not passing down the thunk correctly? Here is App.js
// App.js
{this.props.checkoutStep === checkoutSteps.selectDonation &&
<SelectDonation
dispatch_set_donation_amount = {this.props.dispatch_set_donation_amount}
dispatchChangeCheckoutStep={this.props.dispatchChangeCheckoutStep}
{...this.props}
/>
}
</Modal>
</header>
</div>
);
}
}
const map_state_to_props = (state) => {
return {
log_prop : state.log_to_console,
donation_amount : state.donation_amount,
checkoutStep : state.checkoutStep,
}
};
const map_dispatch_to_props = (dispatch, own_props) => {
return {
dispatch_set_donation_amount : amount => dispatch(set_donation_amount(amount)),
dispatchChangeCheckoutStep : newStep => dispatch(changeCheckoutStep(newStep)),
dispatchUpdateStateData : (stateData, stateVariable) => (dispatch(updateStateData(stateData, stateVariable))),
testThunk
}
};
The action thunk:
// actions.js
export const testThunk = () => {
const testDelay = setTimeout(() => 'Set Timeout done', 2000);
return (dispatch) => {
testDelay.then((data) => dispatch({
type: 'thunkTest',
payload: data })
)
}
};
You need to dispatch the result of the testThunk() action creator. Right now, you're just returning it, and not calling dispatch(testThunk()).
See this gist comparing syntaxes for dispatching to help understand the issue better.
The best way to fix this is to use the "object shorthand" form of mapDispatch. As part of that, I suggest changing the prop names to remove the word "dispatch", which lets you use the simpler ES6 object literal syntax:
const map_dispatch_to_props = {
set_donation_amount,
changeCheckoutStep,
updateStateData,
testThunk,
};
conponentDidMount() {
this.props.testThunk();
}
const map_dispatch_props = {
testThunk
}
//action creator
const fetch = (data) => ({
type: 'thunkTest',
payload: data
})
const fakeFetch = () => new Promise((resolve, reject) => setTimeout(() => resolve('Set Timeout done'), 2000));
export const testThunk = () => (dispatch) => fakeFetch.then(data => dispatch(fetch(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);