I've implemented a Dialog component which displays the values of some state variables.
When the Dialog closes, the state variables are reset to default. The problem is that although the Dialog closes in less than a second, it's still enough time to see that the text has changed.
Is there a way to stop the text from changing until the Dialog has actually closed? I tried setting the dialogOpen state variable to false first, before resetting the other state variables to default, but it didn't help since it happens too fast and React asynchronously batches the state changes anyway.
<Dialog
open = {dialogOpen}
onClose = {() => handleDialogClose()}
... />
handleDialogClose():
const handleDialogClose = () =>
{
setDialogOpen(false)
setStateVariables(DEFAULT_VALUES)
}
I think this issue is general to more components than just the Dialog, since it also happens with text that appears on Snackbar components. A general solution to all of it would be best, but anything will help.
Edit: The Dialog uses a Fade transition, where the timeout.exit delay is about 200ms. Using setTimeout() to delay updating the state variables works, per #technophyle's answer below. The delay passed to setTimeout() can be 0 and the extra time is still enough to prevent the change being seen.
This is a tricky issue to resolve gracefully if your Dialog component has a CSS transition animation.
One solution (though not pretty) if you know the transition duration:
const handleDialogClose = () =>
{
setDialogOpen(false)
setTimeout(() => {
setStateVariables(DEFAULT_VALUES)
}, TRANSITION_DURATION_MS)
}
Related
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>);
I'm creating my own simple snackbar/toast stacker. However, I'm having problems with queing them in an orderly manner. Removing a snackbar from the snackbar que causes re-render and odd behavior.
The basic flow:
Click a button which causes the addSnack function to fire which is provided by the withSnackbar HOC.
Take the parameters from the fired function, and create a snack accordingly and add it to the snackbar list.
At the end, we render the snackbar list.
Each snackbar controls it's own appearance and disappearance, and is controlled by a time out. After the timeout is fired, it calls removeSnack function which is suppose to remove the first snack from the list.
codesandbox
If you click the button for example, four times in a short amount of time. They render nicely, but when the first one is to be deleted, they all disappear and reappear abnormally.
I understand that it's partially the state re-renderings fault, however, I'm not sure how to handle it in a way that the removal is handled gracefully without affecting the rendering of other snacks.
So, after many hours of trial and error, I found a solution that works so far. Moving and reading the snacks outside of the state helped with the bizarre rendering problems, and with it, I was able to create a message que which works well.
Working example
Codesandbox
If you look at splice document, you will notice that it's returning an array of deleted elements and not the initial array.
You can correct it by splicing then updating:
snacks.splice(-1, 1);
addSnacks(snacks);
However you are still going to have some weird behavior and you might need to use a keyed list to fix that.
i had the same issue and i saw your solution, but i was really trying to find out why it happens - here is why:
when u call a useState hook from an async function's callback, you should use the callback format of the hook to make sure that you are working with the latest value. example:
const [messages, setMessages] = useState([]);
const addMessage = ( message ) => {
setMessages( prevMessages => {//prevMessages will be the latest value of messages
return [ ...prevMessages, message ];
});
};
const removeMessage = ( index ) => {
setMessages( prevMessages => {//prevMessages will be the latest value of messages
let newMessages = [...prevMessages];
newMessages.splice( index, 1 );
return newMessages;
});
};
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
With current version of react-navigation, there are two ways to check if a screen is focused or not, by either (1) calling function isFocused of screen's prop navigation, or (2) hook the component to withNavigationFocused and retrieve prop isFocused.
However, both methods always return true when navigation starts. In my case, I need something triggering only when screen transition ends, i.e. new screen is fully focused. This is to deal with heavy-rendered children, such as camera or map, which should be rendered after screen transition to avoid slow transition animation.
Any idea how to achieve that?
You can try subscribing to the navigation lifecycle events, as described in the docs:
const didFocusSubscription = this.props.navigation.addListener(
'didFocus',
payload => {
console.debug('didFocus', payload);
}
);
Another example usage in this repo
While React with Redux is excellent at modeling UI state, there are occasionally situations where something just happens, the UI needs to handle that event in a discrete procedural way, and it doesn’t make sense to think of that transient event as a piece of state that would persist for any period of time.
Two examples, from a JS Bin-like code editor application:
A user exports their code to a GitHub gist. When the export is complete, we want to open a new browser window displaying the gist. Thus, the React component hierarchy needs to know the ID of the gist, but only at a single moment in time, at which point it will open the window and stop caring about the gist export altogether.
A user clicks on an error message, which causes the editor to bring the line where the error occurred into focus in the editor. Again, the UI only cares about which line needs to be focused for a single moment in time, at which point the (non-React-based) editor is told to focus the line, and the whole thing is forgotten about.
The least unsatisfying solution I’ve come up with is:
When the triggering event occurs, dispatch an action to update the Redux state with the needed information (gist ID, line to focus)
The React component that’s interested in that information will monitor the appropriate props in a lifecycle hook (componentWillReceiveProps etc.). When the information appears in its props, it takes the appropriate action (loads the gist window, focuses the editor on the line)
The component then immediately dispatches another event to the Redux store essentially saying, “I’ve handled this”. The transient event data is removed from Redux state.
Is there a better pattern for this sort of situation? I think one perhaps fundamental part of the picture is that the UI’s response to the action always breaks out of the React component structure—opening a new window, calling a method on the editor’s API, etc.
Your solution would certainly work, but these sorts of problems you bring up don't sound particularly well suited to be handled with redux at all. Just using plain React and passing the necessary functions to your component sounds a lot more natural to me.
For the export case, for instance, rather than dispatching an action which updates some state, which then triggers the new window to open, why not just open the new window in place of dispatching that action? If you have the info necessary to dispatch an action to trigger opening a window, you ought to be able to just open the window in the same place.
For the example where clicking an error message triggers calling a non-React, imperative api, pass a function from the nearest common parent of the error message and the editor. The parent can also maintain a ref to the wrapper around the editor. Even if it's multiple levels deep, it's not too bad to get a ref to what you want if you pass down a function to set the ref. Thus, the function passed down from the parent to the error message component can simply call a method on the ref it maintains to the editor. Basically, something like this:
class Parent extends Component {
constructor(...args) {
super(...args)
this.onErrorMessageClick = this.onErrorMessageClick.bind(this)
}
onErrorMessageClick(lineNumber) {
if (this.editor) {
this.editor.focusOnLine(lineNumber)
}
}
render() {
return (
<div>
<ErrorMessage onClick={ this.onErrorMessageClick } lineNumber={ 1 } />
<ErrorMessage onClick={ this.onErrorMessageClick } lineNumber={ 2 } />
<EditorWrapper editorRef={ (editor) => { this.editor = editor } } />
</div>
)
}
}
const ErrorMessage = ({ onClick, lineNumber }) => (
<button onClick={ () => onClick(lineNumber) }>
{ `Error Message For ${lineNumber}` }
</button>
)
// Just adding EditorWrapper because your editor and error messages probably aren't right next to each other
const EditorWrapper = ({ editorRef }) => <Editor ref={ editorRef } />
class Editor extends Component {
focusOnLine(lineNumber) {
// Tell editor to focus on the lineNumber
}
render() {
...
}
}
I often use redux-thunk for these types of events.
Essentially, its not different to setting up a normal thunk, only I don't dispatch an action in it.
const openGist = (id) => () => {
// code to open gist for `id`
}
I then use this action creator like I would any other from the component triggering it, e.g. mapped in mapDispatchToProps and called in an onClick handler.
A common question I get asked is why don't I just put this code straight into the component itself, and the answer is simple - testability. It is far easier to test the the component if it doesn't have events that cause side effects and it's easier to test code that cause side effects in isolation to anything else.
The other advantage is that more often than not, the UX designers will step in at some point and want some kind of feedback to the user for some of the events of this nature (e.g. briefly highlight the error row) so adding an X_COMPLETED action to the event is much easier at this point.