Backbone: Lost input focus after view re-render - backbone.js

I have a form with several input fields. When changing values in inputs, view with form is re-rendering. Here is some piece of Backbone.View code:
initialize: ->
#model.on('change', #render, #)
events:
'change input': 'change'
change: (event) ->
ctrl = #$(event.currentTarget)
#model.set(ctrl.data('name'), ctrl.val())
render: ->
#$el.html(#template(#model.toJSON()))
But re-rendering occurs also when I press tab key to set focus in next input and focus is lost. Is it possible to solve this problem with a little portion of code?

you could make the model update silent to prevent re-rendering:
change: (event) ->
ctrl = #$(event.currentTarget)
#model.set(ctrl.data('name'), ctrl.val(), {silent:'true'})

Currently you're (in essence) re-rendering the whole form every time anything changes. Ideally a better way to go would be to have different views for the different parts of the form, and only re-render the parts that actually need to be render-ed, based on the specifics of what changed.
But that doesn't really help if you're looking for:
is it possible to solve this problem with a little portion of code?
So here's a different approach you can try: modify your render to do something like:
render: ->
focusedElementId = $(':focus').attr('id');
#$el.html(#template(#model.toJSON()))
$('#' + focusedElementId).focus();
In other words, on every render store which element is focused (or rather, its ID, since that element will go away as part of your rendering), and then restore that focus after the render.
Of course, this assumes that all of the elements involved have IDs. If they don't, you'll either need to add IDs to them or find some other way to keep track of which element was focused. For example, you could do something like var focusedIndex = $('input').index();, but don't use that literally without testing first to ensure that the index stays consistent, because it might not be (if say the number or order of inputs on the page changes).

Related

How to hide or remove an element onClick in React?

I am trying to hide an element 'GorillaSurfIn' after I click on it.
But also it should fire the 'setShouldGorillaSurfOut' to 'true'. The second part works, but after I added this function:
function hideGorillaSurfIn() {
let element = document.getElementById('gorilla-surf-in');
ReactDOM.findDOMNode(element).style.display =
this.state.isClicked ? 'grid' : 'none';
}
After I click, the code falls apart.
Once I click, the element should be hidden/removed till the next time the App restarts.
Here is the Code Sandbox Link for further explanation.
I am open to any solutions, but also explanations please, as I am still fresh in learning React.
I have changed your code a bit to make it work. You can make further changes according to your need. A few things that I would like to add: -
You should avoid using findDOMNode (in most cases refs can solve your problem) as there are certain drawbacks associated with findDOMNode, such as the react's documentation states "findDOMNode cannot be used with functional components".
I've used refs (forward ref in this case) to make it work.
GorillaSurfIn was called twice, so there were two Gorilla gifs on the screen with same IDs. Not sure if that was the intended behaviour but each element should have unique ID.
Check out the code sandbox.

Store checkbox values as array in React

I have created the following demo to help me describe my question: https://codesandbox.io/s/dazzling-https-6ztj2
I have a form where I submit information and store it in a database. On another page, I retrieve this data, and set the checked property for the checkbox accordingly. This part works, in the demo this is represented by the dataFromAPI variable.
Now, the problem is that when I'd like to update the checkboxes, I get all sorts of errors and I don't know how to solve this. The ultimate goal is that I modify the form (either uncheck a checked box or vice versa) and send it off to the database - essentially this is an UPDATE operation, but again that's not visible in the demo.
Any suggestions?
Also note that I have simplified the demo, in the real app I'm working on I have multiple form elements and multiple values in the state.
I recommend you to work with an array of all the id's or whatever you want it to be your list's keys and "map" on the array like here https://reactjs.org/docs/lists-and-keys.html.
It also helps you to control each checkbox element as an item.
Neither your add or delete will work as it is.
Array.push returns the length of the new array, not the new array.
Array.splice returns a new array of the deleted items. And it mutates the original which you shouldn't do. We'll use filter instead.
Change your state setter to this:
// Since we are using the updater form of setState now, we need to persist the event.
e.persist();
setQuestion(prev => ({
...prev,
[e.target.name]: prev.topics.includes(e.target.value)
// Return false to remove the part of the array we don't want anymore
? prev.topics.filter((value) => value != e.target.value)
// Create a new array instead of mutating state
: [...prev.topics, e.target.value]
}));
As regard your example in the codesandbox you can get the expected result using the following snippet
//the idea here is if it exists then remove it otherwise add it to the array.
const handleChange = e => {
let x = data.topics.includes(e.target.value) ? data.topics.filter(item => item !== e.target.value): [...data.topics, e.target.value]
setQuestion({topics:x})
};
So you can get the idea and implement it in your actual application.
I noticed the problem with your code was that you changed the nature of question stored in state which makes it difficult to get the attribute topics when next react re-renders Also you were directly mutating the state. its best to alway use functional array manipulating methods are free from side effects like map, filter and reduce where possible.

Scroll to props.location.hash using useEffect after component mounts

