I'm building an open forum and I got everything working the way I want however, I recently ran into a small bug in my code. While a lot of forums require a log in to view topics that are asked, mine is built without the user having to register or log in (I built it like this in case the user simply wants to view topics without having to register or log in). For one of the functionalities, I have it to where users can delete there replies for a specific question thread.
Therefore, I got it to where it only recognizes the users ID and it'll delete the reply based off of that. However, if the user is not logged in, they'll still be able to delete the reply (this is where the bug is).
So my question is it possible that I can check for 2 conditions inside my ternary operator?
I want to check 1st if the user is logged in, if they are, check if the userId matches the reply_user_id and if both cases pass, they'll be able to delete there reply. However, if one fails Don't display the trash icon. Right now, the trash icon works fine when logged in but it's displaying the trash icon if the user is not logged in.
I have a lot of code so I'm only going to show the portion relating to my question:
import React from 'react';
import Header from '../common/Header';
import { Link } from 'react-router-dom';
import { Modal, Button } from 'react-bootstrap';
import Pagination from "react-js-pagination";
import ForumpageService from '../../services/forumService';
import appController from '../../controllers/appController';
import { confirmAlert } from 'react-confirm-alert';
class Forumreplies extends React.Component {
constructor(props){
super(props);
this.deleteReply = this.deleteReply.bind(this);
this.pagination = this.pagination.bind(this);
this.state = {
topicId: 0,
replyId: 0,
userId: this.props.match.params.userid,
postDetails: {},
repliesData: [],
reply: '',
errorMsg: '',
isLoggedin: false,
canDelete: false,
}
}
async componentDidMount(){
// Check if user is logged in
if(localStorage.getItem('userData') !== null) {
this.setState({isLoggedin: true})
}
const topicId = this.props.match.params.topicid
const postDetails = await ForumpageService.replyDetails({topicId: topicId})
this.setState({
postDetails: postDetails[0],
topicId: topicId
})
await this.postReplies();
console.log(this.state);
}
}
deleteReply(id, e){
confirmAlert({
customUI: ({ onClose }) => {
return (
<div className='custom-ui'>
<h1>Are you sure</h1>
<p>You want to delete this reply?</p>
<button className="btn btn-primary" onClick={onClose}>Cancel</button>
<button className="btn btn-primary" onClick={() => {this.confirmDelete(id); onClose();}}>Confirm</button>
</div>
)
}
})
}
render(){
const repliesData = currentReply.map((row, index) => {
return (
<div className="reply-container" key={index}>
{row.reply_status == 0 ?
<div className="row" id="reply-messages-deleted">
<div className="col-md-8">
<p>{row.userName}</p>
<p>{row.reply_message}</p>
<p>This reply has been deleted</p>
</div>
<div className="col-md-2">
// Multiple condition I want to check, right now it's only checking 1 condition which is the userid to the reply id but I want to also check if the user is offline as well.
{this.state.userId == row.reply_user_id ? <i className="far fa-trash-alt" onClick={this.deleteReply.bind(this, row.reply_id)} title="Delete this reply?"></i> : null }
</div>
</div>
:
<div className="row" id="reply-messages" key={index}>
<div className="col-md-8">
<p>{row.userName}</p>
<p>{row.reply_message}</p>
</div>
<div className="col-md-2">
{this.state.userId == row.reply_user_id ? <i className="far fa-trash-alt" onClick={this.deleteReply.bind(this, row.reply_id)} title="Delete this reply?"></i> : null }
</div>
</div>
}
</div>
)
})
export default Forumreplies;
Related
My login page doesn't work and need a refresh to be logged in. input data is displayed in the console with 201 but I have to manually refresh the page to continue.
this is my login.js:
export default class Login extends Component {
state = {};
submitHandler = e => {
e.preventDefault()
const logingdata ={
email : this .Email,
password: this .Password
}
axios
.post('/api/UserLogin', logingdata)
.then(response => {
console.log(response); //to print response in console in developper tool
localStorage.setItem('login', response.data.token);
console.log(this.Email);{
const loginmail=logingdata.email
console.log(loginmail);
}
})
.catch(error => {
console.log(error => console.log(error.response.data))
})
}
render() {
return (
<div className="outer">
<div className="inner">
<form onSubmit={ this.submitHandler}>
<h3>Log in</h3>
//Login form comes here.
<button type="submit" className="btn btn-dark btn-lg btn-block" >Sign in</button>
<p className="forgot-password text-right">
Forgot <Link to= "/Forgotpassword" >password?</Link>
</p>
</form>
</div>
</div>
);
}
}
I can not figure out what the issue is. Can someone help me with this?
The code you are presenting is saving the token in the localStorage and probably other components are handling the redirection when you will have that information in the storage. You will need to redirect to a specific URL ( home page probably ) after localStorage.setItem('login', response.data.token); and that's it.
You are setting user data logged after post api response. but react don't see the changes on localstorage.
You need to make your components to react to local storage changes.
Use something like react-use-localstorage
then you can look if the user data changed and render the logged components, or send the user to a new route for example.
I am trying to build a react page that shows a list of "messages subjects" received and when you click the down icon the messages relating to that subject appear directly below. (Imagine to help explain, when the user clicks the down icon on the line with 'Christmas' a white space needs to appear directly below and BEFORE the line with the 'New Year' text, so I can then display the message body, etc for each message relating to that subject.
Here is my code
import React from "react";
import "./Messages.css";
import { ReactComponent as DownIcon } from "../images/down-chevron.svg";
import Moment from 'moment';
class Messages extends React.Component {
constructor(props) {
super(props);
this.state = {
data: [],
isLoading: false,
};
}
componentDidMount() {
this.setState({ isLoading: true });
const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url =
"<my url>" +
this.props.location.state.userID;
fetch(proxyurl + url)
.then((res) => res.json())
.then((data) => this.setState({ data: data, isLoading: false }));
}
render() {
const { data, isLoading } = this.state;
if (isLoading) {
return <p>Loading ...</p>;
}
if (data.length === 0) {
return <p> no data found</p>;
}
return (
<div>
<div className="messageSubjectHeader">
<div className="innerMS">Message Subject</div>
<div className="innerMS">Number of linked messages</div>
<div className="innerMS">Latest message Date and Time</div>
<div className="innerMS">View Messages</div>
</div>
{data.message_Subjects.map((ms) => (
<div className="messageSubject">
<div className="innerMS">{ms.subject}</div>
<div className="innerMS">{ms.message_Chain.length}</div>
<div className="innerMS">{this.getLatestMessageDateTime(ms.message_Chain)}</div>
<div className="innerMS">
<DownIcon className="innerMSDownIcon" />
</div>
</div>
))}
</div>
);
}
getLatestMessageDateTime(messageChain){
const lastmessage = messageChain.length -1;
Moment.locale('en');
var dt = messageChain[lastmessage].dateTime;
return(Moment(dt).format('ddd DD MMM YYYY hh:mm:ss'))
}
}
export default Messages;
You have to define the selected record id in the state and Update the selected id on the click of view messages button. And also add a Content Panel inside the loop and toggle the visibility based on the selected recorded Id in the state.
{data.message_Subjects.map((ms) => (
<>
<div className="messageSubject">
<div className="innerMS">{ms.subject}</div>
<div className="innerMS">{ms.message_Chain.length}</div>
<div className="innerMS">{"12/08/2020"}</div>
<div className="innerMS">
<button onClick={() => this.handleClick(ms.id)}>
{this.state.selectedId === ms.id ? "hide" : "Show"}
</button>
</div>
</div>
// show/hide the content based on the selection --> Content Panel
{this.state.selectedId === ms.id && (
<div className="content">{ms.description}</div>
)}
</>
))}
I have created a sample Demo - https://codesandbox.io/s/vigorous-hertz-x89p8?file=/src/App.js
Let me know if your use case is different.
You should have an onClick() handler if you want something to happen when user clicks an element, and then define handleOnClick() elsewhere in the component.
And you have no sub-component for the messages of a particular dated group, so, you'll need to code that, too.
Sub-Messages Component
I see that you have not defined any sub-components for messages. I don't know how your API works, so, I'll be general in that regard, but you'll want a <DateMessages/> component. This should have a constructor and render like...
constructor(props) {
super(props);
this.state = {'messages':[]};
}
render() {
return (
this.state.map((message) => {
return message.date + ' ' message.text;
});
);
}
Then, you'll need to populate this. So, add it as a ref in your <Messages/> component, as a child of <div className="messageSubject">. Since it starts out with no messages, it should come out as blank when appended to each date group. That'll look like datekey = ms.subject; <DateMessages ref={(instance) => {this.$datekey = instance}} />.
onClick Handler
So, your onClick handler would look like: <div className="messageSubject" onClick={(e, ms.subject) => this.handleOnClick(e, ms.subject)}>. You might have a handleOnClick() like...
handleOnClick(e, ms.subject) {
var datekey = ms.subject;
this.$datekey.setState(this is where an array of messages for datekey would be stored);
}
Advantages
Why do it this way? By having the state accurately reflect the data that the user is seeing, you'll be taking advantage of all the speedups of using ReactJS.
I want to show a button when user is logged.If user is not logged then I m not showing button.When user logged i will set local storage values.when i set local storage in login Component,Header component must listen to that event and show the button.I m using addEventListener for listening.But its not listening.
I don't know where to listen in header Component.
// HeaderComponent(header.js):
class HeaderComponent extends Component {
componentDidMount(){
if(typeof window!='undefined'){
console.log(localStorage.getItem("token"));
window.addEventListener("storage",function(e){
this.setState({ auth: true});
})
}
}
render() {
return (
<div className="header">
<div className="container">
<div className="header-content">
<img src={logo} alt="logo"></img>
<div className="nav-links" >
<ul >
<li>Home</li>
<li>About</li>
<li>Services</li>
<li><NavLink activeClassName="active" to="/upload" >Upload</NavLink></li>
<li><NavLink activeClassName="active" to="/signup"> Sign Up</NavLink></li>
{ this.state.auth? <li onClick={this.onLogout}>Logout</li> :null}
</ul>
</div>
</div>
</div>
</div>
);
}
}
//loginComponent(login.js)
class LoginComponent extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(event) {
const data = {
username: document.getElementById('name').value,
password: document.getElementById('password').value
}
axios.post(`http://localhost:4000/user/login`, data).then(res => {
this.props.history.push("/");
localStorage.setItem("token",res.data.token);
localStorage.setItem("auth",true);
}).catch(err => console.log(err));
}
render() {
return (
<section class="log-in">
<div class="card-col">
<form>
<h3>LOG IN</h3>
<div class="form-controls">
<input id="name" type="text" placeholder="username" class="input"></input>
</div>
<div class="form-controls">
<input id="password" type="password" placeholder="password" class="input"></input>
</div>
<button type="submit" onClick={this.onSubmit} class="button" >Log in</button>
</form>
</div>
</section>
)
}
}
The current answers are overlooking a really simple and secure option: window.dispatchEvent.
Where you set your localStorage item, if you dispatch an event at the same time then the eventListener in the same browser tab (no need to open another or mess with state) will also pick it up:
const handleLocalStorage = () => {
window.localStorage.setItem("isThisInLocalStorage", "true");
window.dispatchEvent(new Event("storage"));
};
window.addEventListener('storage', () => {
console.log("Change to local storage!");
// ...
})
EDIT:
Because this seems to be helpful, I'd also recommended checking out the useLocalStorage hook from the usehooks-ts team. You don't need to install it as a package; you can just copy the hook wholesale. This hook makes use of the solution I originally shared, but adds a whole lot more sophisticated logic to it.
Please take note of two things
storage event works only when the same application opened in two browser tabs (it is used to exchange info between different tabs of the same app). Storage event will not fire when both components shown on the same page.
When adding event listerner, you're passing function(), not array function. function() doe not capture this so you should explicitly bind(this) or change it to arrow function.
For example
window.addEventListener("storage",(function(e){
this.setState({ auth: true});
}).bind(this));
Or do with arrow function
window.addEventListener("storage",(e) => {
this.setState({ auth: true});
});
Here is simple example.
Be sure to open it in two tabs (the same link). Store value in one tab and see this value in another tab.
I found a really bad hack to accomplish this:
I have a Toolbar and a Login Component where the Toolbar component listens to changes in localStorage and displays the logged-in user name when the Login Component updates local storage if authentication is successful.
The Toolbar Component
(similar to the Header component in your case)
const [loggedInName, setLoggedInName] = useState(null);
useEffect(() => {
console.log("Toolbar hi from useEffect")
setLoggedInName(localStorage.getItem('name') || null)
window.addEventListener('storage', storageEventHandler, false);
}, []);
function storageEventHandler() {
console.log("hi from storageEventHandler")
setLoggedInName(localStorage.getItem('name') || null)
}
function testFunc() {
console.log("hi from test function")
storageEventHandler();
}
Add a hidden button to your Toolbar component. This hidden button will call the testFunc() function when clicked which will update the logged-in user's name as soon as local storage is updated.
<button style={{ display: 'none' }} onClick={testFunc} id="hiddenBtn">Hidden Button</button>
Now, in your Login component
.
.
.
//login was successful, update local storage
localStorage.setItem("name",someName)
//now click the hidden button using Javascript
document.getElementById("hiddenBtn").click();
.
I'm trying to develop a simple CMS for my page. I want it to where I can edit and delete a users reply on click of a button. I got the delete functionality done so I figured for the reply functionality I would use CKeditor. What I'm struggling with is not being able to disable the autoinline feature. I can still select my div on load of the page rather than clicking a button to enable the inline feature but I don't know what I am doing wrong?
I have tried setting the feature directly in my index.html file, a custom js script file and the config.js ckeditor file but none worked. I am using Ckeditor 4.
this is the snippit of code I'm trying to use to disable inline on my div element but it's not working and I don't know why, i currently have it placed in a custom.js script file and I'm calling it from my index.html file
CKEDITOR.disableAutoInline = true;
Here is my code for my replies page:
import React from 'react';
import CKEditor from 'react-ckeditor-component';
import ForumpageService from '../../services/forumService';
import appController from '../../controllers/appController';
class Forumreplies extends React.Component {
constructor(props){
super(props);
this.elementName = "editor_" + this.props.id;
this.editReply = this.editReply.bind(this);
this.state = {
reply: '',
errorMsg: '',
isLoggedin: false,
// Ck Editor State
reply: '',
}
}
async componentDidMount(){
const topicId = this.props.match.params.topicid
const postDetails = await ForumpageService.replyDetails({topicId: topicId})
this.setState({
postDetails: postDetails[0],
topicId: topicId
})
await this.postReplies();
}
// Edit the reply
async editReply(id, e){
//CKEDITOR.inline(this.elementName);
}
async postReplies(){
const repliesData = await ForumpageService.postreplyDetails({topicId: this.state.topicId})
await this.setState({repliesData});
}
render(){
const repliesData = currentReply.map((row, index) => {
return (
<div className="row" id="reply-messages" key={index}>
<div className="col-md-8" suppressContentEditableWarning contentEditable="true" id={this.elementName}>
<p dangerouslySetInnerHTML={{__html: row.reply_message }} />
</div>
<div className="col-md-2">
{this.state.isloggedinuserId == row.reply_user_id && this.state.isLoggedin == true ? <i className="far fa-edit" onClick={this.editReply.bind(this, row.reply_id)} title="Edit this reply?"></i> : null }
</div>
</div>
})
return (
<div className="container" id="forum-replies">
<div className="row">
{repliesData}
</div>
</div>
)
}
}
export default Forumreplies;
Instead of creating a div and calling CKEDITOR.inline, you should try to use the react-ckeditor-component as its own way (not as an inline editor).
You could do something like: if the button wasn't pressed, render a div with the text content, but after the button was pressed, render a <CKEditor /> component as in the documentation
There is no documented way to set the editor as inline in this package that you are using.
EDIT:
I can see you are not using the react-ckeditor-component features, but following what you've done so far, you could set the contentEditable="true" property of the div only when the button is pressed:
<div className="col-md-8" suppressContentEditableWarning contentEditable={this.state.isEditing} id={this.elementName}>
<p dangerouslySetInnerHTML={{__html: row.reply_message }} />
</div>
And then set the isEditing to true on the onClick handler. Your component will update and then re-render with the contentEditable property set to true
I’ve created InitializePhoneNumbersPanel:
class InitializePhoneNumbersPanel extends Component {
constructor(props) {
super(props);
this.onSubmit = this.onSubmit.bind(this);
}
onSubmit(phoneNumbers) {
const { dispatch, operatorId } = this.props;
dispatch(updateOperatorData(operatorId, phoneNumbers, {include: 'phone_numbers'}));
}
render() {
const {
handleSubmit,
submitting,
fields: { phone_numbers }
} = this.props;
console.log('\n... Render ...');
console.log('phone_numbers <<<<< ', phone_numbers);
if (_.isEmpty(phone_numbers)) {
return (
<div className={"fade in"}>
Hello
</div>
)
}
return (
<form onSubmit={handleSubmit(this.onSubmit)}>
<div className="row">
<div className="col-md-12">
<ul className="list-unstyled m-b-0 clearfix">
{phone_numbers && phone_numbers.map((phone, index) =>
<PhoneNumbersPanelItem key={index} phone={phone} phone_numbers={phone_numbers}
index={index}/>
)}
</ul>
</div>
</div>
<div className="row">
<div className="col-md-12">
<button type="button" className="btn btn-sm btn-success" onClick={event => {
event.preventDefault();
phone_numbers.addField();
}}><i className="fa fa-plus"></i>
</button>
</div>
</div>
<hr/>
<div className="row">
<div className="col-md-12">
<button type="submit" disabled={ submitting } className="btn btn-sm btn-success pull-right">
Save
</button>
</div>
</div>
</form>
)
}
}
Then this component is wrapped by Redux-form:
InitializePhoneNumbersPanel = reduxForm({
form: 'phone-numbers-panel',
fields
})(InitializePhoneNumbersPanel);
Then everything is wrapped by connect method to make data from Store accessible in Redux-form as fields:
function select(state) {
return {
initialValues: {
phone_numbers: _.map(state.operators.items[state.operators.selectedOperator] && state.operators.items[state.operators.selectedOperator].phone_numbers, phoneId => {
return state.phoneNumbers.items[phoneId];
})
},
operatorId: state.operators.selectedOperator
};
}
InitializePhoneNumbersPanel = connect(select)(InitializePhoneNumbersPanel);
The error is…
The code above works normally however in PhoneNumbersPanelItem component phone numbers which come from “phone_numbers” variable are repeated.
When the operators page(whose phone numbers are shown using PhoneNumbersPanelItem) is loaded the first time no errors occur, however if I choose other operator, Route will change which means operatorId param in store will change which means operators object will change and the phone numbers will be different… changed data are sent to component here:
function select(state) {
return {
initialValues: {
phone_numbers: _.map(state.operators.items[state.operators.selectedOperator] && state.operators.items[state.operators.selectedOperator].phone_numbers, phoneId => {
return state.phoneNumbers.items[phoneId];
})
},
operatorId: state.operators.selectedOperator
};
}Operator
};
}
InitializePhoneNumbersPanel = connect(select)(InitializePhoneNumbersPanel);
So if the number of phone numbers of chosen operator is less than the previous one had , the error is thrown
Uncaught Invariant Violation: findComponentRoot(...,
.0.0.0.1.2.0.0.2.1.0.1.1.0.0.0.$1.0.0.0.0.1.1.0): Unable to find
element. This probably means the DOM was unexpectedly mutated (e.g.,
by the browser), usually due to forgetting a when using
tables, nesting tags like , , or , or using non-SVG
elements in an parent. Try inspecting the child nodes of the
element with React ID ``.
As I understood, the error is thrown because at the beginning there were 3 phone numbers for example, and when I choose a new operator the number of phones is 2 and React seemingly fails to find html code for the third number as in the new rendering this element was not created
Even though there is an error, everything works ok. Probably with another rendering react understands that the state has updated and rerenders virtual DOM
If all operators have the same number of phone numbers, NO error occur AT ALL
How can I fix this error? Has anybody encountered anything like that? So strange that React doesn’t understand that the virtual DOM has changed when we switch to a new Route.
I’ll appreciate any help/solution to this problem
I've tried multiple things to make it work on mine. I had a similar problem.
Apparently the problem was with the type of the button. ReactDOM gets lost if you use a type="button"
I removed the type="button" and added a event.preventDefault() on my onClick handler and it worked for me.
I had a very similar scenario. I tried a bunch of things and the only thing that worked for me was updating react and react-dom to version 15.3.2 (from 0.14.2).