Unhandled Rejection (TypeError): Cannot read properties of undefined (reading 'data') in my cartActions.js - javascript

I am trying to load a users' cart upon authentication using axios in my react app but it returns an error saying "cannot read properties of undefined(reading 'data')". The error highlights my cartAction.js:15 which is a .catch method that returns the error response data and status but when i console.log the response from my axios.get(/api/cart/${id}) it logs the response successfully in my console but it doesn't render in my react app. I think the problem is from my Cart.js component code but i can't seem to rectify it. The console.log(err) in my cartAction.js getCart constant printed "Error: Checkout(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null." in my console
Here is my cartAction.js code
> export const getCart = (id) => dispatch => {
> dispatch(setCartLoading());
> axios.get(`/api/cart/${id}`)
> .then(res => dispatch({
> type: GET_CART,
> payload: res.data
> })
> )
> .catch(err => {
> console.log(err)
> dispatch(returnErrors(err.response.data, err.response.status))}); }
>
> export const addToCart = (id, productId, quantity) => dispatch => {
> axios.post(`/api/cart/${id}`, {productId, quantity})
> .then(res => dispatch({
> type: ADD_TO_CART,
> payload: res.data
> }))
> .catch(err => dispatch(returnErrors(err.response.data, err.response.status))); }
>
> export const deleteFromCart = (userId, itemId) => dispatch => {
> axios.delete(`/api/cart/${userId}/${itemId}`)
> .then(res => dispatch({
> type: DELETE_FROM_CART,
> payload: res.data
> }))
> .catch(err => dispatch(returnErrors(err.response.data, err.response.status))); }
>
> export const setCartLoading = () => {
> return{
> type: CART_LOADING
> } }
Here is my component/Cart.js code
class Cart extends Component{
state = {
loaded: false
}
static propTypes = {
getCart: PropTypes.func.isRequired,
isAuthenticated: PropTypes.bool,
addToCart: PropTypes.func.isRequired,
deleteFromCart: PropTypes.func.isRequired,
user: PropTypes.object.isRequired,
cart: PropTypes.object.isRequired,
checkout: PropTypes.func.isRequired
}
getCartItems = async (id) => {
await this.props.getCart(id);
this.state.loaded = true;
}
onDeleteFromCart = (id, itemId) => {
this.props.deleteFromCart(id, itemId);
}
render(){
const user = this.props.user;
if(this.props.isAuthenticated && !this.props.cart.loading && !this.state.loaded){
this.getCartItems(user._id);
}
return(
<div>
<AppNavbar/>
{this.props.isAuthenticated ?
<Fragment>
{ this.props.cart.cart ? null :
<Alert className="text-center" color="info">Your cart is empty!</Alert>
}
</Fragment>
: <Alert className="text-center" color="danger">Login to View</Alert>
}
{this.props.isAuthenticated && !this.props.cart.loading && this.state.loaded && this.props.cart.cart ?
<Container>
<div className="row">
{this.props.cart.cart.items.map((item)=>(
<div className="col-md-4">
<Card>
<CardBody>
<CardTitle tag="h5">{item.name}</CardTitle>
<CardSubtitle>NGN {item.price}</CardSubtitle>
<CardText>Quantity - {item.quantity}</CardText>
<Button color="danger" onClick={this.onDeleteFromCart.bind(this, user._id, item.productId)}>Delete</Button>
</CardBody>
</Card>
<br/>
</div>
))}
<div className="col-md-12">
<Card>
<CardBody>
<CardTitle tag="h5">Total Cost = NGN. {this.props.cart.cart.bill}</CardTitle>
<Checkout
user={user._id}
amount={this.props.cart.cart.bill}
checkout={this.props.checkout}
/>
</CardBody>
</Card>
</div>
</div>
</Container>
: null
}
</div>
);
}
}
const mapStateToProps = (state) => ({
cart: state.cart,
isAuthenticated: state.auth.isAuthenticated,
user: state.auth.user
})
export default connect(mapStateToProps, {getCart, deleteFromCart, checkout})(Cart);
Here's my cartReducer.js code
const initialState = {
cart: null,
loading: false
}
export default function cartReducer (state=initialState, action){
switch(action.type){
case GET_CART:
return {
...state,
cart: action.payload,
loading: false
}
case ADD_TO_CART:
return {
...state,
cart: action.payload
}
case DELETE_FROM_CART:
return {
...state,
cart: action.payload
}
case CART_LOADING:
return {
...state,
loading: true
}
default:
return state;
}
}

I think I see the issue now. You are issuing a side-effect from your render method. render is to be considered a synchronous, pure function. If you need to fetch data it should be in one of the component lifecycle methods, i.e. componentDidMount and/or componentDidUpdate.
Move the "getCartItems" conditional logic into a utility function and invoke from the componentDidMount lifecycle method.
fetchCartItems = () => {
const { cart, isAuthenticated, user } = this.props;
const { loaded } = this.state;
if (isAuthenticated && !cart.loading && !loaded) {
this.getCartItems(user._id);
}
}
componentDidMount() {
this.fetchCartItems();
}
...
render() {
const { user } = this.props;
return (
<div>
...
</div>
);
}
And if there's a chance you may need to again fetch the cart items later after the component has mounted, for example, the authentication status changes, use the componentDidUpdate method.
componentDidUpdate() {
this.fetchCartItems();
}