I am creating a glossary page where each term has it's own card that is not expanded by default. Each card uses the term as it's ID because they will all be unique. I want to support direct links to a specific term via URL hash.
So, if the URL is localhost:3000/#/glossary#Actor, the initial load will scroll to the term and 'open' the card by simulating the click on the element (click handler on that element opens the card).
It is working, but too frequently. Every time I enter text into the search bar or cause a render in any way, it re-scrolls and does the card open animation again. I just want to initially scroll to the term if there is a hash, then ignore it unless it changes.
It only works if I don't include a dep array. If I include a dep array with props.location.hash, the el returns as null and nothing happens. I'm aware that the restarting of the effect is happening because of no dep array, but I don't know why it doesn't work when I add it.
useEffect(() => {
//looks for hash in URL; gets element associated with the hash
let el = document.getElementById(decodeURI(props.location.hash.slice(1)));
console.log(el)
//if element exists, get coords and offset for header before scrolling
if (el) {
const yCoordinate = el.getBoundingClientRect().top + window.pageYOffset;
const yOffset = -80;
window.scrollTo({
top: yCoordinate + yOffset,
behavior: 'smooth'
});
//opens the card only if not already open
if (!el.classList.contains('openTermCard')) {
el.click();
}
}
})
useEffect may not be the correct method. I might not even need a hook for this, idk. I just want this initial scroll event to happen on page load and not again.
It's a bit hard to figure out a solution without a working sandbox.
From your description, it seems to me your component is rendering more than once on the first load. If you put [] as dependency array, the effect only runs once, when the element is not defined yet (hence why you get null) and that's why it doesn't work.
One way to go about it is to think what else you can do in terms of designing your app. You mention that every time your component updates it focuses the card again, but that's kind of what you expect when you're using useEffect. Is it possible to re-structure the component so that the parent (the one with this side effect) doesn't re-render as often?
If you can provide a code sandbox I'm sure I (and probably others) will be able to help you further.

AngularJS UI Router is there a way to know when the final state has been entered?

I'm wondering if there is a way to know when the final child state has been entered for UI-Router.
Basically, here's some code...
$transitions.onFinish({ to : '*' }, (transition) => {
... do something
});
I'm doing something in there, but I only really want to do that something when the LAST CHILD STATE has been entered.
So basically, if I have states like:
master -> layout -> child layout
It can fire that onFinish two/three times, it goes top down... This causes a little bit of a problem inside there.
Anyone have a suggestion?
Edit -
This is something that I want happening for all child states. It's a dynamic action. The asterisk wildcard in the state transition is on purpose, and necessary.

Creating a stateful grid with reconfigure in ExtJS

We've been trying to achieve this for some days now but it seems impossible.
Is there a way to create a Stateful grid (i.e. saving the state of the columns etc.) while at the same time loading the data with reconfigure: true?
Essentially what we have is a combobox that decides what columns the grid has.
We want the user to be able to customize the grid's appearance for each of those combobox choices. Whenever the combobox value changes we load the grid with reconfigure: true to bring different columns in. However we have no idea how to save the state for those columns.
The state is saved in a database with a stateId that depends on the combobox choice so that we know which configuration we're looking at and what state to load depending on the combo choice. But from what we've come to understand, the state gets applied first, then the reconfigure so it always messes the state.
Anyone has any ideas about this?
If you don't exactly understand what I'm trying to do ask so I can elaborate more.
EDIT: We found out some stuff about how all this process works but still no answer. We're gonna try to edit the core code of ExtJS but we'd prefer to find a different solution.
Right now when you create a stateful reconfigurable grid this is what happens:
1. The Grid gets created
2. The state gets applied (the one from the database)
3. The grid gets rendered but since it has no data/columns it has an 'empty' state
4. This state gets saved back (essentially ruining our original correct state)
5. The store gets loaded and the columns get added but there is a partial rendering (the grid itself doesn't get re-rendered).
6. The state now is the default one with the columns and it gets saved back to the database
Even manually applying a state does nothing since it doesn't get rendered. We've tried many 'rendering' options but nothing seems to work. Any ideas?
You can override the getState and applyState functions to exactly reflect your needs.
In getState (get current state so your provider can save it to the db) with something like:
getState: function() {
var me = this;
var state = me.callParent();
var storeState = me.store.getState();
if (storeState) state.storeState = storeState;
var columns = me.getColumnManager().getColumns();
var stateColumns = [];
columns.forEach(function(e) {
stateColumns.push({
text: e.text,
dataIndex: e.dataIndex,
hidden: e.hidden,
flex: e.flex,
});
});
state = me.addPropertyToState(state, 'columns', stateColumns);
return state;
}
You are flexible to save any state of your component to the state property.
In your setState method, you can just retrieve the saved attributes and reconfigure your grid depending on it.
applyState: function(state) {
var columns = state.columns;
this.callParent(arguments);
if (columns) {
this.reconfigure(undefined, columns);
}
}
Voila, you have a "dynamic grid" (columns are defined via the saved state in your db) and it is stateful.

Resources