createStore not changing from initial state to Action proved value - javascript

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

Related

Autocomplete data in input

I want to display data from the autocomplete in the input as indicated below:
Autocomplete function
When i'm trying to do this i get an error:
×
TypeError: Cannot read property 'setState' of undefined
onSelect
94 | onSelect={ value => this.setState({ value }) }
I'm stuck on this and i'm probably doing it wrong. Hopefully someone can help me because i've tried everything i know and just cant see the problem. So please help me :)
privateMovie.js
import React, { useState, useEffect, setState } from "react";
import Layout from "../core/Layout";
import axios from "axios";
import { isAuth, getCookie, signout, updateUser } from "../auth/helpers";
import { ToastContainer, toast } from "react-toastify";
import "react-toastify/dist/ReactToastify.min.css";
import Autocomplete from "react-autocomplete";
import { MoviesData, renderMovieTitle } from "./movie-data";
const Private = ({ history }) => {
const [values, setValues ] = useState({
value: "",
suggestions: [],
movie: "",
buttonText: "Submit"
});
const token = getCookie("token");
useEffect(() => {
loadProfile();
}, []);
const loadProfile = () => {
axios({
method: "get",
url: `${process.env.REACT_APP_API}/user/${isAuth()._id}`,
headers: {
Authorization: `Bearer ${token}`
}
})
.then(response => {
console.log("PRIVATE PROFILE UPDATE", response);
const { movie } = response.data;
setValues({ ...values, movie });
})
.catch(error => {
console.log("PRIVATE PROFILE UPDATE ERROR", error.response.data.error);
if (error.response.status === 401) {
signout(() => {
history.push("/");
});
}
});
};
const { movie, buttonText } = values;
const handleChange = value => event => {
// console.log(event.target.value);
setValues({ ...values, [value]: event.target.value });
};
const clickSubmit = event => {
event.preventDefault();
setValues({ ...values, buttonText: "Submitting" });
axios({
method: "POST",
url: `${process.env.REACT_APP_API}/movie/create`,
headers: {
Authorization: `Bearer ${token}`
},
data: { movie }
})
.then(response => {
console.log("PRIVATE PROFILE UPDATE SUCCESS", response);
updateUser(response, () => {
setValues({ ...values, buttonText: "Submitted" });
toast.success("Profile updated successfully");
});
})
.catch(error => {
console.log("PRIVATE PROFILE UPDATE ERROR", error.response.data.error);
setValues({ ...values, buttonText: "Submit" });
toast.error(error.response.data.error);
});
};
const updateForm = () => (
<form>
<div className="form-group">
<label className="text-muted">AUTOCOMPLETE</label>
<Autocomplete
type="text"
getItemValue={item => item.title}
items={MoviesData()}
shouldItemRender={renderMovieTitle}
renderItem={(item, isHighlighted) => (
<div style={{ background: isHighlighted ? "lightgray" : "white" }}>
{item.title}
</div>
)}
onChange={(event, value) => this.setState({ value }) }
onSelect={ value => this.setState({ value }) }
/>
<input
onChange={handleChange("movie")}
value={movie}
type="text"
className="form-control"
/>
</div>
<div>
<button className="btn btn-primary" onClick={clickSubmit}>
{buttonText}
</button>
</div>
</form>
);
return (
<Layout>
<div className="col-md-6 offset-md-3">
<ToastContainer />
<h1 className="pt-5 text-center"></h1>
<p className="lead text-center"></p>
{updateForm()}
</div>
</Layout>
);
};
export default Private;
I think you mixed it up.
Instead you call this.setState()
call your destructed function setValues()
wich you have declared in the beginning of your component function
const [values, setValues ] = useState({
value: "",
suggestions: [],
movie: "",
buttonText: "Submit"
});
And if you use "useState" and destruct it in "getValues" and "setValues" you can get rid of setState in your import.
See the docs:
https://reactjs.org/docs/hooks-state.html

Action being callled but not hitting

