Load SVG div to React component - reactjs

I'm using an NPM package that draws a fretboard using D3 (https://github.com/txels/fretboard) .It outputs a div with SVG. I have it working with a normal HTML page but if I try and load it to a React component with code below it gets appended to end of page rather than in div where I have the expression.
Any ideas how I get this generated div in the component div?
import React from "react";
import { Fretboard, Tunings } from "fretboards";
const GenerateFret = () => {
const config = {
frets: 12, // Number of frets to display
startFret: 0, // Initial fret
strings: 6, // Strings
tuning: Tunings.guitar6.standard, // Tuning: default = Standard Guitar
fretWidth: 50, // Display width of frets in pixels
fretHeight: 20, // Display heigh of frets in pixels
leftHanded: false, // Show mirror image for left handed players
showTitle: true, // Set the note name as the title, so it will display on hover
where="#fret"
};
const notes =
"6:e2 6:f2 6:f#2 6:g2 6:g#2 6:a2 6:a#2 6:b2 6:c3 6:c#3 6:d3 6:d#3 6:e3 " +
"5:a2 5:a#2 5:b2 5:c3 5:c#3 5:d3 5:d#3 5:e3 5:f3 5:f#3 5:g3 5:g#3 5:a3 " +
"4:d3 4:d#3 4:e3 4:f3 4:f#3 4:g3 4:g#3 4:a3 4:a#3 4:b3 4:c4 4:c#4 4:d4 " +
"3:g3 3:g#3 3:a3 3:a#3 3:b3 3:c4 3:c#4 3:d4 3:d#4 3:e4 3:f4 3:f#4 3:g4 " +
"2:b3 2:c4 2:c#4 2:d4 2:d#4 2:e4 2:f4 2:f#4 2:g4 2:g#4 2:a4 2:a#4 2:b4 " +
"1:e4 1:f4 1:f#4 1:g4 1:g#4 1:a4 1:a#4 1:b4 1:c5 1:c#5 1:d5 1:d#5 1:e5";
let board = Fretboard(config);
board.draw(notes);
};
const GameArea = () => {
return (
<div id="fret" className="col-8 border border-primary fb-container">
{GenerateFret()}
</div>
);
};
export default GameArea;
There is a config for [where] which changes the D3 selection from body.
I tried adding an ID to the div and then passing in where="#fret" to the config const but this stops any visual from being output.

Manually changing the DOM in React components is an example of side effect and therefore should be put inside useEffect
Try:
const GameArea = () => {
useEffect(()=>{
GenerateFret(); // call function inside useEffect
}, []) // you need to run it only once, so pass empty array
return (
<div id="fret" className="col-8 border border-primary fb-container"/>
);
};
This is the whole code:
import React from "react";
import { Fretboard, Tunings } from "fretboards";
const GenerateFret = () => {
const config = {
frets: 12, // Number of frets to display
startFret: 0, // Initial fret
strings: 6, // Strings
tuning: Tunings.guitar6.standard, // Tuning: default = Standard Guitar
fretWidth: 50, // Display width of frets in pixels
fretHeight: 20, // Display heigh of frets in pixels
leftHanded: false, // Show mirror image for left handed players
showTitle: true, // Set the note name as the title, so it will display on hover
where="#fret"
};
const notes =
"6:e2 6:f2 6:f#2 6:g2 6:g#2 6:a2 6:a#2 6:b2 6:c3 6:c#3 6:d3 6:d#3 6:e3 " +
"5:a2 5:a#2 5:b2 5:c3 5:c#3 5:d3 5:d#3 5:e3 5:f3 5:f#3 5:g3 5:g#3 5:a3 " +
"4:d3 4:d#3 4:e3 4:f3 4:f#3 4:g3 4:g#3 4:a3 4:a#3 4:b3 4:c4 4:c#4 4:d4 " +
"3:g3 3:g#3 3:a3 3:a#3 3:b3 3:c4 3:c#4 3:d4 3:d#4 3:e4 3:f4 3:f#4 3:g4 " +
"2:b3 2:c4 2:c#4 2:d4 2:d#4 2:e4 2:f4 2:f#4 2:g4 2:g#4 2:a4 2:a#4 2:b4 " +
"1:e4 1:f4 1:f#4 1:g4 1:g#4 1:a4 1:a#4 1:b4 1:c5 1:c#5 1:d5 1:d#5 1:e5";
let board = Fretboard(config);
board.draw(notes);
};
const GameArea = () => {
useEffect(()=>{
GenerateFret(); // call function inside useEffect
}, []) // you need to run it only once, so pass empty array
return (
<div id="fret" className="col-8 border border-primary fb-container"/>
);
};

