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

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

Related

How to combine the previous data with the new data in React and display it at the same time

I'm newbie to Reactjs. The problem I'm encountered:
I want to capture and display new data when I scroll down
I take the data every time I go down and save it in Redax, but because it has pagination, it only shows 10 and does not add to the previous one.
this is my code
import { memo, useEffect, useState } from "react";
import Collapse from "#kunukn/react-collapse";
import CustomHeader from "SharedComponents/CustomHeader";
import InfiniteScroll from "react-infinite-scroller";
import "./styles.scss";
import Item from "./Components/Item/Item";
import { dispatchItemToRedux, getListServiceTransactions } from "Redux/Actions";
import { useDispatch, useSelector } from "react-redux";
import CircleLoading from "SharedComponents/Loading/CircleLoading";
import EmptyList from "./Components/EmptyList";
import ReducerTypes from "Redux/Types/ReducerTypes";
const Page = () => {
const ListServiceTransactions = useSelector(
(state) => state.app.ListServiceTransactions
);
const dispatch = useDispatch();
const [loading, setLoading] = useState(false);
const [type, setType] = useState("");
const [open, setOpen] = useState(false);
const transactionItems = ListServiceTransactions?.items?.data;
const transactionPagination = ListServiceTransactions?.items;
const typeFilters = ListServiceTransactions?.type_filters;
const resultFilters = [];
useEffect(() => {
setLoading(true);
dispatch(getListServiceTransactions(null, 1));
setLoading(false);
}, []);
const handleLoadMore = () => {
const {
total,
per_page,
current_page,
next_page_url,
} = transactionPagination;
if (total > per_page && next_page_url) {
dispatch(getListServiceTransactions(type, current_page + 1)).then(
(res) => {
transactionItems.push(res);
}
);
}
};
const clickFilter = (value) => {
dispatch(getListServiceTransactions(type, 1));
setLoading(true);
setOpen(false);
setLoading(false);
};
return (
<div className="list-transactions-screen">
<CustomHeader title="لیست تراکنش های خدمات" />
<div className="p-4">
<div className="box-filter-tarnsactions mb-3">
<div className="button-open-collapse">
<div
className={`box-value-filter ${
width && open == true ? "animation-filter" : null
}`}
>
{typeTitle}
</div>
<button className="btn-filter-tarnsactions" onClick={OpenBox}>
||
</button>
</div>
<Collapse
isOpen={open}
transition="height 400ms cubic-bezier(0.4, 0, 0.2, 1)"
>
{resultFilters
? resultFilters.map((typeItem) => {
return (
<p
className="m-0 my-2 p-2 bg-white border rounded"
onClick={() => clickFilter(typeItem)}
>
{typeItem.label}
</p>
);
})
: null}
</Collapse>
</div>
<div className="all-tarnsactions-list">
{!loading ? (
transactionItems?.length > 0 ? (
transactionItems ? (
<InfiniteScroll
pageStart={1}
loadMore={handleLoadMore}
hasMore={true}
threshold={200}
loader={
!transactionPagination.next_page_url &&
transactionPagination.next_page_url === null ? null : (
<CircleLoading key={0} />
)
}
>
{transactionItems.map((item) => {
return <Item item={item} />;
})}
</InfiniteScroll>
) : (
<CircleLoading />
)
) : (
<EmptyList />
)
) : (
<CircleLoading />
)}
</div>
</div>
</div>
);
};
export default memo(Page);
enter code here
this is my action
function getListServiceTransactions(
type: any = null,
page: number = 1,
) {
return function (dispatch: ReduxDispatch, getState: ReduxGetState) {
return new Promise(function (resolve, reject) {
const params = {
type: type,
page: page,
};
axios({
method: "GET",
baseURL: Api.PUBLIC_API_V2,
url: "other-services/transaction/list",
params: params,
})
.then((res) => {
dispatch(
dispatchItemToRedux({
type: ReducerTypes.LIST_SERVICE_TRANSACTIONS,
payload: res.data.data,
})
);
resolve(res);
console.log("raft to action v req zad v in response", res);
})
.catch((err) => {
reject(err);
});
});
};
}
this is my reducer
function appReducer(state = INITIAL_STATE, action) {
switch (action.type) {
case ReducerTypes.SET_STEPPER:
return { ...state, activeStep: action.payload };
case ReducerTypes.TOGGLE_BUTTON_SPINNER:
return { ...state, showButtonSpinner: action.payload.value };
case ReducerTypes.LOCATION_PERMISSION:
return { ...state, locationPermission: action.payload };
case ReducerTypes.TOGGLE_LOADING:
return { ...state, showLoading: action.payload.value };
case ReducerTypes.TOGGLE_LOADING_EVERLAY:
return { ...state, showLoadingEverlay: action.payload.value };
case ReducerTypes.TOGGLE_SHOW_SIDEBAR:
return { ...state, showSidebar: action.payload.value };
case ReducerTypes.TOGGLE_SHOW_NOTIFICATION:
return { ...state, showNotification: action.payload.value };
case ReducerTypes.TOAST_MESSAGE_MODAL:
return {
...state,
toastMessage: { ...action.payload.toastMessage },
};
case ReducerTypes.MAIN_PAGE_DATA:
return {
...state,
mainPageState: action.payload,
};
case ReducerTypes.CAMPAIGN_BUSINESSES_DATA:
return {
...state,
campaignBusinessesData: action.payload,
};
case ReducerTypes.CAMPAIGN_BUSINESSES_ITEMS:
return {
...state,
campaignBusinessesItems: action.payload,
};
case ReducerTypes.LOADING_PINS:
return { ...state, loadingPins: action.payload.value };
case ReducerTypes.APP_CATEGORIES:
return {
...state,
categories: action.payload.categories,
structuredCategories: action.payload.structuredCategories,
};
case ReducerTypes.APP_DISCOUNTED_BUSINESS:
return {
...state,
discountedBusinesses: action.payload.discountedBusinesses,
};
**case ReducerTypes.LIST_SERVICE_TRANSACTIONS:
return {
...state,
ListServiceTransactions: action.payload,
};**
case ReducerTypes.FAQ_LIST:
return { ...state, faqList: action.payload.faqList };
default:
return state;
}
}
export default appReducer;
I just need to put the binaries together and handle the rest of the work myself
I think what you need to do is to merge the old state together with the new one in the reducer, I am not exactly sure which key you mean, but I assume ListServiceTransactions, if that's the case, then in your reducer file, it should be:
**case ReducerTypes.LIST_SERVICE_TRANSACTIONS:
return {
...state,
// Combining previous state with the new one
ListServiceTransactions: [
...state.ListServiceTransactions,
...action.payload
],
};**

