React + d3: Passing onHover function to all grouped elements in an SVG - javascript

I am creating an interactive map using React and d3. I am trying to pass an onHover function to all the grouped elements within my SVG illustration, which I used to set the state to the id of the grouped element. Here is the code:
App.js
import React, { Component } from 'react';
import MyMap from './components/MyMap'
import './App.css'
class App extends Component {
constructor(props){
super(props)
this.onHover = this.onHover.bind(this)
this.state = {
hover : "none"
}
}
onHover(d){
console.log("Hovering over element: " + d.id)
this.setState({
hover: d.id
})
}
render() {
return <MyMap onHover={this.onHover} hover={this.state.hover} />
}
}
export default App;
MyMap.js
import React, { Component } from 'react';
import './map-style.css'
import * as d3 from 'd3'
class MyMap extends Component {
constructor(props){
super(props);
this.prepareSvg = this.prepareSvg.bind(this);
}
prepareSvg(){
const node = this.node;
d3.select(node).select('#the_map').selectAll('g').on('mouseover', this.props.onHover);
}
componentDidMount(){
this.prepareSvg();
}
componentDidUpdate(){
this.prepareSvg();
}
render() {
return (
<div className="text-center">
<svg ref={node => this.node = node} className="svg-container" viewBox="0 0 787 1756">
<g id="the_map">
<g id="place_1">...</g>
<g id="place_2">...</g>
...
</g>
</svg>
</div>
}
}
export default MyMap;
When I go to hover over an element, the console message triggers, but "d" is undefined. How do I pass the id of the group I am hovering over to this callback function?
I've referenced the code found in this example to get this far:
https://github.com/emeeks/d3_in_action_2/tree/master/chapter9/reactd3
I can provide more code or context if needed.

"d" comes from the data chain function if the data is an array (iterable)...
.data(data)
or attribute if not an array (string/object etc)
.attr("d", data)
try something like this...
prepareSvg(){
const node = this.node,
data = "some data"
d3.select(node).select('#the_map').selectAll('g').attr("d", data).on('mouseover', this.props.onHover);
}
That is how "d" gets to everything after in the chain... like hover

Related

How to include styles in React create portal

I have ViewAllGraphs class:
import '../styles/Graph.css'
export class ViewAllGraphs extends React.Component {
constructor(props) {
super(props);
this.state = {
showWindowPortal: false,
}
And render method:
return (
<div>
{
this.state.showWindowPortal && (
<Graph closeWindowPortal={this.closeWindowPortal} >
<h1>Id графика : {this.state.currentId}</h1>
<h1>Название графика : {this.state.currentTitle}</h1>
<img o src={`data:image/png;base64,${this.state.currentImage}`} />
<h1>Данные графика : {this.state.currentData}</h1>
<button className="graph-button-close" onClick={() => this.closeWindowPortal()} >
Закрыть график
</button>
</Graph>
)
}
</div>
My CSS file is located in ../styles/Graph.css
I want to style my graph component, for example, the button. This is code of this component:
import React from "react";
import ReactDOM from 'react-dom'
import '../styles/Graph.css'
class Graph extends React.Component {
constructor(props) {
super(props);
this.state = {
id: 0,
}
this.containerEl = null;
this.externalWindow = null;
}
componentDidMount() {
this.externalWindow = window.open('', '');
this.containerEl = this.externalWindow.document.createElement('div');
this.externalWindow.document.body.appendChild(this.containerEl);
this.externalWindow.document.title = 'A React portal window';
this.externalWindow.addEventListener('beforeunload', () => {
this.props.closeWindowPortal();
});
this.shouldComponentUpdate();
this.setState({
id: 1,
})
}
shouldComponentUpdate() {
return true;
}
componentWillUnmount() {
this.externalWindow.close();
}
render() {
if (!this.containerEl) {
return null;
}
else
return ReactDOM.createPortal(this.props.children, this.containerEl);
}
};
export default Graph
I am trying to include the CSS file and apply className="graph-button-close" in render method to my button, but it's not working. Why can't I import the CSS file to graph class?
You can try these code:
this.containerEl = this.externalWindow.document.createElement('div');
this.containerEl.className = 'image';
this.containerEl.style.backgroundImage = 'url(http://via.placeholder.com/350x150)';
// add the image to its container; add both to the body
// this.containerEl.appendChild(img);
this.externalWindow.document.body.appendChild(this.containerEl);
Or for current elem you can use inline styles in parent component
let styleConfig = { backgroundColor: 'blue' }
In render method:
<p style={styleConfig}>Данные графика : {this.state.currentData}</p>
To style a component functionally, and I hope this works for Class Components as well, is that for the styling part of the top of the file, I import the style as a component, something like this,
import componentStyling from '../styles/Graphs.css`;
A bit of advice is that 99% of the time, I want a style to only apply to that component. It's tremendously hard to think of unique class names every single time I make to add styling to a component, so I rename my CSS files with the following format, classComponentName.module.css, or classComponentName.module.scss, if you're using SCSS.
So, whatever the name of the component you're making is, whether it's functional or a class component, name your CSS files with respect to that and then suffix it with .module.css.
Now, the import looks something like this,
import componentStyling from `../styles/Graphs.module.css`;
Now, in the rendering part of the component, wherever I want to apply a class from Graphs.module.css to an HTML component in the component I have, I simply write,
<htmlElement className={componentStyling.classNameFromTheStylesFile}>
{/* some more JSX here */}
</htmlElement>
Where classNameFromTheStylesFile is a class name that exists within Graphs.module.css, which can be for example,
.classNameFromTheStylesFile {
background-color: blue;
};
I hope I got the question right.
Cheers!

How to Programmatically Provide and Consume Context?

So my question is a simple one. In React js I want to pass some states and handlers from a parent to its 3rd grandchild using Context. I have implemented this within the jsx but I want to use the states within the javascript o that I have some logic before I completely output my states.
I have divided my question into 2 parts. 1.) What I have done so far. 2.) What I want to do essentially.
1.)
// this file just stores the Context
MyContext.js
import React, { Component } from 'react';
export const MyContext = React.createContext();
MyProvider.js // this class is used by the parent and the child to have access to the provider
import React, { Component } from 'react';
import {MyContext} from '../MyContext'
class MyProvider extends Component {
state = {
name: 'Wes',
age: 100,
cool: true
}
render() {
return (
<MyContext.Provider value={{
state: this.state,
growAYearOlder: () => this.setState({
age: this.state.age + 1
})
}}>
{this.props.children}
</MyContext.Provider>
)
}
}
export default MyProvider;
// Ok so now I am basically skipping the parent and showing you the consumer grand-child
Person.js
import React, { Component } from 'react';
// first we will make a new context
import { MyContext } from '../MyContext';
class Person extends Component {
render() {
return (
<div className="person">
<MyContext.Consumer>
{(context) => (
<React.Fragment>
<p>Age: {context.state.age}</p>
<p>Name: {context.state.name}</p>
<button onClick={context.growAYearOlder}>🍰🍥🎂</button>
</React.Fragment>
)}
</MyContext.Consumer>
</div>
)
}
}
export default Person;
2.)
// Ok so as you can see here I have had to immediately use the context.growAYearOlder. What I want to do instead is have control of it using javascript and modify it as desired; So something like this:
Child.js
const parentContext = MyContext.getContext();
if(somethingHappens){
parentContext().growAYearOlder();
}
return(
// The now rendered component
);
I tried something like this but it doesnt work:
MyContext.Consumer.context.growAYearOlder();
There are many similar questions with proper answers, docs, examples and so on - but this question kept popping up for me.
So, in case you want to get the context value and use it within your component's render() just import it (export context itself not only provider) and use _currentValue e.g.
const contextData = MyContext._currentValue;
Note that you still have to wrap your components with your given context provider.
Also note that for function components, you need to use useContext e.g.
const contextData = useContext(MyContext);
And for class components you can assign the context to a static var and then use it e.g.
class Main extends React.Component(){
static contextType = MyContext;
componentDidMount(){
const contextData = this.context;
}
render() {
return (
<p>Hey</p>
);
}
Note that the static var has to be called contextType otherwise this.context won't hold the MyContext data.
I've based my answer solely from the docs itself(https://reactjs.org/docs/context.html#updating-context-from-a-nested-component)
import React, { Component } from 'react';
import { MyContext } from '../MyContext'
class MyProvider extends Component {
constructor(props) {
super(props)
// I've moved the state declaration inside the constructor
this.state = {
name: 'Wes',
age: 100,
cool: true
}
// moved the function here and added prevState
this.growAYearOlder = () => {
this.setState(prevState => ({
age: prevState.age + 1,
}))
};
}
render() {
return (
<MyContext.Provider value={{
state: this.state,
growAYearOlder: this.growAYearOlder,
}}>
{this.props.children}
</MyContext.Provider>
)
}
}
export default MyProvider;

Rendering a canvas object received from props

Good day!
I am new to React and html2canvas. I am making an app which will take "screenshots" of my DOM using html2canvas then store it to an array of screenshots which will then be also rendered on the screen.
I am storing each <canvas> object received from the html2canvas promise to an array then pass it to my ScreenshotsContainer component which passes the array to the Screenshots component. The Screenshots component will then map the array of <canvas> objects to individual Screenshot components.
In App.js, I am calling the html2canvas function then pass the array to ScreenshotsContainer component
import React, { Component } from 'react';
import ScreenshotsContainer from './containers/ScreenshotsContainer/ScreenshotsContainer'
import html2canvas from 'html2canvas';
import './App.css';
class App extends Component {
state = {
canvasArray: []
}
getScreenshotHandler = () => {
console.log("[Canvas Array from state length:]" + this.state.canvasArray.length)
let canvasArray = this.state.canvasArray;
html2canvas(document.body).then((canvas) => {
canvasArray.push(canvas)
});
console.log("[Canvas Object value: ]" + canvasArray);
this.setState({ canvasArray: canvasArray })
}
render() {
return (
<React.Fragment>
<button onClick={this.getScreenshotHandler}>Get Screenshot</button>
<ScreenshotsContainer canvasArray={this.state.canvasArray} />
</React.Fragment>
);
}
}
export default App;
The ScreenshotsContainer component will pass the received array to the Screenshots component:
import React, { Component } from 'react';
import './ScreenshotsContainer.css'
import Screenshots from '../../components/Screenshots/Screenshots';
class ScreenshotsContainer extends Component {
render() {
return (
<div className="ScreenshotsContainer">
<Screenshots canvasArray={this.props.canvasArray} />
</div>
);
}
}
export default ScreenshotsContainer;
The Screenshots component will map the array and pass each canvas object to the Screenshot component:
import React, { Component } from 'react';
import Screenshot from './Screenshot/Screenshot';
class Screenshots extends Component {
render() {
const screenshots = this.props.canvasArray.map(canvas => {
return (
<Screenshot
key={Math.random}
canvasObj={canvas}
/>
)
})
return (
<React.Fragment>
{screenshots}
</React.Fragment>
);
}
}
export default Screenshots;
Here is the Screenshot component
import React from 'react';
import './Screenshot.css';
const screenshot = (props) => (
<div className="Screenshot" >
<canvas ref={props.canvasObj} style={{
width: '10%',
height: '10%'
}} />
</div>
);
export default screenshot;
What I actually get when pressing the button:
Actual screenshot of my result
I was wondering which part went wrong. Any help would be appreciated.
This particular library works in a specific way (looks like it's doing a lot of "magic" under the hood - you should look at the source code here more specifically the renderer folder inside src)
Saving the canvas to the state inside of an array (the correct react way of doing things) will be a problem as it saves it as a complex object with many methods etc... and we can not render objects... This lib was not written with React in mind...
The code sample below is a simple implementation in React...
Here is a live demo: https://codesandbox.io/s/9y24vwn1py
import React, { Component } from 'react';
import html2canvas from 'html2canvas';
class App extends Component {
constructor(props) {
super(props);
this.captureRef = React.createRef();
this.displayRef = React.createRef();
}
getScreenshotHandler = () => {
html2canvas(this.captureRef.current).then(canvas =>
this.displayRef.current.appendChild(canvas),
);
};
render() {
return (
<div>
<div ref={this.captureRef}>
<h2>This enitre div will be captured and added to the screen</h2>
</div>
<button onClick={this.getScreenshotHandler}>Get Screenshot!</button>
<section>
<h5>Your screenshots will be availbale below</h5>
<div ref={this.displayRef} />
</section>
</div>
);
}
}
export default App;
EDIT: based on the comment below here is yet another workaround:
class App extends Component {
constructor(props) {
super(props);
this.state = { canvasArray: [] };
this.captureRef = React.createRef();
}
getScreenshotHandler = () => {
html2canvas(this.captureRef.current).then(canvas =>
this.setState({
canvasArray: [canvas.toDataURL(), ...this.state.canvasArray],
}),
);
};
renderCanvas = () => {
return this.state.canvasArray.map((canvas, i) => {
return <img key={i} src={canvas} alt="screenshot" />;
});
};
render() {
return (
<div className="wrapper">
<div ref={this.captureRef}>
<p>This enitre div will be captured</p>
</div>
<button onClick={this.getScreenshotHandler}>Get Screenshot!</button>
<section>
<h5>Your screenshots will be availbale below:</h5>
{this.renderCanvas()}
</section>
</div>
);
}
}
Link to live demo: https://codesandbox.io/s/1r213057vq

--How to make this React/Redux code DRY

I have repetitive code that I do not know how to make DRY ( Don't Repeat Yourself ).
Here are two components "talking" via dispatch() and React's auto re-render.
this.map is repeated twice.
This module will dispatch actions on a click.
import React from 'react';
import { connect } from 'react-redux';
class Icon extends React.Component {
constructor(props) {
super(props);
this.map = {
paper: 'bg_paper.jpg',
light_wood: 'bg_wood.jpg',
graph: 'bg_graph.jpg'
};
}
flip () {
this.props.dispatch({type: 'updateIcon', bg_key: $A.nextKey(this.map, this.props.state.bg_key)});
}
render () {
const style = {
// ... snip
}
return (
<img id = 'bar_icon' onClick={this.flip.bind(this)} style={style} src='_images/sv_favicon.svg'/>
)
}
}
const mapStateToProps = state => {
return {
state: state.Icon
}
}
export default connect(mapStateToProps)(Icon);
while this component will auto re-render. It all works fine. I just want to make it DRY.
import React from 'react';
import { connect } from 'react-redux';
// ... snip
class FrameBody extends React.Component {
constructor(props) {
super(props);
this.map = {
paper: 'bg_paper.jpg',
light_wood: 'bg_wood.jpg',
graph: 'bg_graph.jpg'
};
}
render () {
const style = {
backgroundImage: 'url(' + '_images/' + this.map[this.props.state.bg_key] + ')'
};
return (
<div id='contents' style={style}>
</div>
)
}
}
const mapStateToProps = state => {
return {
state: state.Icon
}
}
export default connect(mapStateToProps)(FrameBody);
What can I do so that there are not two instances of this.map?
You can extract the logic of this.map out to a class function.
getBackgroundImageKey = () => {
const backgroundMap = {
paper: 'bg_paper.jpg',
light_wood: 'bg_wood.jpg',
graph: 'bg_graph.jpg'
}
return backgroundMap[this.props.bg_key]
}
Take a step further and add another function to return the URL and add string interpolation.
getBackgroundImageURL(){
const backgroundMap = {
paper: 'bg_paper.jpg',
light_wood: 'bg_wood.jpg',
graph: 'bg_graph.jpg'
}
return `url(_images/${backgroundMap[this.props.bg_key]})`;
}
Which will let you define the style like this
const backgroundImage = this.getBackgroundImageURL()
const style = { backgroundImage };
Well since you're already using Redux and dispatching an action to flip, why don't you move that logic there?
Keep the current image in the store so you can get it in connect, make your flip action creator a thunk that holds that "map" and decides what's the next image.
Instead of DRYness, your code lacks separation of concerns. The switch/Icon UI component would be much more reusable and terse if it only called a prop whenever the user clicks "flips". Connect this onFlip to the action creator I mentioned and you have the logic in one place, and the UI to interact in another.

React Higher Order Components to add a custom attribute to the rendered JSX

I'm trying to write a React Higher Order Components to add a custom attribute "test_id" to the view of a wrappedComonent, I need that auto-genrated attribute to do some UI testing later. but I have not find a way to achieve that.
import React, {Component, PropTypes} from "react";
const wrapTestableComponent = (ComponentToWrap) => {
class TestableComponent extends Component {
constructor(props){
super(props);
}
render() {
return <ComponentToWrap {...this.props} test_id={this.props.test_id} />;
}
}
TestableComponent.propTypes = {
test_id: PropTypes.string.isRequired,
}
return TestableComponent
}
export default wrapTestableComponent;
I've also tried the below version but I got that error: Uncaught TypeError: Can't add property test_id, object is not extensible
import React, {Component, PropTypes} from "react";
const wrapTestableComponent = (ComponentToWrap) => {
class TestableComponent extends Component {
constructor(props){
super(props);
}
render() {
var wrappedComponentView = <ComponentToWrap {...this.props} />;
wrappedComponentView.test_id = this.props.test_id;
return <ComponentToWrap {...this.props} />;
}
}
TestableComponent.propTypes = {
test_id: PropTypes.string.isRequired,
}
return TestableComponent
}
export default wrapTestableComponent;
EDIT
According to the comments we discussed below, I misunderstood the question before and I revised my answer.
The way you use in http://pastebin.com/0N9kKF73 should be the best way to do what you want.
I've tried to make a function that returns React.creatElement() to make a copy and assigned the extra props for ComponentToWrap but failed because of two main reasons.
React.creatElement() needs a param type.
ReactJS supported HTML attributes
REF:
Get HTML tag name from React element?
React: Can I add attributes to children's resultant HTML?
The revised version from your pastebin and the internet.
const wrapTestableComponent = (ComponentToWrap) => {
class TestableComponent extends React.Component {
componentDidMount() {
ReactDOM.findDOMNode(this.wrappedRef).setAttribute('test_id', this.props.test_id);
}
render() {
return <ComponentToWrap {...this.props}
ref={() => { this.wrappedRef = this; }}
/>;
}
}
TestableComponent.propTypes = {
test_id: React.PropTypes.string.isRequired,
}
return TestableComponent
}
const TestComp = (props) => (<div>Here is the TestComp</div>)
const NewComponent = wrapTestableComponent(TestComp)
ReactDOM.render(<NewComponent test_id="555" />, document.getElementById('View'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="View"></div>
I suppose this is what are you trying to find. ref may do all magic for you. Actually i dont have any others idea how to add custom component. fiddle
export function SelectWrapper(Select){
return class Wrapper extends Component {
componentDidMount(){
var element = ReactDOM.findDOMNode(this.refs.test);
element.setAttribute('custom-attribute', 'some value');
}
...
render(){
return <Select {...this.props} ref='test'/>
}
}
}

Categories

Resources