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.
Related
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.
I'm working on a modal component where users can edit the contents of a post they created.
I have it set up where a notification window pops up if the user tries to close the modal without saving.
However, sometimes users don't make any changes and I don't want them to see this notification, I just want the modal to close.
I could create separate states to store the original state then compare those states to the current states, but I have a lot of states (15) and that seems messy.
I'm wondering if there is a way for me to check if any set of particular states have been changed at all? That way, I would know to show the notification or not.
It seems that you use hooks, so in your case I would suggest to refactor a little bit your component with the states like below and after this has been done to use the JSON.stringify to check if something has been changed. You will actually have one state as an object.
const [var1, setVar1] = useState($initialVar1);
const [var2, setVar2] = useState($initialVar2);
const [var3, setVar3] = useState($initialVar3);
...
const [var14, setVar14] = useState($initialVar14);
to
const [componentState, setComponentState] = useState({
var1: $initialVar1,
var2: $initialVar2,
var3: $initialVar3
...
})
Wherever you want to update the individual states you can use the spread operator
setComponentState({
...componentState,
varX: newValue
})
first I should mention that I'm very new to react so this might be something silly..
I've struggled to get ReactTable's column to be sortable with the help of mattlockyer's gist
What I don't understand is I call setState on columns and then the render call is triggered correctly but it doesn't update the layout.
The render function "columns" contains an updated object with the correct column order, however render does not change the column order nor does sorting columns content.
This is the code in question,
this.setState({
columns: newColLayout,
}, () => {
this.mountEvents();
});
It will trigger a render, but the columns stay the same.
Ive put together a codepen with my current work and with comments about the code in question.
Any help would be greatly appreciated.
OK so i think i figured out whats going on.
The issue is splice modifies the state without triggering a change event, then when the event is fired from setStat, react doesn't see the change made to the column layout as it matches the current value.
The way i get around this is by cloning the column array using slice.
// Clone the current column state.
const columnClone = this.state.columns.slice(0);
// Remove item from array and stick it in a new position.
columnClone.splice(i, 0, columnClone.splice(this.draggedCol, 1)[0]);
Here is the updated code...
Still might be improved by someone who has more experience with react.
I'm having trouble figuring out how i should properly go about adding a empty row to my ag-grid at the top of the table.
Right now this is how my component / user lifecycle is:
Load page
Render Grid (from componentDidMount, asking for data from reducer)
fetch data from db (list of roles)
update my reducer to have list of roles (normalized) -> send to component
My RolesComponent now denormalizes that data it received
Display on grid
Click on [Add Button] -> dispatches action to create role {name: '', description: ''}
Sends to api, api returns new role
Reducers adds new role to list of roles
Refreshes grid component since new props are loaded, new empty role is added to bottom of the table
Because the role I add is new, its 'id' is the highest, hence I believe it goes to the end of the table. However i could skip any reducer logic to add (step 9) and just manually do it like this:
this.props.api.addDispatcher({name: '', description: ''}).then((result) => {
this.props.api.updateRowData({ add: [result] });
});
Where the reducer does not have a case for 'ADD_ROLE', however this feels dirty. I'm pretty stuck and don't believe this is the right path to take. A general or better approach to take is very much appreciated.
Even ontop of that i'm stuck on whether I should be utilizing redux to rehydrate my table or be depending on ag-grids api. Ideally, I feel like using ag-grids api is more efficient.
You can use addIndex property, so in this case
this.props.api.updateRowData({ add: [result], addIndex: 0 });
You can use the grid property of defaultGroupSortComparator and provide a comparator of id it to make grid sorting.
// your comparator
const sortbyId(first, second) => {
// the logic;
}
render() {
return(
<AgGridReact
defaultGroupSortComparator={this.sortById}
/>);
}
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).