Why isn't this button showing when the state is false?

I have created a component to function as a "Like/Unlike" button. When the state is true, the "Unlike" button successfully displays, but when I click "Unlike", and it DOES unlike successfully, the state should be set to false as (liked: false). However, I don't see the button.
One thing I noticed is, when I click "Unlike", the "Unlike" button disappears and the "Like" button does appear, for a millisecond, and then it vanishes in thin air. I cannot figure it out why.
Here are all the codes for my like button component:
import React from "react";
import { API, graphqlOperation } from "aws-amplify";
import { Button } from "element-react";
import { createLike, deleteLike } from "../graphql/mutations";
import { UserContext } from "../App";
class Like extends React.Component {
state = {
liked: "",
};
componentDidMount() {
this.setLiked();
}
setLiked() {
console.log(this.props);
const { user } = this.props;
const { post } = this.props;
if (post.likes.items.find((items) => items.liker === user.username)) {
this.setState({ liked: true });
console.log("liked: true");
} else {
this.setState({ liked: false });
console.log("liked: false");
}
}
handleLike = async (user) => {
try {
const input = {
liker: user.username,
likePostId: this.props.postId,
};
await API.graphql(graphqlOperation(createLike, { input }));
this.setState({
liked: true,
});
console.log("Liked!");
} catch (err) {
console.log("Failed to like", err);
}
};
handleUnlike = async (likeId) => {
try {
const input = {
id: likeId,
};
await API.graphql(graphqlOperation(deleteLike, { input }));
this.setState({
liked: false,
});
console.log("Unliked!");
} catch (err) {
console.log("Failed to unlike", err);
}
};
render() {
const { like } = this.props;
const { liked } = this.state;
return (
<UserContext.Consumer>
{({ user }) => (
<React.Fragment>
{liked ? (
<Button type="primary" onClick={() => this.handleUnlike(like.id)}>
Unlike
</Button>
) : (
<Button
type="primary"
onClick={() => this.handleLike(user, like.id)}
>
Like
</Button>
)}
</React.Fragment>
)}
</UserContext.Consumer>
);
}
}
export default Like;
The code of the parent component:
import React from "react";
import { API, graphqlOperation } from "aws-amplify";
import {
onCreateComment,
onCreateLike,
onDeleteLike,
} from "../graphql/subscriptions";
import { getPost } from "../graphql/queries";
import Comment from "../components/Comment";
import Like from "../components/Like";
import LikeButton from "../components/LikeButton";
import { Loading, Tabs, Icon } from "element-react";
import { Link } from "react-router-dom";
import { S3Image } from "aws-amplify-react";
import NewComment from "../components/NewComment";
class PostDetailPage extends React.Component {
state = {
post: null,
isLoading: true,
isAuthor: false,
};
componentDidMount() {
this.handleGetPost();
this.createCommentListener = API.graphql(
graphqlOperation(onCreateComment)
).subscribe({
next: (commentData) => {
const createdComment = commentData.value.data.onCreateComment;
const prevComments = this.state.post.comments.items.filter(
(item) => item.id !== createdComment.id
);
const updatedComments = [createdComment, ...prevComments];
const post = { ...this.state.post };
post.comments.items = updatedComments;
this.setState({ post });
},
});
this.createLikeListener = API.graphql(
graphqlOperation(onCreateLike)
).subscribe({
next: (likeData) => {
const createdLike = likeData.value.data.onCreateLike;
const prevLikes = this.state.post.likes.items.filter(
(item) => item.id !== createdLike.id
);
const updatedLikes = [createdLike, ...prevLikes];
const post = { ...this.state.post };
post.likes.items = updatedLikes;
this.setState({ post });
},
});
this.deleteLikeListener = API.graphql(
graphqlOperation(onDeleteLike)
).subscribe({
next: (likeData) => {
const deletedLike = likeData.value.data.onDeleteLike;
const updatedLikes = this.state.post.likes.items.filter(
(item) => item.id !== deletedLike.id
);
const post = { ...this.state.post };
post.likes.items = updatedLikes;
this.setState({ post });
},
});
}
componentWillUnmount() {
this.createCommentListener.unsubscribe();
}
handleGetPost = async () => {
const input = {
id: this.props.postId,
};
const result = await API.graphql(graphqlOperation(getPost, input));
console.log({ result });
this.setState({ post: result.data.getPost, isLoading: false }, () => {});
};
checkPostAuthor = () => {
const { user } = this.props;
const { post } = this.state;
if (user) {
this.setState({ isAuthor: user.username === post.author });
}
};
render() {
const { post, isLoading } = this.state;
return isLoading ? (
<Loading fullscreen={true} />
) : (
<React.Fragment>
{/*Back Button */}
<Link className="link" to="/">
Back to Home Page
</Link>
{/*Post MetaData*/}
<span className="items-center pt-2">
<h2 className="mb-mr">{post.title}</h2>
</span>
<span className="items-center pt-2">{post.content}</span>
<S3Image imgKey={post.file.key} />
<div className="items-center pt-2">
<span style={{ color: "var(--lightSquidInk)", paddingBottom: "1em" }}>
<Icon name="date" className="icon" />
{post.createdAt}
</span>
</div>
<div className="items-center pt-2">
{post.likes.items.map((like) => (
<Like
user={this.props.user}
like={like}
post={post}
postId={this.props.postId}
/>
))}
</div>
<div className="items-center pt-2">
{post.likes.items.length}people liked this.
</div>
<div>
Add Comment
<NewComment postId={this.props.postId} />
</div>
{/* Comments */}
Comments: ({post.comments.items.length})
<div className="comment-list">
{post.comments.items.map((comment) => (
<Comment comment={comment} />
))}
</div>
</React.Fragment>
);
}
}
export default PostDetailPage;
I think I know why it doesn't show up. It's because at first when the user hasn't liked it, there is no "like" object, so there is nothing to be shown, as it is only shown when there is a "like" mapped to it. I don't know how to fix it though.

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...!

