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.
Related
After receiving data from the API then mapping it out - I tried toggling one icon, but it toggles all the icons that were mapped out. I am trying to click one of the icon and not all.
import { FaRegHeart, FaHeart } from "react-icons/fa";
import { IconContext } from 'react-icons';
import { useState, useEffect } from 'react';
const Replies = ({ comments, id }) => {
const [liked, setLiked] = useState(false);
const [replies, setReplies] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch('http://192.168.1.98:5000/blogs/' + id, { signal: abortCont.signal })
.then(res => {
if (!res.ok) {
throw new Error('Could not get data from the database');
}
return res.json()
})
.then(data => setReplies(data))
.catch(err => {
if (err.name === 'AbortError') {
// empty if statement
}})
return () => abortCont.abort()
}, [liked])
const likeComment = () => {liked ? setLiked(false) : setLiked(true)}
return (
<>
{replies && <div className="commentSection">
{
replies.discussion.map((discussion) => (
<div key={discussion.id}>
<div className="commentEmoji">
{
liked ? <IconContext.Provider value={{ color: 'red', className: 'love-icon' }}>
<FaHeart onClick={likeComment} />
</IconContext.Provider>
: <FaRegHeart onClick={likeComment} />}
</div>
</div>
))}
</div>
}
</>
);
}
export default Replies;
This is what the API looks like:
"discussion": [
{
"id": 1,
"liked": false,
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/75.jpg",
"user": "Kayode",
"comment": "They know electric vehicles are the future 👌🏾",
"replies": ""
},
{
"user": "Farook",
"liked": true,
"id": 2,
"thumbnail": "https://randomuser.me/api/portraits/thumb/men/74.jpg",
"comment": "I hope they keep buying. TSLA to that freaking moon 🚀",
"replies": ""
}
]
They have a discussion.liked property and I've been trying to access individual and not both.
If I use:
<FaRegHeart onClick={()=> likeComment(discussion.id)} />
Then
const likeComment = (id) => {
console.log(id) //it logs the exact id of the icon i.e 2
}
Try something like this, and pass appropriate props in LikedButton component
import { FaRegHeart, FaHeart } from "react-icons/fa";
import { IconContext } from 'react-icons';
import { useState, useEffect } from 'react';
const LikedButton = () => {
const [liked, setLiked] = useState(false);
const likeComment = () => {liked ? setLiked(false) : setLiked(true)}
return (
<div >
<div className="commentEmoji">
{
liked ? <IconContext.Provider value={{ color: 'red', className: 'love-icon' }}>
<FaHeart onClick={likeComment} />
</IconContext.Provider>
: <FaRegHeart onClick={likeComment} />}
</div>
</div>
)
}
const Replies = ({ comments, id }) => {
const [replies, setReplies] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch('http://192.168.1.98:5000/blogs/' + id, { signal: abortCont.signal })
.then(res => {
if (!res.ok) {
throw new Error('Could not get data from the database');
}
return res.json()
})
.then(data => setReplies(data))
.catch(err => {
if (err.name === 'AbortError') {
// empty if statement
}})
return () => abortCont.abort()
}, [])
return (
<>
{replies && <div className="commentSection">
{
replies.discussion.map((discussion, index) => (
<LikedButton key={index} />
))}
</div>
}
</>
);
}
export default Replies;
import LikedComment from './LikedComment';
import { useState, useEffect } from 'react';
const Replies = ({ comments, id }) => {
const [error, setError] = useState(false);
const [replies, setReplies] = useState(null);
useEffect(() => {
const abortCont = new AbortController();
fetch('http://192.168.1.98:5000/blogs/' + id, { signal: abortCont.signal })
.then((res) => {
if (!res.ok) {
throw new Error('Could not get data from the database');
}
return res.json();
})
.then((data) => {
setReplies(data);
})
.catch((err) => {
if (err.name === 'AbortError') {
// empty if statement
} else {
setError(true);
}
});
return () => abortCont.abort();
}, [id]);
const likeComment = (id) => {
setReplies({
...replies,
discussion: replies.discussion.map((reply) => {
if (reply.id === id) {
return { ...reply, liked: !reply.liked };
} else {
return reply;
}
}),
});
};
return (
<>
{error && <div className="commentError">Cannot retrieve comments </div>}
{replies && (
<div className="commentSection">
{replies.discussion.map((discussion, index) => (
<div key={discussion.id}>
<div className="comment">
<img className="commentImg" src={discussion.thumbnail} alt="" />
<div className="commentBody">
<p>
{' '}
<span>{discussion.user}</span> {discussion.comment}
</p>
</div>
<div className="commentEmoji">
<LikedComment
liked={discussion.liked}
key={index}
likeComment={() => likeComment(discussion.id)}
/>
</div>
</div>
</div>
))}
</div>
)}
</>
);
};
export default Replies;
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,
})
);
};
I have a PostList component with an array of posts objects. I am rendering this list of post using another pure functional component Post using Array.map() method. Post component has another component - LikeButton to like or unlike a post. Now I want to show a spinner during like or unlike on top of that LikeButton component. LikeButton Component looks something like this:
const LikeButton = (props) => {
const likeBtnClasses = [classes.LikeBtn];
const loggedInUserId = useSelector((state) => state.auth.user.id);
const isLoading = useSelector((state) => state.post.loading);
const isPostLiked = props.post.likes.find(
(like) => like.user === loggedInUserId
);
const [isLiked, setLike] = useState(isPostLiked ? true : false);
const token = useSelector((state) => state.auth.token);
const dispatch = useDispatch();
if (isLiked) {
likeBtnClasses.push(classes.Highlight);
}
const postLikeHandler = () => {
if (!isLiked) {
setLike(true);
dispatch(actions.likePost(props.post._id, token));
} else {
setLike(false);
dispatch(actions.unlikePost(props.post._id, token));
}
};
return isLoading ? (
<Spinner />
) : (
<button
className={likeBtnClasses.join(" ")}
onClick={() => postLikeHandler()}
>
<i class="far fa-thumbs-up"></i>
<small>{props.post.likes.length}</small>
</button>
);
};
Instead of showing the spinner to that single post, I am seeing it on all the posts.
My Post component looks like this:
const Post = (props) => {
return (
<div className={classes.Post}>
<div className={classes.Author}>
<img src={props.postData.avatar} alt="avatar" />
<div className={classes.AuthorDetails}>
<h3>{props.postData.name}</h3>
</div>
</div>
<div className={classes.PostText}>
<p>{props.postData.text}</p>
</div>
<hr />
<div className={classes.PostTools}>
<LikeButton post={props.postData} />
<div className={classes.PostBtn}>
<i class="far fa-comments"></i>
<small>3</small>
</div>
<div className={classes.PostBtn}>
<i class="fas fa-share"></i>
<small>2</small>
</div>
</div>
</div>
);
};
PostList component:
class PostList extends React.Component {
state = {
posts: [
{
text: "POST1",
user: "XYZ",
name: "XYZ",
id: "post1",
likes: [],
},
{
text: "POST2",
user: "johndoe#test.com",
name: "John Doe",
id: "post2",
likes: [],
},
],
};
componentDidMount() {
if (this.props.token) {
this.props.onFetchPosts(this.props.token);
this.props.onFetchUserAuthData(this.props.token);
}
}
render() {
let posts = null;
if (this.props.posts.length === 0) {
posts = this.state.posts.map((post) => {
return <Post key={post.id} postData={post} />;
});
} else {
posts = this.props.posts.map((post) => {
return <Post key={post._id} postData={post} />;
});
}
return (
<div>
<CreatePost />
{posts}
</div>
);
}
}
const mapStateToProps = (state) => {
return {
token: state.auth.token,
posts: state.post.posts,
loading: state.post.loading,
error: state.post.err,
};
};
const mapDispatchToProps = (dispatch) => {
return {
onFetchPosts: (token) => dispatch(actions.fetchPosts(token)),
onFetchUserAuthData: (token) => dispatch(actions.fetchUser(token)),
};
};
Please do some change in your to checking like/unlike is loading or not for the LikeButton.
const LikeButton = (props) => {
....
const [isButtonLoading, setButtonLoading] = useState(false);
...
return isButtonLoading ? (
<Spinner />
) : (
<button
className={likeBtnClasses.join(" ")}
onClick={() => postLikeHandler();setButtonLoading(true)}
>
<i class="far fa-thumbs-up"></i>
<small>{props.post.likes.length}</small>
</button>
);
};
Then on your dispatch callback need to set the isButtonLoading value to false.
const buttonCallback() {
// here we need to reset our flag
setButtonLoading(false);
}
const postLikeHandler = () => {
if (!isLiked) {
setLike(true);
// for this action you need to create third parameter called as callback so after response our buttonCallback will call
dispatch(actions.likePost(props.post._id, token, buttonCallback));
} else {
setLike(false);
// for this action you need to create third parameter called as callback so after response our buttonCallback will call
dispatch(actions.unlikePost(props.post._id, token, buttonCallback);
}
};
fore more details please check here.
Hope this will help you.
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...!
this is the code of the StoryCreator
import React from 'react'
import { Helmet } from 'react-helmet'
import { connect } from 'react-redux'
import { Layout, Row, Col, Form, Input, Button, Divider, message, Icon, AutoComplete } from 'antd'
import { Link } from 'react-router-dom'
import ErrorPopover from '../../components/ErrorPopover/ErrorPopover'
import { success } from '../../services/story.services'
import { changeStoryTitle, changeStoryContent , changeStoryBody, changeStoryImage, changeStoryCategoryid, sendStory } from '../../actions/story.actions'
import { fetchCategories } from '../../actions/category.actions'
import '../../vendor/Shadow/Shadow.css'
import '../../vendor/Radius/Radius.css'
import './StoryCreator.css'
const { Content } = Layout
const FormItem = Form.Item
const { TextArea } = Input;
const onload = () => {
const hide = message.loading('Cargando entrada..');
};
class StoryCreator extends React.Component {
componentWillMount() {
this.props.dispatch(fetchCategories())
}
constructor (props) {
super(props)
}
handleChange = (e) => {
if(e.target.id === 'storyTitle') {
this.props.dispatch(changeStoryTitle(e.target.value))
}
}
handleChangeContent = (e) => {
if(e.target.id === 'storyContent') {
this.props.dispatch(changeStoryContent(e.target.value))
}
}
handleChangeBody = (e) => {
if(e.target.id === 'storyBody') {
this.props.dispatch(changeStoryBody(e.target.value))
}
}
handleChangeImage = (e) => {
if(e.target.id === 'storyImage') {
this.props.dispatch(changeStoryImage(e.target.value))
}
}
handleChangeCategoryid = (e) => {
if(e.target.id === 'storyCategoryid') {
this.props.dispatch(changeStoryCategoryid(e.target.value))
}
}
handleSubmit = (e) => {
e.preventDefault()
let storyTitleVal = this.props.storyTitle
let storyContentVal = this.props.storyContent
let storyBodyVal = this.props.storyBody
let storyImageVal = this.props.storyImage
let storyCategoryidVal = this.props.storyCategoryid
if (!storyTitleVal) {
this.storyTitleInput.focus()
return
}
this.props.dispatch(sendStory(storyTitleVal, storyContentVal, storyBodyVal, storyImageVal, storyCategoryidVal), onload)
}
render () {
const {categories} = this.props;
const data = categories;
function Complete() {
return (
<FormItem style={{marginTop: '-10px'}} label='CATEGORY'>
<AutoComplete
style={{ width: 200 }}
dataSource={data}
placeholder="try to type `b`"
filterOption={(inputValue, option) => option.props.children.toUpperCase().indexOf(inputValue.toUpperCase()) !== -1}
/>
</FormItem>
);
}
return (
<div>
<Form onSubmit={this.handleSubmit}>
<FormItem label='TITLE'>
<Input id='storyTitle' value={this.props.storyTitle} onChange={this.handleChange} ref={(input) => { this.storyTitleInput = input }} size='large' />
</FormItem>
<FormItem style={{marginTop: '-10px'}} label='CONTENT'>
<Input id='storyContent' value={this.props.storyContent} onChange={this.handleChangeContent} ref={(input) => { this.storyContentInput = input }} size='large' />
</FormItem>
<FormItem style={{marginTop: '-10px'}} label='Body'>
<TextArea rows={4} id='storyBody' value={this.props.storyBody} onChange={this.handleChangeBody} ref={(input) => { this.storyBodyInput = input }} size='large' />
</FormItem>
<FormItem style={{marginTop: '-10px'}} label='IMAGE'>
<Input id='storyImage' value={this.props.storyImage} onChange={this.handleChangeImage} ref={(input) => { this.storyImageInput = input }} size='large' />
</FormItem>
<FormItem style={{marginTop: '-10px'}} label='CATEGORY'>
<Input id='storyCategoryid' value={this.props.storyCategoryid} onChange={this.handleChangeCategoryid} ref={(input) => { this.storyCategoryidInput = input }} size='large' />
</FormItem>
<Complete />
<Button onClick={onload} disabled={this.props.isBusy} style={{marginTop: '-10px'}} type='primary' size='large' htmlType='submit' className='shadow-1'>
Send
</Button>
</Form>
</div>
)
}
}
function mapStateToProps (state) {
const { isBusy } = state.appReducer
const { storyTitle, storyContent, storyBody, storyImage, storyCategoryid } = state.storyReducer
return {
isBusy,
storyTitle,
storyContent,
storyBody,
storyImage,
storyCategoryid,
categories
}
}
const StoryCreatorConnected = connect(mapStateToProps)(StoryCreator)
export default StoryCreatorConnected
and this one of the category.actions
import { CATEGORY_CHANGE_NAME, CATEGORIES_FETCHED, CATEGORY_DELETED } from "../constants/category.constants";
import { showLoading, hideLoading } from 'react-redux-loading-bar'
import { toggleBusy } from '../actions/app.actions'
import { SaveCategory, GetCategories, DeleteCategory, UpdateCategory } from '../services/category.services'
import { history } from '../helpers/history'
export const deleteCategory = (id) => {
return { type: CATEGORY_DELETED, id: id}
}
export const changeNameCategory = (name) => {
return { type: CATEGORY_CHANGE_NAME, name: name}
}
export const sendCategory = (name) => {
return dispatch => {
dispatch(toggleBusy(true))
dispatch(showLoading())
SaveCategory(name)
.then(
response => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
dispatch(changeNameCategory(''))
dispatch(fetchCategories())
},
error => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
}
)
}
}
export const updateCategory = (id, name) => {
return dispatch => {
dispatch(toggleBusy(true))
dispatch(showLoading())
dispatch(changeNameCategory(''))
history.push('/admin/category')
UpdateCategory(id, name)
.then(
response => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
dispatch(changeNameCategory(''))
dispatch(fetchCategories())
},
error => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
}
)
}
}
export const destroyCategory = (id) => {
return dispatch => {
dispatch(toggleBusy(true))
dispatch(showLoading())
DeleteCategory(id)
.then(
response => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
dispatch(fetchCategories())
},
error => {
dispatch(toggleBusy(false))
dispatch(hideLoading())
}
)
}
}
export const fetchCategories = () => {
return dispatch => {
dispatch(toggleBusy(true))
dispatch(showLoading())
GetCategories()
.then(
response => {
console.log(response)
dispatch(toggleBusy(false))
dispatch(hideLoading())
dispatch(success(response.categories))
},
error => {
console.log(error)
dispatch(toggleBusy(false))
dispatch(hideLoading())
}
)
}
function success(categories) { return { type: CATEGORIES_FETCHED, categories: categories}}
}
what it does is communicate with a service to be able to take all the loaded categories, the problem is that I can not put all those categories in a variable to be able to list them in an Autocomplete, I do not know how to load the array into a variable.
The strange thing is that I could do it before in another component that lists all the categories. There I leave the code, I hope you can help me
import React from 'react'
import {connect} from 'react-redux'
import {
Card,
Alert,
Icon,
Button,
Table,
Divider,
Popconfirm
} from 'antd';
import {Link} from 'react-router-dom'
import {fetchCategories, destroyCategory, editCategory} from '../../actions/category.actions';
const {Meta} = Card;
class Categories extends React.Component {
componentDidMount() {
this.props.dispatch(fetchCategories())
}
handleDeleteCategory(e) {
//var removedItem = fruits.splice(pos, 1);
this.props.dispatch(destroyCategory(e.id))
}
render() {
const {categories} = this.props;
const data = categories;
const columns = [
{
title: 'ID',
dataIndex: 'id',
key: 'id'
}, {
title: 'Nombre de la categoria',
dataIndex: 'name',
key: 'name'
}, {
title: 'Acciones',
key: 'action',
render: (text, record) => (<span>
<Link to={`/admin/category/edit/${record.id}/${record.name}`}>Editar</Link>
<span className="ant-divider"/>
<a onClick={() => this.handleDeleteCategory(record)}>Eliminar</a>
</span>)
}
];
if (this.props.categories.length == 0)
return (<Alert message="No hay categorias para mostrar." type="error"/>);
return <Table dataSource={data} columns={columns}/>
}
}
function mapStateToProps(state) {
const {categories} = state.categoryReducer
return {categories}
}
const connectedCategories = connect(mapStateToProps)(Categories)
export default connectedCategories
solve it, forget to import the variable to the mapStateToProps and to reduce. Thank you!