Related

Category.js:55 Uncaught TypeError: categories is not iterable

I want to display Categories and Category Children in the admin dashboard and the terminal gives no error. But the page consol renders Category.js:55 Uncaught TypeError: categories is not iterable
Uncaught (in promise) TypeError: categories is not iterable
Category Component:
import React, { useEffect ,useState } from 'react';
import { Container, Row, Col ,Modal ,Button} from 'react-bootstrap';
import Layout from '../../components/Layout/Layout'
import { useDispatch, useSelector } from 'react-redux'
import { getAllCategory } from '../../actions'
import Input from '../../components/UI/Input/Input'
import {addCategory} from '../../actions/category.actions'
const Category = () => {
const category = useSelector(state => state.category)
const [categoryName , setCategoryName] =useState('')
const [parentCategoryId , setParentCategoryId] =useState('')
const [categoryImage , setCategoryImage] =useState('')
const dispatch = useDispatch()
useEffect(() => {
console.log('Category.js')
dispatch(getAllCategory())
}, [])
const [show, setShow] = useState(false);
const handleClose = () => {
const form = new FormData()
// const cat ={
// categoryName,
// parentCategoryId,
// categoryImage
// }
form.append('name',categoryName)
form.append('parentId',parentCategoryId)
form.append('categoryImage',categoryImage)
dispatch(addCategory(form))
// console.log('cat',cat)
setShow(false);
}
const handleShow = () => setShow(true);
const renderCategories = (categories) => {
let myCategories = []
for (let category of categories) {
myCategories.push(
<li key={Math.random()}>
{category.name}
{category.children.length > 0 ? (<ul>{renderCategories(category.children)}</ul>) : null}
</li>
)
}
return myCategories;
}
const createCategoryList=(categories,options=[])=>{
for(let category of categories) {
options.push({value: category._id , name: category.name})
if(category.children.length > 0){
createCategoryList(category.children, options)
}
}
return options;
}
const handelCategoryImage =(e)=>{
setCategoryImage(e.target.files[0])
}
return (
<>
<Layout sidebar>
<Container>
<Row>
<Col md={12}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<h3>Category</h3>
<button onClick={handleShow}>Add</button>
</div>
</Col>
</Row>
<Row>
<Col md={12}>
<ul>
{renderCategories(category.categories)}
</ul>
</Col>
</Row>
</Container>
<Modal show={show} onHide={handleClose}>
<Modal.Header closeButton>
<Modal.Title>Add New Category</Modal.Title>
</Modal.Header>
<Modal.Body>
<Input
value={categoryName}
placeholder={'Category Name'}
onChange={(e)=>setCategoryName(e.target.value)}
/>
<select className="form-control" onChange={(e)=>setParentCategoryId(e.target.value)} value={parentCategoryId}>
<option>Select Category</option>
{
createCategoryList(category.categories).map(option =>
<option key={option.value} value={option.value}>{option.name}</option>)
}
</select>
<input type='file' name='categoryImage' onChange={handelCategoryImage}/>
</Modal.Body>
<Modal.Footer>
<Button variant="primary" onClick={handleClose}>
Save Changes
</Button>
</Modal.Footer>
</Modal>
</Layout>
</>
);
};
export default Category;
Category.action.js
import axios from "axios"
import axiosInstance from "../helpers/axios"
import {categoryConstants} from './constants'
export const getAllCategory =()=>{
return async dispatch => {
dispatch({type: categoryConstants.GET_ALL_CATEGORIES_REQUEST})
const res =await axios.get('http://localhost:2000/api/category/getcategory')
console.log("res",res)
if(res.status === 200) {
// const {categoryList} = res.data
// console.log("categoryList",categoryList)
dispatch({
type:categoryConstants.GET_ALL_CATEGORIES_SUCCESS,
payload: {category:res.data.category}
})
}else{
dispatch({
type: categoryConstants.GET_ALL_CATEGORIES_FAILURE,
payload: {error: res.data.error}
})
}
}
}
export const addCategory =(form) => {
const token =window.localStorage.getItem('token')
return async dispatch => {
dispatch({ type: categoryConstants.ADD_NEW_CATEGORY_REQUEST})
const res = await axios.post('http://localhost:2000/api/category/create',form,{headers:{
'Authorization':token ? `Bearer ${token}` :''
}})
if(res.status === 200){
dispatch({
type: categoryConstants.ADD_NEW_CATEGORY_SUCCESS,
payload:res.data.category
})
}else{
dispatch({
type: categoryConstants.ADD_NEW_CATEGORY_FAILURE,
payload:res.data.error
})
}
console.log("res", res)
}
}
category.reducer.js
import {categoryConstants} from '../actions/constants'
const initState ={
categories:[],
loading:false,
error:null,
}
const buildNewCategories =(categories,category)=>{
let myCategories=[]
for(let cat of categories){
myCategories.push({
...cat,
children: cat.children && cat.children.length > 0 ? buildNewCategories(cat.children,category):[]
})
}
return myCategories;
}
export default (state = initState , action)=>{
switch(action.type){
case categoryConstants.GET_ALL_CATEGORIES_SUCCESS:
state={
...state,
categories: action.payload.categories
}
break;
case categoryConstants.ADD_NEW_CATEGORY_REQUEST:
state={
...state,
loading: true,
}
break;
case categoryConstants.ADD_NEW_CATEGORY_SUCCESS:
const updatedCategories=buildNewCategories(state.categories, action.payload.category)
console.log('updated categoires', updatedCategories);
state={
...state,
categories:updatedCategories,
loading: false,
}
break;
case categoryConstants.ADD_NEW_CATEGORY_FAILURE:
state={
...initState,
}
break;
}
return state;
}
When you call const category = useSelector(state => state.category) to get category , you was not sure whether or not category has been fetched successfully yet ( focus on the calling getAllCategory() on your useEffect ).
You just need to check before iterate categories , and some refactor your code like this is fine:
const renderCategories = (categories) => {
if(!Array.isArray(categories)) return null
return categories.map((category, i) => (
<li key={`category-${i}`}>
{category.name}
{Array.isArray(category.children) && category.children.length > 0 ? (
<ul>{renderCategories(category.children)}</ul>
) : null}
</li>)
)
}
Also you can wrap your function renderCategories with useCallback to make more effective
import { useCallback } from 'react'
const renderCategories = useCallback((categories) => {
if(!Array.isArray(categories)) return null
return categories.map((category, i) => (
<li key={`category-${i}`}>
{category.name}
{Array.isArray(category.children) && category.children.length > 0 ? (
<ul>{renderCategories(category.children)}</ul>
) : null}
</li>)
)
}, [])

