How to prevent unnecessary reconciliation when switching React functional components? - reactjs

I have 2 React functional components that return very similar JSX except for a few small differences:
function Foo() {
return <div><h1>Foo</h1></div>;
}
function Bar() {
return <div><h1>Bar</h1></div>;
}
Every 0.5s I switch between them:
function App() {
const [s, setS] = React.useState(false);
React.useEffect(() => {
const interval = setInterval(() => {
setS((p) => !p);
}, 500);
return () => {
clearInterval(interval);
};
},[]);
return <div>{s ? <Foo /> : <Bar />}</div>;
}
Since the structure of the page doesn't change (text inside h1 inside div inside div) I expect just the text in the h1 to change, but looks like React deletes the inner div and replaces with the other component's (which is reconciliation).
How do I make React notice that the structure didn't change so it doesn't need to do a replace so high up the DOM?

Related

How to store input values when moving to another component in react typescript functional components

I have a React Typescript functional component like below,
const Comp = (): JSX.Element => {
const [showNext, setShowNext] = useState(false);
const [data1, setData1] = useState(array);
return showNext ? <AnotherComponent /> : <></>;
};
If I move to the next component and coming back to the previous one, then state values are gone in the previous component. So, in UI already entered inputs are gone. How can I avoid this, or how can I store it?
By using conditional rendering you unmount the component depending on the state, unmounting component loses its state.
Therefore try hiding it with CSS or save its state in the parent component (Comp in this case)
// Save state in parent
const Comp = () => {
const [nextState, setNextState] = useState({})
return showNext ? <AnotherComponent {...nextState}/> : <></>;
};
// Or hide it with CSS
const Comp = () => {
const [nextState, setNextState] = useState({})
return <AnotherComponent className={showNext ? 'myClassName' : 'hideClass'} />;
};
// Possible CSS
.hideClass {
display: none;
}
.myClassName{
display: block;
}

Infinite re-render due to function inside dependency array in React hooks

