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] );
Related
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
I am making a calculator in react in which i made buttons for numbers and when button "7" is pressed then in the input field 7 is added.
My approach:
I am using useState to do this.
I made an arrow function funinpval which takes takes number as string in argument then i am using this function with different buttons onclick handler by passing respective numbers as arguments. But I am getting error
import React from 'react'
import { useState } from 'react';
export const Calculator = () => {
const [inpval, setInpval] = useState("")
const funinpval = (num) => {
setInpval(inpval + num)
}
return(
<>
<input type="text" value={inpval}>
<button onClick={funinpval("7")}>7</button>
<button onClick={funinpval("8")}>8</button>
</>
)
Can anyone please help
<button onClick={funinpval("7")}>7</button>
<button onClick={funinpval("8")}>8</button>
You are not waiting the user to click the buttons to execute the functions, they are instead executed every render phase, directly. Which mean that the component render -> state update -> new re-render -> new state update -> ...
To fix it:
<button onClick={() => funinpval("7")}>7</button>
<button onClick={() => funinpval("8")}>8</button>
There is a syntax error in how you are providing the event handlers.
You have to provide event handlers sonething like:
<button onClick={() => funinpval("7")}>7</button>
<button onClick={() => funinpval("8")}>8</button>
Simply writing onClick={funinpval("7")} will immediately call the function while rendering which sets the state. When state got updated then the component re-renders. Then again while re-rendering, this function got called and so on.
onClick={funinpval("7")}
will return the result of calling that function to the listener rather than a reference to the function that the listener can call. So you're setting state immediately with those two buttons which is causing the render which is calling the function again which is setting the state again... infinity!
In this example I pick up the textContent of the button and use that to set the new input state, and then you can simply just pass the reference to the function to the handler and let the function deal with how state is set.
const { useState, useEffect } = React;
function Calulator() {
const [inpval, setInpval] = useState(0);
function funinpval(e) {
// Grab the `textContent` of the button and
// relabel it to `num` making sure to coerce the
// text to a number first
const { textContent: num } = e.target;
setInpval(inpval + Number(num));
}
return(
<div>
<input type="text" value={inpval} />
<button onClick={funinpval}>7</button>
<button onClick={funinpval}>8</button>
</div>
)
};
// Render it
ReactDOM.render(
<Calulator />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>
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.
I am using Preact with hooks. I have following button component:
export function Button(props) {
return (
<button class={props.class}>{props.children}</button>
);
}
I have another parent component where I need to access actual DOM element button for animation purpose.
export function Parent(props) {
const buttonElm = useRef(null);
useEffect(() => {
console.log(buttonElm.current);
// Animate button using popmotion or similar
});
return (
<div>
<Button ref={buttonElm}>Click me to animate</Button>
</div>
);
}
However, there is a problem. The buttonElm.current points to JSX object i.e. Button but not the DOM element button. I need buttonElm to point to actual DOM element. How do I do that?
Should I go ahead and use buttonElm.current.base property? But that does not feel idiomatic with hooks.
Also, I have two questions.
How does ref behave when I am setting it on a Preact component that returns multiple elements using <Fragment />.
Second, is accessing the children's DOM element for animation purpose acceptable/correct practice in Preact/React? (I can wrap my component in another wrapper div but that causes more animation headaches than solving the problem)
You need to pass ref as props to your child component. By doing this buttonElm will point to actual Button DOM element.
export function Button(props) {
return (
<button class={props.class} ref={props.buttonElm}>{props.children}</button>
);
}
export function Parent(props) {
const buttonElm = useRef(null);
useEffect(() => {
console.log(buttonElm.current);
// Animate button using popmotion or similar
});
return (
<div>
<Button buttonElm={buttonElm}>Click me to animate</Button>
</div>
);
}
I'm trying to get the height of the parent container element, to use as a height of an SVG background element.
Is there a way to get and pass this property?
I keep getting "TypeError: Cannot read property 'refs' of undefined"
const Services = ({ classes }) => {
return (
<div className={classes.Container} ref="container">//Element I want the height of
<div className={classes.BGContainer}>
<BGSVG width={'210'} height={this.refs.container.height} color={'blue'} />//Where I want to pass height to
</div>
<div className={classes.Services}>
{services.map((e, i) => (
<ServiceItem
title={e.title}
icon={e.icon}
info={e.info}
inverted={i % 2 === 1 ? true : false}
/>
))}
</div>
</div>
);
In a functional component, you need to use the React Hook called useRef like this:
const TextInputWithFocusButton = (props) => {
const inputEl = useRef(null);
const onButtonClick = () => {
// `current` points to the mounted text input element
inputEl.current.focus();
};
return (
<>
<input ref={inputEl} type="text" />
<button onClick={onButtonClick}>Focus the input</button>
</>
);
}
you will see if you log out inputEl that a reference to the input element is logged. DOM element references allow you to access most properties of the particular DOM element referenced.
You haven't bound "this". Try height={() => this.refs.container.height}
If your Services component is inside lets say ServicesWrapper Component, what you should do is put on it
`servicesWrapperContainerRef = React.createRef()`;
Then, on JSX of wrapper component place
`ref={this.servicesWrapperContainerRef}`
and then, pass that as a prop to the Services component like this
const Services = ({ classes, servicesWrapperContainerRef }) and then you can use it to find out the parents height
`height={this.props.servicesWrapperContainerRef.current.clientHeight}`