react-google-maps: trying to create a toggleable infowindow for each marker - react-google-maps

I know similar questions have been asked on SO before, but I've tried implementing the solutions in the answers and nothing seems to be working.
So, here's the problem I'm having: I'm using react-google-maps to generate a map. The map has multiple markers. Every time I click on a specific marker, I want an infowindow to pop up. The thing is, right now, everythime I click on a marker, the infowindows for all the markers pop up, as shown below:
Would you care to glance over my code and tell me what the problem is:
render() {
const MapWithAMarker = compose(
withStateHandlers(() => ({
isOpen: false,
}), {
onToggleOpen: ({ isOpen }) => () => ({
isOpen: !isOpen,
})
}),
withScriptjs,
withGoogleMap
)(props =>
<GoogleMap
defaultZoom={7}
defaultCenter={{ lat: 37.468319, lng: -122.143936 }}
containerElement={<div style={{ height: `300px`, width: `400px`, position: `absolute` }} />}
mapElement={<div style={{ height: `300px` }} /> }
containerElement={
<div style={{height: 300, width: 600}}></div>
}
loadingElement={<div style={{ height: `100%` }} />}
googleMapURL={"https://maps.googleapis.com/maps/api/js?key=AIzaSyCk55BnGfoigxDUwDaiYiyn9tFThcJVsPA"}
>
{store.results.data.map( (result, index) => {
if(result.status.color=='success'){
return <Marker
key={ index }
position={{ lat: result.contract_location.lat, lng: result.contract_location.lng }}
icon={ '/img/darkgreen_MarkerA.png' }
onClick={() => props.onToggleOpen(index)}
>
{props.isOpen && <InfoWindow onCloseClick={() => props.onToggleOpen(index)}>
<div>hi</div>
</InfoWindow>}
</Marker>
}
if(result.status.color=='warning'){
return <Marker
key={ index }
position={{ lat: result.contract_location.lat, lng: result.contract_location.lng }}
icon={ '/img/yellow_MarkerA.png' }
onClick={() => props.onToggleOpen(index)}
>
{props.isOpen && <InfoWindow onCloseClick={() => props.onToggleOpen(index)}>
<div>hi</div>
</InfoWindow>}
</Marker>
}
if(result.status.color=='danger'){
return <Marker
key={ index }
position={{ lat: result.contract_location.lat, lng: result.contract_location.lng }}
icon={ '/img/red_MarkerA.png' }
onClick={() => props.onToggleOpen(index)}
>
{props.isOpen && <InfoWindow onCloseClick={() => props.onToggleOpen(index)}>
<div>hi</div>
</InfoWindow>}
</Marker>
}
}
)}
</GoogleMap>
);

You have mapped your InfoWindows out and conditionally render them with props.isOpen.
The onClick event for InfoWindow accepts an index argument.
However... in your onClick function, any click event will toggle isOpen to true (regardless of the index)
Since all InfoWindows depend only on the boolean isOpen, clicking one will set isOpen to true, and all of them open :)
A way around this is to store the index of the clicked marker. eg: clickedMarkerIndex = x.
Then, check {props.clickedMarkerIndex === index } and render the InfoWindow only if its' index matches the index of the clicked marker.

Related

Material Ui 5 Autocomplete does not return filtered options

I am using material ui V5,
Due to the default filtering in Autocomplete does not give proper result array, l have written my own filterOptions function.
const filterOpt = (options, state) => {
let result = options.filter(option => option.name.includes(state.inputValue))
return result }
The result returning from the function is exactly what l want. But still, l can see the undesired options.
Here is my Autocomplete component :
<StyledAutocomplete
disabled={disabled}
id="field1"
getOptionLabel={(option) => option.name || ""}
isOptionEqualToValue={(option, value) => option.id === value.id}
value={values[prop] || ""}
noOptionsText={"No options found"}
options={data}
style={{ width: "100%" }}
PopperComponent={PopperMy}
PaperComponent={CustomPaper}
onChange={(event, newValue) =>
setValues({ ...values, [prop]: newValue })
}
filterOptions={(options, state) => filterOpt(options, state)}
renderInput={(params) => {
const inputProps = params.inputProps;
inputProps.autoComplete = "new-password";
return (
<StyledTextField
{...params}
inputProps={{
...params.inputProps,
autoComplete: "new-password",
}}
name="field1"
id="field1"
autoComplete="off"
type="text"
label=""
variant="outlined"
error={error && !values[prop]}
helperText={error && errorStatus ? errorTexts[prop] : ""}
/>
);
}}
/>
Here are the options that l see after filtering
Here is the results array returned from the function:
Is there any solution to show the exact filtered array to users?
This is because of my data array which includes some items with the same key.

Is it possible to get a set of checkboxes in react-hook-form to bind to a single array?