Hi I am trying to implement live streaming video in my website, For that i have used node media server, The problem i am facing here is my website get caught into infinite re-rendering because of function in which I have created a player. Is there any solution for this specially in react hooks. I am sharing a piece of code sufficient to understand the problem.
const videoRef = useRef();
const {id } = props.match.params;
useEffect(() => {
props.fetchStream(id);
buildPlay();
}, [buildPlay]);
function buildPlay() {
if(player || !props.stream){
return ;
}
var player = flv.createPlayer({
type: 'flv',
url: `http://localhost:8000/live/${id}.flv`
});
player.attachMediaElement(videoRef.current);
player.load();
}
if(!props.stream){
return <div>Loading...</div>
}
return (
<div>
<video ref={videoRef} style={{width: "100%"}} controls/>
<h1>{props.stream.title}</h1>
<h5>{props.stream.description}</h5>
</div>
)
What is causing infinite re-renders is that everything declared in a component function body is re-declared on every render, so buildPlay is a new function every render, and the effect will execute infinitely. It's not really common to specify a function as a dependency (except a props.callback maybe), but if you really want it, you should move the function out of the component and parameterize it (see show code snippet & run below).
Looking at buildPlay, what actually changes and should be specified as dependencies in this case is [props.match.params.id, props.stream, videoRef.current]. Your effect code should look like:
const dependencies = [props.match.params.id, props.stream, videoRef.current];
useEffect(() => {
if (!props.stream || !videoRef.current) return;
// either defined outside of component, parameterized as buildPlay(id, stream, elem)
buildPlay(...dependencies);
// or inlined in the effect
/* if(player || !props.stream){
return ;
}
var player = flv.createPlayer({
type: 'flv',
url: `http://localhost:8000/live/${id}.flv`
});
player.attachMediaElement(videoRef.current);
player.load(); */
}, dependencies);
Have a look at the 2 React apps in the snippet below: the first one takes your approach, the second one moves the dependeny function outside the component and parameterizes it.
const InfiniteRenderApp = () => {
const [runCount, setRunCount] = React.useState(0);
function doSomething() {
if (runCount < 10000) {
setRunCount(runCount + 1);
} else {
throw 'Infinite render loop. stop here!';
}
}
React.useEffect(() => {
try {
doSomething();
} catch (err) {
setRunCount(err);
}
}, [doSomething]);
return <strong>{runCount}</strong>;
};
ReactDOM.render(<InfiniteRenderApp/>, document.getElementById('infinite-render-app'));
function doSomething(runCount, setRunCount) {
setRunCount(runCount + 1);
}
const GoodApp = () => {
const [runCount, setRunCount] = React.useState(0);
React.useEffect(() => {
doSomething(runCount, setRunCount);
}, [doSomething]);
return <strong>{runCount}</strong>;
};
ReactDOM.render(<GoodApp/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="infinite-render-app"></div>
<div id="app"></div>

Define CSS Variable w/o Styled Components

I would like to define or update a CSS variable in React. I have seen numerous examples of this using the Styled Components library. Is there a way to set a CSS variable in React without something like Styled Components?
An example would be to update the definition each time state changes
useEffect( () => {
// update css variable
// --my-css-var: width 75%
}, [state])
Looks like this can be accomplished with style.setProperty() Docs
In React this would require the use of ref Docs so we can apply this to a particular element.
const reference = useRef(null);
const [state, setState] = useState(100);
useEffect(() => {
reference.current.style.setProperty('--my-css-var', state);
}, [state]);
Yes, you can easily set the style. This is an example of setting the style with a function. After one second, the font color is changed.
function App(){
function CanvasStyle(props){
const [a_color,setColor] = React.useState(props.a_color);
React.useEffect(() => {
const timer = setTimeout(() => {
setColor('blue')
}, 1000);
return () => clearTimeout(timer);
}, []);
a_style = `
body {
color:`+a_color+`;
}
`;
return(
<>
<style type="text/css">
{a_style}
</style>
</>
)
}
return (
<>
<CanvasStyle a_color='red' />
Hello World!
</>
)
}

ReactJS how to memoize within a loop to render the same component

I have a component that creates several components using a loop, but I need to rerender only the instance being modified, not the rest. This is my approach:
function renderName(item) {
return (
<TextField value={item.value || ''} onChange={edit(item.id)} />
);
}
function renderAllNames(items) {
const renderedItems = [];
items.forEach(x => {
const item = React.useMemo(() => renderName(x), [x]);
renderedItems.push(item);
});
return renderedItems;
};
return (
<>
{'Items'}
{renderAllNames(names)};
</>
);
This yells me that there are more hooks calls than in the previous render. Tried this instead:
function renderAllNames(items) {
const renderedItems = [];
items.forEach(x => {
const item = React.memo(renderName(x), (prev, next) => (prev.x === next.x));
renderedItems.push(item);
});
return renderedItems;
};
Didn't work either... the basic approach works fine
function renderAllNames(items) {
const renderedItems = [];
items.forEach(x => {
renderedItems.push(renderName(x));
});
return renderedItems;
};
But it renders all the dynamic component everytime I edit any of the fields, so how can I get this memoized in order to rerender only the item being edited?
You're breaking the rules of hooks. Hooks should only be used in the top level of a component so that React can guarantee call order. Component memoisation should also really only be done using React.memo, and components should only be declared in the global scope, not inside other components.
We could turn renderName into its own component, RenderName:
function RenderName({item, edit}) {
return (
<TextField value={item.value || ''} onChange={() => edit(item.id)} />
);
}
And memoise it like this:
const MemoRenderName = React.memo(RenderName, (prev, next) => {
const idEqual = prev.item.id === next.item.id;
const valEqual = prev.item.value === next.item.value;
const editEqual = prev.edit === next.edit;
return idEqual && valEqual && editEqual;
});
React.memo performs strict comparison on all the props by default. Since item is an object and no two objects are strictly equal, the properties must be deeply compared. A side note: this is only going to work if edit is a referentially stable function. You haven't shown it but it would have to be wrapped in a memoisation hook of its own such as useCallback or lifted out of the render cycle entirely.
Now back in the parent component you can map names directly:
return (
<>
{'Items'}
{names.map(name => <MemoRenderName item={name} edit={edit}/>)}
</>
);

How to call react function from external JavaScript file

I have read this post [ https://brettdewoody.com/accessing-component-methods-and-state-from-outside-react/ ]
But I don't understand.
that is not working on my source code.
it's my tsx file
declare global {
interface Window {
fn_test(): void;
childComponent: HTMLDivElement; <-- what the... ref type????
}
}
export default function Contact(): React.ReactElement {
....
function file_input_html( i:number ): React.ReactElement {
return (
<form id={`frm_write_file_${i}`} .... </form>
)
}
....
return (
<div ref={(childComponent) => {window.childComponent = childComponent}}>
....
)
it's my external javascript file
function fn_test(){
window.childComponent.file_input_html(3)
var element = document.getElementById("ifrm_write_file");
// element.value = "mystyle";
}
How can i call file_input_html function?
plase help me ...
You have some logic here that doesn't completely make sense.
In your class, you define file_input_html, which returns a component.
Then, in fn_test, you call attempt to call that function (which doesn't work -- I'll address that in a minute), but you don't do anything with the output.
The article that you linked to tells you how to get a ref to a component (eg the div in this case) -- not the actual Contact, which doesn't have a property named file_input_html anyway -- that's just a function you declared inside its scope.
What I'm assuming you want to happen (based on the code you shared) is for your external javascript file to be able to tell your component to render a form with a certain ID and then be able to get a reference to it. Here's an example of how to do this (it's a little convoluted, but it's a funny situation):
const { useState } = React
const App = (props) => {
const [formId, setFormId] = useState(2)
useEffect(() => {
window.alterFormId = setFormId
},[])
return (<div id={"form" + formId} ref={(ourComponent) => {window.ourComponent = ourComponent}}>
Text {formId}
</div>);
}
setTimeout(() => {
window.alterFormId(8);
setTimeout(() => {
console.log(window.ourComponent)
window.ourComponent.innerText = "Test"
}, 20)
}, 1000)
ReactDOM.render(<App />,
document.getElementById("root"))
What's happening here:
In useEffect, I set alterFormId on window so that it can be used outside of the React files
Using the technique you linked to, I get a ref to the div that's created. Note that I'm setting the ID here as well, based on the state of formId
The setTimeout function at the end tests all this:
a) It waits until the first render (the first setTimeout) and then calls alterFormId
b) Then, it waits again (just 20ms) so that the next run loop has happened and the React component has re-rendered, with the new formId and reference
c) From there, it calls a method on the div just to prove that the reference works.
I'm not exactly sure of your use case for all this and there are probably easier ways to architect things to avoid these issues, but this should get you started.
안녕하세요. 자바스크립트로 흐름만 알려드리겠습니다
아래 코드들을 참고해보세요.
iframe간 통신은
window.postMessage API와
window.addEventListener('message', handler) 메시지 수신 이벤트 리스너 로 구현할 수있습니다. 보안관련해서도 방어로직이 몇줄 필요합니다(origin 체크 등)
in parent
import React from 'react';
export function Parent () {
const childRef = React.useRef(null);
const handleMessage = (ev) => {
// 방어로직들
if (check ev.origin, check ev.source, ....) {
return false;
}
console.log('handleMessage(parent)', ev)
}
React.useEffect(() => {
window.addEventListener('message', handleMessage);
// clean memory
return () => {
window.removeEventListener('message', handleMessage);
}
})
return (
<div>
<iframe ref="childRef" src="child_src" id="iframe"></iframe>
</div>
)
}
in child
import React from 'react';
export function Iframe () {
const handleMessage = (ev) => {
console.log('handleMessage(child)', ev)
}
const messagePush = () => {
window.parent.postMessage({ foo: 'bar' }, 'host:port')
}
React.useEffect(() => {
window.addEventListener('message', handleMessage);
// clean memory
return () => {
window.removeEventListener('message', handleMessage);
}
})
return (
<div>
<button onClick={messagePush}>Push message</button>
</div>
)
}

Resources