Why local state also changed when redux store updated

Now I'm building an application using react redux store and local store.
I have two components "tweetTable_Comp" and "likeButton_Comp".
The redux store has all the tweets record "tweets" fetched by an API, and tweetTable_Comp has a local state "filteredTweets" so as to add filter function later and show only selected genre tweets.
And every tweet has likingUserIds.
tweetTable_Comp passes likingUserIds as props to likeButton_Comp so that it can add different style depending on if you already liked the tweet or not.
The problem here is that changing the "tweets[indexNum].likingUserIds" in the redux store when user push like button also affects on the local state "filteredTweets[indexNum].likingUserIds".
I was gonna change the redux info and local state info one by one like in deleteTweet function which already works well.
But this is not intentionally working.
Can anyone teach me why this is happening?
here is reducer.js
redux tweets has objects as below
・title(string)
・text(string)
・createdDate(string)
・likingUserIds(array)
・userId(number)
const defaultState = {
user: {
loggedIn: false,
id: 0,
account: ''
},
tweets: []
}
export default function reducer(state = defaultState, action) {
switch (action.type) {
case 'UPDATE_TWEETS':
return {
...state,
tweets: action.tweets
}
default:
return state;
}
}
here is actions.js
export function getTweets(tweets){
return {
type: 'UPDATE_TWEETS',
tweets: tweets
}
}
here is tweetTable_Comp
class TweetTable_Comp extends Component{
constructor(props){
super(props)
const {dispatch} = props;
this.action = bindActionCreators(actions, dispatch);
this.deleteButtonClicked = this.deleteButtonClicked.bind(this)
this.editButtonClicked = this.editButtonClicked.bind(this)
this.handleChanged = this.handleChanged.bind(this)
this.state = {
filteredTweets: [],
searchWord: ""
}
}
handleChanged(e){
this.setState({[e.target.name]: e.target.value})
}
deleteButtonClicked(id, index){
confirm("削除しますか?") &&
this.deleteTweet(id, index)
}
editButtonClicked(id){
this.props.history.push("/edit/" + id)
}
deleteTweet(id, index){
fetch("http://localhost:8080/twitter/deleteTweet/" + id, {
method: "DELETE"
})
.then((response) => {
if(response.status === 200) {
const newList = this.props.tweets.slice()
newList.splice(index, 1)
this.action.getTweets(newList)
this.setState({filteredTweets: newList})
}
})
}
componentDidMount(){
fetch("http://localhost:8080/twitter/sendAllTweets", {
method: "GET"
})
.then((response) => {
response.json()
.then(json => {
this.action.getTweets(json)
this.setState({filteredTweets: json.slice()})
})
})
}
render(){
return(
<>
<h1 className="text-center">tweet一覧</h1>
<SearchBar searchWord={this.state.searchWord} handleChanged={this.handleChanged}/>
<Container>
<Row>
<Col>
<br/>
<br/>
<Table striped bordered hover>
<thead>
<tr className="text-center">
<th>投稿日</th>
<th>投稿者</th>
<th>タイトル</th>
<th>内容</th>
<th>いいね</th>
<th>削除</th>
<th>編集</th>
</tr>
</thead>
<tbody>
{ this.state.filteredTweets.map((tweet, index) => (
<tr key={tweet.id}>
<td className="text-center">{tweet.createdDate}</td>
<td className="text-center">{tweet.user.account}</td>
<td>{tweet.title}</td>
<td>{tweet.text}</td>
<td className="text-center">
<LikeButton likingUserIds={tweet.likingUserIds} index={index} id={tweet.id} />
</td>
<td className="text-center">
<Button variant="outline-secondary" onClick={() => this.deleteButtonClicked(tweet.id, index)}>
<FontAwesomeIcon icon={faTrashAlt} />
</Button>
</td>
<td className="text-center">
<Button variant="outline-secondary" onClick={() => this.editButtonClicked(tweet.id)}>
<FontAwesomeIcon icon={faEdit} />
</Button>
</td>
</tr>
))
}
</tbody>
</Table>
</Col>
</Row>
</Container>
</>
)
}
}
TweetTable_Comp.propTypes = {
dispatch: PropTypes.func,
tweets: PropTypes.array,
history: PropTypes.object,
user:PropTypes.object
}
function mapStateToProps(state){
return state
}
export default withRouter(connect(mapStateToProps)(TweetTable_Comp))
here is likeButton_Comp
class LikeButton_Comp extends Component {
constructor(props){
super(props)
const {dispatch} = props
this.action = bindActionCreators(actions, dispatch)
this.likeButtonClicked = this.likeButtonClicked.bind(this)
}
likeButtonClicked(func, index){
const data = {
userId:this.props.user.id,
tweetId:this.props.id
}
if(func === "unlike"){
fetch("http://localhost:8080/twitter/like", {
method: "DELETE",
body: JSON.stringify(data)
})
.then((response) => {
if(response.status === 200){
let tweets = this.props.tweets.slice()
const orgLikingUsers = this.props.tweets[index].likingUserIds.slice()
const newLikingUsers = orgLikingUsers.filter(item => item !== this.props.user.id)
tweets[index].likingUserIds = newLikingUsers
this.action.getTweets(tweets)
} else {
alert("処理に失敗しました")
}
})
.catch(error => console.error(error))
} else {
fetch("http://localhost:8080/twitter/like", {
method: "POST",
body: JSON.stringify(data)
})
.then((response) => {
if(response.status === 200){
let tweets = this.props.tweets.slice()
let likingUsers = this.props.tweets[index].likingUserIds.slice()
likingUsers.push(this.props.user.id)
tweets[index].likingUserIds = likingUsers
this.action.getTweets(tweets)
} else {
alert("処理に失敗しました")
}
})
.catch(error => console.error(error))
}
}
render(){
return(
<>
<span>{this.props.likingUserIds.length} </span>
{this.props.tweets.length > 0 && this.props.likingUserIds.includes(this.props.user.id) ?
<Button variant="outline-danger">
<FontAwesomeIcon icon={faHeart} onClick={() => this.likeButtonClicked("unlike", this.props.index)}/>
</Button> :
<Button variant="outline-secondary">
<FontAwesomeIcon icon={faHeart} onClick={() => this.likeButtonClicked("like", this.props.index)}/>
</Button>
}
</>
)
}
}
LikeButton_Comp.propTypes = {
dispatch: PropTypes.func,
user: PropTypes.object,
tweets: PropTypes.array,
likingUserIds: PropTypes.array,
index: PropTypes.number,
id: PropTypes.number
}
function mapStateToProps(state){
return state
}
export default withRouter(connect(mapStateToProps)(LikeButton_Comp))