Uncaught TypeError: Cannot read property 'then' of undefined React/Redux

I am trying to do a put request using axios to update a given Recipe by the authenticated user in the database basic rest API. But i am having an error in my code saying the 'then' is undefined.
`
// My updateRecipe.jsx Action file
import axios from 'axios';
export const RECIPE_UPDATED = "RECIPE_UPDATED"
const recipeUpdated = (recipe) => {
//console.log(recipe)
return {
type: RECIPE_UPDATED,
recipe
}
}
const updateRecipe = (data) => {
return dispatch => {
// console.log(data)
// console.log(dataid)
axios.put(`http://localhost:8009/api/v1/recipes/${data.id}`, data)
.then(() => dispatch(
//console.log(data),
recipeUpdated(data)
)).catch( error => {
//console.log(error.message)
error} )
}
}
export {
updateRecipe,
recipeUpdated,
}`
This is my Reducer file:
import { SET_RECIPES } from '../action/recipeAction.jsx';
import { RECIPE_FETCH } from '../action/RecipeFetch.jsx'
import { RECIPE_UPDATED } from '../action/updateRecipe.jsx'
export default function recipes(state = [], action = {}) {
switch(action.type) {
case RECIPE_UPDATED:
return state.map(item => {
//console.log(item)
if(item.id === action.recipe.id) return action.recipe;
return item;
})
case RECIPE_FETCH:
const index = state.findIndex(item => item.id === action.recipe.id);
if(index > -1){
return state.map(item => {
if(item.id === action.recipe.id) return action.recipe;
return item;
});
}else{
return [
...state,
action.recipe
];
}
case SET_RECIPES: return action.recipes;
default: return state;
}
}
This is my component file this is where i am having the problem, in my if(id) block the then just underneath the this.props.updatedRecipe.
import React from 'react';
import PropTypes from 'prop-types';
import classnames from 'classnames';
import { connect } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { Alert } from 'reactstrap';
import { saveRecipe, fetchRecipe, updateRecipe } from
'../../action/index.jsx'
import styles from './style.js'
class RecipeForm extends React.Component {
constructor (props) {
super(props)
this.state = {
id: this.props.recipe ? this.props.recipe.id : null,
title: this.props.recipe ? this.props.recipe.title : '',
description: this.props.recipe ? this.props.recipe.description :
'',
imageUrl: this.props.recipe ? this.props.recipe.imageUrl : '',
errors:{},
loading: false,
done: false,
}
}
componentWillReceiveProps (nextProps){
//console.log(nextProps.recipe)
this.setState({
id: nextProps.recipe.id,
title: nextProps.recipe.title,
description: nextProps.recipe.description,
imageUrl: nextProps.recipe.imageUrl,
})
}
componentDidMount () {
if(this.props.match.params.id){
//console.log(this.props.match.params.id);
this.props.fetchRecipe(this.props.match.params.id)
}
}
handleChange(event) {
// this.setState({ [event.target.name] : event.target.value });
// console.log('updatedRecipe: ' + event.target.id + ' == '+
event.target.value )
if (!!this.state.errors[event.target.name]){
let errors = Object.assign({}, this.state.errors);
delete errors[event.target.name];
// console.log(errors)
this.setState({
[event.target.name]: event.target.value,
errors
})
}else{
//console.log(this.state)
this.setState({
[event.target.name]: event.target.value,
// let handleChange = Object.assign({}, this.state);
// handleChange[event.target.id] = event.target.value;
// this.setState({
// recipes: handleChange,
})
}
}
handleSubmit(e){
e.preventDefault();
let errors = {};
if (this.state.title === '') errors.title = "Can't be empty";
if (this.state.description === '') errors.description = "Can't be
empty";
if (this.state.imageUrl === '') errors.imageUrl = "Can't be empty";
this.setState({
errors
})
const isValid = Object.keys(errors).length === 0
if(isValid){
const { id, title, description, imageUrl } = this.state;
//console.log(this.state)
this.setState({ loading: true });
if(id){
this.props.updateRecipe({ id, title, description, imageUrl })
// this is where the BUG is COMING FROM
.then(
()=>{
console.log("see me")
this.setState({ done : true, loading: true})},
(err) => {
err.response.json()
.then(({errors}) => {
console.log(errors)
this.setState({
errors})
}
)
}
)
}else{
this.props.saveRecipe({ title, description, imageUrl, })
.then(
()=>{
this.setState({ done : true, loading: true})},
(err) => {
err.response.json()
.then(({errors}) => {
console.log(errors)
this.setState({
errors})
}
)
}
)
}
}
}
render() {
const form = (
<div className="d-flex justify-content-center align-items-center
container">
<form className={classnames({ loading: this.state.loading })}>
<h1 style={styles.header}>Add New Recipe</h1>
{!!this.state.errors.form && <Alert color="danger">
{this.state.errors.form }</Alert>}
<div className="form-group row">
<div className="form-group col-md-12 col-md-offset-8 text-
right">
<div className={classnames('field', { error:
!!this.state.errors.title})}>
<label htmlFor="title">Recipe Title</label>
<input
name="title"
value={this.state.title}
onChange={this.handleChange.bind(this)}
className="form-control"
id="title"
placeholder="title"/>
<span style={{color: "#ae5856"}} >{this.state.errors.title}
</span>
</div>
<div className={classnames('field', { error:
!!this.state.errors.description})}>
<label htmlFor="title">Recipe Description</label>
<input
name="description"
value={this.state.description}
onChange={this.handleChange.bind(this)}
className="form-control"
id="description"
placeholder="description"/>
<span style={{color: "#ae5856"}}>
{this.state.errors.description}</span>
</div>
<div className={classnames('field', { error:
!!this.state.errors.imageUrl})}>
<label htmlFor="imageUrl">Recipe Image</label>
<input
name="imageUrl"
value={this.state.imageUrl}
onChange={this.handleChange.bind(this)}
className="form-control"
id="imageUrl"
placeholder="Image"/>
<span style={{color: "#ae5856"}}>{this.state.errors.imageUrl}
</span>
</div>
<div>
{this.state.imageUrl !== '' && <img src=
{this.state.imageUrl} alt="" className="img-rounded"/>}
</div>
<button onClick={this.handleSubmit.bind(this)} type="submit"
className="btn btn-primary">Submit</button>
</div>
</div>
</form>
</div>
);
return (
<div>
{this.state.done ? <Redirect to="/recipes"/> : form}
</div>
)
}
}
RecipeForm.propTypes = {
saveRecipe: PropTypes.func.isRequired,
}
function mapStateToProps(state, props){
//console.log(props.match.recipe)
if(props.match.params.id){
//console.log(props.match.params.id)
let recipe = {}
const recipes = state.recipes.filter(item => {
//console.log(item)
//console.log(state.recipes)
if (item.id == props.match.params.id){
recipe = item
}
})
//console.log(recipe)
return {
recipe
}
//console.log(recipe);
}
return { recipe: null };
}
export default connect(mapStateToProps, { saveRecipe, fetchRecipe })
(RecipeForm);
It should just be updateRecipe, not this.props.updateRecipe. You're importing updateRecipe() (as a function) at the top of the <RecipeForm/> component. So it's not a property that's being passed to the component, it's just a function you've imported in that file.

Fetching multiple items in React/Redux causing infinite loop and no jsx on screen?

Goal:
I want to be able to fetch multiple profiles from an array and list them out on the screen. Something like:
John, Sandy, Drew
I am using react and trying to list out users from a friendRequest array. This array is filled with user id's and I want to map over them to get the user and show him/her on the screen.
What is happening is that in the console.log(pendingFriend), it is a infinite loop of in this case two profiles over and over again getting logged. Also no jsx is being displayed on the screen.
Here is the code.
Look in the render > return > where you see the currentUser.friendRequests being mapped over.
import React, { Component } from 'react';
import { connect } from 'react-redux';
import swal from 'sweetalert';
import actions from '../../actions';
import { UpdateProfile } from '../view';
import { DateUtils } from '../../utils';
class Profile extends Component {
constructor() {
super();
this.state = {
profile: {
image:
'https://lh3.googleusercontent.com/EJf2u6azJe-TA6YeMWpDtMHAG6u3i1S1DhbiUXViaF5Pyg_CPEOCOEquKbX3U-drH29oYe98xKJiWqYP1ZxPGUQ545k',
bannerImage:
'https://lh3.googleusercontent.com/RAdfZt76XmM5p_rXwVsfQ3J8ca9aQUgONQaXSE1cC0bR0xETrKAoX8OEOzID-ro_3vFfgO8ZMQIqmjTiaCvuK4GtzI8',
firstName: 'First Name',
lastName: 'Last Name',
email: 'Contact Email',
bio: 'Bio will go here'
}
};
this.deleteProfile = this.deleteProfile.bind(this);
}
componentDidMount() {
const { id } = this.props.match.params;
if (this.props.profiles[id] != null) {
return;
}
this.props
.getProfile(id)
.then(() => {})
.catch(err => {
console.log(err);
});
}
createUpdatedProfile(params) {
const { id } = this.props.match.params;
const profile = this.props.profiles[id];
const { currentUser } = this.props.user;
if (currentUser.id !== profile.id) {
swal({
title: 'Oops...',
text: 'You do not own this profile',
icon: 'error'
});
return;
}
this.props
.updateProfile(currentUser, params)
.then(response => {
swal({
title: `${response.username} Updated!`,
text: 'Thank you for updating your profile',
icon: 'success'
});
})
.catch(err => {
console.log(err);
});
}
deleteProfile() {
const { id } = this.props.match.params;
const profile = this.props.profiles[id];
const { currentUser } = this.props.user;
if (currentUser.id !== profile.id) {
swal({
title: 'Oops...',
text: 'You do not own this profile',
icon: 'error'
});
return;
}
swal({
closeOnClickOutside: false,
closeOnEsc: false,
title: 'Are you sure?',
text:
'All data related to profile will be deleted as well with the profile! If you wish to delete your profile you must type DELETE',
icon: 'warning',
dangerMode: true,
buttons: true,
content: 'input'
}).then(value => {
if (value === 'DELETE') {
const userPosts = this.props.post.all.filter(p => p.profile.id === profile.id);
const userReplies = this.props.reply.all.filter(r => r.user.id === profile.id);
userPosts.map(post => {
this.props.deleteRecord(post);
});
userReplies.map(reply => {
this.props.deleteReply(reply);
});
this.props
.deleteProfile(profile)
.then(data => {
return this.props.logoutUser();
})
.then(data => {
this.props.history.push('/');
swal('Deleted!', 'Your Profile has been deleted.', 'success');
return null;
})
.catch(err => {
console.log(err);
});
}
swal({
title: 'Profile not deleted',
text: 'Make sure you type "DELETE" with caps',
icon: 'error'
});
});
}
addFriend() {
const { id } = this.props.match.params;
const profile = this.props.profiles[id];
const { currentUser } = this.props.user;
if (currentUser == null || profile == null) {
swal({
title: 'Oops...',
text: 'Must be logged in, and user must exist',
icon: 'error'
});
return;
}
const friendRequests = profile.friendRequests || [];
const params = {};
friendRequests.push(currentUser.id);
params.friendRequests = friendRequests;
this.props
.updateProfile(profile, params)
.then(() => {
swal({
title: 'Success',
text: 'Friend Request Sent',
icon: 'success'
});
})
.catch(err => {
console.log(err);
});
}
render() {
const { id } = this.props.match.params;
const profile = this.props.profiles[id];
const { currentUser } = this.props.user;
const defaultProfile = this.state.profile;
const bannerUrl =
profile == null
? defaultProfile.bannerImage
: profile.bannerImage || defaultProfile.bannerImage;
const bannerStyle = {
backgroundImage: `url(${bannerUrl})`,
backgroundSize: '100%',
backgroundRepeat: 'no-repeat',
backgroundPosition: 'center'
};
const nameStyle = {
background: 'rgba(255, 255, 255, 0.7)',
borderRadius: '8px'
};
const imageStyle = {
maxHeight: '150px',
margin: '20px auto'
};
return (
<div>
{profile == null ? (
<div>
<h1>Profile no longer exists</h1>
</div>
) : (
<div>
{currentUser == null ? null : currentUser.id !== profile.id ? null : (
<div className="list-group">
{currentUser.friendRequests
? currentUser.friendRequests.map(request => {
this.props
.getProfile(request)
.then(pendingFriend => {
console.log(pendingFriend);
return (
<div key={pendingFriend.id} className="list-group-item">
<p>{pendingFriend.username}</p>
</div>
);
})
.catch(err => {
console.log(err);
});
})
: null}
</div>
)}
<div className="jumbotron jumbotron-fluid" style={bannerStyle}>
<div className="container" style={nameStyle}>
<img
src={profile.image || defaultProfile.image}
style={imageStyle}
className="rounded img-fluid mx-auto d-block"
/>
</div>
</div>
<div className="row">
<div className="col-sm-12">
<h1 className="display-3 text-center">{profile.username}</h1>
<p className="lead text-center">
{profile.firstName || defaultProfile.firstName}{' '}
{profile.lastName || defaultProfile.lastName}
</p>
<p className="lead text-center text-muted">
{profile.email || defaultProfile.email}
</p>
<p className="text-center text-muted">
Became a User: {DateUtils.relativeTime(profile.timestamp)}
</p>
<hr className="my-4" />
<p className="lead" style={{ border: '1px solid #e6e6e6', padding: '20px' }}>
{profile.bio || defaultProfile.bio}
</p>
</div>
</div>
{currentUser == null ? null : currentUser.id !== profile.id ? (
<div className="row justify-content-center" style={{ marginBottom: '100px' }}>
<div className="col-sm-6">
{profile.friendRequests ? (
profile.friendRequests.indexOf(currentUser.id) === -1 ? (
<button
className="btn btn-primary btn-lg btn-block"
onClick={this.addFriend.bind(this)}
>
Add Friend
</button>
) : (
<button className="btn btn-success btn-lg btn-block">
Pending Friend Request
</button>
)
) : (
<button
className="btn btn-primary btn-lg btn-block"
onClick={this.addFriend.bind(this)}
>
Add Friend
</button>
)}
</div>
</div>
) : (
<div>
<UpdateProfile
currentProfile={profile}
onCreate={this.createUpdatedProfile.bind(this)}
/>
<div className="row justify-content-center" style={{ marginBottom: '100px' }}>
<div className="col-sm-6">
<button
className="btn btn-danger btn-lg btn-block"
onClick={this.deleteProfile}
>
DELETE Profile
</button>
</div>
</div>
</div>
)}
</div>
)}
</div>
);
}
}
const stateToProps = state => {
return {
profiles: state.profile,
user: state.user,
post: state.post,
reply: state.reply
};
};
const dispatchToProps = dispatch => {
return {
getProfile: id => dispatch(actions.getProfile(id)),
updateProfile: (entity, params) => dispatch(actions.updateProfile(entity, params)),
deleteProfile: entity => dispatch(actions.deleteProfile(entity)),
deleteRecord: entity => dispatch(actions.deleteRecord(entity)),
deleteReply: entity => dispatch(actions.deleteReply(entity)),
logoutUser: () => dispatch(actions.logoutUser())
};
};
const loadData = store => {
return store.dispatch(actions.getProfile(this.props.match.params.id));
};
export default {
loadData: loadData,
component: connect(stateToProps, dispatchToProps)(Profile)
};
Your code is breaking the pattern in so many ways, i'm not sure where to start :)
First of all as for your question about the infinite loop, you
probably want to start with this line in your render method:
this.props.getProfile(request)
.then(pendingFriend => {
console.log(pendingFriend);
You should never ever update the state or dispatch actions.
These are the two main ways to re-render a component, state change
and new props. When you dispatch an action you actually causing a
new render call as a new prop will be received to your connected
component. With that said, do not do async calls inside the
render method, render method should be pure with no side effects.
Async calls and data fetching should be triggered in
componentDidMount.
Another thing not related to your problem directly, most of your
handlers are not bind the this object to the class, the only
handler you did bind is deleteProfile. bind them all or use
arrow functions which will use the this in a lexical context.
Again, not related directly to your problem, always pass props when
using the constructor and super:
constructor(props) {
super(props);
Edit
As a followup to your comment:
is it okay if I directly bind the functions like
this.deleteProfile.bind(this) instead of doing this.deleteProfile =
this.deleteProfile.bind(this) in the constructor just do
this.deleteProfile.bind(this) inside the render method
This won't change anything as bind isn't mutating the function, instead it returns a new instance with the this object is attached to it (it uses call behind the scenes) you can see the implementation.
So you must override your handlers with the new instance.
By the way, inside your render function, doing binding or any other operation that will create a new instance of a function or object is less preferable as you will create new instance on each render call of course. if you can "lift" it up to a method like the constructor (which is called only once in a component's life time) is much more performant.

Categories

Resources