React Leaflet with LayersControl handling object datas - javascript

I'd like to have a map using OpenStreetMap showing location of flowers. My point is to have LayersControl with colours and type and be able to check for example Orange and Tulip and see only orange tulips on the map but it seems hard from what i read on React LeafLet documentation.
To be easier to understand i will add some code :
Flower datas example:
const flowers = [
{
"type": "Tulip",
"colour": "Orange",
"latlng": [52.081222, 5.235965],
},
{
"type": "Crocus",
"colour": "Red",
"latlng": [52.081421, 5.235534],
},
]
LeafletMap.jsx (Partial):
const LeafletMap: React.FC = () => {
return (
<MapContainer id="mapId"
center={averagePos}
zoom={zoom}>
{flowerItems.map((flower, index) => (
//Maybe do something here like sorting and creating
//multiple layers
//or having already a layer for every type of search
//but if i want to add more colours ou type it can be very hard to update
))}
</LayersControl>
</MapContainer>
)
}

You could maintain the selected layers using state variables - one array to track the type (Crocus or Tulip) and another to track the colour (red or orange). Initially, the states are setup to show everything:
const [type, setType] = useState(['Tulip', 'Crocus']);
const [colour, setColour] = useState(['Orange', 'Red']);
For each type and colour you could create a LayersControl where you hook into the add and remove event handlers and update the corresponding state (type or colour) when the layer is checked or unchecked by the user:
<LayersControl.Overlay name="Crocus" checked="true">
<LayerGroup
eventHandlers={{
add: (e) => {
updateTypeState('Crocus', true);
},
remove: (e) => {
updateTypeState('Crocus', false);
},
}}
></LayerGroup>
</LayersControl.Overlay>
The functions to update the state look like this (there is one function for updating type and one for updating colour):
const updateTypeState = (key: string, value: boolean) => {
setType((prevState) => {
if (value) {
prevState = [...prevState, key];
} else {
prevState = prevState.filter((e) => e !== key);
}
console.log(prevState);
return prevState;
});
};
const updateColourState = (key: string, value: boolean) => {
setColour((prevState) => {
if (value) {
prevState = [...prevState, key];
} else {
prevState = prevState.filter((e) => e !== key);
}
return prevState;
});
};
You could then render the markers using the map function as you suggested:
{flowers
.filter((flower) => {
if (type.length == 0 && colour.length == 0) {
// No layers selected, so show all
return true;
}
if (type.length > 0 && colour.length > 0) {
// Colours and types selected
return (
type.indexOf(flower.type) >= 0 &&
colour.indexOf(flower.colour) >= 0
);
}
if (type.length > 0) {
// Type selected, no colour selected
return type.indexOf(flower.type) >= 0;
}
// Colour selected, no type selected
return colour.indexOf(flower.colour) >= 0;
})
.map((flower, index) => (
<Marker position={flower.latlng}>
<Popup>
{flower.type}, {flower.colour}
</Popup>
</Marker>
))}
There's a working StackBlitz here, but please note the marker images do not display properly due to this issue, but you should be able to see a broken image icon and click on it to view details of the flower.

Related

Not able to change svg elements color by looping

I'm trying to change the color of svg elements by comparing the id's of the elements with an array. I'm getting the correct comparison results by looping the id's of all the available elements and comparing them with the array data. However, I'm not able to dynamically color the compared element.
This is what I have tried so far: CodeSandBox
import React from "react";
import "./App.css";
import NewTestColor from "./NewTestColor";
const App = () => {
const data = [
{
id: "D",
hmc: "",
lmc: "low"
},
{
id: "B",
hmc: "high",
lmc: ""
},
{
id: "A",
hmc: "high",
lmc: ""
},
{
id: "C",
hmc: "",
lmc: "low"
}
];
const addEventListener =
("mouseup",
(e) => {
// Let's pick a random color between #000000 and #FFFFFF
const rnd = Math.round(Math.random() * 0xffffff);
// Let's format the color to fit CSS requirements
const fill = "#" + rnd.toString(16).padStart(6, "0");
// Let's apply our color in the
// element we actually clicked on
e.target.style.fill = fill;
const id = e.target.getAttribute("id");
console.log(id);
console.log(fill);
// console.log(data);
});
// const Ndata = data.filter(task => task.hmc === "high")
// .map(filteredData => {filteredData.id}, {filteredData.hmc}, {filteredData.color} )
const FilteredData = data.filter((e) => e.hmc.includes("high"));
const OnlyIp = FilteredData.map((e) => `${e.id} ${e.hmc}`);
// console.log(OnlyIp)
console.log(FilteredData);
return (
<div>
<NewTestColor onClick={addEventListener} items={FilteredData} />
</div>
);
};
export default App;
The issue is if you define onClick as a prop on a component you defined it does not work. You need to add it to an actual element like svg.
Change the NewTestColor to pass to onClick function to outer svg
const NewTestColor = ({ onClick }) => (
<svg viewBox="0 0 210 297" height={500} width={500} onClick={onClick}>
...
...
...
</svg>
);
And the addEventListener function in App.js should look like below.
const addEventListener = (e) => {
// Let's pick a random color between #000000 and #FFFFFF
const rnd = Math.round(Math.random() * 0xffffff);
// Let's format the color to fit CSS requirements
const fill = "#" + rnd.toString(16).padStart(6, "0");
// Let's apply our color in the
// element we actually clicked on
e.target.style.fill = fill;
const id = e.target.getAttribute("id");
console.log(id);
console.log(fill);
// console.log(data);
};
Code sandbox