× TypeError: Cannot read property 'dishes' of undefined

Please help!!
I created a web app using react and connected it with node js.
There I need to pass status of a dish to DishDetail Component whether it is in Favorite or not.
If it is not favorite. I have to mark it as favorite.
Whenever a person click on any dish to make it favorite, an entry is going to be added in favorite collection with user id and dish id.
But whenever a new user logs in and try to add dish as favorite as very first time.I'm facing error that ×TypeError: Cannot read property 'dishes' of undefined at favorite={this.props.favorites.favorites.dishes.some((dish) => dish._id === match.params.dishId)} statement in MainComponent.js and in var favorites = props.favorites.favorites.dishes.map((dish) statement of FavoriteDish.js.
MainComponent.js
const DishWithId = ({match}) => {
if(this.props.favorites.favorites!=null) {
if(Array.isArray(this.props.favorites.favorites)) {
this.props.favorites.favorites=this.props.favorites.favorites[0];
}
}
return(
(this.props.auth.isAuthenticated && !this.props.favorites.isLoading)
?
<DishDetail dish={this.props.dishes.dishes.filter((dish) => dish._id === match.params.dishId)[0]}
isLoading={this.props.dishes.isLoading}
errMess={this.props.dishes.errMess}
comments={this.props.comments.comments.filter((comment) => comment.dish === match.params.dishId)}
commentsErrMess={this.props.comments.errMess}
postComment={this.props.postComment}
favorite={this.props.favorites.favorites.dishes.some((dish) => dish._id === match.params.dishId)}
postFavorite={this.props.postFavorite}
/>
:
<DishDetail dish={this.props.dishes.dishes.filter((dish) => dish._id === match.params.dishId)[0]}
isLoading={this.props.dishes.isLoading}
errMess={this.props.dishes.errMess}
comments={this.props.comments.comments.filter((comment) => comment.dish === match.params.dishId)}
commentsErrMess={this.props.comments.errMess}
postComment={this.props.postComment}
favorite={false}
postFavorite={this.props.postFavorite}
/>
);
}
<Route path="/menu/:dishId" component={DishWithId} />
<PrivateRoute exact path="/favorites" component={() => <Favorites favorites {this.props.favorites} deleteFavorite={this.props.deleteFavorite} />} />
DishDetail.js
const DishDetail = (props) => {
return <RenderDish dish={props.dish} favorite={props.favorite} postFavorite={props.postFavorite} />
}
function RenderDish({dish, favorite, postFavorite}) {
return(
<div className="col-12 col-md-5 m-1">
<FadeTransform in
transformProps={{
exitTransform: 'scale(0.5) translateY(-50%)'
}}>
<Card>
<CardImg top src={baseUrl + dish.image} alt={dish.name} />
<CardImgOverlay>
<Button outline color="primary" onClick={() => favorite ? console.log('Already favorite') : postFavorite(dish._id)}>
{favorite ?
<span className="fa fa-heart"></span>
:
<span className="fa fa-heart-o"></span>
}
</Button>
</CardImgOverlay>
<CardBody>
<CardTitle>{dish.name}</CardTitle>
<CardText>{dish.description}</CardText>
</CardBody>
</Card>
</FadeTransform>
</div>
);
}
FavoriteDish.js
if (props.favorites.favorites) {
if(Array.isArray(props.favorites.favorites))
props.favorites.favorites=props.favorites.favorites[0];
var favorites = props.favorites.favorites.dishes.map((dish) => {
return (
<div key={dish._id} className="col-12 mt-5">
<RenderMenuItem dish={dish} deleteFavorite={props.deleteFavorite} />
</div>
);
});
}
favorite reducer
import * as ActionTypes from './ActionTypes';
export const favorites = (state = {
isLoading: true,
errMess: null,
favorites: null
}, action) => {
switch(action.type) {
case ActionTypes.ADD_FAVORITES:
return {...state, isLoading: false, errMess: null, favorites: action.payload};
case ActionTypes.FAVORITES_LOADING:
return {...state, isLoading: true, errMess: null, favorites: null};
case ActionTypes.FAVORITES_FAILED:
return {...state, isLoading: false, errMess: action.payload, favorites: null};
default:
return state;
}
}
ActionCreator.js
export const fetchFavorites = () => (dispatch) => {
dispatch(favoritesLoading(true));
const bearer = 'Bearer ' + localStorage.getItem('token');
return fetch(baseUrl + 'favorites', {
headers: {
'Authorization': bearer
},
})
.then(response => {
if (response.ok) {
return response;
}
else {
var error = new Error('Error ' + response.status + ': ' + response.statusText);
error.response = response;
throw error;
}
},
error => {
var errmess = new Error(error.message);
throw errmess;
})
.then(response => response.json())
.then(favorites => dispatch(addFavorites(favorites)))
.catch(error => dispatch(favoritesFailed(error.message)));
}
export const favoritesLoading = () => ({
type: ActionTypes.FAVORITES_LOADING
});
export const favoritesFailed = (errmess) => ({
type: ActionTypes.FAVORITES_FAILED,
payload: errmess
});
export const addFavorites = (favorites) => ({
type: ActionTypes.ADD_FAVORITES,
payload: favorites
});
Got idea from Giovanni Esposito.
i just changed this line:- favorite={this.props.favorites.favorites.dishes.some((dish) => dish._id === match.params.dishId)}
to this :- favorite={this.props.favorites.favorites ? this.props.favorites.favorites.dishes.some((dish) => dish._id === match.params.dishId): false}
Ciao, you could modify this line (in DishWithId):
(this.props.auth.isAuthenticated && !this.props.favorites.isLoading)
with:
(this.props.auth.isAuthenticated && !this.props.favorites.isLoading && this.props.favorites.favorites)
and this line (in FavoriteDish.js):
var favorites = props.favorites.favorites.dishes.map((dish)...
with:
if (props.favorites.favorites) {var favorites = props.favorites.favorites.dishes.map((dish)...}
Check condition in favorites such that in case favourite is undefined. It automatically sends false there...
favorite={this.props.favorites.favorites ? this.props.favorites.favorites.dishes.some((dish) => dish._id === match.params.dishId): false}

createStore not changing from initial state to Action proved value

Im trying to get access a variable called isSuperAdmin, It basically tells me if the logged in user is a super admin or not allowing me to disable some features.
I currently have no access to the variable in the current page however my redux action is showing it as being there, I think I may have configured something incorrectly, as of now my code doesn't change from the initial state value of null to the bool value isSuperUser. Here is the page that I am trying to use this variable.
import React, { PropTypes } from 'react';
import withStyles from 'isomorphic-style-loader/lib/withStyles';
import { connect } from 'react-redux';
import Modal from '../Modal';
import Summary from '../Summary';
import s from './BookingDetailsModal.scss';
import AmendConsumerDetails from './AmendConsumerDetails';
import ChangeBookingSession from './ChangeBookingSession';
import payReservationCashActionCreator from '../../actions/payReservationCash';
import payReservationCardActionCreator from '../../actions/payReservationCard';
import payRestActionCreator from '../../actions/payRest';
import refundCashActionCreator from '../../actions/refundCash';
import cancelReservationActionCreator from '../../actions/cancelReservation';
import formatPrice from '../../../../../core/formatPrice';
import {
BOXOFFICE_HIDE_BOOKING_DETAILS,
BOXOFFICE_SET_BOOKING_DETAILS_ACTION_TYPE,
resendConfirmationEmail as resendConfirmationEmailActionCreator,
} from '../../actions';
function renderActionButtons({
isSuperAdmin,
setActionType,
resendConfirmationEmail,
order: {
type: orderType,
paid: orderPaid,
amount: orderAmount,
refundedAt: orderRefundedAt,
canceledAt: orderCanceledAt,
sessionId,
},
isCreatingPayment,
payReservationCard,
payReservationCash,
payRest,
refundCash,
cancelReservation,
}) {
debugger;
return (
<div className={s.buttonsContainer}>
<div className={s.buttonsContainer}>
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
setActionType('AMEND_CONSUMER_DETAILS');
}}
>Amend consumer details</button>
</div>
{ sessionId ?
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
setActionType('CHANGE_SESSION');
}}
>Move to another session</button>
</div> : null
}
<div className={s.buttonContainer}>
<button disabled>Amend tickets or products</button>
</div>
{ orderType === 'reservation' && isCreatingPayment && !orderPaid ?
<div>
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
payReservationCash();
}}
>Pay Reservation CASH</button>
</div>
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
payReservationCard();
}}
>Pay Reservation CARD</button>
</div>
</div> :
null
}
{ orderType === 'deposit' && isCreatingPayment && !orderPaid ?
<div>
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
payRest('CASH');
}}
>Pay Rest CASH</button>
</div>
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
payRest('CARD');
}}
>Pay Rest CARD</button>
</div>
</div> :
null
}
{ !orderRefundedAt && orderPaid ?
<div className={s.buttonContainer}>
<button
disabled={isSuperAdmin}
onClick={(e) => {
e.preventDefault();
refundCash(orderAmount);
}}
>Refund CASH, {formatPrice(orderAmount)}</button>
</div> : null
}
{ orderCanceledAt === null && orderType === 'reservation' ?
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
cancelReservation();
}}
>Cancel Reservation</button>
</div> : null
}
<div className={s.buttonContainer}>
<button
onClick={(e) => {
e.preventDefault();
resendConfirmationEmail();
}}
>Resend confirmation email</button>
</div>
</div>
</div>
);
}
renderActionButtons.propTypes = {
isSuperAdmin: PropTypes.bool.isRequired,
setActionType: PropTypes.func.isRequired,
resendConfirmationEmail: PropTypes.func.isRequired,
order: PropTypes.shape({
type: PropTypes.string.isRequired,
paid: PropTypes.bool.isRequired,
sessionId: PropTypes.string.isRequired,
amount: PropTypes.number.isRequired,
// reservationPaidCashAt: PropTypes.string.isRequired,
// reservationPaidCardAt: PropTypes.string.isRequired,
}).isRequired,
payReservationCard: PropTypes.func.isRequired,
payReservationCash: PropTypes.func.isRequired,
payRest: PropTypes.func.isRequired,
isCreatingPayment: PropTypes.bool.isRequired,
refundCash: PropTypes.func.isRequired,
cancelReservation: PropTypes.func.isRequired,
};
const components = {
AMEND_CONSUMER_DETAILS: AmendConsumerDetails,
CHANGE_SESSION: ChangeBookingSession,
};
function renderAction(actionType, props) {
const Component = components[actionType];
return <Component {...props} />;
}
function BookingDetailsModal(props) {
const { hideOrderDetails, orderId, bookingDetailsActionType } = props;
return (
<Modal onClose={hideOrderDetails}>
<div className={s.container}>
<div className={s.summaryContainer}>
<Summary orderId={orderId} withEdits={false} />
</div>
<div className={s.actionsContainer}>
{bookingDetailsActionType ?
renderAction(bookingDetailsActionType, props) :
renderActionButtons(props)
}
</div>
</div>
</Modal>
);
}
BookingDetailsModal.propTypes = {
orderId: PropTypes.string.isRequired,
hideOrderDetails: PropTypes.func.isRequired,
bookingDetailsActionType: PropTypes.oneOf([
'AMEND_CONSUMER_DETAILS',
]),
};
const mapStateToProps = (state, { orderId }) => (
{
ui: { bookingDetailsActionType },
ui: { isSuperAdmin },
orders: {
data: { [orderId]: order },
edits: { [orderId]: orderEdits },
},
}
) => ({
bookingDetailsActionType,
isSuperAdmin,
order,
isCreatingPayment: orderEdits.isCreatingPayment,
});
const mapDispatchToProps = (dispatch, { orderId }) => ({
hideOrderDetails: () => dispatch({ type: BOXOFFICE_HIDE_BOOKING_DETAILS }),
setActionType: actionType =>
dispatch({ type: BOXOFFICE_SET_BOOKING_DETAILS_ACTION_TYPE, actionType }),
resendConfirmationEmail: () => dispatch(resendConfirmationEmailActionCreator(orderId)),
payReservationCard: () => dispatch(payReservationCardActionCreator(orderId)),
payReservationCash: () => dispatch(payReservationCashActionCreator(orderId)),
payRest: type => dispatch(payRestActionCreator(orderId, type)),
refundCash: amount => dispatch(refundCashActionCreator(orderId, amount)),
cancelReservation: () => dispatch(cancelReservationActionCreator(orderId)),
});
export default connect(mapStateToProps, mapDispatchToProps)(withStyles(s)(BookingDetailsModal));
My Redux tab on page load shows the following:
type(pin): "BOXOFFICE_IS_SUPER_USER"
isSuperAdmin(pin): true
This is how I have used createStore to access the variable:
const isSuperAdmin = createStore(null, {
[BOXOFFICE_IS_SUPER_USER]: isSuperAdmin => isSuperAdmin,
});
I then proceeded to add it to the reducer at the bottom.
edit I have changed the variable isSuperAdmin in the createStore to true and this can be read perfectly fine, it must now be an issue with the variable passed to the action in the first place.
Here is the code where I get the value of the variable and pass it on:
Export default ({ knex }) => authenticateAdmin(knex)(
async (req, res) => {
try {
const { eventId } = req;
const event = await fetchEvent(knex, eventId);
const isSuperAdmin = await res.isSuperAdmin;
res.send({ event, isSuperAdmin});
} catch (err) {
res.send(err.stack);
console.error(err.stack); // eslint-disable-line no-console
throw err;
}
}
);
And the dispatch:
export const fetchEvent = () => async (dispatch, getState) => {
try {
const state = getState();
const { auth: { password } } = state;
const response = await fetch('/api/event', {
headers: {
Accept: 'application-json',
'X-Password': password,
},
});
if (response.status === 200) {
const { event, isSuperAdmin } = await response.json();
dispatch({ type: BOXOFFICE_SET_EVENT, event });
dispatch({ type: BOXOFFICE_IS_SUPER_USER, isSuperAdmin });
} else {
localStorage.removeItem('password');
dispatch({ type: BOXOFFICE_UNAUTHENTICATE });
}
} catch (err) {
console.log(err); // eslint-disable-line no-console
throw err;
}
};
EDIT
Here is the reducer:
export default combineReducers({
isSuperAdmin, ------- My variable
isProcessingPayment,
isSelectDateCollapsed,
isLoadingBookings,
shouldShowBookings,
shouldShowDepositModal,
shouldShowReservationModal,
shouldShowConsumerDetailsModal,
shouldShowDiscountModal,
shouldShowOrderConfirmationModal,
bookingFilter,
selectedOrderId,
sendConfirmationEmail,
bookingIds,
orderDetailsId,
bookingDetailsActionType,
});
I guess the way you defined your mapStateToProps is incorrect.
Updated the code
try following:
const mapStateToProps = ({
ui: {
bookingDetailsActionType,
isSuperAdmin
},
orders: {
data,
edits
}
}, {
orderId
}) => {
const order = data[orderId],
orderEdits = edits[orderId];
return {
bookingDetailsActionType,
isSuperAdmin,
order,
isCreatingPayment: orderEdits.isCreatingPayment
};
};
I finally have a solution! Turns out my issue was not setting a property type for my isSuperUser variable. Despite my colleague telling me that it will work without any property type (which still makes sense to me and confuses me as to why it wont work?!).
A simple change in the index.js file from:
[BOXOFFICE_IS_SUPER_USER]: isSuperAdmin => isSuperAdmin,
to
[BOXOFFICE_IS_SUPER_USER]: (state, { isSuperAdmin }) => isSuperAdmin,
and adding a property type to the show.js file where I used res.send()
res.send({ event, isSuperAdmin: isSuperAdmin});
Im still at a loss as to why it won't work with no property type but oh well...!

