Update multiple entries in an ImmutableJS List - javascript

Specifically, I have an ImmutableJS List of Maps, and I want to conditionally update records to the same value (which may include just updating all of them). The use case is revealing mines in minesweeper, when someone loses the game. this is accomplished by setting the isVisible tile to true, if isMine is also true (or just make every tile visible, regardless of isMine)
so my JS schema is like this (where arrays are lists and objects are maps):
game = {
isGameOver: false,
tiles: [
{
isMine: boolean,
isRevealed: boolean
},
...
]
}
and so what I'm trying to do is, starting with game, set isRevealed to true for every tile where isMine is true.
this is what i came up with, but it looks so awful I have to hope there's another way
function revealAll(game){
let revealedTiles;
revealedTiles = game.get('tiles').map(tile => {
if (tile.get('isMine')) {
tile = tile.set('isRevealed', true);
}
return tile;
});
return game.set('isGameOver', true).set('tiles', revealedTiles);
}
this successfully ends the game (sets isGameOver to true) and reveals all tiles that are mines (sets isRevealed to true for every tile with isMine equal to true), but I can tell just looking at it that its both inefficient and messy. Is there a built in way to accomplish what i'm doing here?

From my point of view, the code is quite fine :) There are however few tricks that may make it nicer:
using multiple set on the same Map can be replaced by one merge:
return game.merge({
'isGameOver': true,
'tiles': revealedTiles
});
Also, updating an individual tile can be done nicer:
revealedTiles = game.get('tiles').map(
tile => tile.update('isRevealed', (v) => tile.get('isMine') || v))
so you end up with:
function revealAll(game){
let revealedTiles
revealedTiles = game.get('tiles').map(
tile => tile.update('isRevealed', (v) => tile.get('isMine') || v))
return game.merge({
'isGameOver': true,
'tiles': revealedTiles
});
}
OR, you can do it like this:
const revealAll = (game) =>
game
.set('isGameOver', true)
.update('tiles', (tiles) => tiles.map(
tile => tile.update('isRevealed', (v) => tile.get('isMine') || v)))

Here's a functional programming approach using mudash. The benefit of this approach is that mudash will handle both ImmutableJS data types as well as standard JS (or a mix of both). So your function can be used no matter the format.
import _ from 'mudash'
import fp from 'mudash/fp'
const revealAll = _.compose(
fp.set('isGameOver', true),
fp.update('tiles', fp.map((tile) =>
_.update(tile, 'isRevealed', (isRevealed) => _.get(tile, 'isMine') || isRevealed)))
)
const gameMutable = {
isGameOver: false,
tiles: [
{
isMine: false,
isRevealed: false
},
{
isMine: true,
isRevealed: false
},
{
isMine: false,
isRevealed: true
},
{
isMine: true,
isRevealed: true
}
]
}
const gameImmutable = _.immutable(gameMutable)
console.log(revealAll(gameMutable))
console.log(revealAll(gameImmutable))

Related

How to filter array of objects by different properties based on array that declare what property to check?

