Why does my React component only render after ctrl + shift + R? - javascript

I have a chrome extension and it only shows up after after refreshing the page with ctrl + shift + r, but now I have a problem of whenever I click on a link that ends up refreshing the page, the extension goes away and I have to hard refresh again.
I tried using window.location.reload() but sometimes it'll keep reloading the page non stop.
here's the code to render:
class IconExtChrome extends React.Component<IProps, IState> {
constructor(props) {
super(props);
this.state = { _isLoggedIn: false };
this.login = this.login.bind(this);
this.logout = this.logout.bind(this);
this.popover = this.popover.bind(this);
this.setAuthState = this.setAuthState.bind(this);
this.setAuthState();
}
render() {
return (
<div className="bootsrtap-iso">
<OverlayTrigger trigger="click" rootClose placement="bottom" overlay={this.popover()}>
<img src={chrome.runtime.getURL('favicon.png')} width="40" height="auto" />
</OverlayTrigger>
</div>
)
}
popover(): OverlayChildren {
return (
<Popover className="bootstrap-iso">
<Popover.Body className="p-2 d-grid text-center">
<PopoverHeader as="h4">{(this.state._isLoggedIn ? 'You are currenlty logged in' : 'You are currenlty logged out')}</PopoverHeader>
<Button className="m-auto mt-2" variant={this.state._isLoggedIn ? 'danger' : 'primary'} size="sm" onClick={this.state._isLoggedIn ? this.logout : this.login}>{this.state._isLoggedIn ? 'Logout' : 'Login'}</Button >
</Popover.Body>
</Popover>
);
}
login() { Login(() => { this.setAuthState() }, () => { console.log("failed to login") }) }
logout() { Logout(() => { this.setAuthState() }); }
setAuthState() { IsLoggedIn((isLoggedIn: boolean) => { this.setState({ _isLoggedIn: isLoggedIn }); }); }
refreshToken: () => { RefreshToken(); }
}
const GmailFactory = require("gmail-js");
const gmail = new GmailFactory.Gmail() as Gmail;
var btn = gmail.tools.add_toolbar_button('<div id="icon_placeholder"></div>', function () { }, 'temp_css').get(0)['className'];
const getElement = document.querySelectorAll('.' + btn.toString().replace(' ', '.'))[5]
var app: HTMLElement = document.createElement('div') as HTMLElement;
var pos: HTMLElement = getElement as HTMLElement;
if (pos !== null) {
console.log('pos: ' + pos)
pos.appendChild(app);
ReactDOM.render(<IconExtChrome />, app);
}
Any way I could easy hard refresh the page or a proper fix for only showing up after refreshing with no cache?

