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,
})
);
};
Related
I want to be able to type into my input fields, and then have a button show up beside it upon typing that says submit edit. right now, I have a button that always is there, but I want it to only show up upon typing. this is all in react btw. so far, I have tried jquery, but react doesn't like it.
here's the whole page, to avoid any confusion of what I am doing and where my stuff is located.
import React, { Component } from "react";
import axios from "axios";
import "../styles/TourPage.css";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true,
};
}
componentDidMount() {
axios
.get("/getResults")
.then((res) => {
this.setState({
myData: res.data
});
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
deleteById = (id) => {
console.log(id)
axios
.post(`/deleteDoc`, {id: id} )
.then(() => {
console.log(id, " worked")
window.location = "/tour"
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
}
editById = (id, siteLocation, Services, cnum) => {
console.log(id, siteLocation, Services, cnum)
axios
.post(`/editDoc`, JSON.stringify({id: id, location: siteLocation, Services: Services, cnum: cnum}),{
headers: {
"Content-Type": "Application/json"
}
} )
.then(() => {
console.log(id, " worked")
window.location = "/tour"
})
.catch((error) => {
// Handle the errors here
console.log(error);
})
}
render() {
// You can handle the loader part here with isLoading flag. In this case No data found will be shown initially and then the actual data
let { myData, isLoading } = this.state;
return (
<table id="customers">
<tr>
<th>siteLocation</th>
<th>Services</th>
<th>cnum</th>
</tr>
{myData.length > 0
? myData.map(({ location, Services, cnum, _id }, index) => (
<tr key={index}>
<td><input type="text" placeholder={location} name="location" id="location" /> </td>
<td><input type="text" placeholder={Services} name="Services" id="Services" /> </td>
<td><input type="text" placeholder={cnum} name="cnumhide" id="cnumhide" /> </td>
<td><input type="hidden" placeholder={cnum} name="cnum" id="cnum" /> </td>
<button
onClick={(e) => {
e.preventDefault();
this.deleteById(_id);
}}
disabled={isLoading}
>
Delete
</button>
<button
onClick={(e) => {
e.preventDefault();
var siteLocation = document.getElementById('location').value
var Services = document.getElementById('Services').value
var cnum = document.getElementById('cnum').value
this.editById(_id, siteLocation, Services, cnum)
}}
>
Submit Edit
</button>
</tr>
))
: "No Data Found"}
</table>
);
}
}
const script = document. createElement("script"); $('input').keyup(function(){
if($.trim(this.value).length > 0)
$('#location').show()
else
$('#location').hide()
});
export default TourPage;
thanks 4 the help in advance.
You can use onfocus() in the text element. If you want to hide the button, use onfocusout() or in case if you want to track only after input has changed, use onchange() event
...
//class function
onTyping =()=>{
this.setState({
showSubmit:true
})
}
...
//render part
render(){
...
//input which you want to track typing
<input type="text" onfocus={()=>this.onTyping()} placeholder={location} name="location" id="location" />
...
//element submit button
{this.state.showSubmit && <button
onClick={(e) => {
e.preventDefault();
var siteLocation = document.getElementById('location').value
var Services = document.getElementById('Services').value
var cnum = document.getElementById('cnum').value
this.editById(_id, siteLocation, Services, cnum)
}}
>
Submit Edit
</button>}
...
Here is an example that helps you,
const {
useState
} = React;
const Test = () => {
const [show, setShow] = useState(false);
const handleChange = (event) => {
if (event.target.value.length > 0)
setShow(true);
else
setShow(false)
}
return ( <div>
<input type = "text"
onChange = {
(event) => handleChange(event)
}/>
{show && < button > Submit changes now! </button>}
</div>
)
}
ReactDOM.render( < Test / > ,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>
There is a way to avoid jquery and continue using your react class component to achieve this.
Map over state.myData to render each item with an input and a button.
Use the array index with the input's onChange event callback to add the inputValue into the correct array item's object within state.
Use the array index with the button's onClick event callback to get the item from state.myData before sending it to the server.
If there is an inputValue for the item, you can conditionally render the button.
import React, { Component } from "react";
import axios from "axios";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true
};
}
componentDidMount() {
axios
.get("https://rickandmortyapi.com/api/character")
.then((res) => {
this.setState({
myData: res.data.results
});
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
handleChangeInput = ({ target }, index) => {
const newData = [...this.state.myData];
newData[index].inputValue = target.value;
this.setState({
myData: newData
});
};
handleSubmitEdit = (index) => {
const item = this.state.myData[index];
// submit the edit to the api
console.log(
`Clicked on 'submit edit' for ${item.name} with value ${item.inputValue}`
);
};
render() {
let { myData, isLoading } = this.state;
if (isLoading) {
return "loading...";
}
return (
<div>
{myData.map(({ name, status, species, inputValue }, index) => {
return (
<div key={index}>
<p>{`${name} - ${species} - ${status}`}</p>
<input
type="text"
onChange={(e) => this.handleChangeInput(e, index)}
value={inputValue || ""}
/>
{inputValue && (
<button onClick={() => this.handleSubmitEdit(index)}>
Submit Edit
</button>
)}
</div>
);
})}
</div>
);
}
}
export default TourPage;
If you wanted to have an input per field within each row, you could make some small changes and save your edits to the item's state within a nested object.
Then you could check if there was anything inside that row's edits object to conditionally show the submit button per row.
import React, { Component } from "react";
import axios from "axios";
import isEmpty from "lodash.isempty";
import pick from "lodash.pick";
class TourPage extends Component {
constructor(props) {
super(props);
this.state = {
myData: [],
isLoading: true
};
}
componentDidMount() {
axios
.get("https://rickandmortyapi.com/api/character")
.then((res) => {
this.setState({
// here we create an empty 'edits' object for each row
myData: res.data.results.map((d) => ({
...pick(d, ["name", "status", "species"]),
edits: {}
}))
});
})
.finally(() => {
this.setState({
isLoading: false
});
});
}
handleChangeInput = ({ target }, index) => {
const newData = [...this.state.myData];
const { value, name } = target;
newData[index].edits[name] = value;
this.setState({
myData: newData
});
};
handleSubmitEdit = (index) => {
const item = this.state.myData[index];
// submit the edit to the api
console.log(`Clicked on 'submit edit' for ${item.name} with edits:`);
console.log(item.edits);
console.log("Updated item: ");
const { edits, ...orig } = item;
const newItem = { ...orig, ...edits };
console.log(newItem);
// Once saved to api, we can update myData with newItem
// and reset edits
const newData = [...this.state.myData];
newData[index] = { ...newItem, edits: {} };
this.setState({
myData: newData
});
};
showButton = (index) => {
const { edits } = this.state.myData[index];
return !isEmpty(edits);
};
render() {
let { myData, isLoading } = this.state;
if (isLoading) {
return "loading...";
}
return (
<table>
<tbody>
{myData.map((row, index) => {
const { edits, ...restRow } = row;
return (
<tr key={index}>
{Object.keys(restRow).map((col) => {
return (
<td>
<label>
{col}:
<input
name={col}
value={edits[col] || restRow[col]}
onChange={(e) => this.handleChangeInput(e, index)}
/>
</label>
</td>
);
})}
<td>
{this.showButton(index) && (
<button onClick={() => this.handleSubmitEdit(index)}>
Submit Edit
</button>
)}
</td>
</tr>
);
})}
</tbody>
</table>
);
}
}
export default TourPage;
I am developing a chat app using react js and firebase database everything works fine but when i open a new person to chat the previous chat messages with other person renders..
there is link to deployed app: https://basic-chat-42.web.app
Q:how to clear the state every time i call chat function so there is no message from the previous chat
code:
import React from 'react'
import { connect } from 'react-redux';
import { adding_Dummy, get_user } from '../store/action'
import firebase from '../config/firebase'
class Chat extends React.Component {
constructor() {
super()
this.state = {
chat_user: {},
chat: [],
message: []
}
}
chat = (user) => {
this.setState({
chat_user: user
})
let cu_user= this.props.current_user
let chat_user = this.state.chat_user
let merged_uid = this.uid_merge(cu_user.uid,user.uid);
console.log('hammad',user.uid)
console.log('osama',cu_user.uid)
console.log('uid___WARS',cu_user.uid)
console.log('merged_uid>>>',merged_uid)
this.get_messages(merged_uid)
}
uid_merge =(id1,id2)=>{
if (id1<id2) {
return id1+id2
}
else
return id2+id1
}
send_message=()=>{
let user= this.props.current_user
let chat_user = this.state.chat_user
let merged_uid = this.uid_merge(user.uid,chat_user.uid);
firebase.database().ref('/').child(`chat/${merged_uid}`).push({
message :this.state.message,
name :user.name,
uid:user.uid
})
// this.state.chat.push({
// message : this.state.message
// })
// this.setState({
// chats: this.state.chats,
// message : ''
// })
}
get_messages=(uid)=>{
firebase.database().ref('/').child(`chat/${uid}`).on('child_added',(message)=>{
this.state.chat.push(message.val())
this.setState({
chat:this.state.chat,
message : ''
})
})
}
componentDidMount() {
this.props.get_user()
this.props.get_user()
}
render() {
console.log('stateChat=>', this.state)
let user = this.props.current_user
console.log('thistaet==>s', this.state)
console.log('asdCU',this.props.current_user)
return (
<div>
<h1>WELCOME ! {user.name}</h1>
<img src={user.profile} alt='img' />
<h2>Email: {user.email}</h2>
<button onClick={() => this.props.dum_data()}>adding dummy</button>
<button onClick={() => console.log(this.props)}>PRops</button>
<button onClick={() => this.props.get_user()}>get user</button>
{/* <button onClick={(e)=>console.log(e.target.value)}>button</button> */}
<div style={{ display: "flex" }}>
<div style={{ backgroundColor: "red" }}>
<h4>Chat users</h4>
<ul>
{this.props.users.map((v, i) => {
return v.uid !== user.uid && <li key={i}>{v.name} <button onClick={() => this.chat(v)} >CHAT</button></li>
})}
</ul>
</div>
<div style={{ width: 400, backgroundColor: "yellow" }}>
<h4>CHAT</h4>
{Object.keys(this.state.chat_user).length ?
<div>
<h4>{this.state.chat_user.name}</h4>
<ul>
{this.state.chat.map((v,i)=>{
return <li style={{color : v.uid === user.uid ? 'red' :'green'}}key={i}>{v.message}</li>
})}
<input value={this.state.message} onChange={(e)=>{this.setState({message:e.target.value})}} type="text" placeholder="Enter here" />
<button onClick={()=>this.send_message()}>Send </button>
</ul>
</div>
:
<div>
NO USER
</div>}
</div>
</div>
</div>
)
}
}
const mapStateToProps = (state) => ({
current_user: state.current_user,
users: state.users
})
const mapDispatchToProps = (dispatch) => ({
dum_data: () => dispatch(adding_Dummy()),
get_user: () => dispatch(get_user()),
})
export default connect(mapStateToProps, mapDispatchToProps)(Chat);
You need to clear the message when user clicked on chat button for the name
<button onClick={() => this.chat(v)} >CHAT
In the chat() method, update to reset the messages in state
this.setState({
chat_user: user,
chat: [],
message: []
})
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.
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...!