Why do createPortal renders the whole component? - reactjs

I have a problem with the createPortal function. When I use the "Modal", then by changing the states, the whole "Modal" component will be rendered. Can you help me how to avoid this? So when I comment out the Modal wrap in the Cart component (as I did it below), it works as I expected, but with the Modal wrap, when the states are changed, not the component will be re-rendered, but always the whole Modal Component
Here is my Modal component with createPortal function:
import React from 'react';
import { createPortal } from 'react-dom';
import { useState } from 'react';
import './modal.scss';
export default function Modal({ children, onClick }) {
const BackDrop = () => {
return <div className='backdrop' onClick={onClick}></div>;
};
const Modal = () => {
return (
<div className='modal'>
<div className='content'>{children}</div>
</div>
);
};
return (
<>
{createPortal(<BackDrop />, document.getElementById('modal'))}
{createPortal(<Modal />, document.getElementById('modal'))}
</>
);
}
The Cart component which uses the Modal component:
import React, { useEffect } from 'react';
import Modal from '../UI/Modal';
import './cart.scss';
import { useCartContext } from '../../store/CartContext';
import CartItem from './CartItem';
export default function Cart({ onHideCart }) {
const { cart, totalPrice, updateTotalPrice, addToCartOne, removeFromCartOne } = useCartContext();
useEffect(() => {
updateTotalPrice();
}, [totalPrice, cart]);
const onAddHandler = (id) => {
addToCartOne(id);
updateTotalPrice();
};
const onRemoveHandler = (id) => {
removeFromCartOne(id);
updateTotalPrice();
};
return (
// <Modal onClick={onHideCart}>
<div>
<ul className='cart-items'>
{cart.map((item, idx) => (
<CartItem
key={item.id}
name={item.name}
price={item.price}
amount={item.amount}
onAdd={onAddHandler.bind(null, item.id)}
onRemove={onRemoveHandler.bind(null, item.id)}
/>
))}
</ul>
<div className='total'>
<span>Total Amount</span>
<span>$ {totalPrice.toFixed(2)}</span>
</div>
<div className='actions'>
<button className='button--alt' onClick={onHideCart}>
Close
</button>
<button className='button'>Order</button>
</div>
</div>
// </Modal>
);
}
So by changing the amount with + and - buttons, the html element with id modal, always renders, it's flashes in the devtools... but when I comment out the Modal wrap in the Cart component, there is no flashes by modal ID. I hope it makes sense.

The problem was with the two custom element inside of the Cart element. When I return
createPortal(
<>
<div className='backdrop' onClick={onClick}></div>
<div className='modal'>
<div className='content'>{children}</div>
</div>
</>,
document.getElementById('modal')
)
instead of this:
<>
{createPortal(<BackDrop />, document.getElementById('modal'))}
{createPortal(<Modal />, document.getElementById('modal'))}
</>
Then there is no problem with rendering.

Related

How can I pass a value from a function to any component in React?

