i'm working on a MERN app where the user can receive notifications on certain events, the user can then open the notification page where he can view his notifications, the issue that i have is the notifications have a "seen" state, the notifications that aren't seen will always be on top of the notification results..but say the user clicks on a notification which sets it as seen and then paginates further down the notification list by clicking on "show more" btn, he will find that earlier notification waiting for him in the bottom, basically it's duplicating..how can i fix this?
express controller for notifications
router.get("/", verifyUser, (req, res) => {
let userId = res.locals.user._id; //from the verify user MW, unrelated to question
let pageNum = req.query.page;
const options = {
page: pageNum,
limit: 8,
sort: { seen: 1, createdAt: -1 }
};
Notification.paginate({ receiver: userId }, options, (err, result) => {
if (err) {
console.log(err);
res.sendStatus(400);
} else {
res.status(200).json(result);
}
})
})
react
function Notifications(){
let { texts } = useContext(LangContext);
let [notificationsList, setNotificationsList] = useState([]);
let [isPending, setIsPending] = useState(true);
let [notiPage, setNotiPage] = useState(1);
let [hasNext, setHasNext] = useState(false);
function getNotifications() {
axios.get(`/api/notification?page=${notiPage}`).then(result => {
console.log(result);
setNotificationsList([...notificationsList, ...result.data.docs]);
if (result.data.hasNextPage) {
setNotiPage(result.data.nextPage);
setHasNext(true);
} else {
setHasNext(false);
}
setIsPending(false);
}).catch(err => {
console.log(err);
setIsPending(false);
})
}
//update notification seen status on the backend as well as on the front end
function toggleNotificationView(id, seen) {
let viewStatus = !seen;
axios.put(`/api/notification/${id}`, { seen: viewStatus }).then(result => {
console.log(result);
let notiArray = [...notificationsList];
let index = notiArray.findIndex(noti => {
return noti._id == id;
})
let editedNoti = notiArray[index];
editedNoti.seen = viewStatus;
notiArray.splice(index, 1, editedNoti);
setNotificationsList(notiArray);
}).catch(err => {
console.log(err);
})
}
return <div>
<div className="d-flex flex-column justify-content-center align-content-center my-3">
{notificationsList.map(notification => {
return (<div key={notification._id} id={notification._id} className="w-70 mx-auto">
<div className={`d-flex justify-content-between ${!notification.seen ? "new-notification" : ""}`}>
<a className="text-decoration-none text-dark" href={notification.srcInfo.link}>
<div className={`d-flex justify-content-start align-items-center`}>
<img src={notificationBell} width={100} height={100} />
<p className="text-start fs-4 mx-4" >{parseNotification(notification)}</p>
</div>
</a>
<div className="noti-dropdown noselect align-self-start">
<i onClick={toggleDropDown} className="bi bi-three-dots-vertical fs-4"></i>
<ul className="noti-dropdown-options px-0 fs-5">
<li onClick={() => { deleteNotification(notification._id) }}>{texts.deleteNotification}</li>
<li onClick={() => { toggleNotificationView(notification._id, notification.seen) }}> {notification.seen ? texts.markNotificationNotSeen : texts.markNotificationSeen}</li>
</ul>
</div>
</div>
<hr className="text-main" />
</div>)
})}
</div>
{hasNext && <h4 onClick={getNotifications} className="mx-auto text-center text-main text-decoration-underline"><span className="cursor-pointer">{texts.notificationShowMore}</span></h4>}
</div>
}
the problem is happening because i'm sorting by seen while paginating, what can i do about this?
Related
I am working in MERN project.
what I want
I am fetching the project list from the backend If i click on any project it should give the members list that is working on that project so i want to get the objectid of that clicked project
what i tried
import React, { useState, useEffect } from 'react'
import { NavLink } from 'react-router-dom'
import { useNavigate } from 'react-router-dom';
const AdminDash = () => {
const navigate = useNavigate()
const [userData, setuserData] = useState([])
const [data, setData] = useState({});
const callAboutPage = async () => {
try {
const res = await fetch("/alldata", {
method: "GET",
headers: {
Accept: "application/json",
"Content-Type": "application/json"
},
credentials: "include"
})
const data = await res.json()
setuserData(data)
console.log(setuserData);
if (!res.status === 200) {
const error = new Error(res.error)
throw error
}
} catch (error) {
console.log(error);
navigate("/")
}
}
function handleButtonClick(id) {
fetch(`/api/get-data/${id}`)
.then(response => response.json())
.then(data => {
setData(data);
console.log(data);
});
}
useEffect(() => {
callAboutPage()
}, [])
return (
<>
<div className='container mt-5'>
<div className='row'>
<div className='col-sm-10 col-md-10 col-lg-10'>
<div class="row align-items-md-stretch">
<div class="col-md-6">
<div class="h-100 p-5 text-bg-light rounded-3">
<h2>Current Ongoing Projects</h2>
<ol class="list-group list-group-numbered mt-5">
{
userData.map((item, i) => (
<li class="list-group-item d-flex justify-content-between align-items-start">
<div class="ms-2 me-auto">
<NavLink onClick={() => handleButtonClick()} to="/admindash" className="text-decoration-none"><div class="fw-bold">{item.name}</div></NavLink>
Content for list item
</div>
<i class="bi bi-at"></i>
</li>
))}
</ol>
<label>{data.name}</label>
</div>
</div>
</div>
</div>
</div>
</div>
</>
)
}
export default AdminDash
backend
app.get('/api/get-data/:id', (req, res) => {
ProjectSchema.findById(req.params.id, (err, doc) => {
if (err) {
res.send(err);
} else {
res.json(doc);
}
});
});
the output i get in my console
{stringValue: '"undefined"', valueType: 'string', kind: 'ObjectId', value: 'undefined', path: '_id', …}
How to achieve this ..any suggestions?
In the mapping onClick pass item.id.
First make sure you getting it in your data while Fetching.
<NavLink onClick={() => handleButtonClick(item.id)} to="/admindash" className="text-decoration-none">{item.name}
I'm developing a private chat application using MERN stack and socket.io.
I'm able to send a private message to a specific user successfully but not able to include the message sender to show to both the users.
Client
export default function Chat({ users }) {
const [activeUser, setActiveUser] = useState('');
const [currentUser, setCurrentUser] = useState(null);
const [filteredList, setFilteredList] = useState([]);
const [message, setMessage] = useState('');
const [chats, setChats] = useState([]);
useEffect(() => {
const jwt = isAuthenticated().user;
setCurrentUser(jwt);
config.socket;
config.socket.on('isOnline', (data) => {
console.log(data);
});
}, []);
useEffect(() => {
const list = users.filter(user => currentUser ? user._id !== currentUser._id : user._id);
setFilteredList(list);
}, [currentUser]);
useEffect(() => {
config.socket.on('chat', (data) => {
setChats([...chats, data]);
});
}, []);
const changeActiveUser = (e, user) => {
e.preventDefault();
console.log(`changeActiveUser ${JSON.stringify(user)}`);
setActiveUser(user);
};
const handleClick = e => {
e.preventDefault();
const msg = {
socketId: activeUser.socketId,
to: activeUser._id,
toUser: activeUser.username,
message: message,
from: currentUser._id
}
// config.socket.emit('join', {id: activeUser.socketId, username: activeUser.username})
console.log(`Private MSG ${JSON.stringify(msg)}`);
config.socket.emit('private', msg);
setMessage('');
}
return (
<>
<div className='container'>
<div className='row'>
<div className='col-3 leftSide'>
{filteredList.map((user) => (
<li key={user._id} className={`list ${user._id === activeUser._id ? 'active' : ''}`} onClick={e => changeActiveUser(e, user)}>
<img className='userImg' src={user.gender === 'Female' ? '/female.jpg' : '/male.jpg'} /> {user.name} <small> ({user.username}) <span>{user.online ? 'online' : 'offline'}</span></small>
</li>
))}
</div>
<div className='col-9 rightSide'>
<div className='row'>
<div className='col rightTop'>
<h1>{activeUser.username ? `${activeUser.username} (${activeUser._id})` : 'Start a chat!'}</h1>
<ul>
{chats && chats.map((chat, i) => (
<li key={i}>
<span>{activeUser._id === chat.from ? <span style={{ float: 'left' }}>{`${activeUser.username}: ${chat.message}`}</span> : <span style={{ float: 'right' }}>{`Me: ${chat.message}`}</span>}</span>
{/* <div>{activeUser._id === chat.to ? <div style={{ float: 'right' }}>{`Me: ${chat.message}`}</div> : ''}</div> */}
</li>
))}
</ul>
</div>
</div>
<hr />
{activeUser && (
<div className='row rightBottomContainer'>
<div className='rightBottomInput'>
<input value={message} type='text' onChange={(e) => setMessage(e.target.value)} placeholder=' Enter your chat...' />
</div>
<div className='rightBottomButton'>
<button onClick={(e) => handleClick(e)} type='submit'>Send</button>
</div>
</div>
)}
</div>
</div>
</div>
</>
);
}
Server
const users = {};
io.on('connection', (socket) => {
console.log(`${socket.id} connected`);
socket.on('signin', (data) => {
users[data.username] = data.socketId;
socket.emit('isOnline', { userId: data.userId, online: true });
});
console.log(`USERS: ${JSON.stringify(users)}`);
// socket.on('join', (data) => {
// console.log(`JOIN: ${JSON.stringify(data)}`);
// socket.join(data.id);
// });
socket.on('private', (msg) => {
console.log(`users[msg.toUser] ${users[msg.toUser]} === msg.socketId ${msg.socketId}`);
if (users[msg.toUser] === msg.socketId) {
console.log(msg);
io.to(msg.socketId).emit('chat', msg);
}
});
socket.on('disconnect', () => {
console.log(`${socket.id} disconnected`);
});
});
I need help to include the sender along with the message to show to both the users. And I also want to append the messages properly since the above code is overriding the previous message.
You can create a room with 2 users in it. every time you want to start a private chat. I just use something like the following code to implement this.
Let's assume that we have a user with id = 1 that wants to send a message to a user with id = 2. so we create a room names 1-2 so when the first user wants to send a message to the second user, you have to add 1-2 to the query when you initializing the socket.io in the client like this:
socket = socketIOClient(ENDPOINT, {
query: {
room: "1-2",
},
});
now you know on the server that a user wants to connect to which room. so you can add the room to your socket using this code:
io.use(async function(socket, next) {
socket.room = socket.handshake.query.room;
return next();
}
So now you can use room feature of socket.io to send the message to both of sender and receiver like this:
socket.on('private', (msg) => {
socket.broadcast
.to(socket.room)
.emit("chat", msg)
});
the whole server code will be something like this:
io.use(function(socket, next) {
socket.room = socket.handshake.query.room;
return next();
})
.on("connection", function(socket) {
socket.on('private', (msg) => {
socket.broadcast
.to(socket.room)
.emit("chat", msg)
});
}
I'm trying to delete an item after the user clicks on the Delete button. Through the handleDelete function, I am passing an id (idBooks) via axios when the user clicks a book. How do I withdraw it from the click?
Below you will find the React code and then the backend side code in node js.
Frontend
(class extends React.Component {
handleDelete = (e) => {
e.preventDefault();
const { book } = this.props;
axios.delete("http://localhost:8081/delete", book.idBooks )
.then(res => {
console.log(res.data);
}).catch(err => {
console.warn(err.warn);
});
};
render() {
const { book, classes } = this.props;
const token = localStorage.getItem('token');
return(
<Paper className= { classes.marginTopBottom }>
<h2 className={ classes.title }>
{ book.title }
</h2><hr />
<div className= { classes.scrollingDiv }>
<p>
{ book.plot }
</p>
</div>
<hr/>
<div className={ classes.pStyle }>
<p>Publish date:<br /> { new Date(book.publish_date).toLocaleDateString() }</p>
<p>Author:<br /> { book.author }
</p>
<p>Genre:<br /> { book.genre }</p>
</div>
<div>
{ token && (
<Button className={ classes.delete } size="small" onClick={this.handleDelete} type="button" variant="contained" color="primary"
component= {Link} to="delete">
Delete
<DeleteIcon className={ classes.rightIcon } />
</Button>
)}
</div>
</Paper>
)
}
});
Backend
const deleteBook = (req, res) => {
const connection = mysql.createConnection(connectionProperties);
connection.connect();
const query = `DELETE FROM Books WHERE idBooks = ${ req.body.idBooks }`;
connection.query(query, (err, res) => {
if (err) {
res.status(500).send(err);
} else {
res.status(200).send('Book deleted correctly.');
}
});
};
I'd add a prop onDeleteCallback, and on successful delete call that function with deleted book id. In parent component (with all the books are listed) update the state with filtered out books.
I guess passing the parameter might help you fix this issue.
On the delete Button add a parameter to the onClick={()=>this.handleDelete(e,book.idBooks)}
Change the handleDelete function a bit as below
handleDelete = (e,idBooks) => {
e.preventDefault();
axios.delete("http://localhost:8081/delete", idBooks )
.then(res => {
console.log(res.data);
}).catch(err => {
console.warn(err.warn);
});
};
I am able to fetch the data from the db and it is displaying on the inspect element also but it is not displaying on the browser i mean UI.
//storing the data into the posts
this.state = {
displayMenu: false,
posts: [ ]
};
//click function for the drop down and handling the axios.get
Click = event => {
event.preventDefault();
let currentComponent = this;
axios.get(`http://localhost:4000/api/AMS`)
.then(function (response) {
console.log(response);
currentComponent.setState({posts: response.data})
})
.catch(function (error) {
console.log(error);
});
}
//Render method
render() {
// var back = {backgroundSize : 'cover'};
var textStyle = {
position: 'absolute',
top: '50%',
left: '50%'
};
//From here the checking of data is happening, if the data is found inside the posts it will show it on the browser otherwise it will show no posts.
const { posts } = this.state;
const postList = posts.length ? (
posts.map(post => {
// console.log('hi');
return (
<div className="post card" key={post.ID}>
<div className="card-content">
</div>
</div>
)
})
) : ( <div className="center">No posts yet</div>)
//RETURN method
return (
<div>
{/* <Image
style={back} responsive
src={logo}>
</Image> */}
<div style={textStyle} className="dropdown" style = {{background:"red",width:"200px"}} >
<div className="button" onClick={this.showDropdownMenu}> Regions </div>
{ this.state.displayMenu ? (
<ul>
<li><a className="active" href="/AMS" onClick={this.Click}>AMS</a></li>
<li>EMEA</li>
<li>APJ</li>
</ul>
):(null)
}
</div>
//Here i am calling the postList variable
{postList}
{/* {this.state.posts}<br/>
{this.state.pictures} */}
</div>
);
}
}
Click = event => {
event.preventDefault();
let currentComponent = this;
axios.get(`http://localhost:4000/api/AMS`)
.then(function (response) {
console.log(response);
currentComponent.setState({posts: response.data})
})
.catch(function (error) {
console.log(error);
});
}
render() {
// var back = {backgroundSize : 'cover'};
var textStyle = {
position: 'absolute',
top: '50%',
left: '50%'
};
const { posts } = this.state;
const postList = posts.length ? (
posts.map(post => {
// console.log('hi');
return (
<div className="post card" key={post.ID}>
<div className="card-content">
</div>
</div>
)
})
) : ( <div className="center">No posts yet</div>)
The results that i am getting in the inspect element console is like below:
ID: 229, EMAIL: "anuraguk3#gmail.com", ROLE: "BASE", PASSWORD:"$2b$10$ShTWYAtF8M5JLhEm68JqTuMx7P8x6dtOIkNsGz4wE21LY92xGoDCO"
DOM is rendered before getting server response.So, you need to use async-await in this scenario. For your program:-
Click = async(event) => {
event.preventDefault();
let currentComponent = this;
await axios.get(`http://localhost:4000/api/AMS`)
.then(function (response) {
console.log(response);
currentComponent.setState({posts: response.data})
})
.catch(function (error) {
console.log(error);
});
}
Simply all i'm trying to do is re render the notes list when i add another note to the database. i tried several methods even redux dispatch method. but none worked and it kinda make sense because when i add a note i don't add anything so it can get the updated notes through /budget. maybe i have a big misunderstanding.
here's how i add a new note
export function saveOneNote() {
// saving a note
const _id = $('input[name="_id"]').val(),
firstItem = $('input[name="firstItem"]').val(),
firstPrice = $('input[name="firstPrice"]').val(),
secondItem = $('input[name="secondItem"]').val(),
secondPrice = $('input[name="secondPrice"]').val(),
thirdItem = $('input[name="thirdItem"]').val(),
thirdPrice = $('input[name="thirdPrice"]').val(),
tBudget = $('input[name="tBudget"]').val();
let currency = $("#currency").val();
console.log(currency);
$.ajax({
url: "/newNote",
type: "post",
dataType: "json",
contentType: "application/json",
data: JSON.stringify({
currency,
_id,
firstItem,
firstPrice,
secondItem,
secondPrice,
thirdItem,
thirdPrice,
tBudget
}),
success: function(Data) {
console.log("note was saved!", Data);
},
error: function(err, status, xhr) {
console.log("err", err);
}
});
}
here's how i fetch notes
class ShowAll extends Component {
constructor(props){
super(props);
this.state = {
Data: [],
length:[],
searchbyid:[],
isLoggedIn:[]
}
}
componentDidMount(){
// fetch notes
Rquest.get('/budget').then((res)=>{
let DataString = Array.from(res.body);
this.setState((prevState,props)=>{
return {
Data: DataString,
length: res.body.length
}
})
}).catch((err)=> {
console.log(err);
})
// check if user is logged in
Request.get('/auth').then((user)=>{
if(user){
this.setState({
isLoggedIn: true
})
}
}).catch((err)=> {
this.setState({
isLoggedIn: false
})
});
}
render(){
const count = this.state.length;
const myNotes = this.state.Data;
const isLoggedIn = this.state.isLoggedIn;
const listItems = myNotes.map((dynamicData)=>{
return(
<Fragment key={dynamicData.id}>
<div className='jumbotron'>
<div className='row'>
<button className='btn btn-danger delete-note-btn' onClick={DeleteOneNote}>Delete</button>
<input className='col-12 title form-control' id='deleteById' value={dynamicData._id} readOnly/>
<div className="dropdown-divider"></div> {/*line divider*/}
<div className='col-6' >
<ul className='list-unstyled'>
<li className='items'>items</li>
<li >{dynamicData.firstItem}</li>
<li >{dynamicData.secondItem}</li>
<li >{dynamicData.thirdItem}</li>
{/* <li>Total Budget :</li> */}
</ul>
</div>
<div className='dynamicData col-6'>
<ul className ='list-unstyled'>
<li className='prices'>Prices</li>
<li>{dynamicData.firstPrice} {dynamicData.currency}</li>
<li>{dynamicData.secondPrice} {dynamicData.currency}</li>
<li>{dynamicData.thirdPrice} {dynamicData.currency}</li>
</ul>
</div>
</div>
<h3 className='col-12 totalprice'>{dynamicData.tBudget} {dynamicData.currency}</h3>
</div>
</Fragment>
)
})
return (
<Fragment>
{isLoggedIn ===true?(
<div className='myNotesList '>
number of notes : {count}
{listItems}
</div>
):(
<Fragment>
</Fragment>
)
}
</Fragment>
)
}
}
React components are re-rendering only on state or props change. In your code - you're not mutating state nor props of your component.
What you should do in your case probably is to re-fetch the items after save or add the new items to the state or pass through props.
Example:
class Notes extends React.Component {
state = { note: '', notes: [] }
changeNote = ({ target: { value } }) => {
this.setState({ note: value });
}
addNote = () => {
this.setState((state) => ({ notes: [...state.notes, state.note] }));
}
render() {
return (
<div>
<input type="text" onChange={this.changeNote} />
<button onClick={this.addNote}>Add</button>
<ul>
{this.state.notes.map(note =>
<li>{note}</li>
)}
</ul>
</div>
)
}
}
ReactDOM.render(
<Notes />,
document.getElementById('app')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.2.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.2.0/umd/react-dom.production.min.js"></script>
<div id="app">
</div>