I am using react-hook-form with material-ui checkboxes. The following code works fine; however, each checkbox gets bound to its own field. In partial, when I hit submit, the output is something like this: {option1: true, option2: undefined, option3: true}
What I am hoping for is to get the output from all three checkboxes to bind into a single array, i.e. for the output to be something like this: {checkboxFieldName: [option1, option3]}.
I know that when using Formik, this is possible when the FormControlLabels of all checkboxes have the same names. So, just wondering if something similar is possible with react-hook-form.
Thank you in advance.
const options = [
{ key: 'option1', label: 'Option 1', val: 'option1' },
{ key: 'option2', label: 'Option 2', val: 'option2' },
{ key: 'option3', label: 'Option 3', val: 'option3' },
]
function App() {
const { handleSubmit, control } = useForm();
const onSubmit = data => console.log(data);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<FormControl>
<FormLabel>Check all suitable options</FormLabel>
<FormGroup>
{options.map((option) => (
<FormControlLabel
key={option.key}
name='checkboxFieldName'
value={option.val}
control={
<Controller
control={control}
name={option.key}
render={({ field: { onChange, value }}) => (
<Checkbox
checked={value}
onChange={e => onChange(e.target.checked)}
/>)}
/>}
label={option.label}
/>)
)}
</FormGroup>
</FormControl>
<Button type="submit">Save</Button>
</form>
);
}
I tried to do the same thing with Material UI but I ended up with more problems. Basically, you need to store selected checkboxes somewhere else (within an array) and set them to react-hook-form's state by using setValue function: https://react-hook-form.com/api/useform/setvalue/
Example: https://blog.logrocket.com/using-material-ui-with-react-hook-form/
If you don't want to do it manually, I would suggest using react-spectrum. It has a CheckboxGroup component and it stores all the selected values in an array: https://react-spectrum.adobe.com/react-spectrum/CheckboxGroup.html
Another solution is that you don't try to convert material-ui's object (checkboxFieldName: {option1: true, option2: undefined, option3: true}) into an array ({checkboxFieldName: [option1, option3]}) before submitting. Once you submit your form, you can convert your checkboxFieldName object into an array before passing the data to an API. Such as:
let checkboxFieldName = {option1: true, option2: undefined, option3: true}
let redefinedCheckboxFieldName = []
Object.entries(checkboxFieldName).forEach(([key, value]) => {
if(value){
redefinedCheckboxFieldName.push(key)
}
})

what's ref in react native and when should i use ref?

I'm working on react native project, i created forms with react native components.
I used TextInput to edit a state value like this :
<TextInput
shake
keyboardAppearance='light'
autoFocus={false}
autoCapitalize='none'
autoCorrect={false}
keyboardType='default'
returnKeyType='next'
value={this.state.sector}
onChangeText={sector => this.setState({ sector })}
/>
With console.log the sector value i get correctly the current value after input change, but i have seen some examples with ref like this :
<TextInput
shake
keyboardAppearance='light'
autoFocus={false}
autoCapitalize='none'
autoCorrect={false}
keyboardType='default'
returnKeyType='next'
value={this.state.sector}
ref={input => (this.sectorInput = input)}
onChangeText={sector => this.setState({ sector })}
/>
i don't understand this operation :
ref={input => (this.sectorInput = input)}
can someone explain what's the ref and why we used with an input and when should i use a ref ?
If you want to access TextInput methods then you have to create reference of that component, then using reference you can use it's method.
For example you have form in your app and you want that when user fill the first field and after that you want to set focus on next field then you can do like this :
<TextInput
shake
keyboardAppearance='light'
autoFocus={false}
autoCapitalize='none'
autoCorrect={false}
keyboardType='default'
returnKeyType='next'
value={this.state.sector}
ref={input => { this.sectorInput = input}}
onSubmitEditing={() => {
this.nextField.focus();
}}
onChangeText={sector => this.setState({ sector })}
/>
<TextInput
shake
keyboardAppearance='light'
autoFocus={false}
autoCapitalize='none'
autoCorrect={false}
keyboardType='default'
returnKeyType='next'
value={this.state.sector}
ref={input => { this.nextField = input}}
onSubmitEditing={() => {
this.handleSubmit();
}}
onChangeText={nextField => this.setState({ nextField })}
/>
Now, when user will fill the sector field then if he press next, then nextField will automatically get focused.

Autocomplete - How can I set a default value?

