React component with SVG does not evaluate expression - reactjs

Imagine I have a component like this it encloses an SVG:
class Image extends React.Component {
constructor(props) {
super(props);
this.state = {
message: "Initial message"
};
}
render() {
return ( < svg xmlns = "http://www.w3.org/2000/svg"
width = "300"
height = "450" >
< rect x = "0"
y = "0"
width = "300"
height = "450"
fill = "#d0d0d0" / >
< text id = "textInHere"
x = "50%"
y = "50%"
fill = "#7d7d7d" > {this.state.message} < /text> < /svg > )
}
}
I am simply evaluating expression this.state.message as inner text.
To my surprise it generates the following DOM structure:
This is strange as i have not added any span!
The problem is illustrated in a plunk here
Appreciate, if any one could explain why I see multiple spans, and/or how to fix this problem.

You have a problem with whitespaces in your tags. When you remove them everything looks fine.
UPDATE: apparently, this is caused by spaces surrounding {this.state.message}. They are converted to span which results in breaking your syntax as you have observed (because span is not allowed inside svg tag).

Related

How to customise the width & height of a Victory tooltip text

I'm using Victory Tooltips inside a Victory PieChart. The tooltip text (actually label) is quite huge.
I'm unable to break the text in an automatic manner and so the width of the tooltip overflows the container and I can't see the whole text. How do I add a max-width to the tooltip? Is it possible to break the text to multiple lines?
<VictoryPie
colorScale = { colors } // Some colors
responsive = { true }
data = { data } // Some data
innerRadius = { 100 }
labelRadius = { 100 }
labelComponent = {
<VictoryTooltip
orientation = "top"
cornerRadius = { 8 }
flyoutStyle = {{ fill: "#151515", stroke : '#313131' }}
pointerOrientation = "bottom"
flyoutPadding = { 20 }
text = { (data) => `top \n ${data.datum?.label}` }
style = { [
{fill : 'orange', stroke : 'blue', strokeWidth : '0.1%', fontSize : '20px'},
{fill : 'green', stroke : 'red', fontSize : '14px', dy : '20'}
] }
/>
}
/>
It shows up like so
How do I show the text in a smaller container?
The only way I found was to add a line break ${'\n'} or creating a CustomFlyout using svg (its on the Victory page docs) https://formidable.com/open-source/victory/guides/tooltips/#tooltips-with-victoryvoronoicontainer

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>
)}

ReactJS: Child Component is not updating even I am passing different values

