I have a Map _filterMapLocal: Map<string, Filter>;
And the Filter object has this attribute options: FilterOption[].
It is an array of FilterOption. A FilterOption also has this attribute selected: boolean.
My goal is now to select this Map and change all selected values to false.
Is this possible?
My solution was a nested for loop like that:
this._filterMapLocal.forEach(filter => filter.options.forEach(option => option.selected = false))
But it is not working well... and I think in respect to performance this is bad.
Is there another way?
Basically my goal is to reset the filter.
The initial state is false and when I choose some filter selected will be true.
I have a clear all button which should change the state to false again.
I try to make a minimal reproduciple example...
export interface FilterOption {
selected: boolean;
}
export class Filter {
options: FilterOption[];
constructor(options?: FilterOption[]) {
this.options = options || [];
}
}
const _filterMapLocal = new Map<string, Filter>();
const filter1Options: FilterOption[] = [
{ selected: true },
{ selected: false },
{ selected: false }
];
const filter1 = new Filter();
filter1.options = filter1Options;
_filterMapLocal.set("filter1", filter1);
Related
With my e-commerce requirements, I have a filter that consists of multiple checkboxes. The user selects one or multiple checkboxes to filter the list of items in a table.
The checkboxes arrangement looks something like this (sample):
Mathematics
maths checkbox1
maths checkbox2
maths checkbox3
Physics
phy checkbox1
phy checkbox2
phy checkbox3
As any of the filters(checkboxes) are selected, the filters have to be displayed in a component, like this:
With some workaround, I was able to implement a single array containing the selected filter but not sure how to handle this particular way of grouping filters belonging to a same category.
Tried so far:
initialstate = {
filters: [
{
title: '',
options: [
{
name: '',
key: '',
label: '',
}
],
}
],
selected: []
}
const setFilters = (value) => {
setFilterOptions((state) => {
const selectedFilters = state.selected.find(
(obj) => obj === value
);
const selected = selectedFilters
? state.selected.filter(
(obj) => obj !== value
)
: [...state.selected, value];
return {
selected,
filters: [
...state.filters.map((filter) => {
return {
...filter,
options: filter.options.map((ele) => {
return {
...ele,
checked: selected.includes(ele.label)
};
})
};
})
]
};
});
};
The algo that I could think of:
function setFilters(value) takes the label of the filter selected.
component state:
const [filterOptions, setFilterOptions] = useState(initialState);
inside this function, loop over the state.selected array of objects and check to which category the value belongs.
if the value does not exist for that category, add it an array. If it already exists (for the same category), then push it to the respective array.
map over the final array to render the names of the filters (as in picture).
I am a bit confused about how to implement this logic actually.
Need a bit help to implement this in the best possible way.
Thank you in advance.
I am building a basic visualization system that layers can be toggled from the control box.
I have layers that merge individual layers into one.
const [layers, setLayers] = useState([densityLayer, pedestrianLayer]);
I have filterState that tracks the activity in the control box. It contains the layer object as a property of linkTo
const [filterState, setFilterState] = useState([
{
id: 'densityFilter',
checked: true,
description: 'Population density',
linkedTo: densityLayer
},
{
id: 'pedestrianFilter',
checked: true,
description: 'Pedestrian volume',
linkedTo: pedestrianLayer
}
]);
and everytime checked property in filterState gets updated, it launches renderLayers()
which will select corresponding layers whose checked property is true.
useEffect(()=>{
renderLayers();
},[filterState]);
const renderLayers = () => {
const newLayers = [];
filterState.map(filter => (filter.checked && newLayers.push(filter.linkedTo)));
setLayers(newLayers);
}
Then layers is passed to DeckGL component as a layer prop.
<DeckGL
initialViewState={viewState}
controller={true}
layers={layers}
>
In my program, turning off the layers works fine, but they do not turn back on. In the console, I noticed that the lifecycles between layers are different. Is there anything incorrect about my approach?
Have you try to use visible property of layers? If you are going to switch multiple time and often, deck.gl suggests to use visible instead of recreated a new layer.
Some useful resources about this thread:
Creating layer instances is cheap.
Layer visibility over addition and removal.
Visible property.
First, create a Control box, like you did.
Then pass as props to DeckGL component what you selected from control box.
{
layer1: true, layer2: false, layer3: false, layer4: false,
}
Create a state for layers.
const [activeLayers, setActiveLayers] = useState(layersProps);
Check with useEffect when something changes in layersProps.
useEffect(() => {
const layers = {
layer1: false,
layer2: false,
layer3: false,
layer4: false,
};
if (typeMap === 'layer1') {
layers.layer1 = true;
} else if (typeMap === 'layer2') {
layers.layer2 = true;
}
...
setActiveLayers(layers);
}, [layerProps]);
Or you can create a state
const [activeLayers, setActiveLayers] = useState({
layer1: true, layer2: false,
});
And pass as props only what you selected from control box and check for changes.
useEffect(() => {
const layers = {
layer1: false,
layer2: false,
};
if (typeMap === 'layer1') {
layers.layer1 = true;
} else if (typeMap === 'layer2') {
layers.layer2 = true;
}
...
setActiveLayers(layers);
}, [inputLayerSelected]);
If you prefer you can also split each layer with a single one state (so you have a primitive value).
Finaly, you can create your layer
const layer1 = new ScatterplotLayer({
id: 'scatter',
data: data,
....
visible: activeLayers.layer1, OR
visible: layer1
});
and render
<DeckGL
layers={[layer1, layer2, layer3, layer4]}
...
>
Let's say a component has state such as:
this.state = {
enabled: {
one: false,
two: false,
three: false
}
}
How can this.setState() be used to set the value of a dynamic property?
For instance, this does not work:
let dynamicProperty = "one"
this.setState({
enabled[dynamicProperty]: true
})
However, this does work, but is also bad practice:
this.enabled = {
one: false,
two: false,
three: false
}
let dynamicProperty = "one"
this.enabled[dynamicProperty] = true;
How can this.setState() be used to accomplish the same thing?
You need to create a copy of the original object and only change the property you want to update. The easiest way to do that is to use the object spread operator:
this.setState(currentState => ({enabled: {...currentState.enabled, one: true}}));
or in a more verbose form:
this.setState(currentState => {
const enabled = {...currentState.enabled, one: true};
return {enabled};
});
If the property name is only known at runtime you can do it like this:
const setEnabled = name => {
this.setState(currentState => ({enabled: {...currentState.enabled, [name]: true}}));
};
The standard practice is to copy the the state, modify the copied state, then set state using that clone, like this:
//with spread operator
const enabledClone = {...this.state.enabled};
enabledClone.one = true;
this.setState({enabled : enabledClone});
You can use braces around an object's key to use a variable to determine the key
const dynamicKey = 'one';
const newObj = {[dynamicKey]: true} //equals {one: true}
Since this.setState only merges on toplevel keys, you will have to create a copy of the current enabled object and use the braces notation:
let dynamicProperty = "one"
this.setState({
enabled: {...this.state.enabled, [dynamicProperty]: true}
})
I have no idea if what I'm doing is correct or not, but here's a simplified version of what I'm trying to do:
I want to have 3 file inputs, with the 2nd and 3rd disabled until the 1st one has had a file selected.
I've tried to do is set the Vuex state variable to whatever the first file input is has selected, but upon doing that the other 2 inputs don't update their disabled state.
I have some file inputs that are created dynamically, like so:
Vue.component('file-input', {
props: ['items'],
template: `<div><input type="file" v-on:change="fileSelect(item)" v-bind:id="item.id" v-bind:disabled="disabledState"></div>`,
methods: {
fileSelect: function(item) {
store.commit('fileSelect', file);
}
},
computed: {
disabledState: function (item) {
return {
disabled: item.dependsOn && store.getters.getStateValue(item.dependsOn)
}
}
}
}
The data for the component is from the instance:
var vm = new Vue({
data: {
items: [
{ text: "One", id: "selectOne" },
{ text: "Two", id: "selectTwo", dependsOn: "fileOne" },
{ text: "Three", id: "selectThree", dependsOn: "fileOne" }
}
});
Now, notice the "dependsOn". In the Vuex store, I have a corresponding state item:
const store = new Vuex.Store({
state: {
files: [
{
fileOne: null
}
]
},
mutations: {
fileSelect(state, file) {
state.files.fileOne = file;
}
},
getters: {
getStateValue: (state) => (stateObject) => {
return state.files.findIndex(x => x[stateObject] === null) === 0 ? true : false;
}
}
});
Now, the above works when everything is first initialized. But once the first input has something selected, the other two inputs don't change.
I'm not sure how to update the bindings once a mutation of the state occurs.
I think you need to refactor your mutation to make the state property mutable, like this:
fileSelect(state, file) {
Vue.set(state.files[0].fileOne, file);
}
Well, I figured it out...
Because my state object is an array of objects, I can't just change one of the property's values with state.files.fileOne. I needed to do state.files[0].fileOne.
I can't seem to get my head around how to efficiently set all the radio buttons in a redux state to false then set the original action creator to true.
Right now I struggle by trying to copy the state object using Object.assign whilst also providing a way to change the objects which hold true for item.type === 'radio' to false.
Some code I thought might be close to the true way.
const state = {
'some_id_1': {
type: 'radio',
id: 'some_id_1',
checked: true
},
'some_id_2': {
type: 'radio',
id: 'some_id_2',
checked: false
},
'some_id_3': {
type: 'checkbox',
id: 'some_id_3',
checked: false
}
};
const state = Object.assign({}, state, (item) => {
if(item.type === 'radio') {
console.log('reset='+item);
item.checked = false;
}
return item;
});
return state;
But of course this doesn't work as it's not how Object.assign works.
Of course I could also loop over the object's keys, i.e. 'radio_box_1_id', ... and set anything with item.type === 'radio' to false, or even bring these changed values out to a separate state object and replace it using another argument of Object.assign. But these seem like extra processing time and not the perfect solution I'm looking for.
Thanks
Edit: My goal is to set the items of my state to a state of NOT checked, if they are a radio, but try not to change the others. I want to do this efficiently if it's possible. I can set the right radio afterwards to true.
Although you didn't specify the state shape nor your reducer code, this is a common operation.
If i understand correctly, you have an array of items (objects) with a type and id for each.
If the type is a radio then you will have a checked property.
If this is correct, you can use the Array.prototype.map() method and conditionally return a new object (using the object spread) while you check the id inside your action payload.
Here is a small example:
const state = [{
type: 'radio',
id: 1,
checked: true
}, {
type: 'radio',
id: 2,
checked: false
}, {
type: 'text',
id: 88,
value: 'hi there'
}, {
type: 'radio',
id: 43,
checked: false
}];
const action = {
id: 2
};
console.log('before the change', state);
const nextState = state.map((item) => {
if (item.type === 'radio') {
return {
...item,
checked: item.id === action.id
}
}
return item; // not a radio, go on.
});
console.log('after the change', nextState);
Update
As a followup to your update, now that we know the shape of your state, when dealing with objects instead of arrays, you can iterate over the keys with Object.keys and use Array.prototype.reduce() to create a new object according to your conditions:
const state = {
'some_id_1': {
type: 'radio',
id: 'some_id_1',
checked: true
},
'some_id_2': {
type: 'radio',
id: 'some_id_2',
checked: false
},
'some_id_3': {
type: 'checkbox',
id: 'some_id_3',
checked: false
}
};
const action = {
id: 'some_id_2'
};
console.log('before the change', state);
const nextState = Object.keys(state).reduce((result,currentKey) => {
const currentItem = state[currentKey];
if (currentItem.type === 'radio') {
result[currentKey] = {
...currentItem,
checked: currentItem.id === action.id
}
} else{
result[currentKey] = currentItem // not a radio, go on.
}
return result;
},{});
console.log('after the change', nextState);
Edit
As a followup to Ulysse BN's comment:
I had to downvote: your modified answer doesn't fully clone the state.
if after your code I run state['some_id_3'].foo = 'bar', nextState
will be modified as well since they share the same object reference.
This is misleading, if you are not mutating an object then there is no need to re-create it. This is not a mutation.
This is a common practice in redux, you can learn more about it in Updating an Item in an Array