I want to pass a value which I am receiving in a function like this:
const ViewDetails = item => () => {
console.log(item);
toggleModal();
}
I want to pass the item to Modal component like open,onclose which is called in the Main function:
return (
<Layout title="Dashboard" className="container-fluid">
{<Modal open={modalStatus} onClose={() => setModalStatus(false)} />}
<div className="row">
<div className="col-sm-3">
<UserLinks />
</div>
<div className="col-sm-9">
<UserInfo />
{orders ? <PurchaseHistory /> : ""}
</div>
</div>
</Layout>
)
I am expecting to have something like this:
{<Modal open={modalStatus} onClose={() => setModalStatus(false)} ***item={item}***/>}
so that I can use the values inside item in Modal component.
I would like to add more to #GODWIN GODWIN comment in regards context API, by providing a very simple example along with the React docs about Context hook
Generally in practice people tend to wrap providers at App.js, for the sake of simplicity I am going to wrap at index.js file.
src/index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import App from './App'
import { ModalProvider } from './context/ModalContext'
ReactDOM.createRoot(
document.querySelector('#root')
).render(
/**
* #dev Note everything inside ModalPrivder has access
* to the values provided, such as open, setOpen
*/
<ModalProvider>
<App />
</ModalProvider>
)
src/context/ModalContext.jsx
import React, {useState, createContext, useContext } from 'react'
/**
* #dev inside your createContext object you can pass in
* default values that will be passed in value at provider
*/
export const ModalContext = createContext({
open: false
})
/**
* #dev your provider will enable you to access value all your
* children components. NOTE it will not be able to access your
* parent components.
*/
export function ModalProvider(props) {
const [open, setOpen] = useState(false)
return (
<ModalContext.Provider value={{ open, setOpen }}>
{props.children}
</ModalContext.Provider>
)
}
src/components/Modal.jsx
import { useContext } from 'react'
function Modal(props) {
const { open, setOpen } = useContext(ModalContext)
return (
<>
{ open ?
(<div>
<p>test</p>
<>{props.children}</>
<button onClick={() => setOpen(false)}>Close Close</button>
</div>) :
(<button onClick={() => setOpen(true)}>Open Modal</button>)
}
</>
)
}
export default Modal
src/App.jsx
function App(props) {
return (
<div className='App'>
<h1>Hello React.</h1>
<h2>Start editing to see some magic happen!</h2>
<Modal>
<p> You see content here</p>
</Modal>
</div>
);
}
export default App
I hope this give you a good direction on how to use React's context hook, please note that this is a very basic source code, to understand how props.children works and context hook.
You can try this:
import React from "react";
function Eexample() {
const ViewDetails = (item) => () => {
console.log(item);
toggleModal();
return (
<Modal item={item} /> //This is passing item into the Modal component
)
};
return <div>
{ViewDetails} {/*This returns the function viewDetails*/}
</div>;
}
export default Eexample;
For your Modal component:
function Modal({ item }) { // with this distructuring, you can use the item inside the Modal component
return (
<Layout title="Dashboard" className="container-fluid">
{<Modal open={modalStatus} onClose={() => setModalStatus(false)} />}
<div className="row">
<div className="col-sm-3">
<UserLinks />
</div>
<div className="col-sm-9">
<UserInfo />
{orders ? <PurchaseHistory /> : ""}
</div>
</div>
</Layout>
)
}
Or Consider using context API, it enables you to dispatch the item to your reducer.js file and pull it in your Modal component using StateProvider.js file.
You have to take state for this item. When viewDetails function triggered from inside this function you can set this state with this item afte can be pass this state as a props any component

How to pass a dynamic value from one component to the other in React

I have a Navbar component with an anchor tag containing an onClick event. On click, a value (navvalue) is passed to the function Testfunction, which is a separate component. I want to import Testfunction into the Content component so that I can have access and display the value coming from Navbar (navvalue). How do I access “navvalue” in Content? This is an assignment in a react course I´m taking. I should use props. I´m not supposed to use either state or React Route since we haven´t reach those topics yet. Thank you for your help!
Here´s my code:
App.js
import "./styles.css";
import React from "react";
import Navbar from "./Navbar";
import Content from "./Content";
import Testfunction from "./Testfunction";
export default function App() {
return (
<div className="App">
<Navbar onPageChange={Testfunction} />
<Content />
</div>
);
}
Navbar.js
import React from "react";
const Navbar = (props) => {
const navvalue = "Nav Value";
return (
<a
className="nav-link active text-uppercase"
aria-current="page"
href="#"
onClick={() => props.onPageChange(navvalue)}
>
{navvalue}
</a>
);
};
export default Navbar;
Testfunction.js
const Testfunction = (navvalue) => {
return navvalue;
};
export default Testfunction;
Content.js
import React from "react";
import Testfunction from "./Testfunction";
const Content = () => {
const navvalue = Testfunction();
return (
<p>Here´s the content. Insert value coming from Navbar here: {navvalue}</p>
);
};
export default Content;
To share some state in both <Navbar /> and <Content /> you can put state to their parent -> App. Also to <Navbar /> we pass setter function to update state which is in parent. So it can be like this:
import React from "react";
export default function App() {
const [navValue, setNavValue] = React.useState();
return (
<div className="App">
<Navbar setNavValue={setNavValue} />
<Content navValue={navValue} />
</div>
);
}
const Navbar = ({ setNavValue }) => {
const value = "Nav Value";
return <button onClick={() => setNavValue(value)}>{value}</button>;
};
const Content = ({ navValue }) => {
return (
<p>Here´s the content. Insert value coming from Navbar here: {navValue}</p>
);
};