Related

Rendering .map of object to change its properties

I'm new to React js & React Native please help
so i have this data of partners
const [lessorPartners , setLessorPartners] = useState(null)
const dataPartnerBeforeFetch = [
{id:1, name:"BFI" , img:"lessor1" , code:"PU77"},
{id:2, name:"SMF" , img:"lessor2" , code:"TT38"},
{id:3, name:"Adira" , img:"lessor3" , code:"PT74"},
{id:4, name:"BFI" , img:"lessor1" , code:"PB63"},
{id:5, name:"SMF" , img:"lessor2" , code:"BU42"},
{id:6, name:"Adira" , img:"lessor3" , code:"AL39"}
]
useEffect(() =>{
if(dataPartnerBeforeFetch ){
dataPartnerBeforeFetch.map(dataPartner=>{
dataPartner.color = false
})
setLessorPartners(dataPartnerBeforeFetch)
}
},[dataPartnerBeforeFetch ])
I added color to its end if its true then it will turn transparent / false it will be orange
and I tried to loop it :
and render it all with these functions
const renderingPartners = () => {
return(
lessorPartners.map(lessorPartner => {
renderingPartner(lessorPartner)
})
)
}
const renderingPartner = (lessorPartner) =>{
return(
<div style={{backgroundColor: false ? 'orange' : 'transparent'}}
onClick={()=>{
onClickParter(lessorPartner);
}}
>
<LessorPartner
key = {lessorPartner.id}
object = {lessorPartner}
/>
</div>
)
}
and i tried to call renderingPartners() in my app .js like this
<div>
{ lessorPartners && renderingPartners()}
</div>
but no component returned, just empty and no error
the next idea is to change it's color on click with this function and re render the whole mapping
const onClickParter = (q) =>{
q.color = !q.color
let index = lessorPartners.indexOf(q);
lessorPartners[index]= q
setLessorPartners(lessorPartners)
renderingPartners()
}
just like radio button with list of lessor that i've tried to map
please help i've been stuck here for hours
So map function returns a new array (also it returns a value so if you are specifying braces you have to explicitly write return). you should modify your first snippet like this:
let _dataPartnerBeforeFetch = dataPartnerBeforeFetch.map(dataPartner=>{
return {
...dataPartner,
color : false
}
});
setLessorPartners(_dataPartnerBeforeFetch)
Similarly this snippet should be corrected like this:
lessorPartners.map(lessorPartner => renderingPartner(lessorPartner))
Edited:
Why the color change is not reflected?
Its almost always best to return a new array.
const onClickParter = (q) =>{
let _lessorPartners = lessorPartners.filter(f=> !(f.indexOf(q) >= 0));
_lessorPartners.push({
...q,
color : !q.color
});
setLessorPartners(_lessorPartners);
//renderingPartners();
//we donot have to explicity call the function to enforce rerender because it's already binded by a state variable. So setting the state would do the trick.
}
Here filter returns a new copy of the array without the item in scope (reffered to as 'q'). Then you add a new object with the inverted color to the new array and set the state.

Word Counter React - Display Word Frequency

