How to append div to the body on a button click? - reactjs

How to append div to the body on a button click?
const MyEl = () => {
return (
<>
<div>Hello</div>
</>
);
};
const AppendHtml = () => {
return (
ReactDOM.createPortal(
<MyEl />,
document.body
)
);
};
I want to append MyEl component every time a button is clicked. If a button is clicked 10 times, <div>Hello</div> should be apended to the body.
I've tried below code but it is not working.
export const MyComponent = () => {
const buttonClick = () => {
AppendHtml();
};
return (
<>
<Button onClick={buttonClick}>Click Me!</Button>
</>
);
};
Although if i use AppendHtml component inside jsx it will appear in body but that's not what i want.
I don't want to put it in jsx, I want to append MyEl component directly from code without using that return statement.

You don't need to use ReactDOM.createPortal in this situation. As React Documention says:
Portals provide a first-class way to render children into a DOM node
that exists outside the DOM hierarchy of the parent component.
The Portal's most common use cases are when the child components need to visually break out of the parent container as shown below:
Modal dialog boxes
Tooltips
Hovercards
Loaders
So, It's better to use React.createElement. You can do something like this:
import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
const AppendHtml = (count) => {
const elementArr = [];
for (let index = 0; index < count; index++) {
let div = React.createElement("div", "", "Hello");
elementArr.push(div);
}
ReactDOM.render(elementArr, document.getElementById("myDiv"));
};
export default function App() {
const [count, setCounter] = useState(0);
useEffect(() => {
AppendHtml(count);
}, [count]);
const buttonClick = () => {
setCounter(count + 1);
};
return (
<>
<button onClick={buttonClick}>Click Me!</button>
<div id="myDiv"></div>
</>
);
}

Related

How to prevent extra re-render of child components of a function component react?

I have created a simple Reactjs application. It uses two components a CounterButton and a CounterDisplay. Both are function components.
Both these components are used in another function component FunctionComponent which maintains the state of counter. The problem is that whenever I click on the CounterButton it renders the button again.
In contrast I created another component, ClassComponent which is a class component equivalent to FunctionComponent, and it does not re-render the button on each click.
I understand that the extra render is because I'm using arrow function clickHandler in FunctionComponent, but don't know how to fix this.
import React from 'react';
const CounterButton = React.memo((props) => {
console.log(`${props.name} Counter Button Rendered`);
return (<>
<button onClick={props.onClick}>{props.name}: Click</button>
</>)
});
const CounterDisplay = React.memo((props) => {
console.log('Counter Display Rendered');
return (<>
<div>{props.name} : {props.counter}</div>
</>)
});
function FunctionComponent() {
const [counter, setCounter] = React.useState(0);
var clickHandler = () => {
console.log(">>> FunctionComponent: button clicked <<< ")
setCounter(counter + 1);
};
console.log('---FunctionComponent render---')
return <>
<CounterButton name="FunctionComponent" onClick={clickHandler} />
<CounterDisplay name="FunctionComponent" counter = {counter} />
</>
}
class ClassComponent extends React.Component {
state = {
counter: 0
};
clickHandler = () => {
console.log(">>> ClassComponent: button clicked <<< ")
this.setState(prev => ({counter: prev.counter + 1}));
};
render() {
console.log('---ClassComponent render---')
return <>
<CounterButton name="ClassComponent" onClick={this.clickHandler} />
<CounterDisplay name= "ClassComponent" counter = {this.state.counter} />
</>
}
}
function App() {
return <>
<FunctionComponent/>
<ClassComponent/>
</>
}
export default App;
Application starts and I see all components rendered once
---FunctionComponent render---
FunctionComponent Counter Button Rendered
Counter Display Rendered
---ClassComponent render---
ClassComponent Counter Button Rendered
Counter Display Rendered
When I click on the FunctionComponent's CounterButton react re-renders the button again.
>>> FunctionComponent: button clicked <<<
---FunctionComponent render---
FunctionComponent Counter Button Rendered
Counter Display Rendered
When I click on the ClassComponent's CounterButton react does not re-render the button again.
>>> ClassComponent: button clicked <<<
---ClassComponent render---
Counter Display Rendered
I tried using useCallBack for clickHandler, but it didn't change anything.
var clickHandler = useCallback(() => {
console.log(">>> FunctionComponent: button clicked <<< ")
setCounter(counter + 1);
},[counter]);
How to achieve the same behavior in FunctionComponent i.e. not re-render button on each click?
I tried using useCallBack for clickHandler, but it didn't change anything.
var clickHandler = useCallback(() => {
console.log(">>> FunctionComponent: button clicked <<< ")
setCounter(counter + 1);
},[counter]);
The reason it didn't change anything is that you're creating a new click handler every time counter changes. Change your code to use the function version of setState, and remove counter from the dependency array:
const clickHandler = useCallback(() => {
setCounter(prev => prev + 1);
}, []);