Function returning state[i].setState is not a function

What I've tried and my issue
I started with creating an external function and running it through the onClick... this works partly as it sends the alerts on click. See the services page on test.ghostrez.net.
Click the small images to trigger the alerts that show which if statement, thestate.active:value, and the state.id:value.
So I know the correct statements are being triggered.
My problem is I keep having state[i].setState is not a function returned rather than the state being set as intended.
I have placed the function internally and externally to the class Player and it returned the same issue.
I converted the function to an internal arrow function as suggested HERE.
I converted it to a const changeActiveField = () => {stuff in here}
I attempted to bind it const changeActive = changeActiveField.bind(this) *as suggested HERE and HERE
Each attempt returning the same Error
this is what the debug console returns
Here is my current function its process > 1. if the active object in state has the same id as image clicked - do nothing, 2. if the active object has a different id to the image clicked setState active:value to false then come back and find the object with the id === id of the image clicked and setState active:true from false.
function changeActiveField(im, state) {
console.log(state);
for (var i = 0; i < state.length; i++) {
if (state[i].active === true && state[i].id === im) {
return alert("if " + state[i].active + " " + state[i].id);
} else if (state[i].active === true && state[i].id !== im) {
alert(" elseif set false " + state[i].active + " " + state[i].id);
state[i].setState(false);
} else if (state[i].id === im) {
alert("elseif make true " + state[i].active + " " + state[i].id);
state[i].setState({ active: true });
return;
} else {
return alert("Nope");
}
}
}
changeActiveField is called here
<div className="thumbs">
{this.state.ids.map((i) => (
<>
<Image
className="carouselitem"
rounded
onClick={() => changeActiveField(i.id, this.state.ids)}
src={"http://img.youtube.com/vi/" + i.id + "/hqdefault.jpg"}
size="small"
/>
<h2>
{i.id} {i.active ? "true" : "false"}
</h2>
</>
))}
</div>
No joke I've been trying to resolve this for 4 days now. I'm stumped.
It appears that you are trying to setState on an individual id, but what you are actually doing is trying to call id.setState
From the code you supplied, each id looks basically like this:
{active: //true/false, id: //some Int}
but in reality your code is looking for this...
{active: //true/false, id: //some Int, setState: () => //do something here}
You'll need to handle how to find your specific id object in that array of ids, and then update your full state with the current state AND the modification you are making.
EDIT://my fault, wasn't thinking.
I would recommend making a copy of your state array in a new variable, then mapping through that new array variable making your mutations. Then set your state based on that new array objects...
let newIdArr = this.state.ids
newIdArr.map(id => //do your stuff here...)
this.setState({...this.state, ids: newIdArr})
Lastly, when you setState(false) you are overwriting ALL your state to where it will be just false, losing all your ids along the way.
This is the end product of too many days pulling my hair out... but it works now and hopefully, it helps someone else. (full component code last)
I used an anonymous function in the Image that is being rendered. This finds and updates the object in the this.state array, first, it finds the ids that don't match the value passed in from the "carouselitem" and updates their active values to false, then it finds the id that matches the value passed in and updates it to true.
The old function changeActiveField is now
onClick={() => {
this.setState((prevState) => ({
ids: prevState.ids.map((ob) =>
ob.id !== i.id
? { ...ob, active: false }
: { ...ob, active: true }
),
}));
}}
I have also moved my firstActiveId into the class. This finds the array object with active: true and returns the id value which is placed in the activevid to display and play the appropriate video.
firstActiveId = () => {
for (var i = 0; i < this.state.ids.length; i++) {
if (this.state.ids[i].active) {
return this.state.ids[i].id;
}
}
};
The firstActiveId is used like this to provide playback.
<div className="activevid">
<Embed
active
autoplay={true}
color="white"
hd={false}
id={this.firstActiveId(this.state.ids)}
iframe={{
allowFullScreen: true,
style: {
padding: 0,
},
}}
placeholder={
"http://img.youtube.com/vi/" +
this.firstActiveId(this.state.ids) +
"/hqdefault.jpg"
}
source="youtube"
/>
</div>
TIP: don't over-complicate things like I do
Full Component
import React, { Component } from "react";
import { Embed, Image } from "semantic-ui-react";
import "./Player.css";
export default class Player extends React.Component {
constructor(props) {
super(props);
this.state = {
ids: [
{
id: "iCBvfW08jlo",
active: false,
},
{
id: "qvOcCQXZVg0",
active: true,
},
{
id: "YXNC3GKmjgk",
active: false,
},
],
};
}
firstActiveId = () => {
for (var i = 0; i < this.state.ids.length; i++) {
if (this.state.ids[i].active) {
return this.state.ids[i].id;
}
}
};
render() {
return (
<div className="carouselwrap">
<div className="activevid">
<Embed
active
autoplay={true}
color="white"
hd={false}
id={this.firstActiveId(this.state.ids)}
iframe={{
allowFullScreen: true,
style: {
padding: 0,
},
}}
placeholder={
"http://img.youtube.com/vi/" +
this.firstActiveId(this.state.ids) +
"/hqdefault.jpg"
}
source="youtube"
/>
</div>
<div className="thumbs">
{this.state.ids.map((i) => (
<>
<Image
className="carouselitem"
rounded
onClick={() => {
this.setState((prevState) => ({
ids: prevState.ids.map((ob) =>
ob.id !== i.id
? { ...ob, active: false }
: { ...ob, active: true }
),
}));
}}
src={"http://img.youtube.com/vi/" + i.id + "/hqdefault.jpg"}
size="small"
/>
</>
))}
</div>
</div>
);
}
}

Array.map / Filter a contigous array and then re-order it to be contiguous again

Using React, I have a list component that uses array.map to render a list of items.
The list items are variegated; every other list item has a different background color which depends on if the id field of the data structure that feeds the list item is even or odd:
...
const useStyles = makeStyles((theme) => ({
even: {
backgroundColor: theme.palette.background.paper,
},
odd: {
backgroundColor: "#c8c9c7",
},
}));
...
const classes = useStyles();
...
{!list || list.length < 1 ? (
<p>You have no assets selected...</p>
) : (
list.map((items) => (
<ListItem
className={items.id % 2 === 0 ? classes.even : classes.odd}
key={items.id}
>
...
/>
</ListItem>
))
)}
Here is an example of the data structure it uses:
{
{
"id":0,
"foo":"This is a bar"
},
{
"id":1,
"foo":"This is also a bar"
},
{
"id":2,
"foo":"Yes, this too, is a bar"
}
}
I need to remove items. Normal javascript.filter produces non contiguous ids as expected:
{
{
"id":0,
"foo":"This is a bar"
},
{
"id":2,
"foo":"Yes, this too, is a bar"
}
}
I need them to be contiguous:
{
{
"id":0,
"foo":"This is a bar"
},
{
"id":1,
"foo":"Yes, this too, is a bar"
}
}
I have a function that does what I need that needs some tweaking:
const handleRemoveAsset = (id) => {
const arrayCopy = [...assetListItems];
const filteredArray = arrayCopy
.filter((item) => item.id !== id)
for (var i=0; i < filteredArray.length; i++) {
filteredArray[i].id = i;
}
setAssetListItems(filteredArray);
};
This works, but one does not simply for loop using React... I am hoping to use filter and/or map for the entirety of this and not use the for loop that I have.
I read that you can chain filter and map and tried it but couldn't quite work it out. I came up with this:
const filteredArray = array
.filter((item) => item.id !== id)
.map((item, index) => {
item && item.id ? item.id : index)});
... which fails to compile with - expected an assignment to a function call and instead saw an expression on the line after .map.
Any advice at this point would appreciated, thank you!
You could chain map and filter and return the new object from map which updates the pre-existing id.
[...assetListItems]
.filter(item => item.id !== id)
.map((item, index) => ({
...item,
id: index,
}));
I just considered another scenario where if the id is not starting with 0. And if you want the starting id in the resultant array to be as the id of the first object then this is just another way of achieving the expected output.
let data = [{id:0, foo:'This is a bar'},{id:1, foo:'This is also a bar'},{id:2, foo:'Yes, this too, is a bar'}];
const filterItems = (items, id) => {
let lastPushedId = items[0]?.id;
return items.filter(item => item.id !== id).map(item => ({
...item,
id: lastPushedId++
}))
}
console.log(filterItems(data, 1));
//`id` of the first object is `3`
data = [{id:3, foo:'This is a bar'},{id:4, foo:'This is also a bar'},{id:5, foo:'Yes, this too, is a bar'}];
console.log(filterItems(data, 3));
.as-console-wrapper {
max-height: 100% !important;
}

How to update list of elements in React

I'm having trouble updating a list of elements using React, when I run the code below and click on a 'star' element, react updates ALL the elements in this.state.stars instead of just the element at the given index:
class Ratings extends React.Component {
constructor(props) {
super(props);
let starArr = new Array(parseInt(this.props.numStars, 10)).fill({
icon: "*",
selected: false
});
this.state = {
stars: starArr
};
this.selectStar = this.selectStar.bind(this);
}
selectStar(ind) {
this.setState({
stars: this.state.stars.map((star, index) => {
if (index === ind) star.selected = !star.selected;
return star;
})
});
}
makeStars() {
return this.state.stars.map((star, ind) => (
<span
className={star.selected ? "star selected" : "star"}
onClick={() => this.selectStar(ind)}
key={ind}
>
{star.icon}
</span>
));
}
render() {
return (
<div className="star-container">
<span>{this.makeStars()}</span>
</div>
);
}
}
Can anyone point me in the right direction here? Not sure why this is happening!
Your problem is in how you're instantiating your Array:
let starArr = new Array(parseInt(this.props.numStars, 10)).fill({
icon: "*",
selected: false
});
What that line is doing is filling each item in the array with the same object. Not objects all with the same values, but a reference to the same object. Then, since you're mutating that object in your click handler, each item in the Array changes because they're all a reference to that same object.
It's better to do a non-mutating update, like this:
this.setState({
stars: this.state.stars.map((star, index) =>
(index === ind)
? { ...star, selected: !star.selected }
: star
)
});
This will instead create a copy of the object at the Array index except with the selected property toggled.

Display data on searching an empty string in react

handleChange(e, isEnter) {
const searchData = () => {
let tempMenuProductDetails = this.props.menu_items;
if (this.state.searchString == null) {
this.setState({
displayItems: tempMenuProductDetails
}, function () {
console.log(this.state.displayItems);
})
}
const filterArray = tempMenuProductDetails.reduce((result, category) => {
if (category.categoryName.toLowerCase().indexOf(this.state.searchString.toLowerCase()) > -1) {
result.push(category);
}
if (category.productList && category.productList.length > 0) {
category.productList = category.productList.reduce((productListResult, productList) => {
if (!!productList.productName && productList.productName.toLowerCase().indexOf(this.state.searchString.toLowerCase()) > -1) {
productListResult.push(productList);
}
return productListResult;
}, []);
}
if (category.productList.length) {
result.push(category);
}
return result;
}, []);
this.setState({
displayItems: filterArray
}, function () {
console.log(this.state.displayItems);
})
}
if (!isEnter) {
this.setState({
searchString: e.target.value
});
} else {
searchData();
}
}
search(e) {
if (e.keyCode == 13) {
this.handleChange(e, true);
}
this.handleChange(e, false);
}
render() {
return (
<FormControl value={this.state.searchString} type="text"
placeholder="Search Items"
className="search" onChange={this.handleChange} onKeyDown=
{this.search} />
)
}
The problem with this search function is that it gives the duplicate data when searched and does not re-render the original data when someone searches nothing in the input box instead just presses enter.
For ex- If I search "chicken" and press enter ,it displays correct data twice. Then I delete chicken and press enter with nothing typed in the search box ,it does not re-render the original data. How do I fix this?
It only re-renders the original data when the page is refreshed. And it works fine if I remove the "if (this.state.searchString == null)" part of code.
Thanks in advance.
I searched fries and pressed enter,it shows fries.Then I clear searchbox and press enter,it shows two fries
Please update if condition to check for falsy value i.e. both null and empty string. When you delete "Chicken" after searching for "Chicken", state object is not null rather its empty string with zero length "". Hence we have to check for length.
Its a good approach to check for falsy value like
if (!this.state.searchString) {
this.setState({
displayItems: tempMenuProductDetails
}, function () {
console.log(this.state.displayItems);
})
}

Categories