Hi I'm fairly new to React and currently trying to write a word counter. The idea is that once you type in the text box it will then display a list of all the words and the frequency of how often they're used (This would be displayed in the span tag where it says wordCounts). The issue I'm currently having is it only displays one word with the frequency when I want a list. Moreover I honestly feel like could be achieved in a completely different way but again I'm fairly new to React and learning as I go.
If I need to share any more info or more code, please let me know.
React Code
import { Component } from "react";
import "./App.css";
class App extends Component {
constructor(props) {
super(props);
this.state = {
firstValue: "",
numberOfCharacters: "",
withoutWhiteSpace: "",
numberOfWords: "",
linesCount: "",
wordSelectionCount: "",
};
}
firstHandle = (event) => {
var input = event.target.value;
const text = document.getElementById("textarea").value;
const linesCount = text.split("/\r|\r\n|\n/").length;
const numberOfCharacters = input === "" ? 0 : input.split("").length;
const withoutWhiteSpace =
input === "" ? 0 : input.split("").filter((char) => char !== " ").length;
const words =
input === "" ? 0 : input.split(" ").filter((word) => word.trim()).length;
const lines = input === "" ? 1 : input.split(/\n/g).length;
this.setState({
firstValue: input,
numberOfCharacters: numberOfCharacters,
withoutWhiteSpace: withoutWhiteSpace,
numberOfWords: words,
linesCount: lines,
wordSelectionCount: "",
});
};
// This function is responsible for the word counting
wordCounter = (e) => {
e.preventDefault();
var keys = [];
var counts = {};
const input = this.state.firstValue
.replace(/\W/g, " ")
.replace(/[0-9]/g, " ")
.split(" ")
.filter((word) => word.trim());
for (let i = 0; i < input.length; i++) {
var word = input[i];
if (counts[word] === undefined) {
counts[word] = 1;
keys.push(word);
} else {
counts[word] += 1;
keys.push(word);
// console.log(keys);
}
keys.sort();
for (let i = 0; i < keys.length; i++) {
var key = keys[i];
var result = key + " - " + counts[key];
console.log(result);
}
this.setState({
wordSelectionCount: result,
});
}
};
render() {
var numberOfCharacters = this.state.numberOfCharacters;
var withoutWhiteSpace = this.state.withoutWhiteSpace;
var words = this.state.numberOfWords;
var lines = this.state.linesCount;
var wordCounts = this.state.wordSelectionCount;
console.log(wordCounts);
return (
<div className="App">
<header className="App-header">
<form>
<h1>Character Counter</h1>
<p>
Characters <span>{numberOfCharacters}</span> Without White Space{" "}
<span>{withoutWhiteSpace}</span> Words <span>{words}</span> Lines{" "}
<span>{lines}</span>
</p>
<textarea
id="textarea"
type="text"
placeholder="Please type some text..."
value={this.firstValue}
onChange={this.firstHandle}
/>
<h1>Word Counting</h1>
{/* This button calls the wordCounter Method which should display all the Word listings */}
<button className="btn" onClick={this.wordCounter}>
Words Count
</button>
<p>
<span>{wordCounts}</span>
</p>
</form>
</header>
</div>
);
}
}
export default App;
Issue
The issue is you are not iterating over wordSelectionCount to render your data, therefore your latest value will be displayed.
You can iterate over wordSelectionCount to render all of the data. But, I have a suggestion for you
Suggestion
First suggestion will be, use functional components and react hooks.
Second, use the power of the object's [key-value] pair to store the word counts.
I have created a codesandbox example if you want to have a look. You can start adding words to see the word counter.
Solution for the existing code
instead of rendering <span>{wordCounts}</span> directly, you should iterate over it. Such as:
this.state.wordSelectionCount && Object.keys(this.state.wordSelectionCount).map(word => (
<span>{word} - {this.state.wordSelectionCount[word]}</span>
)}

react-grid layout html not updating cols

