The short question is: how do we freeze a component's content, probably by using useMemo() and telling it to freeze the content?
That's because useMemo(fn, []) takes the array to do a diff of values to decide whether to use the memoized value. It does not take a flag of true to tell it to use the memoized value.
I thought of one way, which is
useMemo(fn, [flag || `${Date.now()} ${Math.random()`])
so if flag is true, it won't evaluate the second part, and when it is true for a second time, the content is frozen. The second option is to use uuid() instead of the second part, which should be unique every time. The third choice is to gather all parameters that causes the output to be the same and put it into the array, which may be difficult to collect all, and is prone to bugs.
But this method is a bit hacky... and it may require comparing the performance of ${Date.now()} ${Math.random() vs uuid() because if it is CPU intensive, it only makes the situation worse.
Details:
This comes from wanting to slide a panel out and not update it, because the panel is very busy updating and the main window is busy updating continuously. To do that, when the user click the "Update Main Window" button, we dispatch an action to set the redux state, and we can slide out the panel, and on complete, we dispatch another action so that a redux state will tell the panel not to update and just return <div></div>. Another way is to just dispatch the first action, and be able to "freeze" the component. In this case, we don't need to dispatch the second action.
But useMemo() doesn't take a "freeze" flag, and take an array of dependencies instead. Is there a way to use a flag to cause it to freeze?
If I understand correctly your actual dependency is on the "Update Main Window" button.
You can add state like this and it should update content after user clicks on the button.
useMemo documentation
const [updateFlag, setUpdateFlag] = useState(false);
useMemo(() => {}, [updateFlag]);
return (
<div>
<button onChange={() => setUpdateFlag(prevUpdateFlag => !prevUpdateFlag)}>update main window</button>);
Related
The kinda the same problem as described here
How to compare oldValues and newValues on React Hooks useEffect?
But in my case, usePrevious hook does not help.
Imagine the form with several inputs, selects, and so on. You may want to look at https://app.uniswap.org/#/swap to make a similar visualization. There are several actions and data updates that will be happened on almost any change, which will lead to several re-renders, at least 4. For example.
I have 2 inputs, each represents a token. Base(first one) and Quote(second one).
This is a state for Base
const [base, setBase] = useState({
balance: undefined,
price: undefined,
value: initState?.base?.value,
token: initState?.base?.token,
tokenId: initState?.base?.tokenId,
});
and for Quote
const [quote, setQuote] = useState({
balance: undefined,
price: undefined,
value: initState?.quote?.value,
token: initState?.quote?.token,
tokenId: initState?.quote?.tokenId,
});
They gonna form a pair, like BTC/USD for example.
By changing token (instead of BTC I will choose ETH) in the select menu I will trigger several actions: fetching wallet balance, fetching price, and there are gonna be a few more rerenders with input view update and modal window close. So at least 4 of them are happening right now. I want to be able to compare base.token and basePrv with
const basePrv = usePrevious(base?.token); but on the second re-render base.token and basePrv gonna have the same token property already and it is an issue.
I also have the swap functionality between the inputs where I should change base with quote and quote with base like that
setBase(prevState => ({
...prevState,
base: quote
}));
setQuote(prevState => ({
...prevState,
quote: base
}));
In that case, there are no additional requests that should be triggered.
Right now I have useEffect with token dependency on it. But it will be fired each time when the token gonna be changed which will lead to additional asynchronous calls and 'tail' of requests if you gonna click fast. That's why I need to compare the token property that was before the change to understand should I make additional calls and requests because of the formation of new pair (BTC/USD becomes ETH/USD) or I should ignore that because it was just a "swap" (BTC/USD becomes USD/BTC) and there is no need to make additional calls and fetches. I just had to, well, swap them, not more.
So in my story, usePrevious hook will return the previous token property only once, and at the second and third time, it would be overwritten by multiple re-renders(other properties would be fetched) to the new one. So at the time when useEffect gonna be triggered, I would have no chance to compare the previous token property and the current one, because they will show the same.
I have several thoughts on how to solve it, but I am not sure is it right or wrong, because it seemed to me that the decisions look more imperative than declarative.
I can leave everything as it is (requests would be triggered always on any change no matter what it was. Was it a swap or user changed a pair). I can disable the swap button until all of the requests would be finished. It would solve the problem with requests 'tail'. But it is a kinda hotfix, that gonna be work, but I do not like it, because it would lead to additional unnecessary requests and it would be slow and bad for UX.
I can use a state to keep the previous pair on it right before the update by setBase or setQuote happens. It will allow me to use useEffect and compare previous pair to the current one to understand did the pair was changed, or just swapped and take the decision should I make fetches and calls or not.
I can get rid of useEffect with base.token and quote.token dependencies and handle everything inside of onClick handler. Because of that, the swap functionality would not trigger useEffect, and calls and fetches would be fired only if the user gonna click and choose something different. But as I said this option seemed a little bit odd to me.
I tried to use closure here, to "remember" the previous state of tokens, but it is kinda similar to use the current component state. Also, you have to initialize closure outside of the functional component body, and I do not see a possibility to transfer the init state into it that way, so the code becomes more spaghettified.
So any other ideas guys? I definitely missing something. Maybe that much of re-renders is an antipattern but I am not sure how to avoid that.
There could be multiple solutions to your problem. I would suggest to pick one which is easier to understand.
1. Modify the usePrevious hook
You can modify the usePrevious hook to survive multiple renders.
Tip: use JSON.stringify to compare if you think the value will be a complex object and might change the reference even for same real value.
function usePrevious(value) {
const prevRef = useRef();
const curRef = useRef();
if (value !== curRef.current){
// or, use
// if ( JSON.stringify(value) !== JSON.stringify(curRef.current)){
prevRef.current = curRef.current;
curRef.current = value;
}
return prevRef.current;
}
2. Sort useEffect dependency array
Since you're using tokens(strings) as dependency array of useEffect, and you don't mind their order (swap shouldn't change anything), sort the dependency array like
useEffect(
() => {
// do some effect
},
[base.token, quote.token].sort()
)
3. Store the currently fetched tokens.
While storing the API response data, also store the tokens(part of request) associated with that data. Now, you'll have 2 sets of tokens.
currently selected tokens
currently fetched tokens
You can chose to fetch only when the currently fetched tokens don't fulfil your needs. You can also extend this and store previous API request/responses and pick the result from them if possible.
Verdict
Out of all these, 3rd seems a nice & more standardised approach to me, but an overkill for your need (unless you want to cache previous results).
I would have gone with 2nd because of simplicity and minimalism. However, It still depends on what you find easier at the end.
I have a table of editable data that's displayed with react-table. I'm using react-select for the cells. Whenever I change an entry, it takes a long time (close to a second) for the page to update. How can I speed this up?
Here's a fork of the official "editable table" example: https://codesandbox.io/s/gracious-cdn-fcu3i?file=/src/App.js, modified to use <Select> instead of <input>.
Part of the code from that official example updates the data on blur:
// We'll only update the external data when the input is blurred
const onBlur = () => {
updateMyData(index, id, value)
}
This means that even tabbing through the elements is extremely slow. I was able to address that by adding a check in updateMyData to abort if the data didn't actually change:
const updateMyData = (rowIndex, columnId, value) => {
if (data[rowIndex][columnId] === value) return;
etc. // (otherwise update)
}
So that fixes the tabbing issue, I believe, but what about data entry? Could it be made asynchronous, for example, or would that have dangerous consequences?
The reason they use onBlur with inputs is to avoid re-rendering the whole table after each keystroke, with selects you don't have the same problem, so you don't need to use onBlur at all, just report the update in the onChange handler (no need to keep the value in the cell state either). Saying that, the whole solution doesn't look particularly efficient - after each change the table is re-rendered twice. I've wrapped updateMyState into useCallback and SelectCell in memo, which seems to make it a bit better, but you might want to find another approach, particularly if you have a big table. Anyways, have a look here https://codesandbox.io/s/tender-night-vymfi?file=/src/App.js
With react-color https://casesandberg.github.io/react-color/ .
I can use ready-made onChangeComplete function from react-color.
But I wonder how can I create that onChangeComplete by using input type color tag.
I tried onBlur but the color won't change until user clicks or presses tab
On the other hand, using onChange keep firing updates.
Because currently I'm using redux state, so dispatching update continuously when I drag and choose color isn't a good way.
Any ideas how to create onChangeComplete?
It depends on how you'd like to define a change. To prevent continuous updates every time the mouse moves, you'll probably want to update Redux state only when the mouse stops moving. (This seems to be the behaviour on the page you linked to).
You could use the following JavaScript to detect when the mouse stops moving:
let timer;
window.addEventListener('mousemove', function (e) {
console.log('Mouse moving');
clearTimeout(timer);
timer = setTimeout(() => console.log('Mouse stopped'), 300);
});
Then, try putting the above code inside your ComponentDidMount() method and replace console.log('Mouse stopped') with whatever Redux action you want to dispatch!
300 is the number of milliseconds without movement that will trigger the action, which could be changed depending on how sensitive you want your app to feel.
Lastly, remember to remove the event listener in your ComponentWillUnmount() method.
https://github.com/casesandberg/react-color/blob/7ee60d845e5d5e11e4f6ecb895b2d9755c59d09d/src/components/common/ColorWrap.js#L30
Here is the code that how react-color implemented onChangeComplete.
It is hard coded using debounce.
I put the link there for anyone interested in using that solution
I have a problem and I'm pretty sure I'm not the only one who ever had it... Although I tried to find a solution, I didin't really find something that fits my purpose.
I won't post much code, since its not really a code problem, but more a logic problem.
Imagine I have the following hook:
useEffect(() => {
fetchFromApi(props.match.params.id);
}, [props.match.params.id]);
Imagine the result of fetchFromApi is displayed in a simple table in the UI.
Now lets say the user clicks on an entity in the navigation, so the ID prop in the browser URL changes and the effect triggers, leading to an API call. Lets say the call with this specific ID takes 5 seconds.
During this 5 seconds, the user again clicks on an element in the navigation, so the hook triggers again. This time, the API call only takes 0,1 seconds. The result is immediatly displayed.
But the first call is still running. Once its finished, it overwrites the current result, what leads to wrong data being displayed in the wrong navigation section.
Is there a easy way to solve this? I know I can't cancel promises by default, but I also know that there are ways to achieve it...
Also, it could be possible that fetchFromApi is not a single API call, but instead multiple calls to multiple endpoints, so the whole thing could become really tricky...
Thanks for any help.
The solution to this is extremely simple, you just have to determine whether the response that you got was from the latest API call or not and only then except it. You can do it by storing a triggerTime in ref. If the API call has been triggered another time, the ref will store a different value, however the closure variable will hold the same previously set value and it mean that another API call has been triggered after this and so we don't need to accept the current result.
const timer = useRef(null);
useEffect(() => {
fetchFromApi(props.match.params.id, timer);
}, [props.match.params.id]);
function fetchFromApi(id, timer) {
timer.current = Date.now();
const triggerTime = timer.current;
fetch('path').then(() => {
if(timer.current == triggerTime) {
// process result here
// accept response and update state
}
})
}
Other ways to handle such scenarios to the cancel the previously pending API requests. IF you use Axios it provides you with cancelToken that you can use, and similarly you can cancel XMLHttpRequests too.
I need to propagate state changes to user screen as quickly as possible for some important UI elements, defer other element renderring a bit.
I know about setState's callback, it doesn't help me here.
I think fiber priorities could help me, but I don't know how to use them.
Example:
I have a button that must be disabled immediately after click.
I also have many other slow components that change on that button click.
React batches rendering of the disabled button and other slow components together so the button does not get disabled immediately.
Current workaround is to delay other state changes, to make React immediately disable the button, and only then start to modify other components:
this.setState({ enabled: false }, () => {
this.debounce = setTimeout(() => {
this.props.onModified(value);
}, 200);
})
Is there some explicit way to tell React to be fast to render in some important state changes, without batching them?
(The problem is not only with buttons, but with immediate closing of the modal dialogs as well)
https://codesandbox.io/s/kk4o612ywr
You can use callback function of the setstate, something like this, which will ensures the rendering of the first change. so, your button will get the disabled first and then you can update your state with different operations. using timeout will not be accurate as there is fixed timing which will cause the inaccurate results.
Here is what I did:
this.setState({ enabled1: false },function(){
this.setState(prevState => ({ data: prevState.data + 1 }));
});
Demo