I solved the problem of not rendering after a normal (F5) refresh by adding an event listener to check if the page is fully loaded. Example:
window.addEventListener('load', function () {
const app: HTMLElement = document.createElement('div') as HTMLElement;
const pos: HTMLElement = document.evaluate('/html/body/div[7]/div[3]/div/div[1]/div[3]/header/div[2]/div[2]', document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue as HTMLElement;
pos.appendChild(app);
ReactDOM.render(<IconExtChrome />, app);
}, false);

Related

setState updates state and triggers render but I still don't see it in view

I have a simple word/definition app in React. There is an edit box that pops up to change definition when a user clicks on "edit". The new definition provided is updated in the state when I call getGlossary(), I see the new definition in inspector and a console.log statement in my App render() function triggers too. Unfortunately, I still have to refresh the page in order for the new definition to be seen on screen. I would think that calling set state for this.state.glossary in the App would trigger a re-render down to GlossaryList and then to GlossaryItem to update it's definition but I'm not seeing it :(.
App.js
class App extends React.Component {
constructor() {
super();
this.state = {
glossary: [],
searchTerm: '',
}
this.getGlossary = this.getGlossary.bind(this); //not really necessary?
this.handleSearchChange = this.handleSearchChange.bind(this);
this.handleAddGlossaryItem = this.handleAddGlossaryItem.bind(this);
this.handleDeleteGlossaryItem = this.handleDeleteGlossaryItem.bind(this);
//this.handleUpdateGlossaryDefinition = this.handleUpdateGlossaryDefinition.bind(this);
}
getGlossary = () => {
console.log('getGlossary fired');
axios.get('/words').then((response) => {
const glossary = response.data;
console.log('1: ' + JSON.stringify(this.state.glossary));
this.setState({ glossary }, () => {
console.log('2: ' + JSON.stringify(this.state.glossary));
});
})
}
componentDidMount = () => {
//console.log('mounted')
this.getGlossary();
}
handleSearchChange = (searchTerm) => {
this.setState({ searchTerm });
}
handleAddGlossaryItem = (glossaryItemToAdd) => {
//console.log(glossaryItemToAdd);
axios.post('/words', glossaryItemToAdd).then(() => {
this.getGlossary();
});
}
handleDeleteGlossaryItem = (glossaryItemId) => {
console.log('id to delete: ' + glossaryItemId);
axios.delete('/words', {
data: { glossaryItemId },
}).then(() => {
this.getGlossary();
});
}
render() {
console.log('render app fired');
const filteredGlossary = this.state.glossary.filter((glossaryItem) => {
return glossaryItem.word.toLowerCase().includes(this.state.searchTerm.toLowerCase());
});
return (
<div>
<div className="main-grid-layout">
<div className="form-left">
<SearchBox handleSearchChange={this.handleSearchChange} />
<AddWord handleAddGlossaryItem={this.handleAddGlossaryItem} />
</div>
<GlossaryList
glossary={filteredGlossary}
handleDeleteGlossaryItem={this.handleDeleteGlossaryItem}
getGlossary={this.getGlossary}
//handleUpdateGlossaryDefinition={this.handleUpdateGlossaryDefinition}
/>
</div>
</div>
);
}
}
export default App;
GlossaryItem.jsx
import React from 'react';
import EditWord from './EditWord.jsx';
const axios = require('axios');
class GlossaryItem extends React.Component {
constructor(props) {
super(props);
this.state = {
isInEditMode: false,
}
this.glossaryItem = this.props.glossaryItem;
this.handleDeleteGlossaryItem = this.props.handleDeleteGlossaryItem;
this.handleUpdateGlossaryDefinition = this.handleUpdateGlossaryDefinition.bind(this);
this.handleEditClick = this.handleEditClick.bind(this);
}
handleUpdateGlossaryDefinition = (updateObj) => {
console.log('update object: ' + JSON.stringify(updateObj));
axios.put('/words', {
data: updateObj,
}).then(() => {
this.props.getGlossary();
}).then(() => {
this.setState({ isInEditMode: !this.state.isInEditMode });
//window.location.reload();
});
}
handleEditClick = () => {
// display edit fields
this.setState({ isInEditMode: !this.state.isInEditMode });
// pass const name = new type(arguments); data up to App to handle with db
}
render() {
return (
<div className="glossary-wrapper">
<div className="glossary-item">
<p>{this.glossaryItem.word}</p>
<p>{this.glossaryItem.definition}</p>
<a onClick={this.handleEditClick}>{!this.state.isInEditMode ? 'edit' : 'cancel'}</a>
<a onClick={() => this.handleDeleteGlossaryItem(this.glossaryItem._id)}>delete</a>
</div>
{this.state.isInEditMode ?
<EditWord
id={this.glossaryItem._id}
handleUpdateGlossaryDefinition={this.handleUpdateGlossaryDefinition}
/> : null}
</div>
);
}
}
EditWord
import React from 'react';
class EditWord extends React.Component {
constructor(props) {
super(props);
this.state = {
definition: ''
};
this.handleUpdateGlossaryDefinition = this.props.handleUpdateGlossaryDefinition;
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
let definition = event.target.value;
this.setState({ definition });
}
handleSubmit(event) {
//console.log(event.target[0].value);
let definition = event.target[0].value;
let update = {
'id': this.props.id,
'definition': definition,
}
//console.log(update);
this.handleUpdateGlossaryDefinition(update);
event.preventDefault();
}
render() {
return (
<form onSubmit={this.handleSubmit} className="glossary-item">
<div></div>
<input type="text" name="definition" placeholder='New definition' value={this.state.definition} onChange={this.handleChange} />
<input type="submit" name="update" value="Update" />
</form>
);
}
}
export default EditWord;
Thank you
One possible way I can see to fix this is to map the data to make the id uniquely identify each list item (even in case of update). We can to do this in getGlossary() by modifying the _id to _id + definition.
getGlossary = () => {
console.log('getGlossary fired');
axios.get('/words').then((response) => {
// Map glossary to uniquely identify each list item
const glossary = response.data.map(d => {
return {
...d,
_id: d._id + d.definition,
}
});
console.log('1: ' + JSON.stringify(this.state.glossary));
this.setState({ glossary }, () => {
console.log('2: ' + JSON.stringify(this.state.glossary));
});
})
}
In the constructor of GlossaryItem I set
this.glossaryItem = this.props.glossaryItem;
because I am lazy and didn't want to have to write the word 'props' in the component. Turns out this made react loose reference somehow.
If I just remove this line of code and change all references to this.glossaryItem.xxx to this.pros.glossaryItem.xxx then it works as I expect! On another note, the line of code can be moved into the render function (instead of the constructor) and that works too, but have to make sure I'm accessing variables properly in the other functions outside render.

Invisible reacaptcha position problem when submitting redux form

I have problem with invisible recaptcha in react.
I have registration form in modal window and if the recaptcha is
showed and i am detected as a bot, the puzzle box is sending the
user to the bottom of the page. The recaptcha puzzle is adding a
div to the DOM and for positioning is using the top: max of the
screen property.
This is the npm package that i am using
https://www.npmjs.com/package/react-google-recaptcha
export class Registration extends React.Component {
onRecaptchaChange = (recaptchaResponse) => {
const data = {
form: this.state.form,
recaptchaResponse,
};
this.props.submitQuickSignupDetails(data);
};
submitQuickSignupDetails = (form) => {
this.setState({ form: form.toJS() });
this.captcha.reset();
this.captcha.execute();
};
render() {
return (
<React.Fragment>
<RegistrationForm
onSubmit={this.submitQuickSignupDetails}
onAlreadyRegistered={this.props.hideSignupModal}
/>
<ReCaptcha
ref={(ref) => { this.captcha = ref; }}
sitekey={XXXXXXX}
size="invisible"
badge="bottomleft"
onChange={this.onRecaptchaChange}
/>
</React.Fragment>
);
}
}
For now the only possible solution that is just resolving my problem only the first time that the recaptcha is triggered is:
submitQuickSignupDetails = (form) => {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
const nodesList = mutation.addedNodes;
for (let idx = 0; idx < nodesList.length; idx += 1) {
const node = nodesList.item(idx);
if (node.tagName && node.tagName === 'DIV' && node.querySelector('iframe[title="recaptcha challenge"]')) {
const visibilityInterval = setInterval(() => {
if (node.style.visibility === 'visible') {
node.style.top = `${window.scrollY + 150}px`;
clearInterval(visibilityInterval);
observer.disconnect();
}
}, 250);
}
}
});
});
this.setState({ form: form.toJS() });
this.captcha.reset();
this.captcha.execute().then(() => {
observer.observe(document.body, {
childList: true,
});
});
};
But if the user make mistake during resolving the recaptcha puzzle the recaptcha is sending the user to the bottom of the page again

