Duplicating same helper functions over and over in D3 React Component - javascript

I am working on a d3 react component, and am running into this issue where I have to re-define my constants and helper functions over and over again. I have the following, general layout for my d3 component:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
chartType: 'A'
}
}
getHitData() {...}
drawChartA() {...}
drawChartB() {...}
drawChartC() {...}
drawChartD() {...}
drawChartE() {...}
toggleButton() {...}
componentDidMount() {
this.drawChartA()
}
componentDidUpdate() {
const { chartType } = this.state;
if(chartType == "A") {
this.drawChartA()
}
else if(..B) {...}
else if(..C) {...}
...
}
render() {
return (
<div>
<svg className='chart'>
<g className='chartA' />
<g className='chartB' />
<g className='chartC' />
<g className='chartD' />
</svg>
</div>
)
}
}
In my actual code, in each of my different drawChartA(), drawChartB(), etc. functions, I am finding myself having to redefine both constants (chartWidth, chartHeight, padding, margin), as well as certain important d3 helper functions (colorScales, xScale, yScale, radiusScale, etc.), and this doesn't feel right. In particular, I am breaking the DRY principle.
Should chartWidth / padding / etc. constants be defined in this.state? And what should I do with all of my helper d3 functions? I don't think these should be parameters to the drawChart() functions.
Any thoughts on this would be greatly appreciated, thanks!

Should chartWidth / padding / etc. constants be defined in this.state?
No, constants should never be part of the state. Only data that changes how your component is rendered or behaves when the data is changed should be part of the state.
You can store constants as class properties and initialize them in the constructor.
Similarly you can define helper functions on the class.
constructor(props) {
super(props);
this.state = {
chartType: 'A'
}
this.chartProperties = {
width: 25,
height: 30,
margin: 35
};
}
colorScales() { ... }
drawChartA() {
const colorScales = this.colorScales();
return <chart width={ this.chartProperties.width } />
}

Related

React Component Inheritance to use parent method and child method