Getting error TypeError: Cannot read property 'id' of undefined using React/Redux action

I am using react/redux and the error is happening after a deleteRequest is called using an action. The reducer removes the item from the array and I think that is why this is happening, but I should be changing the location so it should not be rendering this page anymore.
The direct error is coming from the Post component down below from the this.props.deleteRecord in the catch block.
I am also using turbo which is how I make the deleteRequest and store the data. If you need a reference here is the docs. https://www.turbo360.co/docs
Reducer:
import constants from '../constants';
const initialState = {
all: null
};
export default (state = initialState, action) => {
switch (action.type) {
case constants.POST_CREATED:
return {
...state,
all: state.all.concat(action.data),
[action.data.id]: action.data
};
case constants.RECORD_UPDATED:
return {
...state,
[action.data.id]: action.data,
all: all.map(item => (item.id === action.data.id ? action.data : item))
};
case constants.RECORD_DELETED:
const newState = {
...state,
all: state.all.filter(item => item.id !== action.data.id)
};
delete newState[action.payload.id];
return newState;
case constants.FETCH_POSTS:
const sortedData = action.data.sort((a, b) => {
return new Date(b.timestamp) - new Date(a.timestamp);
});
return { ...state, all: sortedData };
case constants.FETCH_POST:
return { ...state, [action.data.id]: action.data };
default:
return state;
}
};
Component:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
import swal from 'sweetalert2/dist/sweetalert2.all.min.js';
import actions from '../../actions';
import { DateUtils } from '../../utils';
import { Reply } from '../containers';
import { UpdateRecord } from '../view';
class Post extends Component {
constructor() {
super();
this.state = {
editShow: false
};
}
componentDidMount() {
const { id } = this.props.match.params;
if (this.props.posts[id] != null) {
return;
}
this.props
.getRecord(id)
.then(() => {})
.catch(err => {
console.log(err);
});
}
updateRecord(params) {
const { id } = this.props.match.params;
const post = this.props.posts[id];
const { currentUser } = this.props.user;
if (post.profile.id !== currentUser.id) {
swal({
title: 'Oops...',
text: 'Must be owner of post',
type: 'error'
});
return;
}
this.props
.updateRecord(post, params)
.then(response => {
swal({
title: 'Success',
text: `${currentUser.username} Your post has been updated!`,
type: 'success'
});
})
.catch(err => {
console.log(err);
});
}
deleteRecord() {
const { id } = this.props.match.params;
const post = this.props.posts[id];
const { currentUser } = this.props.user;
if (currentUser.id !== post.profile.id) {
swal({
title: 'Oops...',
text: 'Must be owner of post',
type: 'error'
});
return;
}
this.props
.deleteRecord(post)
.then(() => {
this.props.history.push('/');
swal({
title: 'Post Delete',
text: 'Please create a new post',
type: 'success'
});
})
.catch(err => {
console.log(err);
});
}
render() {
const { id } = this.props.match.params;
const post = this.props.posts[id];
const { currentUser } = this.props.user;
if (post == null) {
return <div />;
}
return (
<div>
<div className="jumbotron">
<h1 className="display-3">{post.title}</h1>
<div className="row" style={{ marginBottom: '25px' }}>
<img className="img-fluid mx-auto" src={`${post.image}`} style={{ maxHeight: '400px' }} />
</div>
<p className="lead">{post.text}</p>
<hr className="my-4" />
{post.video == undefined ? null : (
<div className="row justify-content-center">
<div className="col-8">
<div className="lead" style={{ marginBottom: '25px' }}>
<div className="embed-responsive embed-responsive-16by9">
<video style={{ background: 'black' }} width="800" controls loop tabIndex="0">
<source src={post.video} type={post.videoType} />
Your browser does not support HTML5 video.
</video>
</div>
</div>
</div>
</div>
)}
<div className="lead">
<Link to={`/profile/${post.profile.id}`}>
<button className="btn btn-secondary btn-lg">{post.profile.username}</button>
</Link>
<p style={{ marginTop: '10px' }}>{DateUtils.relativeTime(post.timestamp)}</p>
</div>
{currentUser.id !== post.profile.id ? null : (
<div className="row justify-content-end">
<div className="col-md-2">
<button
onClick={() => {
this.setState({ editShow: !this.state.editShow });
}}
className="btn btn-success"
>
Edit
</button>
</div>
<div className="col-md-2">
<button onClick={this.deleteRecord.bind(this)} className="btn btn-danger">
Delete
</button>
</div>
</div>
)}
</div>
{this.state.editShow === false ? null : (
<div>
<UpdateRecord onCreate={this.updateRecord.bind(this)} currentRecord={post} />
</div>
)}
<div>
<Reply postId={post.id} />
</div>
</div>
);
}
}
const stateToProps = state => {
return {
posts: state.post,
user: state.user
};
};
const dispatchToProps = dispatch => {
return {
getRecord: id => dispatch(actions.getRecord(id)),
updateRecord: (entity, params) => dispatch(actions.updateRecord(entity, params)),
deleteRecord: entity => dispatch(actions.deleteRecord(entity))
};
};
export default connect(stateToProps, dispatchToProps)(Post);
Take a look here
case constants.RECORD_DELETED:
const newState = {
...state,
all: state.all.filter(item => item.id !== action.data.id)
};
delete newState[action.payload.id];
return newState;
You are using action.data when filtering, and action.payload when deleting from object

Categories

Resources