ReactDOM.findDOMNode with getAttribute("") returns null - javascript

I am defining tooltip as
<MACDTooltip ref="MACDTooltip" forChart={chartId} forDataSeries={dataSeriesId} key={`${chartId}-${dataSeriesId}`} calculator={macdCalculator}
onClick={logger.bind(null, { chartId, dataSeriesId }, options)} origin={[-48, 15]}/>
but in componentDidMount() method
ReactDOM.findDOMNode(this.refs.MACDTooltip).getAttribute("transform")
return null
ReactDOM.findDOMNode(this.refs.MACDTooltip)
returns
<g><g class="react-stockcharts-toottip" transform="translate(-48, 15)"...</g></g>
and
ReactDOM.findDOMNode(this.refs.MACDTooltip).innerHTML
return "<g class="react-stockcharts-toottip" transform="translate(-48, 15)"></g>
How do i use ReactDOM.findDOMNode to get proper value

Since the g tags are nested, you want to find the childNode of it and get the attribute from there. Example:
class Example extends React.Component {
componentDidMount() {
console.log(
ReactDOM.findDOMNode(this.refs.MACDTooltip).childNodes[0].getAttribute('transform')
);
}
render() {
return(
<MACDTooltip ref="MACDTooltip" />
);
}
}
// Sample MACDTooltip class for demo.
class MACDTooltip extends React.Component {
render() {
return(
<g>
<g transform="Attribute"><text>Tooltip</text></g>
</g>
);
}
}
ReactDOM.render(<Example />, 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>

Related

React.cloneElement not appending className

How can I append or change the className prop with React.cloneElement?
When I use React.cloneElement I'm unable to change or append the className prop. I've searched for hours but I found nothing. React.Children.only or removing the spread don't change the behavior. It appear to be a bug, or a performance optimization feature?.
Expect html: <div class="parent"><div class="child other-class">testing...</div></div>
Result html: <div class="parent"><div class="child">testing...</div></div>
Class example:
class Parent extends React.Component {
render() {
return (
<div className={"parent"}>
{React.cloneElement(React.Children.only(this.props.children), {
...this.props.children.props,
className: `${this.props.children.props.className} other-class`,
})}
</div>
);
}
}
class Child extends React.Component {
render() {
return <div className={"child"}>{"testing..."}</div>;
}
}
Functional component example:
const Parent = ({ children }) => (
<div className={"parent"}>
{React.cloneElement(React.Children.only(children), {
...children.props,
className: `${children.props.className} other-class`,
})}
</div>
);
const Child = () => <div className={"child"}>{"testing..."}</div>;
const Parent = ({ children }) => (
<div className={"parent"}>
{React.cloneElement(React.Children.only(children), {
...children.props,
className: `${children.props.className} other-class`,
})}
</div>
);
const Child = () => <div className={"child"}>{"testing2..."}</div>;
ReactDOM.render(
<React.StrictMode>
<Parent>
<Child />
</Parent>
</React.StrictMode>,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
The problem is that you're never using className in Child, which is what you're manipulating in Parent. Child puts a className on a div, but that isn't Child's className, it's just a hardcoded one that Child puts on the div.
If you want Child to put that class on the div, you have to write the code to do that. Also, you don't need the spread, the props are merged. Finally, to get the original className, I'd use the result of calling Children.only, rather than going back to this.props.children (though that will work because only would throw if there weren't only one).
See comments:
class Parent extends React.Component {
render() {
// Get the `className` from the child after verifying there's only one
const child = React.Children.only(this.props.children);
const className = `${child.props.className} other-class`;
return (
<div className={"parent"}>
{React.cloneElement(child, {
// No need to spread previous props here
className,
})}
</div>
);
}
}
class Child extends React.Component {
render() {
// Use `className` from `Child`'s props
const className = (this.props.className || "") + " child";
return <div className={className}>{"testing..."}</div>;
}
}
// Note the `classname` on `Child`, to show that your code using
// `this.props.children.props.className`
ReactDOM.render(<Parent><Child className="original"/></Parent>, document.getElementById("root"));
.child {
color: red;
}
.child.other-class {
color: green;
}
.original {
font-style: italic;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>

React HOC with multiple components

I want to create a React HOC that would ideally receive two components instead of one wrapped component and toggle between them. That is, in the code below, instead of <h3>component one</h3> and <h3>component two<h3>, they would each represent child components. How would I be able to accomplish this? Some psuedo code for how I would write this HOC:
<HOC>
<ComponentOne />
<ComponentTwo />
</HOC>
<HOC
componentOne={<ComponentOne />}
componentTwo={<ComponentTwo />}
/>
hoc(componentOne, componentTwo)
class HOC extends React.Component {
constructor() {
super();
this.state = {
onClick: false,
};
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({onClick: !this.state.onClick});
}
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me!</button>
{
this.state.onClick ?
<h3>component one</h3> :
<h3>component two</h3>
}
</div>
);
}
}
ReactDOM.render(<HOC />, app);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></div>
I am not sure if I understood you. Why do you need it to be HOC?
If you would pass components as props like that:
<HOC
componentOne={<ComponentOne />}
componentTwo={<ComponentTwo />}
/>
Then you would be able to access them using props.
render() {
return (
<div>
<button onClick={this.handleClick}>Click Me!</button>
{
this.state.onClick ?
this.props.componentOne :
this.props.componentTwo
}
</div>
);
}
If a component has more than one child then this.props.children will be an array.
class HOC extends React.Component {
// ... rest of code ....
render() {
const { onClick } = this.state;
const { children } = this.props;
return !onClick ? children[0] : children[1];
}
}
Then use it like so:
<HOC>
<div>Child One</div>
<div>Child Two</div>
</HOC>
Obviously this will only work with two children but you could extend it by passing an integer to <HOC> through props to tell it what child to select.
Edit
After a quick look at the docs this is a better version of what I wrote above as this.props.children is not an array, it is an opaque data structure:
class HOC extends React.Component {
// ... rest of code ...
render() {
const { onClick } = this.state;
const children = React.Children.toArray(this.props.children);
return !onClick ? children[0] : children[1];
}
}

How to connect a button event to another React component?

I'm learning React and I'm trying to make a simple application: you click on a button and it increments a counter. I've prepared two components, ClickCounter and ClickButton, but I'm not sure how to connect them together. I've read different tutorials but they expect my components to be Parent/Child - is there something I'm completely missing from a architectural perspective?
class ClickCounter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
render() {
return <h1>{this.state.count}</h1>;
}
}
function ClickButton(props) {
function handleClick(e) {
e.preventDefault();
console.log("clicked");
// increment the ClickCounter..how?
}
return (
<button id="btn" onClick={handleClick}>Click me</button>
);
}
function Container() {
return (
<div>
<ClickCounter />
<ClickButton />
</div>
);
}
const root = document.getElementById("root");
ReactDOM.render(<Container />, root);
A common technique for when two sibling components need to share some state is to lift the state up to the first common ancestor (Container in this case) and pass down the state and state-altering functions as props to the children.
Example
function ClickCounter(props) {
return <h1>{props.count}</h1>;
}
function ClickButton(props) {
return (
<button id="btn" onClick={props.handleClick}>Click me</button>
);
}
class Container extends React.Component {
state = { count: 0 };
onClick = () => {
this.setState(prevState => {
return { count: prevState.count + 1 };
});
};
render() {
return (
<div>
<ClickCounter count={this.state.count} />
<ClickButton handleClick={this.onClick} />
</div>
);
}
}
ReactDOM.render(<Container />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>

How does this React code translate to class representation?

I'm trying to understand how the code below, which is from Redux examples TODOMVC, can be written using the class notation.
The code is
const App = ({todos, actions}) => (
<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>
I tried the following but it doesn't work, I get Warning: App(...): When calling super() inApp, make sure to pass up the same props that your component's constructor was passed.
class App extends React.Component {
constructor({todos, actions}) {
super({todos, actions});
this.todos = todos;
this.actions = actions;
}
render() {
return(
<div>
<Header addTodo={this.actions.addTodo} />
<MainSection todos={this.todos} actions={this.actions} />
</div>
)
}
}
Whatever is passed to App is props. And ({ todos, actions }) is just destructuring from props. This should work:
class App extends React.Component {
render() {
const { todos, actions } = this.props;
return(
<div>
<Header addTodo={actions.addTodo} />
<MainSection todos={todos} actions={actions} />
</div>
)
}
}
By setting this.todo = todos in constructor, you're setting an instance level property. Which means if the props changes later, Header and MainSection will not be updated.
You can simply do what React asks, pass the whole props to the superclass and get out the properties you want explicitly
class App extends React.Component {
constructor(props) {
super(props);
this.todos = props.todos;
this.actions = props.actions;
}
render() {
return(
<div>
<h1>Actions: {this.actions}</h1>
{/*<Header addTodo={this.actions.addTodo} />
<MainSection todos={this.todos} actions={this.actions} />*/}
</div>
)
}
}
ReactDOM.render(<App todos={[]} actions={'some action'} />, document.getElementById('app'))
<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="app"></div>

Wrapping a component with itself in React?

Let's say I have a component named Match (renders span) and a string like Matched text to be wrapped with that component.
I'm expecting a final result like this:
<Match>Matched <Match>text</Match></Match>
On first wrap, my source text turns into a component like this:
<Match>Matched text</Match>
With this, is it possible wrap the inside of it (the text part) with another Match component? What should be the approach here?
Thanks.
What you need is ofcourse possible, each instance of the Match is different and whatever you write between Match tags are the children of the match component. So you just need
class Match extends React.Component {
render() {
return (
<span>{this.props.children}</span>
)
}
}
class App extends React.Component {
render() {
return (
<Match>Matched <Match>text</Match></Match>
);
}
}
ReactDOM.render(<App />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="app"></div>
It should work just right. Is it what you are looking for?
class Match extends React.Component {
render() {
if (this.props.children === 'text') {
return (
<span style={{ backgroundColor: 'yellow' }}>{this.props.children}</span>
)
} else {
return (
<span style={{ color: 'blue' }}>{this.props.children}</span>
)
}
}
}
class Test extends React.Component {
matchString(string) {
/* you can work with the raw string here if you want */
return string.split(' ')
.map(word => <Match>{word}</Match>);
/* inside the map you can do whatever you want here, maybe conditional wrapping, like if(word === 'text') <Match>word</Match> else word ... */
}
render() {
return (
<div>
{this.matchString('Matched text')}
</div>
);
}
}
ReactDOM.render(<Test />, document.getElementById('root'));
<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>
<body id="root">
</body>

Categories