React load in component onClick

I'm trying to load in a component when a button is clicked but when I click on the button () in the below code nothing appears to be happening. I'm just trying to display a copied message and then have it disappear shortly after it appears to show the user the selected text was copied to their clipboard.
This is my current code:
import React, { useState } from 'react'
import Clipboard from 'react-clipboard.js';
const AddComponent = () => {
console.log("copied")
return (
<p className="copied">copied to clipboard!</p>
)
};
export default function Item(props) {
const { itemImg, itemName } = props
return (
<>
<Clipboard data-clipboard-text={itemName} onClick={AddComponent} className="item-container display-flex">
<img src={itemImg} alt={itemName} className="item-img" />
<h3>{itemName}</h3>
</Clipboard>
{AddComponent}
</>
)
}
mostly you want to have a state control, to conditionally render the given component like { isTrue && <MyComponent /> }. && operator only evaluates <MyComponent /> if isTrue has truthy value. isTrue is some state that you can control and change to display MyComponent.
in your case your onClick should be responsible to control the state value:
import React, { useState } from 'react'
export default function Item(props) {
const { itemImg, itemName } = props
const [isCopied, setIsCopied] = useState(false)
const onCopy = () => {
setIsCopied(true)
setTimeout(() => {
setIsCopied(false)
}, 600)
}
return (
<>
<div data-clipboard-text={itemName} onClick={onCopy} className="item-container display-flex">
<img src={itemImg} alt={itemName} className="item-img" />
<h3>bua</h3>
</div>
{isCopied && <AddComponent/>} // short circuit to conditional render
</>
)
}
you could consider check the repo react-toastify that implements Toast messages for you.
You'll want to have onClick be a regular function instead of a functional component, and in the regular function implement some logic to update the state of Item to record that the Clipboard was clicked. Then in Item, instead of always including <AddComponent />, only include it based on the state of Item.

React and React Hooks: Using an onClick function in a child to fire a function of a parent wrapping component

I have a wrapper component that conditionally renders it's children based on it's own state (isDeleted). Basically I have a 'delete-able item' component where if a button is clicked to delete, the item itself will be removed from the DOM (by returning an empty ReactNode i.e. <></>). The problem is, I can't figure out how to have the button click event, which appears as a child of the wrapper, to be passed INTO the wrapped component itself:
export default function App() {
return (
<DeleteableItemComponent>
{/* lots of other markup here */}
<button onClick={triggerFunctionInsideDeletableItemComponent}>
</DeleteableItemComponent>
)
}
and the most basic version of my delete-able item component:
export default function DeleteableItemComponent() {
const [isDeleted, setIsDeleted] = useState(false);
const functionIWantToFire = () => {
// call API here to delete the item serverside; when successful, 'delete' on frontend
setIsDeleted(true)
}
if (isDeleted) {
return <></>
}
return <>{children}</>
}
So put very simply, I just want to call the functionIWantToFire from the button onClick callback.
How can this be done properly via hooks? I've thought of using the context API but I've never seen it used to trigger function firing, only for setting values, and in this case I want to fire the event itself, not communicate specific values to the wrapper component. I also can't do it correctly through just passing a boolean prop, because then I can only set it once i.e. from false to true.
You could use React.cloneElement API to pass props to your child while iterating through it using React.children.map.
React.Children.map(children, (child) => {
return React.cloneElement(child, { /* .. props here */ });
});
A simple example would be.
You could check the example here
function App() {
return (
<Delete>
<Child1 />
<Child2 />
</Delete>
);
}
function Delete({ children }) {
const [clicked, setClicked] = React.useState(0);
const inc = () => setClicked(clicked + 1);
const dec = () => setClicked(clicked - 1);
return React.Children.map(children, (child) => {
return React.cloneElement(child, { inc, clicked, dec });
});
}
function Child1(props) {
return (
<div>
<p>{props.clicked}</p>
<button onClick={props.inc}>Inc</button>
</div>
)
}
function Child2(props) {
return (
<div>
<button onClick={props.dec}>Dec</button>
</div>
)
}

How to use context within same component file using react and typescript?

