Can I createPortal from an onClick handler? - reactjs

Given something like this:
import { render, createPortal } from "react-dom"
const MyPage = () => {
const handler = () => {
render(<MyModalDialog />, document.body)
}
return (
<div>
<button onClick={handler}>Click me</button>
</div>
)
}
I can conditionally render my modal dialog directly onto the body (leaving cleanup to the dialog perhaps, but that's not important for this question). This works, but the problem is there is no Context available (i.e., useContext).
As of v16. there is an api for this case, createPortal. This keeps the context around and allows rendering onto an arbitrary DOM node. But, this has no effect inside a click handler as shown above - it is meant to be returned from the <MyPage/> component, and react handles the actual rendering elsewhere.
So my question is - can I conditionally render an arbitrary component to a specified DOM node as part of an event handler using createPortal or some other API?

A better method to do it would be having the portaled element in a separate component and having a state in your MyPage component that renders the modal conditionally
Something like
const MyPortaledModal = () => createPortal(<MyModalDialog />, document.body)
const MyPage = () => {
const [shouldShowModal, setShouldShowModal] = useState(false);
const toggleModal = () => setShowModal(prevValue => !prevValue)
return (
<div>
<button onClick={toggleModal}>Click me</button>
{shouldShowModal && <MyPortaledModal/>}
</div>
)
}

Related

onCallback React Hook - best practice query

This is more of a best practice question than anything. But I'm wondering if any inline functions within a React component should typically be wrapped with an onCallback for performance? Under what circumstances would I not wrap a function like this with onCallback?
For example:
const ToolSearch = (props: ToolsSearchProps) => {
const handleOnClick = () => {
alert('do something');
}
return (
<NoCardOverflow>
<ToolSearchCollapse openState={filtersOpen} onClick={handleOnClick} />
</NoCardOverflow>
);
};
In this example should I be doing this:
const ToolSearch = (props: ToolsSearchProps) => {
const handleOnClick = useCallback(() => {
alert('do something');
},[]);
return (
<NoCardOverflow>
<ToolSearchCollapse openState={filtersOpen} onClick={handleOnClick} />
</NoCardOverflow>
);
};
I will try to explain the useCallBack with an example. We all know the definition of useCallBack, but when to use is the trick part here. So, let me take an example.
const RenderText = React.memo(({ text }) => {
console.log(`Render ${text}`);
return (<div>{text}</div>);
});
const App = () => {
const [count, updateCount] = React.useState(0);
const [lists, updateLists] = React.useState(["list 1"]);
const addListItem = () => {
updateLists([...lists, "random"]);
};
return (
<div>
{`Current Count is ${count}`}
<button onClick={() => updateCount((prev) => prev + 1)}>Update Count</button>
{lists.map((item: string, index: number) => (
<RenderText key={index} text={item} />
))}
</div>
);
};
ReactDOM.render(<App />, document.querySelector("#app"));
jsFiddle: https://jsfiddle.net/enyctuLp/1/
In the above, there are two states.
count - Number
lists - Array
The RenderText just renders the text passed to it. (which is the item in the list). If you click on the Update Count button, the RenderText will not re-render because it is independent from the main component (App) and during the updateCount, only the App component will re-render, since it needs to update the count value.
Now, pass the addListItem into RenderText component and click on the Update Count button and see what happens.
jsFiddle: https://jsfiddle.net/enyctuLp/2/
You can see that the RenderText will re-render even though there is no change in the list array and this is BECAUSE :
when the count is updated, the App will re-render
which, will re-render the addListItem
which causes the RenderText to re-render.
To avoid this, we should use useCallBack hook.
jsFiddle: https://jsfiddle.net/enyctuLp/4/
Now, the addListItem function has been memoized and it will only change when the dependency are changed.
ToolSearchCollapse is your child component, and so wrapping your handleClick function makes sense. Coz every time the parent component re-renders a new reference of handleclick will be passed to ToolSearchCollapse, and your child will re-render every time a parent state changes, (from states that aren't passed to the child also). Using useCallback will not allow creating new references, and thus you can control when your child should render.
If you don't pass this fn to the child, I don't see any reason to use useCallback.

How to pass data from child component to parent component without the child component rerendering in React?

My child component is an accordion with controls in it. The problem is, everytime you input data on the controls and it triggers an onChange event which passes its data to its parent component, the accordion closes/collapses and I don't need that behavior.
The accordion is just imported from a library and I can't change its behavior.
Is there anyway to prevent this or pass props/data from child component to parent component without rerendering/restarting DOM of child component?
Parent component:
const ParentComponent = () => {
const handleChangeChildAccordion = (e) => {
console.log(e);
}
return (
<ChildComponentAccordion currentData={currentData} onChangeTPAccordion={handleChangeChildAccordion}/> }
);
}
Child Component:
const ChildComponent = (props) => {
const onDataChangeHandler = (e) => {
props.onChangeTPAccordion(e);
};
return (
<Accordion onChange={onDataChangeHandler}/>
);
}
The child component doesn't remount, it rerenders. Normally, a component rerenders any time any its parent rerenders. To avoid that you:
Avoid changing the child component's props, and
Memoize the child component so it doesn't rerender when its props don't change in a way it cares about.
You do the first by ensuring that, for instance, callbacks you pass the component are stable (they don't change on every render). In a function component, you do it by using useMemo, useCallback, and/or useRef. In a class component, you typically do that by making the callbacks properties on the component instance that aren't recreated.
You do the second by using React.memo (if it's a function component) or for a class component by extending PureComponent (for simple memoization) or implementing shouldComponentUpdate.
You haven't given us much code to work with, so here's a simple example:
const { useState, useCallback, useRef } = React;
const log = (...msgs) => console.log(...msgs);
const Child1 = React.memo(({value, doUpdate}) => {
log(`Child1 rendering`);
return <div>
Child1 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
</div>;
});
const Child2 = React.memo(({value, doUpdate}) => {
log(`Child2 rendering`);
return <div>
Child2 - Value: {value} <input type="button" value="+" onClick={doUpdate} />
</div>;
});
const Parent = () => {
log(`Parent rendering`);
const [counter1, setCounter1] = useState(0);
const [counter2, setCounter2] = useState(0);
// `useCallback` remembers the first function you pass it and returns
// it to you, only returning the new one you pass it if the
// dependencies array changes. This is one way to create a stable
// callback.
const update1 = useCallback(() => setCounter1(c => c + 1), []);
// `useRef` gives you an object you can put arbitrary properties on,
// which is another way to have a stable callback.
const update2Ref = useRef(null);
if (!update2Ref.current) {
update2Ref.current = () => setCounter2(c => c + 1);
}
const update2 = update2Ref.current;
return <div>
<Child1 value={counter1} doUpdate={update1} />
<Child2 value={counter2} doUpdate={update2} />
</div>;
};
ReactDOM.render(<Parent />, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>
TypeScript playground with types added
That shows two different ways of making acallback stable (useCallback and useRef). (useMemo is what useCallback uses under the hood, so I didn't do an example of it.)
In that example, notice how the child telling the parent to update its counter doesn't cause the other child to re-render. That's because:
We don't change the other child's props (because our callbacks are stable and the other child's counter didn't change), and
We've memoized the child components
You can customize whether the component re-renders by passing a second argument to React.memo which is a function that says whether the new props coming in are "the same" (for rendering purposes) as the previous ones. By default, React.memo does a simple === comparison on each prop. And again, for class components, PureComponent does the simple === comparison, or you can implement shouldComponentUpdate yourself for more finely-tuned checks.

Why React is rendering parent element, even if changed state isn't used in jsx? (Using React Hooks)

Im doing a React small training app using Hooks. Here's the example:
There is a MainPage.js and it has 3 similar child components Card.js. I have global state in MainPage and each Card has its own local state. Every Card has prop "id" from MainPage and clickButton func.
When I click button in any Card there are 2 operations:
Local variable 'clicked' becomes true.
The function from parent component is invoked and sets value to global state variable 'firstCard'.
Each file contains console.log() for testing. And when I click the button it shows actual global variable "firstCard", and 3x times false(default value of variable "clicked" in Card).
It means that component MainPage is rendered after clicking button ? And every Card is rendered too with default value of "clicked".
Why MainPage componenet is rendered, after all we dont use variable "firsCard", except console.log()?
How to make that after clicking any button, there will be changes in exactly component local state, and in the same time make global state variable "firstCard" changed too, but without render parent component(we dont use in jsx variable "firstCard")
Thanks for your help !
import Card from "../Card/Card";
const Main = () => {
const [cards, setCards] = useState([]);
const [firstCard, setFirstCard] = useState(null);
useEffect(() => {
setCards([1, 2, 3]);
}, []);
const onClickHandler = (id) => {
setFirstCard(id);
};
console.log(firstCard); // Showing corrrect result
return (
<div>
{cards.map((card, i) => {
return (
<Card
key={Date.now() + i}
id={card}
clickButton={(id) => onClickHandler(id)}
></Card>
);
})}
</div>
);
};
import React, { useState } from "react";
const Card = ({ id, clickButton }) => {
const [clicked, setClicked] = useState(false);
const onClickHandler = () => {
setClicked(true);
clickButton(id);
};
console.log(clicked); // 3x false
return (
<div>
<h1>Card number {id}</h1>
<button onClick={() => onClickHandler()}> Set ID</button>
</div>
);
};
export default Card;
You have wrong idea how react works.
When you change something in state that component will re render, regardless if you use that state variable in render or not.
Moreover, react will also re render all children of this component recursively.
Now you can prevent the children from re rendering (not the actual component where state update happened though) in some cases, for that you can look into React.memo.
That said prior to React hooks there was a method shouldComponentUpdate which you could have used to skip render depending on change in state or props.

What invokves the 2nd function call when using React Hooks?

I wrote the following React exercise which uses no hooks and renders a button.
const Button = ({ onClick }) => <button onClick={onClick}>Do Nothing</button>;
const Base = () => {
const onClickFunction = (() => {
console.log("Creating OnClick Function");
return () => {};
})();
return (
<div className="App">
<h1>Hello</h1>
<Button onClick={onClickFunction} />
</div>
);
};
onClickFunction uses a self-invoking function, so that I can place a console.log to see the following behaviour. In this example, when Base is rendered, the message Creating OnClick Function appears only once 👍
If I change Base to the following however, adding a hook usage:
const Button = ({ onClick }) => <button onClick={onClick}>Do Nothing</button>;
const Base = () => {
const notUsedRef = React.useRef();
const onClickFunction = (() => {
console.log("Creating OnClick Function");
return () => {};
})();
return (
<div className="App">
<h1>Hello</h1>
<Button onClick={onClickFunction} />
</div>
);
};
You will see the Creating OnClick Function message twice.
This CodeSandbox illustrates what I've been seeing: https://codesandbox.io/s/dawn-forest-99clo?file=/src/App.js
Using React DevTools Profiler, we can see there is no rerender of this component.
Using <React.Profiler, it reports this component also didn't update.
I know that using React.useCallback wouldn't trigger a second invokation, however the question would still stand why we are in the situation Base is called twice.
My question is: why and what is triggering Base to be invoked when there is no need for a rerender.
This is due to the way React implements hooks.
If you invoke any hook, even if you don't use the resulting value, you are telling React to render twice before mounting, even if the props don't change. You can substitute the usage of useRef by useState, useEffect, etc. Try below.
You can also wrap your component with React.memo. Every function defined inside the function is recreated in every render.
https://codesandbox.io/s/elastic-water-y18w0?file=/src/App.js
EDIT: Only happens during development and in components wrapped by React.StrictMode. In the words of gaearon:
It's an intentional feature of the StrictMode. This only happens in
development, and helps find accidental side effects put into the
render phase. We only do this for components with Hooks because those
are more likely to accidentally have side effects in the wrong place.
https://github.com/facebook/react/issues/15074

Prevent Unnecessary Re-rendering of Child Elements

I am creating a global notifications component in react that provides a createNotification handle to its children using Context. The notifications are rendered along with props.children. Is there anyway to prevent the re-rendering of the props.children if they haven't changed?
I have tried using React.memo and useMemo(props.children, [props.children]) to no prevail.
const App = () => {
return (
<Notifications>
<OtherComponent/>
</Notifications/>
);
}
const Notifications = (props) => {
const [notifications, setNotifications] = useState([]);
const createNotification = (newNotification) => {
setNotifications([...notifications, ...newNotification]);
}
const NotificationElems = notifications.map((notification) => <Notification {...notification}/>);
return (
<NotificationContext.Provider value={createNotification}>
<React.Fragment>
{NotificationElems}
{props.children}
</React.Fragment>
</NotificationContext.Provider>
);
};
const OtherComponent = () => {
console.log('Re-rendered');
return <button onClick={() => useContext(NotificationContext)(notification)}>foo</button>
}
Every time a new notification is created, props.children is re-rendered even though nothing actually changes within it. It just adds elements along side it. This can be quite expensive if you have a big app and everything re-renders for each notification that shows up. If there is no way to prevent this, how can I split it up so I can do this:
<div>
<OtherComponent/>
<Notifications/>
</div>
and share with OtherComponent the createNotification handle?
You need to use the useCallback hook to create your createNotification imperative handler. Otherwise you will create a new function on every render of the Notifications component which will lead to all components consuming your context to re-render because you always pass a new handler whenever you add a notification.
Also you likely didn't mean to spread the newNotification into the array of notifications.
The next thing you need to do is to provide the updater callback version of setState inside setNotifications. It gets passed the current list of notifications that you can use the append the new one. This makes your callback independent of the current value of the notification state. It is usually an error to update the state based on the current state without using an updater function because react batches multiple updates.
const Notifications = props => {
const [notifications, setNotifications] = useState([]);
// use the useCallback hook to create a memorized handler
const createNotification = useCallback(
newNotification =>
setNotifications(
// use the callback version of setState
notifications => [...notifications, newNotification],
),
[],
);
const NotificationElems = notifications.map((notification, index) => <Notification key={index} {...notification} />);
return (
<NotificationContext.Provider value={createNotification}>
<React.Fragment>
{NotificationElems}
{props.children}
</React.Fragment>
</NotificationContext.Provider>
);
};
Another issue is that you conditionally call the useContext hook which is not allowed. Hooks must be called unconditionally:
const OtherComponent = () => {
// unconditiopnally subscribe to context
const createNotification = useContext(NotificationContext);
console.log('Re-rendered');
return <button onClick={() => createNotification({text: 'foo'})}>foo</button>;
};
Fully working example:

Resources