Background
I created a fully functional component. The component has state and props, and there are many methods inside. My component should work differently according to the os(ios / android). So I solved this problem by if statement like below.
if( platform.os == 'ios') { ... } else { ... }
The problem was that as the code volume increased, there was a problem with readability, and I decided to make a separate component for IOS and for Android. The first thing that came to mind was inheritance because ES6 and Typescript Support Class. The picture of concept is this.
However, React does not recommend inheritance. So I was just going to hand over the functions overridden by props to the Speech component in the SpeechIOS component's render function.
The code is as follows.
Speech.tsx
type Props = {
team: number,
onSpeechResults: (result: string) => void
...
}
type States = {
active: boolean;
error: string;
result: string;
...
};
export default class Speech extends Component<Props,States> {
state = { ... };
constructor(props: Props) {
super(props);
...
}
// render
render() {
...
return (
<ImageBackground source={require("../images/default-background.jpeg")} style={styles.full}>
...
</ImageBackground>
);
}
sendCommand = (code: number, speed: number, callback?: () => void) => { ... }
getMatchedSpell = (spellWord: string): { code: number, speed: number } => { ... }
onSpeechResults(e: Voice.Results) { ... };
...
}
SpeechIOS.tsx
import Speech from './Speech';
type Props = {}
type States = {}
export default class SpeechIOS extends Component<Props,States> {
constructor(props: Props) {
super(props);
...
}
// render
render() {
...
return ( <Speech team="1" onSpeechResults={this.onSpeechResults}></Speech> );
}
sayHello() {
console.log("Hello!!");
}
// I want that Speech Component call this onSpeechResults function
onSpeechResults(result: string) {
this.setState({...});
let temp = this.getMatchedSpell( ... ); // which is in Speech Component
this.sendCommand( 10, 100 ... ); // which is in Speech Component
this.sayHello(); // which is in SpeechIOS component only
... other things..
};
}
Problem.
As you can see, the onSpeechResults which is in SpeechIOS Component use some functions in Speech Component and in SpeechIOS Component also.
So, How to solve this problem? Should I use Inheritance?
Alternatively, break out any common logic into a utility function like SpeechUtil.ts, a whole new file that holds this shared logic and each util function, and exports them. Then, each component separately imports them. That ensures that if you ever update that logic it affects both components
You should define a top level component that defines the shared props and methods, and use those to render either your ios or android component. pass the props and methods to the children. This is composition which is favored over inheritance in react. example:
class Speech extends React.Component {
state ={shared: 'state'}
sharedMethod = () => {
this.setState({blah: 'blah})
}
render() {
const Root = Platform.select({
ios: SpeechIOS,
android: SpeechAndroid
})
return <Root onClick={this.sharedMethod} shared={this.state.shared}/>
}
}
You can use React.createRef for this purpose.
Below is an example code.
import Speech from './Speech';
type Props = {}
type States = {}
export default class SpeechIOS extends Component<Props, States> {
constructor(props: Props) {
super(props);
this.speech = React.createRef();
}
// render
render() {
...
return (<Speech ref={this.speech} team="1" onSpeechResults={this.onSpeechResults}></Speech>);
}
sayHello() {
console.log("Hello!!");
}
// I want that Speech Component call this onSpeechResults function
onSpeechResults(result: string) {
this.setState({ ...});
let temp = this.speech.current.getMatchedSpell(... ); // which is in Speech Component
this.sendCommand(10, 100 ... ); // which is in Speech Component
this.sayHello(); // which is in SpeechIOS component only
...other things..
};
}

The pros/cons of React class property initializers

With React, I know you can initialize component state like this:
class Foo extends Component {
constructor() {
super();
this.state = { count: 0 };
}
}
And if state needs to be initialized with props:
class Foo extends Component {
constructor(props) {
super(props);
this.state = { this.props.count: 0 };
}
}
However, using the transform-class-properties plugin, you can initialize state like so:
class Foo extends Component {
state = { count: 0 };
}
Since this refers to the instance under class during construction, initial state can still use props: state = { this.props.count: 0 }
Besides the obvious benefit of less lines, I wanted to know what are some pros/cons of this syntax.
*Examples don't include binding of class methods as I know binding can be done when declaring those methods with fat arrow syntax.
Pros:
less code, more implicit
Con:
you can't do any step-by-step calculations like with constructor
e.g
constructor(props){
super(props)
const z = props.x - props.y;
const g = props.a + props.b;
const total = z ** g;
const shouldBeOpened = total > 1000;
this.state = {
shouldBeOpened,
initialSomething: z > g,
}
}

React Component with shared mouseOver handler

I have a simple react component that wraps the SVG 1.1 specifications recttag and adds in some defaults and looks like this:
import React, { Component } from 'react';
const defaults = {
x: 100,
y: 100,
width: 200,
height: 100,
rx: 0,
ry: 0,
};
export default class Rectangle extends Component {
constructor(props) {
super(props);
this.state = {
...defaults,
...props,
};
}
render() {
return (
<rect
{...this.state}>
{this.props.children}
</rect>
);
}
}
I am trying to find a good way to add on a mouseOver function, but would like to do it in a way that can be reused with other components similar to this.
I have looked into Higher Order Components, but am having trouble when I try to set the state, because the state is not part of the above component's state.

React. How to collect and transfer the collection of nodes as props to children

So, I try to collect the nodes after that the page are builded in the module iteamsHolder. It works for main App component and it see all collected iteams when I invoke the module iteamsHolder inside it.
But when I try to transfer the iteamsHolder module with iteams to children componennts of App, I have an error or undefined for this module iteams. So, I understand that the problem is in the component queue render. But how we can solve that error?
/* MAIN APP COMPONENT */
import iteamsHolder from '../view/iteamsHolder'
import sidebarWidgetHide from '../view/sidebarWidgetHide'
class App extends React.Component {
constructor(props) {
super(props);
this.handleKeyCode = this.handleKeyCode.bind(this);
this.handleClick = this.handleClick.bind(this);
}
render() {
return (
<Fragment>
<Preloader/>
<LeftSideInfo state={this.state.toggle} updateState={this.updateState}
coordY={this.state.coordY}/>
<MenuButtonOpen state={this.state.toggle} updateState={this.updateState}
iteamsHolder={iteamsHolder().iteamsMain}/> // this is where I'm
// trying to transfer the iteams module.
</Fragment>
)
}
/* ITEAM HOLDER MODULE */
const iteamsHolder = () => {
if (document.readyState === "complete") {
let iteamsMain = {
sidebar: document.querySelector('.left-side__column'),
rightColumn: document.querySelector('.right-side__column')
};
let iteamsSupport = {
header: document.querySelectorAll('.menu-button__container'),
menuButton: document.querySelectorAll('.menu-button'),
menuName: document.querySelector('.personal-name'),
experienceblock: document.querySelector('.block-headline.block-headline__experience')
};
return { iteamsMain, iteamsSupport };
} else {
return 'Sorry';
}
};
export default iteamsHolder;
/CHILD COMPONENT WITH NESTED ITEAMS MODULE/
class MenuButtonOpen extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.handleScroll = this.handleScroll.bind(this);
}
render() {
return (
{console.log(this.props.iteamsHolder)} // undefined of error
)
}
You're getting undefined because iteamsHolder is returning the default value 'Sorry', not the object { iteamsMain, iteamsSupport }. If you change is to:
<MenuButtonOpen
state={this.state.toggle}
updateState={this.updateState}
iteamsHolder={iteamsHolder()}
/>
you'll see that 'Sorry' is being passed to the component. This is because when iteamsHolder is being evaluated, the webpage is not yet fully loaded. Without knowing why you've structured your code the way you did, I can't make a good suggestion on how to "fix" it. What may help is looking at how document.readyState works and reading through suggestions like what's listed here.

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

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

Categories

Resources