thank you for help.
I have an array of objects.
And i can't find good solution except of a lot of IF's how to filter it depends on array of properties that should be checked.
const offers = [
{
isGrouped: true,
isDiscarded: false,
isCopy: false,
isWarned: false,
name: "qw",
},
{
isGrouped: false,
isDiscarded: true,
isCopy: false,
isWarned: false,
name: "qwer",
},
{
isGrouped: false,
isDiscarded: true,
isCopy: true,
isWarned: false,
name: "erw",
},
{
isGrouped: false,
isDiscarded: false,
isCopy: true,
isWarned: false,
name: "frew",
}]
and here is the array of filters
export enum Status {
DISCARDED = "Discarded",
WARNED = "Warning",
COPY = "Copy",
GROUPED = "Grouped",
}
const filters: string[] = [Status.DISCARDED, Status.COPY];
filters could include all four statuses or one, two, three or any of them.
function check(offer, filters) {
// I don't know actually how to handle filtering here.
// only one idea is to check all possible variants ex.
// if (filters.includes(Status.DISCARDED) && filters.length === 1) {
// return offer.isDiscarded
// } else if (filters.includes(Status.DISCARDED) && filters.includes(Status.COPY) && filters.length === 2) {
// return offer.isDiscarded || offer.isCopy
// }
}
offers.filter(offer => check(offer, filters))
My solutuion doesn't looks nice. Could someone take a look please?
type Offer = { [K in Status as `is${K}`]?: boolean }
function check(offer: Offer, filterStatus: Status[]): boolean {
for (const status of filterStatus) {
const propertyName = "is"+status as `is${typeof status}`
if (!offer[propertyName]) return false
}
return true
}
I would layer this atop a more general-purpose function that tests whether a collection of key/value pairs matches your object. We can layer on something which maps Discarded to isDiscarded, etc.
const checkProps = (entries) => (x) =>
entries .every (([k, v]) => x [k] == v)
const check = (filters) =>
checkProps (filters .map (k => [`is${k}`, true]))
const findMatches = (offers, filters) =>
offers .filter (check (filters))
const offers = [{isGrouped: true, isDiscarded: false, isCopy: false, isWarned: false, name: "qw"}, {isGrouped: false, isDiscarded: true, isCopy: false, isWarned: false, name: "qwer",}, {isGrouped: false, isDiscarded: true, isCopy: true, isWarned: false, name: "erw", }, {isGrouped: false, isDiscarded: false, isCopy: true, isWarned: false, name: "frew"}]
const Status = {DISCARDED: "Discarded", WARNED: "Warning", COPY: "Copy", GROUPED: "Grouped"}
console .log (findMatches (offers, [Status.DISCARDED, Status.COPY]))
checkProps takes an array of key-value pairs and returns a function which accepts an object, reporting whether all those keys in the object match the relevant value. check takes your filters and turns them into such an array of key-value pairs by prepending "is" to the filter name and pairing it up with the value true. We could use this by directly calling
offers .filter (check (filters))
but I find it cleaner to wrap that in the function findMatches
This offers a fairly flexible API, but we can probably get even more flexible without making it much harder to use. Imagine this:
students .filter (where ({
gpa: gte (3.0),
grade: eq (11),
gender: eq ('male'),
demerits: lt (3)
}))
It would be relatively easy to build an API that worked like that on very similar principles to the above, and it would be straightforward to layer your function on top of this. I wont try to write it here. But this is a demonstration of a useful point: it often helps simplify your code to think of it more generically and then add your special case on top of the generic abstraction.

JavaScript - Explanation of reduce example

Its my first question on StackOverflow (if i do something wrong - sorry!)
I have a reducer function where my teacher helped me, and i can really grasp what it means.
It's a todo app where i have a button. When pressing the button all items in the todo list should be marked as completed. When i press the button again, all items should be in-completed.
I get most of the code. The one part I cant graps my mind round is the explanation mark before the items array !store.items.find.
If anyone could give an explanation i would be thankful!
Also let me know if you need me to share more code.
completeAllTodo: (store, action) => {
const areAllTasksCompleted = !store.items.find(todo => !todo.isComplete)
if (areAllTasksCompleted) {
const completedItems = store.items.map(todo => {
return {
...todo,
isComplete: false
}
})
store.items = completedItems
} else {
const completedItems = store.items.map(todo => {
return {
...todo,
isComplete: true
}
})
store.items = completedItems
}
},
well
store.items.find(todo => !todo.isComplete)
means find any item who is not complete yet if there is no such an item then find will return undefined, and so
!store.items.find(todo => !todo.isComplete)
means if there is an item which is not completed then this will be false if all items are complete then find will return undefined and this whole expression will be true
a better way to express the same logic will be
const areAllTasksCompleted = store.items.every(todo => todo.isComplete)
const areAllTasksCompleted = !store.items.find(todo => !todo.isComplete)
store.items.find returns the first item that matches criteria of the predicate callback.
todo => !todo.isComplete is your predicate callback, which is passed one argument (the current item in the iteration) and returns true if the item is not complete (or false if it is complete).
Therefore, this expression will return the value of the first element in the array that is not completed.
The ! before the whole expression negates the result of the expression. In JavaScript, any object value will be coerced to true, and a null or undefined value will be coerced to false, so if the expression finds a value that matches the predicate, your const will be assigned to false, otherwise it will be true.
Array#find will return an array of matching items if any otherwise returns undefined.
So, if there's some incomplete todos find will return an array and negating that array will result in false, and if there's no incomplete todos it will return undefined and negating undefined will result in true.
Consider the code snippets below:
const todo = [
{ task: "Exercise", isComplete: true },
{ task: "Buy Grocery", isComplete: false },
];
// There is one incomplete todo in the above todo array
const resultFromFind = todo.find(t => !t.isComplete);
console.log(resultFromFind); // { task: 'Buy Grocery', isComplete: false }
const allComplete = !resultFromFind
console.log(allComplete) // false
const todo = [
{ task: "Exercise", isComplete: true },
{ task: "Buy Grocery", isComplete: true },
];
// There is no incomplete todo in the above todo array
const resultFromFind = todo.find(t => !t.isComplete);
console.log(resultFromFind); // undefined
const allComplete = !resultFromFind
console.log(allComplete) // true

How do I dynamically toggle multiple layers in Deck.gl?

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]}
...
>