Show and Hide specific component in React from a loop

I have a button for each div. And when I press on it, it has to show the div with the same key, and hide the others.
What is the best way to do it ? This is my code
class Main extends Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", key: "1" },
{ message: "message2", key: "2" }
]
};
}
handleClick(message) {
//something to show the specific component and hide the others
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<Button key={message.key} onClick={e => this.handleClick(message)}>
{message.message}
</Button>
)
});
let messageNodes2 = this.state.messages.map(message => {
return <div key={message.key}>
<p>{message.message}</p>
</div>
});
return <div>
<div>{messageNodes}</div>
<div>{messageNodes2}</div>
</div>
}
}
import React from "react";
import { render } from "react-dom";
class Main extends React.Component {
constructor(props) {
super(props);
this.state = {
messages: [
{ message: "message1", id: "1" },
{ message: "message2", id: "2" }
],
openedMessage: false
};
}
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}
render() {
let messageNodes = this.state.messages.map(message => {
return (
<button key={message.id} onClick={e => this.handleClick(message.id)}>
{message.message}
</button>
);
});
let messageNodes2 = this.state.messages.map(message => {
return (
<div key={message.key}>
<p>{message.message}</p>
</div>
);
});
const { openedMessage } = this.state;
console.log(openedMessage);
return (
<div>
{openedMessage ? (
<div>
{openedMessage.map(item => (
<div>
{" "}
{item.id} {item.message}{" "}
</div>
))}
</div>
) : (
<div> Not Opened</div>
)}
{!openedMessage && messageNodes}
</div>
);
}
}
render(<Main />, document.getElementById("root"));
The main concept here is this following line of code.
handleClick(id) {
const currentmessage = this.state.messages.filter(item => item.id === id);
this.setState({ openedMessage: currentmessage });
}`
When we map our messageNodes we pass down the messages id. When a message is clicked the id of that message is passed to the handleClick and we filter all the messages that do not contain the id of the clicked message. Then if there is an openedMessage in state we render the message, but at the same time we stop rendering the message nodes, with this logic {!openedMessage && messageNodes}
Something like this. You should keep in state only message key of visible component and in render method you should render only visible component based on the key preserved in state. Since you have array of message objects in state, use it to render only button that matches the key.
class Main extends Component {
constructor(props) {
super(props);
this.state = {
//My array messages: [],
visibleComponentKey: '',
showAll: true
};
handleClick(message) {
//something to show the specific component and hide the others
// preserve in state visible component
this.setState({visibleComponentKey : message.key, showAll: false});
};
render() {
const {visibleComponentKey, showAll} = this.state;
return (
<div>
{!! visibleComponentKey && ! showAll &&
this.state.messages.filter(message => {
return message.key == visibleComponentKey ? <Button onClick={e => this.handleClick(message)}>{message.message}</Button>
) : <div /> })
}
{ !! showAll &&
this.state.messages.map(message => <Button key={message.key} onClick={e => this.handleClick(message)}>{message.message}</Button>)
}
</div>
);
}
}
I haven't tried it but it gives you a basic idea.
I cannot reply to #Omar directly but let me tell you, this is the best code explanation for what i was looking for! Thank you!
Also, to close, I added a handleClose function that set the state back to false. Worked like a charm!
onCloseItem =(event) => {
event.preventDefault();
this.setState({
openedItem: false
});
}

I want to enable pagination in my elasticsearch results by adding new page of results below the older one in React Js

I am Building an UI (using ReactJs) for a search engine (using elasticsearch) and it returns 20 results per page.
When I click next button, it gives out next 20 results but old results are replaced by the new one. All I want is that the new results should be appended to the old results.
here is my code :
import React from 'react'
import SearchResults from './searchresults';
import elasticsearch from 'elasticsearch';
let client = new elasticsearch.Client({
host: 'localhost:9200',
log: 'trace'
})
var size = 20;
var from_size = 0;
var search_query = '*'
class Searchbox extends React.Component {
constructor(props) {
super(props);
this.state = { results: [], notFound: true }
this.handleChange = this.handleChange.bind(this);
this.next = this.next.bind(this);
this.prev = this.prev.bind(this);
this.er = this.er.bind(this);
this.esSearch = this.esSearch.bind(this);
}
componentWillMount() {
search_query = '*';
this.esSearch(search_query, from_size);
}
handleChange ( event ) {
search_query = event.target.value + '*';
from_size = 0;
this.esSearch(search_query, from_size);
}
next() {
from_size += size;
if(from_size<=size) {
console.log(from_size);
console.log(search_query);
this.esSearch(search_query, from_size);
}
else {
this.er();
from_size -= size;
}
}
er() {
alert("NO MORE PAGES");
}
esSearch( sq, from ) {
var search_query = sq;
client.search({
index: 'photos',
type: 'photo',
q: search_query,
size: size,
from: from
}).then(function ( body ) {
if(body.hits.max_score===null) {
this.setState({notFound: true})
}
else {
this.setState({notFound: false})
}
this.setState({ results: body.hits.hits })
}.bind(this), function ( error ) {
console.trace( error.message );
});
}
renderNotFound() {
return <div className="notFound">Not found. Try a different search.</div>;
}
renderPosts() {
return(
<div className="results">
<SearchResults key={this.from_size} results={ this.state.results } />
<button id="prev" type="button" className="btn btn-primary" onClick={this.prev} >Prev</button>
</div>
)
}
render() {
const { notFound } = this.state;
return (
<div>
<input id="search" className="form-control form" type="text" placeholder="Start Searching" name="search" onChange={ this.handleChange }></input>
<div>
{notFound ? this.renderNotFound() : this.renderPosts()}
</div>
</div>
)
}
}
export default Searchbox;
This app by default, shows all the results.
Inside your esFunction, you could try something like this:
let oldState = this.state.results.slice();
body.hits.hits.forEach(function (searchResult) {
oldState.push(searchResult)
});
this.setState({
results: oldState
});
There is a SO post that talks about this topic.
More details at Facebook React page.

Click handler for each button incorrectly returns the last button of a set

I am trying to add a click handler to each button that is generated in a loop and inserted into an array.
However, clicking a button always outputs the last button of each row of buttons and not the specific button itself.
My code is rather verbose, but we only need to be looking at the time.push() part and the click handler setup. Everything else is just setup.
import React from 'react';
import { friendlyTimeSlot, scopedTimeslots } from '../../utilities/helpers';
class TimeSlotStack extends React.Component {
constructor() {
super();
this.clickHandler = this.clickHandler.bind(this);
this.state = {
times: undefined
};
}
componentWillMount() {
this.updatePropsAndState(this.props);
}
componentWillReceiveProps(nextProps) {
this.updatePropsAndState(nextProps);
this.forceUpdate();
}
updatePropsAndState(props) {
const time = [];
let matchedTimeSlots;
if (props.promotionId) {
matchedTimeSlots = props.timeSlots.filter(timeSlot => {
const timeSlotsIds = timeSlot.AvailablePromotions.map(p => p.Id);
if (timeSlotsIds.includes(props.promotionId)) {
return timeSlot;
}
return false;
});
} else {
matchedTimeSlots = props.timeSlots.filter(timeSlot => timeSlot.HasStandardAvailability);
}
const scopedTimes = scopedTimeslots(matchedTimeSlots, props.preferredTimeSlot);
scopedTimes.forEach((item, i) => {
const friendlyTime = friendlyTimeSlot(item.TimeSlot, true);
const leaveTimeRequired = item.IsLeaveTimeRequired;
let itemPromo;
let leaveTime;
let itemPrice;
if (props.promotionId) {
itemPromo = item.AvailablePromotions.find(ourItem => ourItem.Id === props.promotionId);
leaveTime = itemPromo.LeaveTime || item.LeaveTime;
itemPrice = (itemPromo.BasePrice > 0) ? `£${itemPromo.BasePrice}` : '';
} else {
leaveTime = item.LeaveTime;
}
time.push(
<button
className="btn btn-default"
type="button"
onClick={(e) => this.clickHandler(e)}
ref={input => {
this.button = input;
}}
key={i}
data-time={friendlyTime}
data-leave-time-required={leaveTimeRequired}
data-leave-time={leaveTime.slice(0, -3)}
data-promotion-id={props.promotionId}
>
{friendlyTimeSlot(item.TimeSlot)}<br />{itemPrice}
</button>
);
});
this.setState({
times: time
});
}
clickHandler(e) {
e.preventDefault();
console.log(this.button.dataset);
}
render() {
if (this.state.times && this.props.name && this.props.description) {
return (
<div className="panel panel-default">
<div className="panel-heading">
<h3 className="panel-title">{this.props.name}</h3>
</div>
<div className="panel-body">
<p>{this.props.description}</p>
{this.state.times}
</div>
</div>
);
}
return (
<p>No times available.</p>
);
}
}
TimeSlotStack.propTypes = {
name: React.PropTypes.string.isRequired,
description: React.PropTypes.string.isRequired,
timeSlots: React.PropTypes.array.isRequired,
preferredTimeSlot: React.PropTypes.string.isRequired,
promotionId: React.PropTypes.number
};
export default TimeSlotStack;
When I then click a button, I always get the last button from each list. Hopefully the screenshot below will help make this clearer:
The log above comes from:
clickHandler(e) {
e.preventDefault();
console.log(this.button.dataset);
}
...but was generated by clicking the first buttons of each row. You can see that it always outputs the last only.
Is there something I'm doing wrong? This is my first React project and it's gotten me all flustered. Please let me know if I'm doing something that's not the React way that could be causing this.
Thanks!
You are overwriting the button variable, this in this context is a reference to a TimeSlotStack instance. To do what you want you need to maintain a list of buttons, for instance.
constructor() {
super();
this.clickHandler = this.clickHandler.bind(this);
this.buttons = [];
this.state = {
times: undefined
};
}
....
// using a IFE so `clickHandler` is called with the correct index
((idx) => {
time.push(
<button
className="btn btn-default"
type="button"
onClick={(e) => this.clickHandler(e, idx)}
ref={button => {
this.buttons.push(button);
}}
key={idx}
data-time={friendlyTime}
data-leave-time-required={leaveTimeRequired}
data-leave-time={leaveTime.slice(0, -3)}
data-promotion-id={props.promotionId}
>
{friendlyTimeSlot(item.TimeSlot)}<br />{itemPrice}
</button>
);
})(i);
....
clickHandler(e, i) {
e.preventDefault();
console.log(this.buttons[i].dataset);
}

Categories

Resources