I am using MERN stack and redux, i have created a findOneAndUpdate api and tested it on Postman. All works as it should but i can't get it working on my website. It never actually hits the updateSubject action. Am i passing in the data incorrectly? Anyone any idea what i am missing?
action
export const updateSubject = (id, rate, noOfVotes, rating) => (dispatch) => {
console.log("updateSubject hitting");
fetch(`/api/subjects/Subject/${id}/${rate}/${noOfVotes}/${rating}`)
.then((res) => res.json())
.then((subject) =>
dispatch({
type: UPDATE_SUBJECT,
subjects: subject,
})
);
};
api
// update subject
subjectRouter.put("/subject/:_id/:rate/:noOfVotes/:rating", (req, res) => {
Subject.findOneAndUpdate(
{ _id: req.params._id },
{
rating: Number(req.params.rating) + Number(req.params.rate),
noOfVotes: Number(req.params.noOfVotes) + 1,
},
{
new: true,
useFindAndModify: false,
}
)
.then((subjects) => res.json(subjects))
.catch((err) => console.log(err));
});
component
import React, { Component } from "react";
import PropTypes from "prop-types";
import GoogleSearch from "./GoogleSearch";
import { connect } from "react-redux";
import { fetchSubjects } from "../../actions/subject";
import { fetchComments } from "../../actions/comment";
import { updateSubject } from "../../actions/subject";
class Subject extends Component {
// on loading the subjects and comments
// are fetched from the database
componentDidMount() {
this.props.fetchSubjects();
this.props.fetchComments();
}
constructor(props) {
super(props);
this.state = {
// set inital state for subjects description
// and summary to invisible
viewDesription: -1,
viewSummary: -1,
comments: [],
};
}
componentWillReceiveProps(nextProps) {
// new subject and comments are added to the top
if (nextProps.newPost) {
this.props.subjects.unshift(nextProps.newPost);
}
if (nextProps.newPost) {
this.props.comments.unshift(nextProps.newPost);
}
}
clickHandler = (id) => {
// when a subject title is clicked pass in its id
// and make the desciption visible
const { viewDescription } = this.state;
this.setState({ viewDescription: viewDescription === id ? -1 : id });
// add relevant comments to the state
var i;
var temp = [];
for (i = 0; i < this.props.comments.length; i++) {
if (this.props.comments[i].subject === id) {
temp.unshift(this.props.comments[i]);
}
}
this.setState({
comments: temp,
});
// save the subject id to local storage
// this is done incase a new comment is added
// then the subject associated with it can be retrieved
// and added as a property of that comment
localStorage.setItem("passedSubject", id);
};
// hovering on and off subjects toggles the visibility of the summary
hoverHandler = (id) => {
this.setState({ viewSummary: id });
};
hoverOffHandler = () => {
this.setState({ viewSummary: -1 });
};
rateHandler = (id, rate) => {
var currRate;
var currVotes;
var i;
for (i = 0; i < this.props.subjects.length; i++) {
if (this.props.subjects[i]._id === id) {
currRate = this.props.subjects[i].rating;
currVotes = this.props.subjects[i].noOfVotes;
}
}
updateSubject(id, rate, currVotes, currRate);
console.log(id, rate, currVotes, currRate);
};
findAuthor(id) {
// search users for id return name
}
render() {
const subjectItems = this.props.subjects.map((subject) => {
// if the state equals the id set to visible if not set to invisible
var view = this.state.viewDescription === subject._id ? "" : "none";
var hover = this.state.viewSummary === subject._id ? "" : "none";
var comments = this.state.comments;
return (
<div key={subject._id}>
<div
className="subjectTitle"
onClick={() => this.clickHandler(subject._id)}
onMouseEnter={() => this.hoverHandler(subject._id)}
onMouseLeave={() => this.hoverOffHandler()}
>
<p className="title">{subject.title}</p>
<p className="rate">
Rate this subject:
<button onClick={() => this.rateHandler(subject._id, 1)}>
1
</button>
<button onClick={() => this.rateHandler(subject._id, 2)}>
2
</button>
<button onClick={() => this.rateHandler(subject._id, 3)}>
3
</button>
<button onClick={() => this.rateHandler(subject._id, 4)}>
4
</button>
<button onClick={() => this.rateHandler(subject._id, 5)}>
5
</button>
</p>
<p className="rating">
Rating: {(subject.rating / subject.noOfVotes).toFixed(1)}/5
</p>
<p className="summary" style={{ display: hover }}>
{subject.summary}
</p>
</div>
<div className="subjectBody " style={{ display: view }}>
<div className="subjectAuthor">
<p className="author" style={{ fontWeight: "bold" }}>
Subject created by: {subject.author} on {subject.date}
</p>
</div>
<div className="subjectDescription">
<p className="description">{subject.description}</p>
</div>
<div className="subjectLinks">Links:</div>
<div className="subjectComments">
<p style={{ fontWeight: "bold" }}>Comments:</p>
{comments.map((comment, i) => {
return (
<div key={i} className="singleComment">
<p>
{comment.title}
<br />
{comment.comment}
<br />
Comment by : {comment.author}
</p>
</div>
);
})}
<a href="/addcomment">
<div className="buttonAddComment">ADD COMMENT</div>
</a>
</div>
</div>
</div>
);
});
return (
<div id="Subject">
<GoogleSearch />
{subjectItems}
</div>
);
}
}
Subject.propTypes = {
fetchSubjects: PropTypes.func.isRequired,
fetchComments: PropTypes.func.isRequired,
subjects: PropTypes.array.isRequired,
comments: PropTypes.array.isRequired,
newPost: PropTypes.object,
};
const mapStateToProps = (state) => ({
subjects: state.subjects.items,
newSubject: state.subjects.item,
comments: state.comments.items,
newComment: state.comments.item,
});
// export default Subject;
export default connect(mapStateToProps, { fetchSubjects, fetchComments })(
Subject,
Comment
);
You are not attaching the updateSubject to the component itself, currently you only have fetchSubjects and fetchComments instead.
So, you would need to change your connect function like so:
export default connect(mapStateToProps, { fetchSubjects, fetchComments, updateSubject })(
Subject,
Comment
);
and then your calling function could be changed like so:
rateHandler = (id, rate) => {
const subject = this.props.subjects.find( subject => subject._id === id);
// when no subject was found, the updateSubject won't be called
subject && this.props.updateSubject( id, rate, subject.noOfVotes, subject.rating );
};
This would also mean that you should update your proptypes like:
Subject.propTypes = {
fetchSubjects: PropTypes.func.isRequired,
fetchComments: PropTypes.func.isRequired,
updateSubject: PropTypes.func.isRequired,
subjects: PropTypes.array.isRequired,
comments: PropTypes.array.isRequired,
newPost: PropTypes.object,
};
As you mentioned in the comments, you are using a put endpoint, so you should probably update your updateSubject method to the following
export const updateSubject = (id, rate, noOfVotes, rating) => (dispatch) => {
console.log("updateSubject hitting");
fetch(`/api/subjects/Subject/${id}/${rate}/${noOfVotes}/${rating}`, { method: 'PUT' })
.then((res) => res.json())
.then((subject) =>
dispatch({
type: UPDATE_SUBJECT,
subjects: subject,
})
);
};

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.

