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);
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)
On the outside it seems not an issue, but when I open the DevTools and then go to network tab. It shows that there are 500 requests made. So how can I refactor the code so this will not happens?
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(
"https://raw.githubusercontent.com/XiteTV/frontend-coding-exercise/main/data/dataset.json"
);
dispatch(getData(response.data));
console.log('input');
} catch (err) {
console.log(err);
}
};
fetchData();
}, [dispatch]);
with redux first create a function which will push your request data into redux state like that outside your react component
export const getMyData = () => {
return async (dispatch) => {
const response = await axios.get(
"https://raw.githubusercontent.com/frontend-coding-exercise/main/data/dataset.json"
);
dispatch(getData(response.data));
}
}
create a function that will extract data from redux. state is redux current state, ownProps is your component props, like <Component customProp={1}/>
const mapStateToProps = (state: any, ownProps: any) => {
return {
...ownProps,
myProps: state.user
};
};
In your connect Function pass the function which will store your data in redux state
export default connect(mapStateToProps, { getMyData })(MyReactComponent);
that way you'll be able to access your data via your component props and also access your getMyData function and also the redux state you mapped to props
props.getMyData();
I am trying to refactor code from Javascript React to Typescript React.
I have an action here which performs an API call and returns a function with dispatch
My UserActions.ts file looks like this
export const login = ({username, password}) => async (dispatch: any) => {
try {
let r = await apiCall(); //performing api call here
return r.code; //returning the custom responseCode here from my server
console.log(auth)
} catch (err) {
throw new Error(err);
}
}
In my component file MyComponent.ts there is a member function for the component class
public formSubmit = (e: React.FormEvent) => {
e.preventDefault();
const { password, email } = this.state;
this.props.login({
email,
password,
}).then(responseCode => {
//I want to access responseCode here
});
}
The connection to Redux looks like this in MyComponent.ts
const mapStateToProps = (store: IAppState) => {
return {
}
}
export default connect(mapStateToProps, {
login
})(SignIn);
So, as you can see, the login actually returns a function which is passed into Redux Thunk Middleware and is then passed in this.props to MyComponent
To access return variable from function, I have to type the outer function in login action UserActions.ts.
So how do I access with proper types ? Because typescript won't allow me to put this.props.login().then() in MyComponent.ts
The best practise is to dispatch action and populate the data to reducer. Try to avoid passing data back from async functions. You can have a AuthReducer and update the data and use it directly in the component.
export const login = ({username, password}) => async (dispatch: any) => {
let r = await apiCall(); //performing api call here
dispatch({ type: 'UPDATE_AUTH', payload: auth })
}
and in your reducer
case 'UPDATE_AUTH':
return { ...state, ...action.payload }
and in your component
const mapStateToProps = ({ auth }) => ({ auth })
export default connect(mapStateToProps, {
login
})(SignIn);
I didn't know the actual Typescript way to do it, so I used the any workaround
Instead of doing
this.props.login({
email,
password,
}).then(responseCode => {
//I want to access responseCode here
});
You can just do
const responseCode:any = await this.props.login({email, password});
Typescript is checking await here and I'm casting the returned object to any and in runtime the actual responseCode is returned from Redux Thunk
I'm trying to figure out the best way to display a sweetalert message after a successful async action. So I have an ExcursionDetail component that allows you to book the excursion. Here is the simplified component:
class ExcursionDetails extends Component {
bookExcursion() {
const userId = jwt_decode(localStorage.getItem('token')).sub;
this
.props
.actions
.bookExcursion(userId, this.props.match.params.id);
}
render() {
....
<RaisedButton label="Book Excursion" onClick={e => this.bookExcursion()}/>
....
)
}
}
const mapStateToProps = (state, ownProps) => {
return {excursion: state.excursion}
}
const mapDispatchToProps = (dispatch) => {
return {
actions: bindActionCreators(ExcursionActions, dispatch)
}
}
export default connect(mapStateToProps, mapDispatchToProps)(ExcursionDetails);
The action creator:
export const bookExcursion = (userId, excursionId) => {
return (dispatch, state) => {
dispatch(requestBookExcursions())
return ExcursionApi
.bookExcursion(userId, excursionId)
.then(resp => {
if (resp.ok) {
return resp
.json()
.then(payload => {
dispatch(bookExcursionsSuccess(payload.data));
})
}
}).catch(err => {
dispatch(bookExcursionsFailed(err));
})
}
}
What would be the best practice to then display the sweet alert notification? The options I thought of were:
Add a bookSuccess property that I can view if true or false in my ExcursionDetails component and if true call the sweetalert function.
Create notification specific actions and reducers and listen for it in my components. Only issue with this is I would need to have some sort of setTimeout after every notification call to clear the notification reducer and this seems a bit hacky.
call the sweet alert function within my reducer
pass a callback to the action creator
redux-thunk returns a promise; however even if the http call fails it will return a successful promise so this option doesn't seem viable.
I would and is using the first option that you mentioned.
I have created a new component and pass the redux store using connect. I check for it if the value is true on componentWillReceiveProps and set the state according and then you can display your sweetalert.
Well you can call it in the action creator.
You can use something like toastr.
Simple and clean.
export const bookExcursion = (userId, excursionId) => {
return (dispatch, state) => {
dispatch(requestBookExcursions())
return ExcursionApi
.bookExcursion(userId, excursionId)
.then(resp => {
if (resp.ok) {
return resp
.json()
.then(payload => {
dispatch(bookExcursionsSuccess(payload.data));
//Here is your notification.
toastr.success('Have fun storming the castle!', 'Miracle Max Says')
})
}
}).catch(err => {
dispatch(bookExcursionsFailed(err));
})
}
}
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();
};