Toggle one component from another using React hook

I have a two child components and I want a button in one of them to toggle (on/off) the html in another. I have watched tutorials on lifting state up, and communication between components but most of it has to do with moving data. I want to manipulate a component from another. I would like the achieve this using useState, and without useContext if possible. Thanks!
THE PARENT
import React from "react";
function App() {
return (
<div>
<AppProvider>
<ComponentOne/>
<Slideshow />
<ComponentTwo/ >
</AppProvider>
</div>
);
}
CHILD 1
export default function ComponentOne() {
return (
<div>
<button>The button that toggles</button>
</div>
);
}
CHILD 2
export default function ComponentTwo() {
return (
<div>
<div>The content that hides/shows</div>
</div>
);
}
You need to use state to toggle the values. Pass the state to the ComponentTwo as props and pass the state updater function as the callback function to the ComponentOne as a prop.
import React , { useState, useCallback } from 'react';
function ComponentOne({ onToggle}) {
return (
<div>
<button onClick={onToggle}>The button that toggles</button>
</div>
);
}
function ComponentTwo({show}) {
return (
<div>
{show && <div>The content that hides/shows</div>}
</div>
);
}
export default function App() {
const [show, setShow ] = useState(true);
const handleToggle = useCallback(() => setShow(prevShow => !prevShow),[])
return (
<div className="App">
<ComponentOne onToggle={handleToggle} />
<ComponentTwo show={show} />
</div>
);
}
Refer
useState
useCallback

Algolia - Store RefinementList value when close Modal

I'm building a filter Modal in algolia. On that filter Modal, I have a standard refinementlist (see below code). When the user hits "Search" on the modal, the refinementlist values are lost (ie not applied to my component), but there is no guide on how to store refinementlist output.
What I'd like is to essentially have my Refinement list values not to clear when I close my modal.
refinementlist.js
import React from 'react';
import { RefinementList } from 'react-instantsearch-dom';
const toRefine = () => {
return (
<div>
<RefinementList
attribute={`tags`}
searchable
limit={5}
/>
</div>
);
};
export default toRefine;
filter.js
import React from 'react';
import toRefine from './toRefine';
const Filters = () => {
return (
<div>
<toRefine />
</div>
);
};
export default Filters;
MainPage.js
import React, { useState } from 'react';
import Hits from './hits';
import algoliasearch from 'algoliasearch/lite';
import { InstantSearch } from 'react-instantsearch-dom';
import Modal from 'react-modal';
import Filters from './filters';
Modal.setAppElement('#root');
const searchC = algoliasearch($ENV_VARS);
const Page = () => {
const [ modalIsOpen, setIsOpen ] = useState(false); //Hook for modal
function openModal() {
setIsOpen(true);
}
function closeModal() {
setIsOpen(false);
}
return (
<div>
<InstantSearch
indexName="index"
searchClient={searchC}
>
<CustomSearchBox />
<button onClick={openModal}>Show FILTERS</button>
<Configure hitsPerPage={20} />
<Hits />
<Modal
isOpen={modalIsOpen}
onRequestClose={closeModal}
contentLabel="filterElement"
className={styles.modal}
overlayClassName={styles.overlay}
>
<FilterPage />
</Modal>
</InstantSearch>
</div>
);
};
export default Page;
Each React InstantSearch widget is responsible for its UI state and needs to be mounted. I'm not familiar with react-modal, but from what I gather reading their documentation, the modal instance is destroyed when closing, not hidden, so the RefinementList is unmounted as well.
What you can do to circumvent this behavior is persist the widget's state manually whenever it changes except when closing the modal, and inject it to the widget as its default refinement.
function App() {
const [brandState, setBrandState] = React.useState([]);
// ...
return (
<InstantSearch
onSearchStateChange={(state) => {
if (modalIsOpen && state.refinementList?.brand) {
setBrandState(state.refinementList.brand);
}
}}
>
<Modal isOpen={modalIsOpen}>
<RefinementList
defaultRefinement={brandState}
attribute="brand"
/>
</Modal>
</InstantSearch>
);
}
You always need to have the RefinementList mounted in the application so that the state is persisted in React InstantSearch's internal state. You can do it declaratively by creating a virtual refinement list, which doesn't render anything, using the connectRefinementList connector.
import { connectRefinementList } from 'react-instantsearch-dom';
// ...
const VirtualRefinementList = connectRefinementList(() => null);
function App() {
// ...
return (
<InstantSearch
onSearchStateChange={(state) => {
if (modalIsOpen && state.refinementList?.brand) {
setBrandState(state.refinementList.brand);
}
}}
>
<VirtualRefinementList defaultRefinement={brandState} attribute="brand" />
{/* ... */}
</InstantSearch>
);
}
You can see it in action in this CodeSandbox demo.

