I am pretty new guy to react js.I am little bit confused why input type=file action is not working in Mozilla and IE it is working fine in Chrome.I don't why it is not working...Haaa that is pretty hard to find my mistake.I know this might be a simple noobie Mistake
Pls help me
import React from 'react';
import {connect} from 'react-redux';
import uuid from 'node-uuid'
import * as headerAction from '../../Actions/headerActions';
import * as uploadActions from '../../Actions/uploadActions';
import * as notificationActions from '../../Actions/notificationActions';
import shortid from 'shortid'
class Header extends React.Component{
static contextTypes = {
router:React.PropTypes.object
};
constructor(props){
super(props);
this.Hovered = this.Hovered.bind(this);
this.UnHovered = this.UnHovered.bind(this);
}
UnHovered(){
this.props.toggleMenu(false);
}
uniqueNameAndId(){
return uuid.v1().replace(/-/g, '');
}
//below function not triggered When onChange Event happen But file upload popsup
handleFileUpload(e){
//Not working
e.preventDefault();
this.props.setMainPostId(shortid.generate())
//Upload for single File not working
const reader = new FileReader();
//const file = e.target.files;
//console.log(file.length);
reader.onload = () => {
console.log("Hello",file.name)
};
let file = e.target.files[0];
reader.readAsDataURL(file);
//Upload for Multiple files NOt working
{/*if(file.length <= 5){*/}
{/*for(let i=0;i<file.length;i++){*/}
// const Reader = new FileReader();
// Reader.onload = () => {
// let pushData = {
// postOwnerUsername:null,
// id:this.uniqueNameAndId(),
// name:this.uniqueNameAndId(),
// caption:null,
// blobData:Reader.result
// };
// console.log(pushData)
// this.props.pushtoReducer(pushData)
// };
// Reader.readAsDataURL(file[i])
// }
// this.props.removeUploadMenu(false)
// this.context.router.push('/upload');
// }else{
// console.log('No Dude')
// this.props.popErrorNotification(true,"#FF5D5D","Current Max Photo 5")
// }
}
loggedInMenu(){
return(
<div>
<li>Explore</li>
<li>My uploads</li>
{this.props.toggle.removeUploadMenu ?
<li>
<label htmlFor="upload-photo">Upload</label>
<input onChange={this.handleFileUpload.bind(this)} id="upload-photo" type="file" multiple/>
</li>:
""
}
<li>Profile</li>
<li>Logout</li>
</div>
)
}
loggedOutMenu(){
return(
<div>
<li onClick={()=>this.props.toogleSignInOut(true)}>SignUp/SignIn</li>
<li>Explore</li>
</div>
)
}
renderMenu(){
return(
<div onMouseLeave={this.UnHovered}>
<div className="dtcen">
<div className="dt">
</div>
</div>
<div className="dropdown">
{this.props.logInStatus.loginStatus ? this.loggedInMenu():this.loggedOutMenu()}
</div>
</div>
)
}
Hovered(){
this.props.toggleMenu(true);
}
render(){
// console.log('From uuis',this.uniqueNameAndId())
//console.log("Login Status",this.props.toggle.removeUploadMenu)
return(
<header>
<div className="logo">
<p>Masklor </p>
</div>
<div className="navtoggle">
<div onMouseEnter={this.Hovered} className="triangle">
<p>Menu</p>
</div>
{this.props.toggle.menuToggle ? this.renderMenu() : ""}
</div>
</header>
)
}
}
const mapStateToProps = (state) => {
return{
toggle:state.toggle,
logInStatus:state.logInStatus,
photos:state.photoUpload
}
};
const mapDispatchToProps = (dispatch) => {
return{
toggleMenu:bool => dispatch(headerAction.toggleStatus(bool)),
toogleSignInOut:bool => dispatch(headerAction.toggleSignPop(bool)),
pushtoReducer:object => dispatch(uploadActions.setPhotosState(object)),
popErrorNotification:(bool,color,message) => dispatch(notificationActions.popUpNotification(bool,color,message)),
removeUploadMenu:bool => dispatch(headerAction.removeUploadMenu(bool)),
setMainPostId:id =>dispatch(uploadActions.setIdforMainPost(id))
}
}
export default connect(mapStateToProps,mapDispatchToProps)(Header)
I see your code might be generating javascript functions inside control flow blocks, in chorme ie will work fine but wont with mozilla, so you will have to put functions outside conditional blocks, see my sample:
In mozilla, this wont work:
function myDataCall(data) {
if(data) {
processData(data);
function processData(obj) {
console.log(obj);
}
}
}
This will work:
function myDataCall(data) {
//Work, cross browser compatible
if(data) {
processData(data);
}
function processData(obj) {
console.log(obj);
}
}
I hope this helps, regards!
Related
I have a React component to upload files. Below is part of the code that handles the file selection and once files are selected, it will display thumbnails.
I want to use RTL to test the file selection: clicking button which is EvidenceUploaderPanel, this opens the file selector input element, then choosing files.
This will also make the requests to upload the files as they are selected.
But I have no idea how to start.
function UploadScreen({
title,
maxNumberOfUploadFiles = 3,
acceptedFileTypes,
}: Props) {
const [documents, setDocuments] = useState<FileObject[]>([]);
const handleFileSelection = (files: FileList) => {
const documentsWithThumbnails = Array.from(files).map((file) => {
// here I also make a request to upload each file.
return {
file,
thumbnailURL: URL.createObjectURL(file),
name: file.name,
};
});
setDocuments((currentDocuments) => [...currentDocuments, ...documentsWithThumbnails]);
};
const inputRef = useRef(
(() => {
const element = document.createElement('input');
element.multiple = true;
element.type = 'file';
element.accept = acceptedFileTypes?.join(',') || IMAGE_MIME_TYPES.join(',');
element.addEventListener('change', () => {
if (element.files && documents.length + element.files.length < maxNumberOfUploadFiles) {
handleFileSelection(element.files);
element.value = '';
}
});
return element;
})(),
);
const handleOpenFileClicker = () => {
inputRef.current.click();
};
return (
<div>
<h2 className="container">{title}</h2>
{documents.length > 0 ? (
<section>
<div className="body-text">
Add files
</div>
<div className="thumbnail-container">
{documents.map((doc) => {
return (
<BaseThumbnail
src={doc?.thumbnailURL}
key={doc.name}
deleteAction={() => {
deleteDocument(doc.name);
}}
/>
);
})}
</div>
<Link onPress={handleOpenFileClicker}>
Add photos
</Link>
</section>
) : (
<section>
<div className="text">
Add files
</div>
<div className="upload-container" />
<EvidenceUploaderPanel
labelText="upload files"
openFilePicker={handleOpenFileClicker}
/>
</section>
)}
</div>
);
}
Have you thought about maybe using cypress for this?
They have a nice built in function that does exactly what you want and the setup Is really easy.
I’d recommend you using the cypress component testing for react, they have an entire page on their website’s docs explaining how to set it up. And than you can just mount the file selection component and use their cy.selectFile() method.
Good luck :)
I have two components,
Uploader Component
Which uploads the file to Azure storage
ContainerList Component
Which fetches the uploaded file's data from Azure storage.
For fetching data from azure and show in ContainerList, I referred to ListBlobs in (https://dmrelease.blob.core.windows.net/azurestoragejssample/samples/sample-blob.html)
My components are completely independent components that were imported in a different react project by using npm-link. For npm link, I referred to this documentation (https://60devs.com/simple-way-to-manage-local-node-module-using-npm-link.html)
import {Uploader, ContainerList} from 'blobuploader';
import React from 'react';
class App extends React.Component{
render(){
return (
<Uploader
accountName={azureCredentials.accountName}
sasToken={azureCredentials.sasToken}
multiple={true}/>
<ContainerList
accountName={azureCredentials.accountName}
sasToken={azureCredentials.sasToken} />
);
}
}
The main issue is when I uploaded files to Azure storage by using the Uploader Component. Container List is not updating i.e the newly uploaded file to azure storage is not reflected in ContainerList. Need to reload the page for seeing updated data in ContainerList.
For example, you can see below the image where a new file has been uploaded but not showing in the list(i.e ContainerList)
Please help me to resolve this issue.
Thanks in advance
ContainerList code
import React from 'react';
import '../css/containerList.css';
import axios from 'axios';
import PROGRESS from '../assets/progress.svg'
import PropTypes from 'prop-types';
class ContainerList extends React.Component{
constructor(props){
super(props);
this.state= {
datas: [],
};
this.blobService = null;
this.initConnections.bind(this);
}
componentDidMount(){
const script = document.createElement('script');
script.src = "https://dmrelease.blob.core.windows.net/azurestoragejssample/bundle/azure-storage.blob.js";
script.async = true;
document.body.appendChild(script);
script.onload = () => {
this.initConnections();
};
console.log('script ended');
}
initConnections = () => {
var accountName = this.props.accountName;
var SasToken = this.props.sasToken;
var blobUri = 'https://' + accountName + '.blob.core.windows.net';
this.blobService = this.blobService === null ? AzureStorage.Blob.createBlobServiceWithSas(blobUri, SasToken) : this.blobService;
this.blobService.getServiceProperties({options: ['clientRequestId']}, (error, result)=>{
if(error){
console.log('Error Creating Blob Service..')
console.log(error);
} else {
this.blobService.listBlobsSegmented('my-con', null, {include:["metadata"]}, (error, results) => {
if (error) {
console.log(error);
}
else {
var temp =[]
results.entries.map(async (ele,index) => {
console.log('Tag',ele);
var val = index+1;
var type = ele.contentSettings.contentType;
temp.push({number:val,name:ele.name,type,status:<button className='container-button'>PASS</button>,email:"testtagcheck123456#gmail.com",apiStatus:PROGRESS});
if(results.entries.length === temp.length){
this.setState({datas:temp});
}
});
}
});
}
});
}
render(){
//SHOWING DATA IN UI LIST FROM this.state.datas
return (
<div className= "table-box">
<div className="table-row-head">
{
(["No","File-Name","Type","Status","Outlook-Mail","API-Status"]).map((ele => {
return( ele === "No" ?<div className="table-cell table-head first-cell" style={{width:'4%'}}>
<p className ="heading-label">{ ele} </p>
</div> : <div className="table-cell table-head" style={{width:'20%'}}>
<p className="heading-label">{ ele} </p>
</div>)
}))
}
</div>
{
this.state.datas.map( (eles,index) => {
return (
<div className='table-row' key={index}>
<div className="table-cell first-cell" style={{width:'4%'}}>
<p>{ eles.number} </p>
</div>
<div className="table-cell" style={{width:'23%'}} >
<p>{eles.name} </p>
</div>
<div className="table-cell" style={{width:'20%'}} >
<p >{eles.type} </p>
</div>
<div className="table-cell" style={{width:'15%'}} >
<p>{eles.status} </p>
</div>
<div className="table-cell" style={{width:'23%'}}>
<p>{eles.email} </p>
</div>
<div className="table-cell" style={{width:'15%'}}>
<img src={eles.apiStatus} alt="PROGRESS" style={{width:'30%',}}/>
</div>
</div>
);
}
)
}
</div>
);
}
}
ContainerList.propTypes = {
accountName: PropTypes.string,
sasToken: PropTypes.string,
}
export default ContainerList;
I am trying to get my form to upload several files, but once I upload the first one, I have no chance to load a second one. Any Idea what I am doing wrong?
This is my upload component:
import React, { Component } from 'react'
import * as RB from 'react-bootstrap'
import Button from 'components/Button/Button'
class uploadMob extends Component {
constructor(props) {
super(props)
this.state = {
files: [],
}
}
onFilesAdded = (e) => {
const filesArray = this.state.files
filesArray.push(e.target.files[0])
this.setState({ files: filesArray })
this.uploadFiles()
}
async uploadFiles() {
this.state.files.forEach((file) => {
this.sendRequest(file)
})
}
sendRequest(file) {
const { pdfUploadToState } = this.props
pdfUploadToState(file)
}
render() {
const files = this.state.files
return (
<RB.Form.Group>
<div className="upload-btn-wrapper">
<div className="Files">
{files.map((file, key) => {
return (
<div key={key} className="Row">
<span className="Filename">
{file.name}
</span>
</div>
)
})}
</div>
<Button size="sm" variant="light">
Dateien hochladen
</Button>
<input
type="file"
name="files"
id="files"
onChange={(e) => {
this.onFilesAdded(e)
}}
multiple
/>
</div>
</RB.Form.Group>
)
}
}
export default uploadMob
The first file is uploaded perfectly, but as mentioned, the button does not respond when trying to upload a second one.
Thanks for the help!
Your code seems correct but when you use input type file with multiple attribute you need to select multiple files and then hit upload button insted of selecting files one by one.
also replace
filesArray.push(e.target.files[0])
with
for (var i = 0; i < files.length; i++)
{
filesArray.push(e.target.files[i]);
}
to upload file one by one
replace
onFilesAdded = (e) =>
{
this.state.files.push(e.target.files[0])
this.uploadFiles()
}
hope this will help you
Good evening,
as a learning project I want to build a simple "Learning Cards" App. The structure is quite simple: you have cards with questions. After a button click, you can show the correct solution. You can also click on "Question solved" to move the learning card to the absolved cards.
I am struggling to realize the "moving the learning card to the absolved" cards part. I have a "questions" array. After "onSolvedClick" the solved card gets copied to the "solved" array which is set as the new solved state.
When I click on the "Frage gelöst" (question solved) button, a new card appears in the solved questions region. The problem is: the new card is empty (without the question / answer). It would be great if someone could help me at this point! I already spent hours on this problem today.
I guess my mistake is within the App.Js code, probably in "onSolvedKlick" or "solveFragen".
Thanks a lot!
App.Js:
import React, {Component} from 'react';
import CardList from './CardList.js';
import { fragen } from './fragen';
import SearchBox from './SearchBox';
class App extends Component { // As Class to access objects
constructor () {
super();
this.state = { // State needed to change state
fragen: fragen,
solved : [] ,
searchfield: ''
}
}
onSearchChange = (event) => {
this.setState({searchfield: event.target.value});
}
onSolvedKlick = (id) => {
console.log("Klick on solved"+id);
var frage = this.state.fragen.filter(function(e) // Bei Klick auf Solved: filtere aus Ursprungsarray das Element mit gelöster iD
{
return e.id === id;
});
console.log(frage);
const newSolvedArray = this.state.solved.slice();
newSolvedArray.push(frage);
this.setState({solved: newSolvedArray});
}
render(){ // DOM rendering
const filteredFragen = this.state.fragen.filter(fragen =>{
return fragen.frage.toLowerCase().includes(this.state.searchfield.toLowerCase());
})
const solveFragen = this.state.solved;
return(
<div className='tc'>
<h1>Learning Cards</h1>
<SearchBox searchChange={this.onSearchChange}/>
<h2>Cards: To be learned!</h2>
<div>
<CardList fragen={filteredFragen} onSolvedKlick={this.onSolvedKlick}/>
<CardList fragen={solveFragen} onSolvedKlick={this.onSolvedKlick}/>
</div>
</div>
)
}
}
export default App;
CardList.js:
import React from 'react';
import Card from './Card';
const CardList = ({fragen, onSolvedKlick}) => {
const cardComponent = fragen.map( (user, i) => {
return(<Card key={i} id={fragen[i].id} frage = {fragen[i].frage} antwort = { fragen[i].antwort} onSolvedKlick = {onSolvedKlick}/>);
}
)
return (
<div>
{cardComponent}
</div>
);
}
export default CardList;
Card.js:
import React, {Component} from 'react';
import 'tachyons';
class Card extends Component {
constructor(props) {
super(props);
this.state = {
frage : props.frage,
showAnswer : false
};
}
_showAnswer = () => {
const before = this.state.showAnswer;
const after = !before;
this.setState({
showAnswer: after
});
}
render() {
return (
<div className ="fl w-50 w-25-m w-20-l pa2 bg-light-red ma3">
<div>
<h2>{this.props.frage}</h2>
{ this.state.showAnswer && (<div>{this.props.antwort}</div>) }
<p></p>
<input type="button" value="Antwort anzeigen" className ="ma2"
onClick={this._showAnswer.bind(null)}
/>
<input type="button" name="solved" value="Frage gelöst" className = "ma2 bg-light-green"
onClick={() =>this.props.onSolvedKlick(this.props.id)}
/>
</div>
</div>
);
}
}
fragen.js (Questions):
export const fragen = [
{
id: 1,
frage: 'What are trends in CPU design?',
antwort: 'Multi-core processors, SIMD support, Combination of core private and shared caches Heterogeneity, Hardware support for energy control',
topic: 'Cloud'
},
{
id: 2,
frage: 'What is typical for multi-core processors?',
antwort: 'Cache Architecture (L1 private to core, L2 private to tile), Cache Coherence',
topic: 'Cloud'
},
{
id: 3,
frage: 'What memory modes exist?',
antwort: 'Flat mode, Cache Mode, Hybrid Mode',
topic: 'Cloud'
},
{
id: 4,
frage: 'What memory modes exist?',
antwort: 'Flat mode, Cache Mode, Hybrid Mode',
topic: 'Cloud'
},
];
Try this on your onSolvedKlick function:
onSolvedKlick = (id) => {
console.log("Klick on solved"+id);
var frage = this.state.fragen.filter((e) => e.id === id);
this.setState({solved: [...this.state.solved, frage]});
}
Try to avoid so many empty lines.
Also keep your code always in english so it's easier for others to understand. I had the luck to be german too :)
Assuming that you want to move the questions from fragen array to solved array, here is how to do that.
onSolvedKlick = id => {
console.log("Klick on solved" + id);
var elementPos = this.state.fragen.map(function(x) {return x.id; }).indexOf(id); // Find the position of the selected item in the fragen
const currentItem = this.state.fragen.splice(elementPos,1)
const newSolvedArray = this.state.solved;
newSolvedArray.push(currentItem[0]);//splice gives an array
this.setState({ solved: newSolvedArray }, function() {console.log(this.state)});
};
I am very new to react, and i decided to build a website using create-react-app for experience. I imported a Gallery component into my code which is causing me some problems. It's basically a Picture Gallery with a filtering and shuffle option which works fine whenever you completely reload the page, yet stops working once you switch between routes (with react-router) inside the webpage itself.
My guess is that the eventListeners are not being added to my buttons once react unmounts them, and I am really unsure as to how to refactor the code at hand, inorder to get it going. I have tried using Hooks but I can't make it work.
How do I refactor the
componentDidMount () {
document.addEventListener('DOMContentLoaded', () => {
window.demo = new Demo(document.getElementById('grid'));
});
}
part of the code in order to get it working without having to reload the entire page?
Homebase.js
import React, { Component } from 'react';
import './Homebase.scss';
import Shuffle from 'shufflejs';
import { Link } from 'react-router-dom';
import Demo from './Homebasescript.js';
class homebase extends Component {
componentDidMount () {
document.addEventListener('DOMContentLoaded', () => {
window.demo = new Demo(document.getElementById('grid'));
});
}
render() {
return (
<section>
<div className="homebase-page">
<div className="container-about">
<div className="row-about">
<div className="homebase-title-container">
<h1 className="homebase-about-title">Townhall <span style={{color: 'rgb(21, 115, 209)'}}>12</span> Base Designs</h1>
<div className="switch-container-th12">
<button className="switch-buttons-th12 switch-to-th9-12">
<div className="switch-buttons-text-12">coming soon</div>
</button>
<button to="/Townhall-10" className="switch-buttons-th12 switch-to-th10-12">
<div className="switch-buttons-text-12">Townhall 10</div>
</button>
<button to="/Townhall-11" className="switch-buttons-th12 switch-to-th11-12">
<div className="switch-buttons-text-12">Townhall 11</div>
</button>
<button to="/Townhall-12" className="switch-buttons-th12 switch-to-th12-12">
<div className="switch-buttons-text-12">Townhall 12</div>
</button>
</div>
</div>
</div>
</div>
<div className="container-about">
<div className="row-about">
<div className="col-4#sm col-3#md">
<div className="filters-group filters-group-left">
<label htmlFor="filters-search-input" className="filter-label filter-color">Search</label>
<input className="textfield filter__search js-shuffle-search" type="search" id="filters-search-input" />
</div>
</div>
</div>
<div className="row-about">
<div className="col-12#sm filters-group-wrap">
<div className="filters-group filters-group-right">
<p className="filter-label filter-color">Filter</p>
<div className="btn-group filter-options">
<button className="btn btn--primary" data-group="war">War</button>
<button className="btn btn--primary" data-group="trophy">Trophy</button>
<button className="btn btn--primary" data-group="farm">Farm</button>
<button className="btn btn--primary" data-group="fun">Fun</button>
<button className="btn btn--primary" data-group="contributors">Contributors</button>
</div>
</div>
<fieldset className="filters-group">
<legend className="filter-label filter-color">Sort</legend>
<div className="btn-group sort-options">
<label className="btn active">
<input type="radio" name="sort-value" value="dom" defaultChecked /> Default </label>
<label className="btn">
<input type="radio" name="sort-value" value="title" /> Title </label>
<label className="btn">
<input type="radio" name="sort-value" value="date-created" /> Date Created </label>
</div>
</fieldset>
</div>
</div>
</div>
</div>
</section>
);
};
};
export default homebase;
and the other file is
Homebasescript.js
import Shuffle from 'shufflejs';
class Demo {
constructor(element) {
this.element = element;
this.shuffle = new Shuffle(element, {
itemSelector: '.picture-item',
sizer: element.querySelector('.my-sizer-element'),
});
// Log events.
this.addShuffleEventListeners();
this._activeFilters = [];
this.addFilterButtons();
this.addSorting();
this.addSearchFilter();
}
/**
* Shuffle uses the CustomEvent constructor to dispatch events. You can listen
* for them like you normally would (with jQuery for example).
*/
addShuffleEventListeners() {
this.shuffle.on(Shuffle.EventType.LAYOUT, (data) => {
console.log('layout. data:', data);
});
this.shuffle.on(Shuffle.EventType.REMOVED, (data) => {
console.log('removed. data:', data);
});
}
addFilterButtons() {
const options = document.querySelector('.filter-options');
if (!options) {
return;
}
const filterButtons = Array.from(options.children);
const onClick = this._handleFilterClick.bind(this);
filterButtons.forEach((button) => {
button.addEventListener('click', onClick, false);
});
}
_handleFilterClick(evt) {
const btn = evt.currentTarget;
const isActive = btn.classList.contains('active');
const btnGroup = btn.getAttribute('data-group');
this._removeActiveClassFromChildren(btn.parentNode);
let filterGroup;
if (isActive) {
btn.classList.remove('active');
filterGroup = Shuffle.ALL_ITEMS;
} else {
btn.classList.add('active');
filterGroup = btnGroup;
}
this.shuffle.filter(filterGroup);
}
_removeActiveClassFromChildren(parent) {
const { children } = parent;
for (let i = children.length - 1; i >= 0; i--) {
children[i].classList.remove('active');
}
}
addSorting() {
const buttonGroup = document.querySelector('.sort-options');
if (!buttonGroup) {
return;
}
buttonGroup.addEventListener('change', this._handleSortChange.bind(this));
}
_handleSortChange(evt) {
// Add and remove `active` class from buttons.
const buttons = Array.from(evt.currentTarget.children);
buttons.forEach((button) => {
if (button.querySelector('input').value === evt.target.value) {
button.classList.add('active');
} else {
button.classList.remove('active');
}
});
// Create the sort options to give to Shuffle.
const { value } = evt.target;
let options = {};
function sortByDate(element) {
return element.getAttribute('data-date-created');
}
function sortByTitle(element) {
return element.getAttribute('data-title').toLowerCase();
}
if (value === 'date-created') {
options = {
reverse: true,
by: sortByDate,
};
} else if (value === 'title') {
options = {
by: sortByTitle,
};
}
this.shuffle.sort(options);
}
// Advanced filtering
addSearchFilter() {
const searchInput = document.querySelector('.js-shuffle-search');
if (!searchInput) {
return;
}
searchInput.addEventListener('keyup', this._handleSearchKeyup.bind(this));
}
/**
* Filter the shuffle instance by items with a title that matches the search input.
* #param {Event} evt Event object.
*/
_handleSearchKeyup(evt) {
const searchText = evt.target.value.toLowerCase();
this.shuffle.filter((element, shuffle) => {
// If there is a current filter applied, ignore elements that don't match it.
if (shuffle.group !== Shuffle.ALL_ITEMS) {
// Get the item's groups.
const groups = JSON.parse(element.getAttribute('data-groups'));
const isElementInCurrentGroup = groups.indexOf(shuffle.group) !== -1;
// Only search elements in the current group
if (!isElementInCurrentGroup) {
return false;
}
}
const titleElement = element.querySelector('.picture-item__title');
const titleText = titleElement.textContent.toLowerCase().trim();
return titleText.indexOf(searchText) !== -1;
});
}
}
export default Demo;
I would be extremely thankful for any help, sincei have been stuck on this for days!
Like #charlietfl said, DOMContentLoaded has already occurred on initial page load, and will not be triggered again when you navigate between routes in your SPA.
Try removing the listener:
componentDidMount () {
//document.addEventListener('DOMContentLoaded', () => {
// window.demo = new Demo(document.getElementById('grid'));
//});
window.demo = new Demo(document.getElementById('grid'));
}