How to select sibling element with React? - javascript

React Component
import React, {useState} from 'react';
import './CounterButton.css';
const CounterButton = (props)=>{
const [currentCount, setCurrentCount] = useState(0);
const handleClick = (event)=>{
if(currentCount == 9){
event.target.classList.toggle('bound-hit');
}
setCurrentCount(currentCount+props.incrementVal);
};
return (
<div class="count-container">
<button onClick={handleClick}>+{props.incrementVal}</button>
<p>{currentCount}</p>
</div>
);
};
export default CounterButton;
External stylesheet for this component
.count-container {
display: flex;
padding: 10px;
}
.count-container > button {
width: 50px;
margin: 0 10px;
}
.bound-hit {
color: red;
}
I have a react component and stylesheet for that component. In this case it toggle class bound-hit to the classList of button. I could select button using event.target and but I want to toggle this class to the <p></p> tag inside my div. My question is how can I select that p tag using event. p tag is like a sibling of button. div with class count-container is parent. I can also select parent div by event.target.parent but I want to select p tag and toggle class bound-hit to that.. How can I do that?

I don't think you need a React specific answer here.
In vanilla JS you can use the nextElementSibling method.
const handleClick = (event) => {
const p = event.target.nextElementSibling
}
Or instead you can do it in CSS with the adjacent sibling combinator.
.bound-hit + p {
// apply styles to the <p> that's just after .bound-hit in the DOM
}
However, if you "manually" add a class in a react component (meaning that this class gets added to the DOM without any representation in the state), some virtual DOM reconciliations might end up removing it.
In a lot of cases, this won't be a problem, but if it is, then you should use a state for it. Here's a simplified example of what that would look like:
const [pClass, setPClass] = useState('')
const handleClick = () => {
setPClass('bound-hit')
}
return (
<p className={pClass} />
)

The question shouldn't be "how to select a sibling" but "how to assign CSS class to the P element on [condition]".
If a React component directly has ownership over the (child) elements you can simple change the components state and apply it to the class list of the element using className.
Doing any DOM manipulation/traversing within a component is mainly bad form using React and overcomplicates the solution.
const CounterButton = (props)=>{
const [currentCount, setCurrentCount] = useState(0);
const [currentClass, setCurrentClass] = useState();
const handleClick = (event)=>{
if(currentCount == 9){
setCurrentClass('bound-hit');
}
setCurrentCount(currentCount+props.incrementVal);
};
return (
<div class="count-container">
<button onClick={handleClick}>+{props.incrementVal}</button>
<p className={currentClass}>{currentCount}</p>
</div>
);
};

Related

Rendering a div element inside existing div during page rendering

I'm stuck at rendering a component insider a div. My code is like below, I want to render the div with classname RequestNewOrgBtn inside the parentDiv. How can I do it at runtime of render?
const labelItem = Array.from(document.querySelectorAll('label'))
.find(el => el.textContent === 'External Org Name');
const parentDiv = labelItem?.parentNode?.parentNode?.parentNode;
return(
<>
{
//render below div inside parentDiv
<div className="RequestNewOrgBtn">
<Button disabled={false} onClick={onAddClick}>
{ "Request New External org" }
</Button>
<RequestExternalOrgModal
isOpen={ShowReqExOrgModal}
onClose={onReqExOrgModalClose} />
</div>
}
</>
);
Probably the easiest way to do this would be to render the appropriate parts of your React app into the element in question, similar to how you would render <App /> into your root element.
For example...
import { createRoot } from "react-dom/client";
import RequestNewOrgBtn from "./RequestNewOrgBtn"; // your component
const labelItem = Array.from(document.querySelectorAll("label")).find(
(el) => el.textContent === "External Org Name"
);
const parentDiv = labelItem?.parentNode?.parentNode?.parentNode;
if (parentDiv) {
// create a new root for your button component
const btnRootElement = document.createElement("div");
// append it into the found `parentDiv`
parentDiv.appendChild(btnRootElement);
const btnRoot = createRoot(btnRootElement);
btnRoot.render(<RequestNewOrgBtn />);
}
For React 16, you just use the ReactDOM equivalent
import ReactDOM from "react-dom";
// ...
ReactDOM.render(<RequestNewOrgBtn />, btnRootElement);
This is a little ambiguous. But, if I am understanding correctly, I think you can set the innerHTML to append the div
parentDiv.innerHTML += "<div>Your div content here</div>"
better yet, set your div as a variable and then just += the variable for cleaner code.
const myDiv = "<div>Your div content here</div>"
parentDiv.innerHTML += myDiv
Used this to make sure I was remembering correctly