Rendering a Bootstrap Component Using JSX

Editing for clarity: I cannot figure out how to dynamically create Boostrap Components using JSX in a react app. End goal is to get the new button in the "newBtnSpace" div when the first button is clicked. I have tried using show.hide methods, but those need to be hard coded. Trying to create buttons based off an array. code:
./components/newBSBtnSpaceFunc.js
import React, { Component } from 'react'
import { Button } from 'reactstrap'
export default function NewBSBtnFunc() {
let BtnArray = ["red", "blue", "green"].map((btn) => {
return React.createElement(
Button,
{variant: 'primary'},
'New Button',
{id: "newBtn"},
btn
)
}
./components/BSBtn.js
import React, { Component } from 'react'
import { Button } from 'reactstrap'
import NewBSBtnFunc from "./NewBSBtnFunc"
export default class BSBtn extends Component {
render() {
return (
<div>
<Button onClick={NewBSBtnFunc}>Click Me</Button>
<div id="newBtnSpace"></div>
</div>
)
}
}
App.js
import React from 'react';
import 'bootstrap/dist/css/bootstrap.min.css';
import BSBtn from "./components/BSBtn"
function App() {
return (
<div>
<BSBtn></BSBtn>
</div>
);
}
export default App;
github link: https://github.com/mollygilbert389/testingBootstrapBtn
You can conditionally show the new button by setting a state item (in this case showNewButton) to true in the onClick of the original button.
render() {
return (
<div>
<Button onClick={() => this.setState({ showNewButton: true }))}>Click Me</Button>
<div id="newBtnSpace">{ this.state.showNewButton && <Button variant="primary" id="newBtn">New Button</Button> }</div>
</div>
)
}
PS you've already successfully worked out how to create Bootstrap buttons in jsx:
<Button onClick={NewBSBtnFunc}>Click Me</Button>
onClick does not expect a return value so returning the new button won't do anything.
The way you have things organized makes it very difficult since you can't return anything from the function, and you can't modify state from outside the class. I would suggest moving your click handler into the component and using to to modify a state value that will show the second button.
Here is my suggestion:
import React, { Component } from 'react'
import { Button } from 'reactstrap'
export default class BSBtn extends Component {
state = {show: false}
handleClick = () => {
this.setState({ show: !this.state.show })
}
render() {
return (
<div>
<Button onClick={this.handleClick}>Click Me</Button>
<div id="newBtnSpace">
{this.state.show ?
<Button variant="primary" id="newBtn">New Button</Button>
: null}
</div>
</div>
)
}
}
Updated solution to your updated question:
class BSBtn extends React.Component {
state = {
show: false,
buttons: []
}
handleClick = () => {
this.setState({ show: !this.state.show })
}
handleAdd = () => {
this.setState({ buttons: [...this.state.buttons, (this.state.buttons.length + 1)] })
}
render() {
return (
<div>
<h3>Option 1</h3>
<button onClick={this.handleClick}>Click Me</button>
<div id="newBtnSpace">
{this.state.show ? [1,2,3].map((value) => (
<div>
<button>Button {value}</button>
</div>
))
: null}
</div>
<hr/>
<div style={{ marginTop: '30px' }}>
<h3>Option 2</h3>
<button onClick={this.handleAdd}>Click Me</button>
{this.state.buttons.map((value) => (
<div>
<button>Button {value}</button>
</div>
))}
</div>
</div>
)
}
}
ReactDOM.render(<BSBtn />, 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' />

Resources