Edit table values using react redux

I'm learning React-Redux and I'm stuck in a part of learning, I'm trying to edit the user value from table using the form that already exists, thereby taking the value of the line, and passing to the form, and also saving the user id in a variable or something of the type to update the user, I do not know how to proceed with that part
my actions: src/actions/user.js
import { ADD_USER, DELETE_USER, UPDATE_USER, FETCH_USER } from '../constants/ActionTypes';
import axios from 'axios';
const apiUrl = 'http://localhost:8080/api/v1';
export const createUser = ({ name, cpfcnpj }) => {
return (dispatch) => {
return axios.post(`${apiUrl}/users/`, { name, cpfcnpj })
.then(response => {
dispatch(createUserSuccess(response.data.data))
})
.catch(error => {
throw (error);
});
};
};
export const createUserSuccess = (data) => {
return {
type: ADD_USER,
payload: {
_id: data._id,
name: data.name,
cpfcnpj: data.cpfcnpj
}
}
};
export const deleteUserSuccess = id => {
return {
type: DELETE_USER,
payload: {
id
}
}
}
export const deleteUser = (id) => {
console.log(id)
return (dispatch) => {
return axios.delete(`${apiUrl}/users/${id}`)
.then(response => {
dispatch(deleteUserSuccess(id))
})
.catch(error => {
throw (error);
});
};
};
export const updateUserSuccess = (data) => {
return {
type: UPDATE_USER,
payload: {
_id: data._id,
name: data.name,
cpfcnpj: data.cpfcnpj
}
}
}
export const updateUser = (id, name, cpfcnpj) => {
console.log(id, name, cpfcnpj)
return (dispatch) => {
return axios.put(`${apiUrl}/users/${id}`, { name, cpfcnpj })
.then(response => {
dispatch(updateUserSuccess(response.data.data))
})
.catch(error => {
throw (error);
});
};
};
export const fetchUsers = (users) => {
return {
type: FETCH_USER,
users
}
};
export const fetchAllUsers = () => {
return (dispatch) => {
return axios.get(`${apiUrl}/users/`)
.then(response => {
dispatch(fetchUsers(response.data.data))
})
.catch(error => {
throw (error);
});
};
};
my Smart Component CreateUser: src/containers/CreateUser.js
import { connect } from 'react-redux';
import { createUser, updateUser } from '../actions/user';
import NewUser from '../components/NewUser';
const mapDispatchToProps = dispatch => {
return {
onAddUser: user => {
dispatch(createUser(user));
}
};
};
export default connect(
null,
mapDispatchToProps
)(NewUser);
my Dummy Component NewUser: src/components/NewUser.js
import React, { Component } from 'react';
class NewUser extends Component {
state = {
name: '',
cpfcnpj: '',
isEdit: false
};
handleInputChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleSubmit = e => {
e.preventDefault();
if (!this.state.isEdit) {
if (this.state.name.trim() && this.state.cpfcnpj.trim()) {
this.props.onAddUser(this.state);
this.handleReset();
}
} else {
if (this.state.name.trim() && this.state.cpfcnpj.trim()) {
this.props.onEdit(this.state);
this.handleReset();
}
}
};
handleReset = () => {
this.setState({
name: '',
cpfcnpj: ''
});
};
render() {
return (
<div>
<form className="form-inline" onSubmit={this.handleSubmit}>
<div className="form-group margin-right">
<input
type="text"
placeholder="Name"
className="form-control"
name="name"
onChange={this.handleInputChange}
value={this.state.name}
/>
</div>
<div className="form-group margin-right">
<input
type="text"
placeholder="CPF/CNPJ"
className="form-control"
name="cpfcnpj"
onChange={this.handleInputChange}
value={this.state.cpfcnpj}>
</input>
</div>
<div className="form-group">
<button type="submit" className={this.state.isEdit ? "btn btn-success margin-right hidden" : "btn btn-success margin-right"}>
<span className="glyphicon glyphicon-plus" aria-hidden="true"></span>
Adicionar
</button>
<button type="submit" className={this.state.isEdit ? "btn btn-primary margin-right" : "btn btn-primary margin-right hidden"}>
<span className="glyphicon glyphicon-floppy-disk" aria-hidden="true"></span>
Salvar
</button>
<button type="button" className="btn btn-default margin-right" onClick={this.handleReset}>
<span className="glyphicon glyphicon-erase" aria-hidden="true"></span>
Limpar
</button>
</div>
</form>
</div>
);
}
}
export default NewUser;
src/containers/UserList.js
import React from 'react';
import { connect } from 'react-redux';
import User from '../components/User';
import { deleteUser, updateUser } from '../actions/user';
function UserList({ users, onDelete, onEdit }) {
if (!users.length) {
return (
<div className="margin-top">
No Users
</div>
)
}
return (
<div className="margin-top">
<table className="table table-striped">
<thead>
<tr>
<th scope="col">Nome</th>
<th scope="col">CPF/CNPJ</th>
</tr>
</thead>
<tbody>
{users.map(user => {
return (
<User user={user} onDelete={onDelete} key={user._id} onEdit={onEdit} />
);
})}
</tbody>
</table>
</div>
);
}
const mapStateToProps = state => {
return {
users: state.users
};
};
const mapDispatchToProps = dispatch => {
return {
onDelete: id => {
dispatch(deleteUser(id));
},
onEdit: id => {
dispatch(updateUser(id))
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserList);
src/reducers/userReducer.js
import { ADD_USER, DELETE_USER, UPDATE_USER, FETCH_USER } from '../constants/ActionTypes';
export default function userReducer(state = [], action) {
switch (action.type) {
case ADD_USER:
return [...state, action.payload];
case DELETE_USER:
return state.filter(user => user._id !== action.payload.id);
case UPDATE_USER:
return [...state, action.payload];
case FETCH_USER:
return action.users;
default:
return state;
}
}
Solution I thought but could not reproduce
I need to get the value of the id of the value table item that comes as key in the UserList, pass to the onClick parameter of the user component button, and pass the value of the id when I click edit in some table item to the Form in NewUser, in order to be able to edit the table item using onEdit in NewUser.
Stucked in solution ma_dev_15
I created a const initialState, with current user, but my userReducer State just looks the users,
src/recuders/userReducer.js
import { ADD_USER, DELETE_USER, UPDATE_USER, UPDATE_CURRENT_USER, FETCH_USER } from '../constants/ActionTypes';
const initialState = {
users: [],
currentUser: {},
}
export default function userReducer(state = initialState, action) {
switch (action.type) {
case ADD_USER:
return [...state, action.payload];
case DELETE_USER:
return state.filter(user => user._id !== action.payload.id);
case UPDATE_USER:
return updateObject(state, action)
case UPDATE_CURRENT_USER:
return [...state, action.currentUser];
case FETCH_USER:
return action.users;
default:
return state;
}
}
function updateObject(array, action) {
return array.map((item, index) => {
if (item._id !== action.payload._id) {
return item
}
return {
...item,
...action.payload
}
})
}
export reducers
src/reducers/index.js
import { combineReducers } from 'redux';
import users from './userReducer';
import currentUser from './userReducer';
export default combineReducers({
users: users,
currentUser: currentUser
});
user actions: src/actions/user.js
//Ommited
export const updateCurrentUserSuccess = (currentUser) => {
return {
type: UPDATE_CURRENT_USER,
currentUser
}
}
export const updateCurrentUser = (id) => {
return (dispatch) => {
return axios.get(`${apiUrl}/users/${id}`)
.then(response => {
dispatch(updateCurrentUserSuccess(response.data.data))
})
.catch(error => {
throw (error);
});
};
};
//Ommited
I make my UserList connected to CreateUser
src/components/containers/UserList
import React from 'react';
import { connect } from 'react-redux';
import User from '../components/User';
import { deleteUser, updateCurrentUser } from '../actions/user';
import NewUser from '../components/NewUser';
function UserList({ users, onDelete, onEditUser }) {
if (!users.length) {
return (
<div className="margin-top">
No Users
</div>
)
}
return (
<div className="margin-top">
<table className="table table-striped">
<thead>
<tr>
<th scope="col">Nome</th>
<th scope="col">CPF/CNPJ</th>
</tr>
</thead>
<tbody>
{users.map(user => {
return (
<User user={user} onDelete={onDelete} onEditUser={onEditUser} key={user._id} />
);
})}
</tbody>
</table>
</div>
);
}
const mapStateToProps = state => {
return {
users: state.users
};
};
const mapDispatchToProps = dispatch => {
return {
onDelete: id => {
dispatch(deleteUser(id));
},
onEditUser: (id) => {
dispatch(updateCurrentUser(id))
}
};
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(UserList, NewUser);
And when I click in Edit, and try to see console.log(Store.getState) in NewUser.js just returns me all users, I don't have currentUser.
You can create userReducer state like this:
const initialState = {
users: [],
currentUser :{},
}
export default function userReducer(state = initialState, action) {
switch (action.type) {
case ADD_USER:
return [...state, action.payload];
case DELETE_USER:
return state.filter(user => user._id !== action.payload.id);
case UPDATE_USER:
return [...state, action.payload];
case FETCH_USER:
return action.users;
default:
return state;
}
}
After that let me tell you few simple steps:
On clicking edit button pass userId and corres to that action update
the currentUser in userReducer.
Make the NewUser component connected component and get the currentUser from store and set to the input fields.
On successfull edit update the currentUser as empty and update users list
I hope you got what I mean. Let me know if you stuck somewhere.

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