ImmutableJs - How to Retrieve Key Based On Position in Map

Im using immutableJs
My state object looks like this:
export const initialToolbarState = Map({
temSelectionCtrl : Map({
temSelect : true,
}),
functionalCtrl : Map({
addEle : true,
grpSelect : true,
drawShape : true
}),
operationalCtrl : Map({
zoomIn : true,
zoomOut : true,
pan : true,
temSide : true
}),
referenceCtrl : Map({
saveCtrl : true
})
});
So there are objects with keys which have boolean values.
I want to map (loop) over these objects & get their keys. The boolean values tell whether to render the key or not. Immutable lets us map over Maps using its custom map function. So, the following works, however not as intended:
// map over the initialToolbarState Map object
let ctrls = initialToolbarState.map(( eachToolbar ) => {
// map over the child Map objects like temSelectionCtrl, functionalCtrl, operationalCtrl etc
return eachToolbar.map(( eachCtrl, i ) => {
// get the key corresponding to 'eachCtrl' value
let propKey = eachToolbar.keyOf( eachCtrl );
// propKey is always the first property (1st prop) of 'eachToolbar'
console.log( propKey );
...
Using immutableJs, is there a way to get the correct key corresponding to the currect 'eachCtrl' value within the loop? Could I make sure of the i to help pointing it towards the correct value for which to match the key?
You can use .map again on your objects. The second argument is the key, with the full argument signature being (mapper (value: V, key: K, iter: this))
So, this snippet:
initialToolbarState.map(( eachToolbar ) => {
eachToolbar.map((value, key) => {
console.log(key, ' ==> ', value);
});
});
Will log:
temSelect ==> true
addEle ==> true
grpSelect ==> true
drawShape ==> true
// etc…
Now just chain your returns to create the data structure that you need or do whatever with your keys.
Also, reconsider if this “Map of Maps” is the best structure for the problems you are solving. Perhaps a “List of Maps” is better if you need to iterate often. You won’t have instant read/update for individual items, but if your list consists of only a couple of items, then the performance will not suffer.

React-native get all photos from Camera Roll and Camera Roll API

I am trying to get camera roll photos using react native CameraRoll.getPhotos API. The issue I found that the documentation is not great. In react-native official documentation there are two terms that were mentioned getPhotosReturnChecker and getPhotosParamChecker where I can get the detail about this parameters.
I found the following object that can be passed to CameraRoll.getPhotos from bhwgroup blog
{
first: ..., // (required) The number of photos wanted in reverse order of the photo application
after: ..., // A cursor returned from a previous call to 'getPhotos'
groupTypes: ..., // Specifies which group types to filter the results to
// One of ['Album', 'All', 'Event', 'Faces', 'Library', 'PhotoStream', 'SavedPhotos'(default)]
groupName: ..., // Specifies filter on group names, like 'Recent Photos' or custom album titles
assetType: ... // Specifies filter on assetType
// One of ['All', 'Videos', 'Photos'(default)]
}
According to these it always require a parameter first which dictates how many pictures we can get from CameraRoll. Instead if I want all the photos from camera roll how can I get it?
You'll want to do some paging to access all photos. Basically, you are loading them in chunks, and keeping track of the place where you left off after each fetch. You'll want a state similar to this:
this.state = {
dataSource: ds.cloneWithRows([]),
assets: [],
lastCursor: null,
noMorePhotos: false,
loadingMore: false,
};
Then fetching functions similar to these. This example assumes you are using a ListView to display your photos using a ListView.DataSource
tryPhotoLoad() {
if (!this.state.loadingMore) {
this.setState({ loadingMore: true }, () => { this.loadPhotos(); });
}
}
loadPhotos() {
const fetchParams = {
first: 35,
groupTypes: 'SavedPhotos',
assetType: 'Photos',
};
if (Platform.OS === 'android') {
// not supported in android
delete fetchParams.groupTypes;
}
if (this.state.lastCursor) {
fetchParams.after = this.state.lastCursor;
}
CameraRoll.getPhotos(fetchParams).then((data) => {
this.appendAssets(data);
}).catch((e) => {
console.log(e);
});
}
appendAssets(data) {
const assets = data.edges;
const nextState = {
loadingMore: false,
};
if (!data.page_info.has_next_page) {
nextState.noMorePhotos = true;
}
if (assets.length > 0) {
nextState.lastCursor = data.page_info.end_cursor;
nextState.assets = this.state.assets.concat(assets);
nextState.dataSource = this.state.dataSource.cloneWithRows(
_.chunk(nextState.assets, 3)
);
}
this.setState(nextState);
}
endReached() {
if (!this.state.noMorePhotos) {
this.tryPhotoLoad();
}
}

Categories

Resources