Difference between inner ou outer function - reactjs

I'm building a front application with reactjs and material-ui. I have Form that call Field components.
To have more beautiful forms i use Tabs. So i follow the material-ui doc that use TabPanel function to wrap tab content. But i made a mistake, i put function inside my component Test
export default function Test(props) {
function TabPanel(props) {
const { children, value, index, ...other } = props;
return value === index && <Box p={3}>{children}</Box>;
}
}
Instead of doing this :
function TabPanel(props) {
const { children, value, index, ...other } = props;
return value === index && <Box p={3}>{children}</Box>;
}
export default function Test(props) {
}
With the first version, i lost my focus on my input field after each change. On the second version everything was ok.
Could you tell me why it's different.
Thanks and regards

The first scenario is as this:
Because you have declared your TabPanel renderer function with the keyword function, it won't be bound to the scope of your functional component Test, in order to do this, you'll have to do a lot more job -if you chose the purest JS way-, bounding a function to it's Direct Parent Scope(Test) makes it statically preserve the first copy of it(TabPanel) during the life time of the parent.
In other words, bounding your TabPanel function to the local scope of the Test function will preserve the same copy of the TabPanel function.
While, if it's not bound, a new function TabPanel will be created and used each time a render happens in the Test component, thus, it'll entirely re-render your input element and then lose it's focus.
to make the first scenario work you can use the ES6 Arrow Function, as this:
export default function Test(props) {
const TabPanel = (props) => {
const { children, value, index, ...other } = props;
return value === index && <Box p={3}>{children}</Box>;
}
}
Why would that work? but not the function keyword way? because ES6 Arrow Functions Auto bind them self to their Lexical Scope -The scope when they where created(Test() Scope)-, remember, they do it automatically, there is a bunch of stuff that happen under the hood in order for that to happen..
While the second way works just fine because JS will keep a copy of your function in the global scope, in our case window is the global scope, it'll be something like window.TabPanel, and it'll be preserved the first time JS goes into this file, so, extracting it out of the Test function into the global scope will protect it from re-creating itself over and over again whenever a re-render occurs..
I hope I was able to deliver the idea..
If you are willing to understand how binding happens, you'll need to go through the prototype nature of JS.

Related

How to change property of Instantiated component in react?