I am new to React.
My child component (SmithchartSeriesDirective) successfully displays the data passed from the server, when the parent component (SimplePanel) is loaded for the first time. On subsequent calls the data received from server changes, it is reflected in the props, but once I bind this data to the child component, it does not reflect the updated data in the component.
I am binding the data in listResult array.
Below is Parent Component SimplePanel
export class SimplePanel extends Component<Props> {
render() {
var reactance: number[] = [];
var resistance: number[] = [];
this.props.data.series.map((anObjectMapped, index) => {
if(index==0)
{
reactance = anObjectMapped.fields[0].values.toArray();
}
else
{
resistance = anObjectMapped.fields[0].values.toArray();
}
});
var resultArr =
{
resistance:0,
reactance:0
};
let listResult =[];
for (let index = 0; index < resistance.length; index++) {
var newObj = Object.create(resultArr);
newObj.resistance = Number(resistance[index]);
newObj.reactance=reactance[index];
listResult.push(newObj);
}
return (<div className='control-pane' style={{ height:'100%', width:'100%', backgroundColor:'#161719' }} >
<div className='col-md-12 control-section' style={{ height:'100%', width:'100%' }}>
<SmithchartComponent id='smith-chart' theme="MaterialDark" legendSettings={{ visible: true, shape: 'Circle' }}>
<Inject services={[SmithchartLegend, TooltipRender]}/>
<SmithchartSeriesCollectionDirective>
<SmithchartSeriesDirective
points= {listResult}
enableAnimation={true}
tooltip={{ visible: true }}
marker={{ shape: 'Circle', visible: true, border: { width: 2 } }}
>
</SmithchartSeriesDirective>
</SmithchartSeriesCollectionDirective>
</SmithchartComponent>
</div>
</div>);
welcome to stack overflow.
First remember that arrays saved by reference in JavaScript. So if you change any array by push() or pop() methods, reference to that array doesn't change and React can't distinguish any change in your array (to re-render your component).
let a = [2];
let b = a;
b.push(4);
a == b; //a is [2] and b is [2,4] but the result is true.
You can use this approach as a solution to this problem:
let listResult = [...oldListResult, newObj]; // ES6 spread operator
Also consider for rendering array elements you need to use key prop, so React can render your components properly. more info can be found here.

How to create and set a setMapTypeId using react-google-maps

I was looking for a way to create my own mars map in a website, using google maps.
I found this example in google map api
function initMap() {
var map = new google.maps.Map(document.getElementById('map'), {
center: {lat: 0, lng: 0},
zoom: 1,
streetViewControl: false,
mapTypeControlOptions: {
mapTypeIds: ['moon']
}
});
var moonMapType = new google.maps.ImageMapType({
getTileUrl: function(coord, zoom) {
var normalizedCoord = getNormalizedCoord(coord, zoom);
if (!normalizedCoord) {
return null;
}
var bound = Math.pow(2, zoom);
return '//mw1.google.com/mw-planetary/lunar/lunarmaps_v1/clem_bw' +
'/' + zoom + '/' + normalizedCoord.x + '/' +
(bound - normalizedCoord.y - 1) + '.jpg';
},
tileSize: new google.maps.Size(256, 256),
maxZoom: 9,
minZoom: 0,
radius: 1738000,
name: 'Moon'
});
map.mapTypes.set('moon', moonMapType);
map.setMapTypeId('moon');
}
// Normalizes the coords that tiles repeat across the x axis (horizontally)
// like the standard Google map tiles.
function getNormalizedCoord(coord, zoom) {
var y = coord.y;
var x = coord.x;
// tile range in one direction range is dependent on zoom level
// 0 = 1 tile, 1 = 2 tiles, 2 = 4 tiles, 3 = 8 tiles, etc
var tileRange = 1 << zoom;
// don't repeat across y-axis (vertically)
if (y < 0 || y >= tileRange) {
return null;
}
// repeat across x-axis
if (x < 0 || x >= tileRange) {
x = (x % tileRange + tileRange) % tileRange;
}
return {x: x, y: y};
}
/* Always set the map height explicitly to define the size of the div
* element that contains the map. */
#map {
height: 100%;
}
/* Optional: Makes the sample page fill the window. */
html, body {
height: 100%;
margin: 0;
padding: 0;
}
<div id="map"></div>
<!-- Replace the value of the key parameter with your own API key. -->
<script
async
defer
src="https://maps.googleapis.com/maps/api/js?key=AIzaSyCkUOdZ5y7hMm0yrcCQoCvLwzdM6M8s5qk&callback=initMap">
</script>
https://jsfiddle.net/dobleuber/319kgLh4/
It works perfect, but I would like to create the same thing with react using react-google-maps.
I looked out in the react-google-maps code but I only see getters no setters for the map props:
getMapTypeId, getStreetView, ect.
Is there any way to achieve this without modify the react-google-maps code?
Thanks in advance
use props mapTypeId="moon" in react-google-maps
I've found a better way to solve this that preserve the changes on re-render, leaving it here to anyone who comes here.
there is an onLoad function that exposes a map instance, we can use this to set mapTypeId instead of passing it as an option. In this way, if the user changes the map type later, it will preserve the changes on re-render.
<GoogleMap
onLoad={(map) => {
map.setMapTypeId('moon');
}}
/>

Array of components in react does not render properly when updated

I am trying to build a component containing an array of components in react, giving a new size to each component in array according to total number of elements. However the view does not update all the components. Only newly added component is updated with size even though i rebuild the array each time.
class StampArea extends React.Component{
constructor(props){
super(props);
this.state = {updateView: true};
this.getStampSize = this.getStampSize.bind(this);
this.prepareStamps = this.prepareStamps.bind(this);
this.balance = 1;
}
prepareStamps(fulfill){
var stamps = [];
console.log('stamps: ', stamps);
for (var i = 1; i <= fulfill; i++) {
if(i<=this.balance){
stamps.push(<Stamp key={i} stampSize={this.getStampSize(fulfill)} stamped={true} stampBackgroundImage={this.props.stampBackgroundImage} stampPunchImage={this.props.stampPunchImage}/>);
}else{
stamps.push(<Stamp key={i} stampSize={this.getStampSize(fulfill)} stamped={false} stampBackgroundImage={this.props.stampBackgroundImage} stampPunchImage={this.props.stampPunchImage}/>);
}
}
console.log('stamps: ', stamps);
return(stamps);
}
getStampSize(fulfill){
if(fulfill >= 20){return 10}
if(fulfill >= 15){return 20}
if(fulfill >= 10){return 50}
if(fulfill >= 8 ){return 35}
if(fulfill >= 5 ){return 40}
if(fulfill >= 4 ){return 60}
if(fulfill >= 1 ){return 80}
}
render(){
return( <div style={styles.stampsContainer}>{this.prepareStamps(this.props.fulfill)}</div>);
}
}
class Stamp extends React.Component{
constructor(props){
super(props);
this.stamp=null;
this.buildStamp=this.buildStamp.bind(this);
this.buildStamp();
}
buildStamp(){
// Assigning correct stylesheet to build the stamp as asked.
if(this.props.stamped){
this.punched = (<img style={{position:'absolute',height:this.props.stampSize, width:this.props.stampSize, resizeMode:'contain'}} src={this.props.stampPunchImage}/>);
}else{
this.punched = null;
}
console.log('stampsize inside, ', this.props.stampSize);
this.backgroundImage = (<img style={{position:'absolute',height:this.props.stampSize, width:this.props.stampSize, resizeMode:'contain'}} src={this.props.stampBackgroundImage}/>);
this.stamp = (<div style={{position:'relative', margin:5,height:this.props.stampSize, width:this.props.stampSize}}>{this.backgroundImage}{this.punched}</div>);
}
render(){
return(this.stamp);
}
}
And here are some pics to explain better,
The initial state always renders correct
This is how it looks when i update the component via props
Additionally when i check the array object, i see size props are set with correct value but somehow it doesn't render properly.
How can i solve this?
This is because your render method just returns this.stamp which is generated only once inside the constructor. Instead of calling buildStamp inside a constructor, just move it inside render method and you should be fine.

Resources