I use react Grid-layout : https://github.com/react-grid-layout/react-grid-layout
I do not understand why the number of column does not update in my table through the web browser.
In fact, this.props.cols.lg is either 17 or 24.
When it's 17, my table is displayed correctly. On the other hand, when I choose a period with 24 columns, my columns are placed one below the other from the 18th.
this.props.cols.lg to the correct value (17 or 24) but my HTML page does not update to the design level.
This is the right table on one single row :
Right table
Like you can see, the firefox web console shows react properties are good with 17 :
and the bad table
Bad table
here again, the firefox web console shows react properties are right (24) but not applied by the web browser :
Important information,
this.props.cols.lg=loopValue+1; **contains the right value (17 or 24) but apply on 17**
Thank you in advance for your help.
Here is a part of my code:
import React, { useState, useEffect, Fragment } from 'react';
import { WidthProvider, Responsive } from "react-grid-layout";
import ReactTooltip from 'react-tooltip';
import _ from "lodash";
import { Button, Container } from 'react-bootstrap';
const ResponsiveReactGridLayout = WidthProvider(Responsive);
import { Responsive as ResponsiveGridLayout } from 'react-grid-layout';
/**
* This layout demonstrates how to use a grid with a dynamic number of elements.
*/
export class AddRemoveLayout extends React.PureComponent {
//les propriétés par défaut
static defaultProps = {
className: "layout border-darken-1 child-background-color",
cols:
{ lg: 10, md: 10, sm: 10, xs: 10, xxs: 10 },
width: 1000,
margin: [0, 0],
preventCollision: true,
autoSize: true,
// preventCollision: true,
rowHeight: 70,
// Build HTML to insert
render() {
//Week
let createDateItem =
(x, y, day) => ({
i: `date-${x}_${y}`,
x,
y,
w: 1,
h: 1,
myText: `${day}` ,
static: true
});
/**
*
* #param {*} y
* #param {*} xStart
* #param {num semaine} dateStart
* #param {nombre semaine total} count
*/
let getDateItems =
(y, xStart, dateStart, count,dateEnd) => {
let items = [];
let loopValue = 0;
while(dateStart<=dateEnd){//semainee
if(loopValue>1){
dateStart.setDate(dateStart.getDate() + 7)
}
if(dateStart<=dateEnd){
items.push(
createDateItem(xStart + loopValue, y, loopValue === 0 ? 0 :
(dateStart.getDate()+"/" +(dateStart.getUTCMonth()+1)+ " (Sem. "+loopValue +") ")
))
}
loopValue++;
}
console.log('props:')
console.log(this.props);
this.props.cols.lg=loopValue+1;
this.props.cols.md=loopValue+1;
this.props.cols.sm=loopValue+1;
this.props.cols.xs=loopValue+1;
this.props.cols.xxsloopValue+1;
console.log(this.props);
// console.log(AddRemoveLayout.defaultProps)
return items;
}
;
We found the solution :
data: { idHoraire: 1 },
It was always the same item sent to PHP, with this code, I have a great dispaly of my cols because I either send 1 (17 cols) or 2 (25 cols) :
data: { idHoraire: url === "index2" ?2 : 1, },

Wrapping an an html anchor tag around an array is causing unexpected results in my React component

I have a component that I use in my react app that generates a random game on the screen.
It's working, but now I'm trying to add some html into the game title.
When I do that, my game titles come up as:
[object Object]
Here is where I generate a random game:
const newGame = () => {
return {
title: gameTitleArray[Math.floor(Math.random() * gameTitleArray.length)],
type: gameTypeArray[Math.floor(Math.random() * gameTypeArray.length)],
startDate: getDate(new Date(2019, 0, 1), new Date()),
endDate: getDate(new Date(2022, 0, 1), new Date()),
}
}
You can see I'm trying to wrap an html anchor tag around the 'title' portion.
Here is how I'm exporting the component:
export default function makeGameData(...lens) {
const makeGameDataLevel = (depth = 0) => {
const len = lens[depth]
return range(len).map(d => {
return {
...newGame(),
subRows: lens[depth + 1] ? makeGameDataLevel(depth + 1) : undefined,
}
})
}
return makeGameDataLevel()
}
Here is an example of the gameTypeArray:
const gameTypeArray = ['RPG', 'Western', 'Real-Time Strategy', 'Fantasy', 'First Person Shooter']
And an example of gameTitleArray:
const gameTitleArray = ['Future Agent','Human Universe','Chase of Resitution','Destroy of Resitution','Days and Glitch','Mayhem and Faith','Dynaworks','Crystalback','Fusionheart','Hellscape']
I even tried creating a separate function like this:
function gameTitleArrayLink() {
const gameTitleArray = ['Future Agent','Human Universe','Chase of Resitution','Destroy of Resitution','Days and Glitch','Mayhem and Faith','Dynaworks','Crystalback','Fusionheart','Hellscape']
const title = gameTitleArray[Math.floor(Math.random() * gameTitleArray.length)]
const titleUrl = {title}
return <div dangerouslySetInnerHTML={{ __html: titleUrl }} />
}
And then setting the title like this:
title: researchSummaryList()
Obviously I'm doing something wrong, but I'm not getting any errors, just the [object Object]
Is there anything I'm doing wrong?
Thanks!
Try wrapping the array value in {} to have it treated as an expression:
{gameTitleArray[Math.floor(Math.random() * gameTitleArray.length)]}
I think that the item you want to render
(gameTitleArray[Math.floor(Math.random() * gameTitleArray.length)])
is an object and not a string

Combining react-leaflet and leaflet-pixi-overlay

I am trying to display a complex map with many moving markers, with different icons etc... I have a pure react / react-leaflet solution, but it start to struggle with ~1k markers moving every second.
It looks like leaflet-pixi-overlay could be a way of really speeding things up. But I am just starting with the whole chain (react/javascript/maps/leaflet/etc..) and have problems when connecting all this.
Right now I am just trying to display a polygon in my overlay, and nothing is rendered. It turns out that the translation of lat/long to x/y is failing for that polygon's points. Pixi's latLngToLayerPoint function returns 'infinity' for x and y.
This seems to come from the fact that the zoom is not defined for the layer in question (it is 'infinity' also).
Even if I call latLngToLayerPoint with the zoom setting from the map, things fail too (x/y values are not infinite any more, but they are way out there).
This is my code:
import React, { useEffect, useRef } from 'react';
import { Map, TileLayer } from 'react-leaflet'
import "leaflet/dist/leaflet.css"
import * as PIXI from 'pixi.js'
import 'leaflet-pixi-overlay' // Must be called before the 'leaflet' import
import L from 'leaflet';
let polyLatLngs = [[50.630, 13.047], [50.645, 13.070], [50.625, 13.090], [50.608, 13.070], [50.630, 13.047]]
let pixiContainer = new PIXI.Container()
let prevZoom
let firstDraw = true;
let projectedPolygon;
var shape = new PIXI.Graphics();
pixiContainer.addChild(shape);
let myOverlay = L.pixiOverlay(function (utils) {
let map = utils.getMap()
let zoom = map.getZoom()
console.log('map zoom=' + zoom + ', center=' + map.getCenter())
console.log(' bounds=' + JSON.stringify(map.getBounds()))
console.log(' size=' + map.getSize() + ', panes=' + JSON.stringify(map.getPanes()))
if (map) {
var container = utils.getContainer();
var renderer = utils.getRenderer();
var project = utils.latLngToLayerPoint;
var scale = utils.getScale();
if (firstDraw) {
projectedPolygon = polyLatLngs.map((coords, index) => {
console.log('i=' + index + ', coords=' + coords + ', proj=' + project(coords))
return project(coords)
// return project(coords, zoom) // : this fails too
})
}
if (firstDraw || prevZoom !== zoom) {
shape.clear();
shape.lineStyle(3 / scale, 0x3388ff, 1);
shape.beginFill(0x3388ff, 0.2);
projectedPolygon.forEach(function (coords, index) {
if (index === 0) shape.moveTo(coords.x, coords.y);
else shape.lineTo(coords.x, coords.y);
});
shape.endFill();
}
firstDraw = false;
prevZoom = zoom;
renderer.render(container);
}
}, pixiContainer)
function PxMap(props) {
const mapRef = useRef(null);
useEffect(() => {
if (mapRef.current !== null) {
let map = mapRef.current.leafletElement;
console.log('useEffect: add overlay ')
console.log(JSON.stringify(map.getPane('overlayPane').childElementCount))
myOverlay.addTo(map);
console.log(JSON.stringify(map.getPane('overlayPane').childElementCount))
}
}, [mapRef]);
return (
<div style={{ flexgrow: 1, height: '100%' }}>
<Map
preferCanvas={true}
ref={mapRef}
style={{ height: '100%' }}
center={[50.63, 13.047]}
zoom={12}
>
<TileLayer
attribution='&copy OpenStreetMap contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png?"
/>
</Map>
</div>
)
}
export default PxMap
I think that things are connected correctly between React and leaflet, the map displays ok, I can see the overlay being added etc...
BUT there is a connection missing somewhere, to give more context / information to PIXI.
Any ideas? Thanks!
Finally found the problem, drilling down into the leaflet-pixi-overlay lib.
The solution is to define the minZoom and maxZoom in the Map element:
<Map
preferCanvas={true}
ref={mapRef}
style={{ height: '100%' }}
center={[50.63, 13.047]}
zoom={12}
minZoom={ 9} // Add these options...
maxZoom={ 16} //
Internally, L.PixiOverlay.js relies on these two values to define:
// return the layer projection zoom level
projectionZoom: function (map) {return (map.getMaxZoom() + map.getMinZoom()) / 2;},
Which in turn is used to define the default zoom setting?
this._initialZoom = this.options.projectionZoom(map);
....
zoom = (zoom === undefined) ? _layer._initialZoom : zoom;
var projectedPoint = map.project(L.latLng(latLng), zoom);
Not sure why this is done this way, but setting these options solves the problem.

Resources