Given the following code:
export const AdmButton = ({icon,title,iconSize}) =>{
return({icon} {title})
buttondata.map((el, index) => {
<AdmButton title={el.title} icon={el.icon} iconSize="3em"/>
})
export const buttondata=[{
title:'calc',
icon: <ImIcons.ImCalculator size="2em"/>,
},*/.../*
]
The AdmButton is by iteration created as many times as objects in buttondata. The object contains as icon the component from react-icons, in the example ImIcons.ImCalculator is being used. Now, this component has a property called size which I'd like to change depending the place the button is shown, but I don't find a way to refer to size (or another property) when the component is being called via object property. What's the right way to modify or overwrite a Property of a given component iterated this way?
Instead of being a hard-coded component, the icon property in your object can be a function which generates a component. And that function can accept an argument:
icon: (size) => <ImIcons.ImCalculator size={size} />
Then in your AdmButton component you can invoke that function with the parameter:
return({icon(iconSize)} {title})
So each iteration when mapping will pass the parameter along and instantiate the icon on the fly.
Side note: Your call to .map() is missing the return in the callback, and as such currently won't render anything. Either include a return or remove the curly braces to make use of an implicit return.

How to return two JSX elements in React to be rendered in different places?

Say I have a sub component responsible for handling some user input
function MyInputComponent(props: {...}) {
return <input>...</input>;
}
Now say I want to add a button to control that input, normally I would do:
function MyInputComponent(props: {...}) {
return <>
<input>...</input>
<button>...</button>
</>;
}
This works so far. But now I want to render the button at some other places (i.e. not within the same component). e.g. if we look at the final html, I want to have the button to be after another component:
<parent-component>
<input>...</input>
<some-other-component>...</some-other-component>
<button>...</button>
</parent-component>
Of course, one way is to pass in <some-other-component> to MyInputComponent for it to render, but I want that code separation as MyInputComponent has nothing to do with that other component. And I don't want the parent component to handle the parameters for the <button> either as all the control logic (e.g. onClick action, etc.) are local to MyInputComponent only.
Is there a way to return {<input>, <button>} to the parent component, so that it can render them at desire places?
Try to use array instead of fragment
const [MyFirstComponent, MySecondComponent] = () => {
return [
<h1>Element 1</h1>,
<span>Element 2</span>
];
}

useEffect not triggering when object property in dependence array

I have a context/provider that has a websocket as a state variable. Once the socket is initialized, the onMessage callback is set. The callback is something as follows:
const wsOnMessage = (message: any) => {
const data = JSON.parse(message.data);
setProgress(merge(progress, data.progress));
};
Then in the component I have something like this:
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress[pvc.metadata.uid]])
return (
{/* stuff */}
);
}
However, the effect isn't triggering when the progress variable gets updated.
The data structure of the progress variable is something like
{
"uid-here": 0.25,
"another-uid-here": 0.72,
...etc,
}
How can I get the useEffect to trigger when the property that matches pvc.metadata.uid gets updated?
Or, how can I get the component to re-render when that value gets updated?
Quoting the docs:
The function passed to useEffect will run after the render is
committed to the screen.
And that's the key part (that many seem to miss): one uses dependency list supplied to useEffect to limit its invokations, but not to set up some conditions extra to that 'after the render is committed'.
In other words, if your component is not considered updated by React, useEffect hooks just won't be called!
Now, it's not clear from your question how exactly your context (progress) looks like, but this line:
setProgress(merge(progress, data.progress));
... is highly suspicious.
See, for React to track the change in object the reference of this object should change. Now, there's a big chance setProgress just assignes value (passed as its parameter) to a variable, and doesn't do any cloning, shallow or deep.
Yet if merge in your code is similar to lodash.merge (and, again, there's a huge chance it actually is lodash.merge; JS ecosystem is not that big these days), it doesn't return a new object; instead it reassigns values from data.progress to progress and returns the latter.
It's pretty easy to check: replace the aforementioned line with...
setProgress({ ...merge(progress, data.progress) });
Now, in this case a new object will be created and its value will be passed to setProgress. I strongly suggest moving this cloning inside setProgress though; sure, you can do some checks there whether or not you should actually force value update, but even without those checks it should be performant enough.
There seems to be no problem... are you sure pvc.metadata.uid key is in the progress object?
another point: move that dependency into a separate variable after that, put it in the dependency array.
Spread operator create a new reference, so it will trigger the render
let updated = {...property};
updated[propertyname] =value;
setProperty(()=>updated);
If you use only the below code snippet, it will not re-render
let updated = property; //here property is the base object
updated[propertyname] = value;
setProperty(()=>updated);
Try [progress['pvc.metadata.uid']]
function PVCListTableRow(props: any) {
const { pvc } = props;
const { progress } = useMyContext();
useEffect(() => {
console.log('Progress', progress[pvc.metadata.uid])
}, [progress['pvc.metadata.uid']])
return (
{/* stuff */}
);
}

What is Reacts function for checking if a property applies?

Based off this Q&A:
React wrapper: React does not recognize the `staticContext` prop on a DOM element
The answer is not great for my scenario, I have a lot of props and really dislike copy-pasting with hopes whoever touches the code next updates both.
So, what I think might work is just re-purposing whatever function it is that React uses to check if a property fits to conditionally remove properties before submitting.
Something like this:
import { imaginaryIsDomAttributeFn } from "react"
...
render() {
const tooManyProps = this.props;
const justTheRightProps = {} as any;
Object.keys(tooManyProps).forEach((key) => {
if (imaginaryIsDomAttributeFn(key) === false) { return; }
justTheRightProps[key] = tooManyProps[key];
});
return <div {...justTheRightProps} />
}
I have found the DOMAttributes and HTMLAttributes in Reacts index.t.ts, and could potentially turn them into a massive array of strings to check the keys against, but... I'd rather have that as a last resort.
So, How does React do the check? And can I reuse their code for it?
The following isn't meant to be a complete answer, but something helpful for you in case I forget to come back to this post. The following code is working so far.
// reacts special properties
const SPECIAL_PROPS = [
"key",
"children",
"dangerouslySetInnerHTML",
];
// test if the property exists on a div in either given case, or lower case
// eg (onClick vs onclick)
const testDiv = document.createElement("div");
function isDomElementProp(propName: string) {
return (propName in testDiv) || (propName.toLowerCase() in testDiv) || SPECIAL_PROPS.includes(propName);
}
The React internal function to validate property names is located here: https://github.com/facebook/react/blob/master/packages/react-dom/src/shared/ReactDOMUnknownPropertyHook.js
The main thing it checks the properties against is a "possibleStandardNames" property-list here: https://github.com/facebook/react/blob/master/packages/react-dom/src/shared/possibleStandardNames.js
So to reuse their code, you can copy the property-list in possibleStandardNames.js into your project, then use it to filter out properties that aren't listed there.

declaring jsx element as a global variable

Hello is there a way to declare react component globally so that when I render the same component the values are persisted, I am aware the redux or some other state management lib can be used, but for the time being I want to use this option cuz I'm in the middle of the project. For example
constructor() {
this.accountInfoJSX = <AccountInformation ref={(sender) => {
this.accountInformationMod = sender;
}} />;
}
I can try to save the element in the global variable but each time when the render is called it initializes a new instance.
Edit:
yes sure. i have this method. I have 2 buttons on screen based on which i render different components. User can made some changes on each component, so i am trying to to use same component so that the changes made in component are persisted. i have tried returning this.accountInfoJSX as well as this.accountInformationMod not its not working. On former it re render the new component and values are lost and on latter it show nothing on screen.
get getCurrentScreen() {
if (this.state.selectedScreen == Screens.accountInformation) {
return this.accountInformationMod;
} else if (this.state.selectedScreen == Screens.myInformation) {
return <MyInformation />;
}
}

Resources