Does anyone know how to add a default value to the Autocomplete component?
The component have a dataSource, and I'd like to load the page with a specific item already selected(e.g. fill the text field with the selected item's text and it's value already set).
Does anyone knows how? Big thanks for any help <3
You can achieve this by setting the appropriate state in componentDidMount, for example:
componentDidMount() {
// load your items into your autocomplete
// set your default selected item
this.setState({ allItems: [itemYouWantToSet], selectedItem: item.name, selectedItemId: item.id }
}
render() {
return (
<Autocomplete
value={this.state.selectedItem}
items={this.state.allItems}
getItemValue={(item) => item.name}
onSelect={(value, item) => {
this.setState({ selectedItem: value, selectedItemId: value, allItems: [item] });
}}
/>
)
}
Then your item is correctly selected from the list of available options when it loads.
I tried all the above solutions and nothing worked. Perhaps the API has changed since then.
I finally figured out a solution. It's not so elegant, but in principle it makes sense. In my case the options are objects. I just had to set the "value" prop using the exact item from my options array. This way componentDidMount and getOptionSelected aren't needed.
Autocomplete is wrapped inside another component in our case. This is the main code:
class CTAutoComplete extends React.PureComponent {
getSelectedItem(){
const item = this.props.options.find((opt)=>{
if (opt.value == this.props.selectedValue)
return opt;
})
return item || {};
}
render() {
return (
<Autocomplete
id={this.props.id}
className={this.props.className}
style={{ width: '100%' }}
options={this.props.options}
getOptionLabel={this.props.getOptionLabel}
renderInput={params => (
<TextField {...params} label={this.props.label} variant="outlined" />
)}
onChange={this.props.onChange}
value={this.getSelectedItem()}
/>
);
}
}
IMPORTANT: When setting "value", you have to make sure to put the null case " || {} ", otherwise React complains you are changing from an uncontrolled to controlled component.
you can provide the defaultValue prop for AutoComplete.
<Autocomplete
multiple
id="tags-outlined"
options={this.state.categories}
getOptionLabel={(option) => option.category_name}
onChange={this.handleAutocomplete}
defaultValue={'yourDefaultStringValue'} //put your default value here. It should be an object of the categories array.
filterSelectedOptions
renderInput={(params) => (
<TextField
fullWidth
{...params}
variant="outlined"
label="Add Categories"
placeholder="Category"
required
/>
}
/>
This approach works for me (using hooks):
First of all define the options you need in a variable:
const genderOptions = [{ label: 'M' }, { label: 'V' }];
Second you can define a hook to store the selected value (for example store it in session storage for when the page refreshes, or use useState directly):
const age = useSessionStorage('age', '');
Next you can define your Autocomplete as follows (notice the default values in value and getOptionLabel, if you omit those you'll get those controlled to uncontrolled warnings):
<Autocomplete
id="id"
options={ageOptions}
getOptionLabel={option => option.label || ""}
value={ageOptions.find(v => v.label === age[0]) || {}} // since we have objects in our options array, this needs to be a object as well
onInputChange={(_, v) => age[1](v)}
renderInput={params => (
<TextField {...params} label="Leeftijd" variant="outlined" />
)}
/>
It is tricky specially in case of you are using along with filter option which load API on every filter. I was able to load initial value by setting up within state and onInputChange option.
Below is code that work for me or click below link for full working demo.
https://codesandbox.io/s/smoosh-brook-xgpkq?fontsize=14&hidenavigation=1&theme=dark
import React, { useState, useEffect } from "react";
import TextField from "#material-ui/core/TextField";
import Typography from "#material-ui/core/Typography";
import Autocomplete from "#material-ui/lab/Autocomplete";
export default function CreateEditStrategy({ match }) {
const [user, setUser] = useState({
_id: "32778",
name: "Magic User's Club!"
});
const [filter, setFilter] = useState("");
const [users, setUsers] = useState([]);
const [openAutoComplete, setOpenAutoComplete] = React.useState(false);
useEffect(() => {
(async () => {
//Will not filter anything for testing purpose
const response = await fetch(
`https://api.tvmaze.com/search/shows?q=${filter}`
);
const shows = await response.json();
setUsers(
shows.map((a, i) => {
return { _id: a.show.id, name: a.show.name };
})
);
})();
}, [filter]);
return (
<div>
<Typography variant="h6">Autocomplete</Typography>
<Autocomplete
open={openAutoComplete}
onOpen={() => setOpenAutoComplete(true)}
value={user}
inputValue={filter}
onClose={() => setOpenAutoComplete(false)}
onChange={(event, user) => {
if (user) setUser(user);
else setUser({ _id: "", name: "" });
}}
onInputChange={(event, newInputValue) => setFilter(newInputValue)}
getOptionSelected={(option, value) => option.name === value.name}
getOptionLabel={(option) => option.name}
options={users}
renderInput={(params) => (
<TextField
{...params}
label="Asynchronous"
variant="outlined"
InputProps={{
...params.InputProps
}}
/>
)}
/>
</div>
);
}
Call your component like this
<SelectCountryAutosuggest searchText="My Default Value" />
Make sure you apply the default value to state on class load
class SelectCountryAutosuggest extends React.Component {
state = {
value: this.props.searchText, //apply default value instead of ''
suggestions: [],
};
...
}
The api docs suggest the best approach in the current version (June 2022) is to use value and isOptionEqualToValue.
So for example, if I have a list of users and am choosing which user this thing is assigned to:
const [assignedTo, setAssignedTo] = useState(initialOption);
return (<Autocomplete
options={users.map((i) => ({
label: i.name,
value: i._id,
}))}
isOptionEqualToValue={(o, v) => o.value === v.id}
value={assignedTo}
onChange={(evt, val) => setAssignedTo(val)}
renderInput={(params) => (
<TextField {...params} label="Assigned To" />
)}
/>);
We can setup initial value through value property of Autocomplete component
<Autocomplete
fullWidth={true}
label={'Location'}
margin={'noraml'}
multiple={false}
name={'location'}
getOptionSelected={useCallback((option, value) => option.value === value.value)}
value={formValues.location === '' ? {label: ''} : {label: formValues.location}}
options={location}
ref={locationRef}
onChange={useCallback((e, v) => handleInputChange(e, v))}
/>
You can use the searchText property.
<AutoComplete searchText="example" ... />
Try this...
componentWillReceiveProps(nextProps) {
let value = nextProps.value
if (value) {
this.setState({
value: value
})
}
}
onUpdateInput worked for me - for anyone looking through all this as I was
Have you tried setting the searchText prop dynamically? You can pass the value you want to set to the Autocomplete component as the searchText prop. Something like,
<Autocomplete
searchText={this.state.input.name} // usually value={this.state.input.name}
/>
By default, it'll have the initial value set into the TextField of the Autocomplete component but when the user makes any modifications, it calls up the Autocomplete options depending on the dataSource prop.

Telerik MVC Grid not sorting when reloaded

My Telerik MVC grid is Ajax bound and I need to ability to apply custom filtering via two checkboxes (in the DIV at the top). When a checkbox is checked, the parameters would be set and the grid is reloaded. This is working fine. During the initial load the data is sorted based on the sorting settings in Telerik, but after I click a checkbox, the data is ordered by record Id and no longer by Priority. If I then hit F5 the page is reloaded and the data is sorted correct. The sorting might be a parameter for grid.rebind() or provided in OnDataBinding, but so far I have not found what I am looking for.
QUESTION: How do I specify the sorting order in the OnDataBinding or perhaps in another client event.
Here is my code:
<div style="float:right;width:600px;text-align:right">
<span>My Items <%=Html.CheckBox("newItems") %></span>
<span>Closed Items <%=Html.CheckBox("Inactive") %></span>
</div>
<% Html.Telerik().Grid<IssueModel>()
.Name("Grid")
.PrefixUrlParameters(false)
.Columns(col =>
{
col.Bound(o => o.Title);
col.Bound(o => o.Priority).Width(50).Title("Priority ID");
col.Bound(o => o.PriorityName).Width(100).Title("Priority");
col.Bound(o => o.IssueStateName).Width(100).Title("Status");
col.Bound(o => o.AssignedToName).Width(140).Title("Assigned To");
})
.DataBinding(d => d.Ajax().Select("AjaxSelect", "Ticket", new { isNew = false, isInactive = false }))
.ClientEvents(e => e.OnDataBinding("onDataBinding"))
.Sortable(s => s
.SortMode(GridSortMode.MultipleColumn)
.OrderBy(order =>
{
order.Add(o => o.Priority);
order.Add(o => o.Sequence);
})
)
.Pageable(p => p.PageSize(15))
.Filterable()
.Render();
%>
<script type="text/javascript">
function onDataBinding(e) {
e.data = {
isNew: $("#newItems").is(':checked'),
isInactive: $("#Inactive").is(':checked')
};
e.orderBy = "Severity~desc~Ranking~asc";
}
$("input[type='checkbox']").click(function () {
var grid = $('#Grid').data('tGrid');
var param = {
isNew: $("#newItems").is(':checked'),
isInactive: $("#Inactive").is(':checked')
};
grid.rebind(param);
});
</script>
I found the solution in case others need the answer. I used grid.sort() in place of grid.rebind(); The sort method takes a string in the format: column-name dash direction. Example First
<script type="text/javascript">
function onDataBinding(e) {
e.data = {
isNew: $("#newItems").is(':checked'),
isInactive: $("#Inactive").is(':checked')
};
}
$("input[type='checkbox']").click(function () {
var grid = $('#Grid').data('tGrid');
var param = {
isNew: $("#newItems").is(':checked'),
isInactive: $("#Inactive").is(':checked')
};
grid.sort("Severity-desc~Ranking-asc";);
//grid.rebind(param);
});
</script>