I am going through the code of a project and I see the following code:
export const FileLink = React.memo(({ url, data, ext, linkContent }) => {
...
...
if (!url.includes('?')) {
url += '?'
}
if (!url.endsWith('?')) {
url += '&'
}
return <a href={`${url}file_format=${ext}`}>{linkContent}</a>
})
It is working fine and no bugs appear in app behavior. But url is a passed parameter and it is changed within the FileLink: from what I read React components should be pure functions. So, I wonder whether its ok to do that, under which circumstances, and if not - why? What can go wrong? Any examples of how it could mess up the app?
(If interested to see the full code: https://github.com/broadinstitute/seqr/blob/8b4419285dfac9365c5c500bbb87b89808c0cedd/ui/shared/components/buttons/ExportTableButton.jsx#L37)
url is a local variable. Reassigning that variable, which is all this code is doing to it, has no possibility of affecting code outside of this function call. It doesn't make the function impure.
Now, if you were passed in an object, and you started mutating that object, then that would break purity. Because if the component that passed you this object is still using it, then it can "see" that change. For example:
const Example = ({ someObjectProp }) => {
someObjectProp.name = 'bob';
}
Related
I got multiple buttons that render different modals.
The modals may render different results according to the data provided in the state.
I got 3 state brackets I need to consider
const [cartChangeStoreShow, setCartChangeStoreShow] = useState(false);
const [orderLineId, setOrderLineId] = useState('');
const [storeId, setStoreId] = useState('');
cartChangeStoreShow is for controlling the visible state of the modal
What I want to do is I wanna change OrderLineId and storeId before rendering the component.
The data will change according to the orderlineId and storeId.
The component is like this
<CartChangeStorePopUp
visibility={cartChangeStoreShow}
setCartChangeStoreShow={setCartChangeStoreShow}
orderLineId={orderLineId}
storeId={storeId}
/>
I am calling api inside CartChangeStorePopUp component according to prop data.
So I am handing the user press button like this.
<TouchableOpacity
onPress={() => renderCartChangeStore(cartItem)}>
<Text>
Change Store
</Text>
</TouchableOpacity>
const renderCartChangeStore = async cartItem => {
try {
await setOrderLineId(cartItem.orderLineId);
await setStoreId(cartItem.storeId);
} catch (err) {
console.log(err);
} finally {
setCartChangeStoreShow(true);
}
};
the code is working now but from what I read before
Async Await doesn't work properly with setState,So I wanna know if there is potential error with the code written here
To me, it does not make sense both the async/await presence and the try/catch/finally.
Async/await is useful when the function you're calling is dealing with something like I/O, time-consuming, where you cannot do anything than "wait" for the completion. Since "to wait" might be something not desirable in a UI context, the async/await pattern helps you to keep track to the "slow function", but even leave the CPU free to serve other useful tasks.
That being said, the "setXXX" functions of React.useState are not time-consuming: no I/O or similar task involves. Hence, the async/await is not applicable.
Going further, the "setXXX" functions of React.useState throw no error on setting. They're much like setting a variable like so:
var storeId = "";
function setStoreId(value) {
storeId = value;
}
That is, the try/catch/finally is quite useless.
If you want, you might optimize the code by grouping the three variables as a single immutable object. However, that's up to your real code.
const [storeState, setStoreState] = useState({
cartChangeStoreShow: false,
storeId: "",
orderLineId: ""
});
const renderCartChangeStore = cartItem => {
setStoreState({
cartChangeStoreShow: true,
storeId: cartItem.storeId,
orderLineId: cartItem.orderLineId,
});
};
Here is a more compact way to achieve the same behavior:
const renderCartChangeStore = cartItem => {
setStoreState({
cartChangeStoreShow: true,
...cartItem,
});
};
Bear in mind that is very important that you treat the storeState as immutable. That is, never ever change a field of the object, rather create a brand new object with the new field value.
At that point, the component should be called like so:
const handleCartChangeStoreShow = value => {
setStoreState({
...storeState,
cartChangeStoreShow: value,
});
}
<CartChangeStorePopUp
visibility={storeState.cartChangeStoreShow}
setCartChangeStoreShow={handleCartChangeStoreShow}
orderLineId={storeState.orderLineId}
storeId={storeState.storeId}
/>
Notice the handler to correctly alter the storeState object. Worthwhile mention how the new value is set. First, all the current storeState is copied to a fresh new object, then the new show value is also copied on the same object. However, since that happens after, it'll have an override-effect.
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 */}
);
}
The current problem I have is that i want to use navigation parameters to update the state
The tutorial in the link above uses React Navigation 4.x while I use React Navigation 5.x
Tutorial:
function onSaveNote() {
navigation.state.params.addNote({ noteTitle, noteValue })
navigation.goBack()
}
MyProject:
function onSaveAuction() {
navigation.navigate('Home', { auctionTitle, auctionValue }
}
This is the warning I would get whenever I used used the code for 4.x
I have tried using the second bullet point which is to use navigate instead but it still does not seem to work.
Any help would be appreciated.
There is nothing wrong with the syntax(except for the bracket you forgot to close). Your problem is with the data you are trying to pass. The warning tells you that you are trying to pass non-serializable values such as class instances, functions etc. So check again what are the values of auctionTitle and auctionValue.
We don't know your data, however you shouldn't pass functions or class in nav params.
To make sure that your data doesn't have non-serializable data, as mentioned above, you can try do a JSON.stringify(), then JSON.parse in next screen to see if this warning disappears.
The best solution is to check your data, but if you need to pass non-serializable data, feel free to use JSON.
I made a example to you:
Passing params:
function onSaveAuction() {
/* It will remove any functions, class or other non-serializable from params. */
const data = JSON.stringify({ auctionTitle, auctionValue });
navigation.navigate('Home', { data });
}
Home.js
function Home({ route, navigation }) {
/* Get the param, then parse to object */
const data = JSON.parse(route.params.data);
}
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.
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.