Add onclick or eventListener in insertAdjacentHTML in react-js

I am building a simple react app for learning purpose, I just started learning react-js, I was trying to add paragraph dynamically on user action and it worked perfectly But I want to add an onClick event in insertAdjacentHTML (basically innerHTML).
But onclick event is not working in innerHTML
app.js
const addParagraph = () => {
var paragraphSpace = document.getElementById('container')
paragraphSpace.insertAdjacentHTML('beforeend', `<p>I am dynamically created paragraph for showing purpose<p> <span id="delete-para" onClick={deleteParagraph(this)}>Delete</span>`
}
const deleteParagraph = (e) => {
document.querySelector(e).parent('div').remove();
}
class App extends React.Component {
render() {
return (
<div>
<div onClick={addParagraph}>
Click here to Add Paragraph
</div>
<div id="container"></div>
</div>
)
}
}
What I am trying to do ?
User will be able to add multiple paragraphs and I am trying to add a delete button on every paragraph so user can delete particular paragraph
I have also tried with eventListener like :-
const deleteParagraph = () => {
document.querySelector('#delete').addEventListener("click", "#delete",
function(e) {
e.preventDefault();
document.querySelector(this).parent('div').remove();
})
}
But It said
deleteParagraph is not defined
I also tried to wrap deleteParagraph in componentDidMount() But it removes everything from the window.
Any help would be much Appreciated. Thank You.
Do not manipulate the DOM directly, let React handle DOM changes instead. Here's one way to implement it properly.
class App extends React.Component {
state = { paragraphs: [] };
addParagraph = () => {
// do not mutate the state directly, make a clone
const newParagraphs = this.state.paragraphs.slice(0);
// and mutate the clone, add a new paragraph
newParagraphs.push('I am dynamically created paragraph for showing purpose');
// then update the paragraphs in the state
this.setState({ paragraphs: newParagraphs });
};
deleteParagraph = (index) => () => {
// do not mutate the state directly, make a clone
const newParagraphs = this.state.paragraphs.slice(0);
// and mutate the clone, delete the current paragraph
newParagraphs.splice(index, 1);
// then update the paragraphs in the state
this.setState({ paragraphs: newParagraphs });
};
render() {
return (
<div>
<div onClick={this.addParagraph}>Click here to Add Paragraph</div>
<div id="container">
{this.state.paragraphs.map((paragraph, index) => (
<>
<p>{paragraph}</p>
<span onClick={this.deleteParagraph(index)}>Delete</span>
</>
))}
</div>
</div>
);
}
}
insertAdjecentHTML should not be used in javascripts frameworks because they work on entirely different paradigm. React components are rerendered every time you change a component state.
So you want to manipulate look of your component by changing its state
Solution:
In constructor initialize your component's state which you will change later on button click. Initial state is array of empty paragraphs.
constructor() {
super()
this.state = {
paragraphs:[]
}
}
And alter that state on button click - like this:
<div onClick={addParagraph}>
Add Paragraph function
const addParagraph = () =>{
this.state = this.state.push('New paragraph')
}
Rendering paragraphs
<div id="container">
this.state.paragraphs.map(paragraph =>{
<p>{paragraph}</p>
})
</div>
Additional tip for ReactJS in 2022 - use Functional components instead of Class components

How can I add styling to an element in React?

In normal JavaScript you can grab an element by its id and add a style to it.
For example:
var element = document.getElementById("myElement");
element.style.backgroundColor = "#f5f5f5";
My question is how you can do this in react. Is it even possible to add this style?
In react im using onChange in a function outside the render(). I looked at the React DOM for styling and tried but since styling is in different function it will tell me how the variable is undefined.
this is my code:
ChangeImage() {
var imgStyles = {
backgroundColor: '#000',
padding: 5,
}
}
render() {
return (
<div className="class">
<div className="img-surround">
<img
src={this.state.file}
id="img"
style={imgStyles}/>
</div>
Everything is working except styles and I even tried putting in different functions
If you want to render the element with the style you can return the element like this in a react functional component:
return <div style={{backgroundColor: "#f5f5f5"}}></div>
If you want the element to only have that style in a certain condition you can use the useState hook in a react functional component:
const [myState, setMyState] = useState(false);
return <div style={myState && {backgroundColor: "f5f5f5"}}></div>
And you should change myState's value using setMyState however you like. For example:
const [myState, setMyState] = useState(false);
return <div onClick={() => myState ? setMyState(true) : setMyState(false)} style={myState && {backgroundColor: "f5f5f5"}}></div>
In this example whenever you click on the div the style is added or removed by case

React add html attribute to external component

I am using a component that I cannot change directly, but I would like to extend.
import { Button } from '#external-library'
// Currently how the button component is being used
<Button click={() => doSomething()} />
// I would like to add a tabIndex to the button
<Button click={() => doSomething()} tabIndex={0} />
I cannot add an attribute because the component is not expecting a tabIndex. I cannot directly modify the Button component.
How can I extend the <Button /> component so I can add attributes like tabIndex, etc?
I was hoping something like the following would work:
export default class ExtendedButton extends Button { }
// except I'm dealing with functional components
You can't edit custom component implementation without changing its internals.
// You can't add tabIndex to internal button without changing its implementation
const Button = () => <button>Click</button>;
In such cases, you implement a wrapper with desired props:
const Component = () => {
return (
<div tabIndex={0}>
<Button />
</div>
);
};
If the component forwarding ref (also depends to which element it forwarded in the implementation), you can use its attributes:
// Assumption that Button component forwards ref
const Button = React.forwardRef((props,ref) => <button ref={ref}>Click</button>);
<Button ref={myRef}/>
// Usage
myRef.current.tabIndex = 0;
You can access the inner DOM button element using React refs(read here)
most likely the external-lib you use provide a ref prop for the Button component which you use to pass your own create ref
const buttonRef = useRef(null);
<Button ref={buttonRef}/>
Then you can use buttonRef.current to add tabIndex when your data is ready to be populated in like
useEffect( () => {
if(buttonRef && buttonRef.current){
buttonRef.current.tabIndex = 2;
}
}, [props.someProperty] );

How to change text in a <p> tag when hovering a button in ReactJs?

I'm trying to change the text of a <p> to David and Larry accordingly when each button (that has an image inside) has hovered. I have experimented with numerous things and found a way to change the CSS of the button with a function. But I was unable to find anything to change text since <p> is in a different class. Any suggestions to address this problem?
For your information, I have added a CSS color changing function I used earlier to the below code sample.
here's my code.
import React from 'react';
import "./Tri.css";
function Tri() {
function davidon(e) {
e.target.style.background = 'red';
}
function davidoff(e) {
e.target.style.background = 'green';
}
function larryon(e) {
e.target.style.background = 'red';
}
function larryoff(e) {
e.target.style.background = 'green';
}
return (
<div>
<div>
<div>
<button onMouseOver={davidon} onMouseLeave={davidoff}>
<img src={require(`./images/david.png`)} className="david"/>
</button>
<button onMouseOver={larryon} onMouseLeave={larryoff}>
<img src={require(`./images/larry.png`)} className="larry"/>
</button>
</div>
<div className="plex">
<p>Larry Or David?</p>
</div>
</div>
</div>
);
}
export default Tri;
Thanks in advance for you replies.
You need to think more in "React", and use component state and props. The offical documentation is a good place to start.
Here I've got two components.
1) Tri: which has it's own state, and builds the HTML using Button components
2) Button: since you need each button to change color depending on the mouse action it's best to separate that functionality out into a new component so that each instance can have its own state.
(I've intentionally left out the images in this example, but you could pass in a src prop to the button and have that handle the images too if you wanted.)
const { useState } = React;
// `Button` accepts a props object
// Here I've destructured out the button name,
// and the handleHover function
function Button({ name, handleHover }) {
// We initialise the state with "green"
const [ color, setColor ] = useState('green');
function handleColor() {
// We set the new color based on the current color
setColor(color => color === 'red' ? 'green' : 'red');
// And then call the `handleHover` function, passing in `name`
handleHover(name);
}
return (
<button
className={color}
onMouseOver={handleColor}
onMouseLeave={handleColor}
>
{name}
</button>
);
}
function Tri() {
// In `Tri` we set its own state for the name
// initialised to an empty string
const [ name, setName ] = useState('');
// A handler that changes the name
// This is the function we pass to each button
function handleHover(name) {
setName(name);
}
// Set up two buttons using our Button component
// assigning a name to each, and passing in our handler
// Whenever the name (state) is changed the name in the
// paragraph also changes
return (
<div>
<div>
<Button name="Larry" handleHover={handleHover} />
<Button name="David" handleHover={handleHover} />
</div>
<p>{name}</p>
</div>
);
}
ReactDOM.render(
<Tri />,
document.getElementById('react')
);
.red { background-color: red; }
.green { background-color: green; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="react"></div>
Try using states. And don't change DOM-nodes dynamically in event handlers. Always use React functionality:
React uses a declarative form of programming (The Virtual DOM specifically). You define variables and set them and React updates the DOM if those change.
useState gives you the opportunity to declare an editable (through a setter function) variable. See Docs on State and Props.
import React from 'react';
import "./Tri.css";
function Tri(props) {
// props as immutable arguments (if needed)
// useState keeps an internal state in the component
let initialTxt = 'Larry Or David?';
const [text, setText] = React.useState(initialTxt);
return (
<div>
<div>
<div>
<button
className="david-btn"
onMouseOver={() => setText('David')}
onMouseLeave={() => setText(initialTxt)}>
<img src={require(`./images/david.png`)} className="david"/>
</button>
<button
className="larry-btn"
onMouseOver={() => setText('Larry')}
onMouseLeave={() => setText(initialTxt)}>>
<img src={require(`./images/larry.png`)} className="larry"/>
</button>
</div>
<div className="plex">
<p>{text}</p>
</div>
</div>
</div>
);
}
Also, extend ./Tri.css with the following code. You could use a style-variable but that would make your code more bloated and unreadable if you have access to CSS.
.david-btn,
.larry-btn {
background-color: green;
}
.david-btn:hover,
.larry-btn:hover {
background-color: red;
}
You are looking for Refs. You can read more about them in documentation.
I've created a simple example (based on your code).
Step by step what I did:
import useRef hook which is used to create reference.
import React, { useRef } from "react";
created reference:
const pTagRef = useRef();
passed reference to your p tag
<div ref={pTagRef} className="plex">
<p>Larry Or David?</p>
</div>
created function which can change the content of this reference where pTagRef.current is DOM element.
function setName(name) {
if (pTagRef.current) {
pTagRef.current.innerText = name;
}
}
called the function whenever name changed
setName("larry");
You should definitely use state for this but I hope this one helps you to get started.

Categories

Resources