I want to access the state from one component to another. To do so i want to wrap contextprovider only to the component where state changes on clicking a button and return state from a usehook so that another component can access the state.
below is how the componnet looks without context applied,
function UploadButton () { //this is where state is set
const [isDialogOpen, setIsDialogOpen] = React.useState(false);
const handleClick = () => {
setIsDialogOpen(!isDialogOpen);
}
return (
<>
<Button onClick={handleClick}/>
{isDialogOpen && <Upload/>}
</>
);
}
function UserButton() { //this is where state is accessed
return (
<Icon/> //this icon should be displayed only if !isDialogOpen
);
}
With context looks like below, I have DialogContext within same file where UploadButton is.
interface DialogCtxState {
isDialogOpen: boolean;
setIsDialogOpen: React.Dispatch<React.SetStateAction<boolean>>;
}
const initialDialogState: DialogCtxState = {
isDialogOpen: false,
setIsDialogOpen: () => {},
};
const DialogContext = React.createContext<DialogCtxState>(
initialDialogState
);
export const DialogContextProvider: React.FC = ({ children }) => {
const [isDialogOpen, setIsDialogOpen] = React.useState<boolean>(false);
return (
<DialogContext.Provider
value={{
isDialogOpen,
setIsDialogOpen,
}}
>
{children}
</DialogContext.Provider>
);
}
function UploadButton () {
const {isDialogOpen, setIsDialogOpen} = React.useContext(DialogContext);
const handleClick = () => {
setIsDialogOpen(!isDialogOpen);
console.log('isDialogOpen', isDialogOpen) //prints false here.
}
return (
<DialogContextProvider>
<>
<Button onClick={handleClick}/>
{isDialogOpen && <Upload/>} //this doesnt render on clicking button as isDialogOpen
//is false
</>
</DialogContextProvider>
);
}
The above snippet doesn't render the Upload component as isDialogOpen is always false no matter if I click the button.
I am not knowing what is wrong. could someone help me with this? thanks.
The issue is that you need to move your context provider one level higher in your react components in order to use useContext in you UploadButton component. You cannot place the context provider inside of UploadButton in order to use it's context. You need to move the provider into a parent component.
Here's a codesandbox demostrating it.

Unable to update the text using useState

I am converting a class based component to a functional component
But in functional component state is not getting updated.
Could you please update what might be the issue.
I tried putting a alert & its working
I have converted previously working below code-base :
class Spice extends React.Component {
constructor() {
super();
this.state = {
title: 'Welcome to XYZ '
};
}
nameChangeFunction() {
this.setState({
title: 'Welcome to Authentic XYZ World'
});
}
And the Calling Snippet:
render() {
const spiceList = [
..,
..,
..
];
return (
<div>
<h1>{this.state.title}</h1>
<button className='tc ma5' onClick={() => this.nameChangeFunction()}>
Check Out
</button>
<br />
</div>
);
}
New functional component is created as below, but its not updating the title, onClick.
const SpiceHookBased = () => {
const [title, setTitle] = useState("INITIAL XYZ Title");
const changeTitle = () => {
return setTitle("some value changed");
}
return {
render() {
const spiceList = [
..,
..,
..
];
return (
<div>
<h1>{title}</h1>
<button className='tc ma5' onClick={changeTitle}>
</button>
);
}
}
}
In second case, its not updating new text title.
Kindly suggest a correction
Thanks
In the codesnippet there was multiple return and render. See the below codesnippet and check
Apart from that the code looks fine, you can remove the return and write it in a single line
import React, { useState } from "react";
import ReactDOM from "react-dom";
import "./styles.css";
const App = () => {
const [title, setTitle] = useState("INITIAL XYZ Title");
const changeTitle = () => setTitle("some value changed")
return (
<div>
<h1>{title}</h1>
<button className="tc ma5" onClick={changeTitle}>
Click Me to change title
</button>
</div>
);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
Working codesandbox
You don't need to return the setTitle function in your update function.
Also there is no need for the render() method in functional components, when you return an object like you are doing it just turns the component into a regular function, not a react Component, so that is likely the reason your useState hooks aren't working.
Try changing your functional component to this:
const SpiceHookBased = () => {
const [title, setTitle] = useState("INITIAL XYZ Title");
const changeTitle = () => {
setTitle("some value changed");
}
return (
<div>
<h1>{title}</h1>
<button className='tc ma5' onClick={changeTitle}>
</button>
);
}
EDIT: I saw on another answer you said you are using this component elsewhere which "does the final job of rendering". That is the problem, you are essentially trying to use this component as a function, which doesn't understand react hooks, and thats why the hooks aren't working. If you want to use that type of pattern you need to return a new Component with any added props that you need.

Resources