I'd like to have my markers popup open even after rerender. I have a marker showing user position so that updates all the time. After that marker rerenders that closes the popup of other marker.
My code:
const StaticReferencePoints = (props) => {
const { selectedCalibrationPoint, setSelectedCalibrationPoint, staticReferencePoints } = props;
const addToast = useToastContext();
const [snackbarOpen, setSnackbarOpen] = useState(true);
const [latitude, longitude] = useGeolocationStoreHelper(['latitude', 'longitude']);
const [map, setMap] = useState(null);
const center = [latitude, longitude];
const markerRef = useRef({});
const [selectedMarker, setSelectedMarker] = useState(null);
useEffect(() => {
// open popup if it exists
if (selectedMarker) {
markerRef.current[selectedMarker]?.openPopup();
}
}, [selectedMarker, latitude, longitude]);
useEffect(() => {
if (map) {
map.addEventListener('click', () => {
setSelectedMarker(null);
});
}
}, [map]);
const handleSnackbarClose = (event, reason) => {
if (reason === 'clickaway') {
return;
}
setSnackbarOpen(false);
};
const handleDeletePoint = (point) => {
if (confirm('Are you sure you want to delete this point?')) {
Meteor.call(...) => {
if (err) {
console.log(err);
addToast('Error deleting point', 'error');
}
if (res) {
console.log(res);
addToast('Point deleted', 'success');
}
});
}
};
const SingleMarker = (point) => {
const [anchorEl, setAnchorEl] = useState(null);
const handleClick = (event) => {
setAnchorEl(event.currentTarget);
};
const handleClose = () => {
setAnchorEl(null);
};
return (
<Marker
key={'staticPoint-' + point.point?.name}
position={point.point?.coordinates}
ref={(el) => (markerRef.current[point.point.name] = el)}
eventHandlers={{
click: () => {
setSelectedMarker(point.point.name);
},
}}
>
<Popup options={{ autoClose: false }}>
<Grid container item justifyContent={'center'} xs={12}>
<Grid item container direction={'row'} xs={12}>
<Grid item xs={1} container justifyContent={'center'} alignItems={'center'}>
{ACL.check) && (
<div>
<IconButton size="small" onClick={handleClick}>
<MoreVertIcon />
</IconButton>
<Menu anchorEl={anchorEl} keepMounted open={Boolean(anchorEl)} onClose={handleClose}>
<MenuItem onClick={() => handleDeletePoint(point.point)}>
<ListItemIcon>
<DeleteIcon />
</ListItemIcon>
<ListItemText primary="Delete" />
</MenuItem>
</Menu>
</div>
)}
</Grid>
<Grid item xs={10} container justifyContent={'center'} alignItems={'center'}>
<Typography>{point.point?.name}</Typography>
</Grid>
<Grid item xs={1}></Grid>
</Grid>
<Button
onClick={() => {
setSelectedCalibrationPoint(point.point);
setSelectedMarker(null);
}}
>
Select For Calibration
</Button>
</Grid>
</Popup>
</Marker>
);
};
const StaticPointMarkers = () => {
return staticReferencePoints.map((point, i) => {
return <SingleMarker key={'marker-' + i} point={point} />;
});
};
const UserPositionMarker = () => <Marker position={[latitude, longitude]} icon={userPosition}></Marker>;
return (
<Grid item xs={12}>
<Snackbar
anchorOrigin={{
vertical: 'top',
horizontal: 'center',
}}
open={snackbarOpen}
onClose={handleSnackbarClose}
autoHideDuration={5000}
message="Calibrate first by selecting a point from the map"
action={
<IconButton size="small" aria-label="close" color="inherit" onClick={handleSnackbarClose}>
<Close fontSize="small" />
</IconButton>
}
/>
<MapContainer
center={center}
zoom={18}
maxZoom={28}
scrollWheelZoom={false}
style={{ height: '65vh' }}
whenCreated={setMap}
>
<TileLayer
url="https://{s}.google.com/vt/lyrs=s&x={x}&y={y}&z={z}"
maxZoom={28}
subdomains={['mt0', 'mt1', 'mt2', 'mt3']}
/>
{staticReferencePoints && <StaticPointMarkers />}
{latitude && longitude && <UserPositionMarker />}
</MapContainer>
<Grid id={'info'} item container xs={12} justifyContent={'center'} marginTop={'1rem'}>
<Typography>{`${latitude}, ${longitude}`}</Typography>
</Grid>
<Grid id={'info'} item container xs={12} justifyContent={'center'}>
<InfoIcon onClick={() => setSnackbarOpen(true)} />
</Grid>
</Grid>
);
};
export default StaticReferencePoints;
I have now set current open popup on state and on useEffect open the popup but that creates flickering.
What would be the best way to force popup stay open?
If I understand you question, the problem is that the selected marker popup flickers when the user location changes?
In general the popup should be able to stay open as long as the marker is not created again.
I can see that you have latitude and longitude as dependencies in the useEffect they are not used in the useEffect tough. This will cause the useEffect too trigger on each update on latitude and longitude.
My first suggestion is to remove them from the dependency array.
Change this:
useEffect(() => {
// open popup if it exists
if (selectedMarker) {
markerRef.current[selectedMarker]?.openPopup();
}
}, [selectedMarker, latitude, longitude]);
Secondly a good thing to do is to make sure that the popup is not already open this can be done with
To this:
useEffect(() => {
// open popup if it exists
if (selectedMarker && markerRef.current[selectedMarker]?isPopupOpen()) {
markerRef.current[selectedMarker]?.openPopup();
}
}, [selectedMarker, markerRef]);
As a side note in my experience with react-leaflet, unnecessary rerenders can cause some visual irritation such as flickering. You should strive to reduce the amount of re renders. This can be done using useMemo and useCallback hooks. Genrally passing props that are functions, arrays or object might cause rerenders even if they are the same.
From what I can see your code is deeply nested and it looks like you define component inside other components. Try to break out you code to clear and clean components instead and pass props to them instead, you can still have them all in the same file.
Using a eslint and typescript might also help you to find issues with the code.
Than you #Disco for your answer.
I managed to solve the problem with:
const StaticPointMarkers = (markers) => {
return useMemo(() => markers.map((point, i) => <SingleMarker key={'marker-' + i} point={point} />), [markers])};
Related
I painstakingly put together a mui-datatable that displays the content of a CSV file.
Now I want to read another CSV file when a button is clicked.
Unfortunately, I'm a complete beginner - and I don't really know how to implement it.
I tried the following - but unfortunately it doesn't work that way.
Can someone help me with that?
As a 2nd step, I want to show a mui-alert for 3 seconds when I click on a cell. At the same time, a filter with the content of the cell is set. I have already successfully implemented this functionality using the setTimeout function. Strangely enough, only if I remove a filter within the 3 seconds "setTimeout" and then add a new filter, both filters (the one already removed and the new filter) are added again. Somehow the "setTimeout" function seems to be blocking further input in the mui-datatable....
Here is my code:
import {
Typography,
AppBar,
Button,
CssBaseline,
Toolbar,
Container,
Grid,
} from "#mui/material";
import React, { useState} from "react";
import { BackupTable, Refresh } from "#mui/icons-material";
import useStyles from "./styles";
//Tabelle
import MUIDataTable, { ExpandButton } from "mui-datatables";
import { TableRow, TableCell } from "#mui/material";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import total from "./test.csv";
import lost1 from "./test.csv";
import lost2 from "./test.csv";
//const App = () => {
function App() {
const { classes } = useStyles();
const [file, setFile] = useState();
const [array, setArray] = useState([]);
const csvFileToArray = (string) => {
const csvHeader = string.slice(0, string.indexOf("\n")).split(",");
const csvRows = string.slice(string.indexOf("\n") + 1).split("\n");
const array = csvRows.map((i) => {
const values = i.split(",");
const obj = csvHeader.reduce((object, header, index) => {
object[header] = values[index];
return object;
}, {});
return obj;
});
setArray(array);
setCols(initFilter(Object.keys(Object.assign({}, ...array))));
};
const headerKeys = Object.keys(Object.assign({}, ...array));
let [selectedFilter, setSelectedFilter] = useState([new Array()]);
let [cols, setCols] = useState();
//Tabelle
const components = {
ExpandButton: function (props) {
return <ExpandButton {...props} />;
},
};
var removedFilterList=[];
const options = {
filter: true,
filterType: "multiselect",
onFilterChange: (changedColumn, filterList) => {
cols = modifyFilter(cols, filterList);
},
onFilterChipClose: (index, removedFilter, filterList) => {
removedFilterList = filterList;
selectedFilter = filterList;
},
responsive: "standard",
selectableRowsOnClick: false,
rowHover: true,
expandableRows: false,
expandableRowsHeader: false,
expandableRowsOnClick: true,
selectableRows: "none",
rowsPerPage: 100,
filterList: [],
selectableRowsHideCheckboxes: false,
onCellClick: (rowData, rowMeta) => {
onFilter(rowData, rowMeta);
},
};
const theme = createTheme({
overrides: {
MUIDataTableSelectCell: {
expandDisabled: {
// Soft hide the button.
visibility: "hidden",
},
},
MUIDataTableBodyCell: {
styleOverrides: {
root: {
backgroundColor: "#FF0000",
},
},
},
},
});
const initFilter = (cols) => {
//add options to columns
for (let i = 0; i < cols.length; i++) {
cols[i] = {
name: cols[i],
options: {
filterList: []
}
}
}
return cols;
}
let modifyFilter = (colss, newFilterArray) => {
for (let i = 0; i < colss.length; i++) {
colss[i].options.filterList = newFilterArray[i];
}
return colss;
}
const onFilter = (value, rowMeta) => {
let filteredCols = [...cols];
if (value !== "All") {
if (selectedFilter.length < rowMeta.colIndex + 1) {
for (let i = selectedFilter.length; i < rowMeta.colIndex + 1; i++) {
selectedFilter.push(new Array());
}
}
let schonVorhanden = selectedFilter[rowMeta.colIndex].indexOf(value);
if (schonVorhanden == "-1") {
selectedFilter[rowMeta.colIndex].push(value);
}
else {
console.log(value, "schon vorhanden");
}
}
filteredCols[rowMeta.colIndex].options.filterList = selectedFilter[rowMeta.colIndex];
setCols(filteredCols);
};
//---------------------------
//TEST
const ImportSamples = () => {
let input = total;
if(file == "lost1"){
input=lost1;
}
if (array.length == 0) {
fetch(input)
.then((r) => r.text())
.then((text) => {
csvFileToArray(text);
});
}
};
const doSetFile = (wert) => {
setFile(wert);
ImportSamples();
Refresh();
}
ImportSamples();
return (
<>
<CssBaseline />
<AppBar position="relative">
<Toolbar>
<BackupTable className={classes.icon} />
<Typography variant="h6">SampleProject Tabelle</Typography>
</Toolbar>
</AppBar>
<main>
<div className={classes.container}>
<Container maxWidth="sm">
<Typography
variant="h2"
align="center"
color="textPrimary"
gutterBottom
>
SampleProject
</Typography>
<Typography
variant="h5"
align="center"
color="textSecondary"
paragraph
>
Viewer
</Typography>
<div className={classes.button}>
<Grid container spacing={2} justifyContent="center">
<Grid item>
<Button variant="contained" color="primary" onClick={() => doSetFile('lost1')}>
Erste Aktion
</Button>
</Grid>
<Grid item>
<Button variant="outlined" color="primary">
Zweite Aktion
</Button>
</Grid>
</Grid>
</div>
</Container>
</div>
<div className={classes.container}>
<Container maxWidth="false">
<ThemeProvider theme={theme}>
<MUIDataTable
title={"SampleProject"}
data={array}
columns={cols}
components={components}
options={options}
/>
</ThemeProvider>
</Container>
</div>
</main>
<footer className={classes.footer}>
<Typography variant="h6" align="center" gutterBottom>
Footer
</Typography>
<Typography variant="subtitle1" align="center" color="textSecondary">
Something here to give the footer a purpose!
</Typography>
</footer>
</>
);
}
export default App;
I would like to set the state of a checkbox to true in a custom component for Atlassian Jira when a button is clicked. I am using the UI kit with Forge.
So what do I have to write in the line // todo: check Checkbox "V2"?
import ForgeUI, { CustomField, CustomFieldEdit, render, Text, TextField, useProductContext, CheckboxGroup, Checkbox, Select, Option, Button } from "#forge/ui";
const View = () => {
const { extensionContext: { fieldValue } } = useProductContext();
return (
<CustomField>
<Text
content={`Hello ${fieldValue || "world"}!`}
/>
</CustomField>
);
};
const Edit = () => {
const onSubmit = values => {
return values.text
};
const onClick = () => {
// todo: check Checkbox "V2"
};
return (
<CustomFieldEdit onSubmit={onSubmit}>
<CheckboxGroup label="Products" name="products">
<Checkbox value="V1" label="V1" />
<Checkbox value="V2" label="V2" />
<Checkbox value="V3" label="V3" />
</CheckboxGroup>
<Button text="button" onClick={onClick} />
</ CustomFieldEdit>
);
}
export const runView = render(
<View/>
);
export const runEdit = render(<Edit/>)
I'm basically new to React-native and I'm trying to integrate it with MongoDB, apollo-graphql to implement a basic chat app.
I need to update the screen automatically and show the newly created group when I add a new group. Now what happens is, when I create the group, I need to reload the app every time to show the updation made.
GroupScreen.tsx
const MY_GROUPS = gql`
query chatRooms {
chatRooms {
id
name
createdAt
imageUri
}
}
`;
export default function GroupScreen() {
const [groups, setGroups] = useState(null);
const { data, error, loading } = useQuery(MY_GROUPS);
useEffect(() => {
if (error) {
Alert.alert("Something went Wrong! Please reload.");
}
}, [error]);
useEffect(() => {
if (data) {
//console.log(data);
setGroups(data.chatRooms);
}
}, [data]);
return (
<View style={styles.container}>
<FlatList
style={{ width: "100%" }}
data={groups}
renderItem={({ item }) => <GroupListItem chatRoom={item} />}
keyExtractor={(item) => item.id}
/>
<NewGroupButtonItem />
</View>
);
}
NewGroupButtonItem.tsx
const CREATE_CHATROOM = gql`
mutation Mutation(
$createChatRoomName: String!
$createChatRoomImageUri: String
) {
createChatRoom(
name: $createChatRoomName
imageUri: $createChatRoomImageUri
) {
id
name
imageUri
createdAt
users {
id
name
}
}
}
`;
const NewGroupButtonItem = () => {
const [modalVisible, setModalVisible] = useState(false);
const [groupName, setGroupName] = useState("");
const [groupPic, setGroupPic] = useState(null);
const [newGroup, { data, error, loading }] = useMutation(CREATE_CHATROOM);
const onPress = () => {
setGroupName("");
setGroupPic(null);
setModalVisible(!modalVisible);
};
const onPressSave = () => {
newGroup({
variables: {
createChatRoomName: groupName,
createChatRoomImageUri: groupPic,
},
});
setModalVisible(!modalVisible);
};
return (
<View style={styles.container}>
<Modal animationType="fade" transparent={true} visible={modalVisible}>
<TouchableOpacity
style={styles.touchableContainer}
activeOpacity={1}
onPress={() => setModalVisible(!modalVisible)}
>
<View style={styles.mainContainer}>
<View style={styles.innerContainer}>
<Pressable
onPress={() => {
console.warn("Clicked Image!");
}}
>
<Image source={{}} style={styles.avatar} />
</Pressable>
<TextInput
placeholder={"Group Name"}
style={styles.inputBox}
value={groupName}
onChangeText={setGroupName}
/>
<Pressable
onPress={() => {
console.warn("Clicked Emojies!");
}}
>
<Entypo name="emoji-flirt" size={30} color="#37474f" />
</Pressable>
</View>
{!groupName ? (
<Text style={styles.saveButton} onPress={onPress}>
Cancel
</Text>
) : (
<Text style={styles.saveButton} onPress={onPressSave}>
Save
</Text>
)}
</View>
</TouchableOpacity>
</Modal>
<TouchableOpacity onPress={onPress}>
<MaterialIcons name="group-add" size={30} color="white" />
</TouchableOpacity>
</View>
);
};
export default NewGroupButtonItem;
I'm looking for a text field with multiple inputs as:
Here as you can see I can add new text and on press of enter it saves that keyword.
Can someone guide which package to look for.... I found something similar in material ui autocomplete's costomized hook: https://material-ui.com/components/autocomplete/,
but I don't want it to be drop down I want it to be simply be a text field with option as shown in image.
I cannot find with such functionality or I might be looking work as I don't know proper term for it.
Any guidance will be of great help.
I've included material-ui , reactstrap as this is something I have worked with so if there is any other package also let me know
I was also building something like this few days back. This is what I've built till now.
import {
Chip,
FormControl,
Input,
makeStyles,
} from "#material-ui/core";
import React, { useEffect, useState } from "react";
import "./styles.css";
export default function App() {
const classes = useStyles();
const [values, setValues] = useState(["test"]);
const [currValue, setCurrValue] = useState("");
const handleKeyUp = (e) => {
console.log(e.keyCode);
if (e.keyCode == 32) {
setValues((oldState) => [...oldState, e.target.value]);
setCurrValue("");
}
};
useEffect(() => {
console.log(values);
}, [values]);
const handleChange = (e) => {
setCurrValue(e.target.value);
};
const handleDelete = ( item, index) =>{
let arr = [...values]
arr.splice(index,1)
console.log(item)
setValues(arr)
}
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<FormControl classes={{ root: classes.formControlRoot }}>
<div className={"container"}>
{values.map((item,index) => (
<Chip size="small" onDelete={()=>handleDelete(item,index)} label={item}/>
))}
</div>
<Input
value={currValue}
onChange={handleChange}
onKeyDown={handleKeyUp}
/>
</FormControl>
</div>
);
}
const useStyles = makeStyles((theme) => ({
formControlRoot: {
display: "flex",
alignItems: "center",
gap: "8px",
width: "300px",
flexWrap: "wrap",
flexDirection: "row",
border:'2px solid lightgray',
padding:4,
borderRadius:'4px',
"&> div.container": {
gap: "6px",
display: "flex",
flexDirection: "row",
flexWrap: "wrap"
},
"& > div.container > span": {
backgroundColor: "gray",
padding: "1px 3px",
borderRadius: "4px"
}
}
}));
Here is the working demo:
One possible way to do this using react-hook-form and Autocomplete using the Chip with renderTags function here is an example:
import {
Box,
TextField,
Autocomplete,
Chip,
} from '#mui/material'
...
const {
handleSubmit,
control,
formState: { errors },
} = useForm()
...
<Box mt={2}>
<Controller
control={control}
name="tags"
rules={{
required: "Veuillez choisir une réponse",
}}
render={({ field: { onChange } }) => (
<Autocomplete
defaultValue={
useCasesData?.tags ? JSON.parse(useCasesData?.tags) : []
}
multiple
id="tags-filled"
options={[]}
freeSolo
renderTags={(value, getTagProps) =>
value.map((option, index) => (
<Chip
variant="outlined"
label={option}
{...getTagProps({ index })}
/>
))
}
onChange={(event, values) => {
onChange(values);
}}
renderInput={(params) => (
<TextField
{...params}
label="Métadonnées"
placeholder="Ecriver les métadonnées"
helperText={errors.tags?.message}
error={!!errors.tags}
/>
)}
/>
)}
/>
</Box>
I have a component(toggle Menu List) that is supposed to load a child component(toggle Menu Items) on button click.
Here is How it works.
'btnId' init state = null
-> button click
-> update state to index number 1
-> (btnId !== null) && load the child component
However the child component is not being displayed on state update.
If I set init state to 1, it is displayed on button click.
toggleMenuList.js
import React, { useState, useRef } from 'react';
import Button from '#material-ui/core/Button';
import { withStyles } from '#material-ui/core/styles';
/* --- Components --- */
import Loader from '../../shared/loader';
const ToggleMenuItems = Loader({
loader: () =>
import('./toggleMenuItems' /* webpackChunkName: 'ToggleMenuItems' */),
});
const styles = theme => ({
...
});
const ToggleMenuList = ({ navAdminList, navAdminItems, classes }) => {
const [open, setOpen] = useState(false);
const [btnId, setBtnId] = useState(null);
const anchorRef = useRef(null);
const handleToggle = async id => {
await setBtnId(id);
return setOpen(prevOpen => !prevOpen);
};
const handleClose = event => {
...
};
console.log('Toggle Menu List is rendered');
console.log('btnId: ', btnId);
return (
<React.Fragment>
<div className={`nav-menu ${classes.root}`}>
{navAdminList.map(e => (
<Button
key={e.id}
ref={anchorRef}
aria-owns={open ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={() => handleToggle(e.id)}
className={e.className}
>
{e.name}
</Button>
))}
</div>
{btnId !== null && (
<ToggleMenuItems
handleClose={handleClose}
open={open}
anchorRef={anchorRef}
items={navAdminItems[btnId]}
/>
)}
</React.Fragment>
);
};
export default withStyles(styles)(ToggleMenuList);
toggleMenuItems.js
const ToggleMenuItems = ({ handleClose, open, anchorRef, items }) => {
console.log('Toggle Menu Items is rendered.');
console.log('open: ', open);
return (
<Popper
open={open}
anchorEl={anchorRef.current}
keepMounted
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === 'bottom' ? 'center top' : 'center bottom',
}}
>
<Paper id="menu-list-grow">
<ClickAwayListener onClickAway={handleClose}>
<MenuList>
{items.map(e => (
<MenuItem key={e.id} onClick={handleClose}>
<Link to={e.to} className={e.className}>
{e.name}
</Link>
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
);
};
export default ToggleMenuItems;
Here is console logs i gets.
* On page load
toggleMenuList.js: Toggle Menu List is rendered
toggleMenuList.js: btnId: null
* On button click
toggleMenuList.js: Toggle Menu List is rendered
toggleMenuList.js: btnId: 1
toggleMenuList.js: Toggle Menu List is rendered
toggleMenuList.js: btnId: 1
toggleMenuItems.js: Toggle Menu Items is rendered.
toggleMenuItems.js: open: true
result
The state value is updated.
The child component seems to be loaded. (console.log gets fired)
But It is not displayed.
I resolved the problem with a different approach. I set activeId state with initial value 'null' which updates with 'clicked button id' on button click. And load the child component only when 'button Id' matches 'activeId'.
And I also add this condition in child component.
const isOpen = activeId === id;
return (
<Popper
open={isOpen}
This way, when one button's open is true, the rest is set to false.
toggleMenuList.js
const ToggleMenuList = ({ navAdminList, navAdminItems, classes }) => {
const [activeId, setActiveId] = useState(null);
const anchorRef = useRef(null);
const handleToggle = id => {
setActiveId(id);
};
const handleClose = (event) => {
if (anchorRef.current && anchorRef.current.include(event.target)) {
return;
}
setActiveId(null);
};
return (
<React.Fragment>
<div className={`nav-menu ${classes.root}`}>
{navAdminList.map(e => (
<div key={e.id}>
<Button
ref={anchorRef}
aria-owns={activeId === e.id ? 'menu-list-grow' : undefined}
aria-haspopup="true"
onClick={() => handleToggle(e.id)}
className={e.className}
>
{e.name}
</Button>
{activeId === e.id && (
<ToggleMenuItems
id={e.id}
activeId={activeId}
handleClose={handleClose}
anchorRef={anchorRef}
items={navAdminItems[e.id]}
/>
)}
</div>
))}
</div>
</React.Fragment>
);
};
toggleMenuItems.js
const ToggleMenuItems = ({ id, activeId, handleClose, anchorRef, items }) => {
const isOpen = activeId === id;
return (
<Popper
open={isOpen}
anchorEl={anchorRef.current}
keepMounted
transition
disablePortal
>
{({ TransitionProps, placement }) => (
<Grow
{...TransitionProps}
style={{
transformOrigin:
placement === 'bottom' ? 'center top' : 'center bottom',
}}
>
<Paper id="menu-list-grow">
<ClickAwayListener onClickAway={() => handleClose(id)}>
<MenuList>
{items.map(e => (
<MenuItem key={e.id} onClick={() => handleClose(id)}>
<Link to={e.to} className={e.className}>
{e.name}
</Link>
</MenuItem>
))}
</MenuList>
</ClickAwayListener>
</Paper>
</Grow>
)}
</Popper>
);
};