How can I create a react-leaflet v3 custom control from a react component? - react-leaflet-v3

I need to tranform a regular react component into a react-leaflet component, as for instance:
const Sidebar = (props) {
const [state, setState] = useState({
collapsed: true,
selected: 'home'
})
function onSidebarClose() {
setState({collapsed: true})
}
function onSidebarOpen(id) {
setState({
collapsed: false,
selected: id
})
return ( ... )
}
Can I use L.Control.extend()? If so, how should I modify the onAdd option?
Is there another way to do it? In react-leaflet v2 it was very simple. You just defined:
class Sidebar extends MapComponent { ... }
MapComponent is not present anymore in react-leaflet v3.
Any help is very appreciated.

Related

Set a form value using react-hook-form within React-Admin

In my React-Admin app, I'd like to leverage react-hook-form's useFormContext for various things, such as, for example, setting the default pre-selected choice in this custom input field:
...
import {
Create, SimpleForm, SelectInput
} from 'react-admin';
import { useFormContext } from 'react-hook-form';
const MyInput = () => {
const formContext = useFormContext();
formContext.setValue('category', 'tech');
return (
<SelectInput source="category" choices={[
{ id: 'tech', name: 'Tech' },
{ id: 'people', name: 'People' },
]}
/>
);
};
...
const ItemCreate = () => {
return (
<Create>
<SimpleForm>
<MyInput />
</SimpleForm>
</Create>
);
};
...
This sets the pre-selected value of the field, just as intended. But it throws a warning: Cannot update a component ("Form") while rendering a different component ("MyInput")...
Is there some way to achieve this without getting the warning?
Note: The only reason I'm using a custom input field here is because when I put useFormContext() directly into the component that contains SimpleForm it returns null (similarly described here).
The warning is related to the fact that the entire body of the MyInput() function is executed during each render, you need to call the setValue() function inside the useEffect hook.
Got this working by moving formContext.setValue into a useEffect hook:
...
import {
Create, SimpleForm, SelectInput
} from 'react-admin';
import { useFormContext } from 'react-hook-form';
const MyInput = () => {
const formContext = useFormContext();
// moved the setValue into a useEffect
useEffect(() => {
formContext.setValue('category', 'tech');
});
return (
<SelectInput source="category" choices={[
{ id: 'tech', name: 'Tech' },
{ id: 'people', name: 'People' },
]}
/>
);
};
...
const ItemCreate = () => {
return (
<Create>
<SimpleForm>
<MyInput />
</SimpleForm>
</Create>
);
};
...

How to use mobx observer with antd table columns render function

When wrapping an antd table column render function with observer, I get an error of render is not a function. I'm trying to avoid the need to every time create a separate observer wrapped functional component that I call from a column render function. Anybody had any luck or knows how to directly wrap a column render function with an observer (or an observer like implementation)?
Here is the typescript code:
import { observer } from "mobx-react-lite";
import { Button } from "antd";
import { ColumnsType } from "antd/lib/table";
import { TableDataSourceRecord } from "../models/TableDataSourceRecord";
const columns: ColumnsType<TableDataSourceRecord> = [
{
title: "View",
dataIndex: "url",
key: "url",
render: observer((url: any, _record: TableDataSourceRecord) => {
return (
<Button type="link" href={url.value}>
View
</Button>
);
}),
},
];
Thanks
Since render expects a function you can't pass React.Component to it, I guess (observer creates component).
Have you tried <Observer> wrapper component?
import { Observer } from "mobx-react-lite";
import { Button } from "antd";
import { ColumnsType } from "antd/lib/table";
import { TableDataSourceRecord } from "../models/TableDataSourceRecord";
const columns: ColumnsType<TableDataSourceRecord> = [
{
title: "View",
dataIndex: "url",
key: "url",
render: (url: string, _record: TableDataSourceRecord) => {
return (
<Observer>
{() => (
<Button type="link" href={url}>
View
</Button>
)}
</Observer>
);
},
},
];
Anyway, there is no point in having Observer in your example since you have no observable values there, url is just string primitive.

ReactNative IOS Testflight Firebase MapStateToProps Update Issue

I am having an extremely weird issue where I don't even know where to begin to debug since it only happens when I get the app into test flight. Basically I am trying to load channels based on geolocation. Some automatically load and then some are loaded if less than 100 miles from a mountain (lets call these PUBLIC and PRIVATE channels- both of which are in the same list). I have these 2 firebase calls in my action creator and push them into an array and then call dispatch after. I have an issue with the FlatList where it loads the PUBLIC channels and only when I scroll do the PRIVATE channels. There are a bunch of things I have tried including the most common from that specific github issue (flatlist updating) 'removeClippedSubviews={false}', extra data, pure components, etc, but none have worked.
Instead I found a way to get around this (I know it isn't the best, but I just want a solution that works for now) by just setting a timeout and waiting for the channels before dispatching the action:
setTimeout(function(){ dispatch(loadPublicChannelsSuccess(data)); }, 10);
Unfortunately, now is when the crazy issue comes in. Basically, this works for debug, release/ everything I have tried with XCode, but when it gets to my device the render method basically sits at a loading indicator until I switch tabs with react navigation. This makes no sense to me since it doesn't happen always (maybe 80%) of the time, and only in test flight so far. I had never seen this before setting the timeout either so not really sure where to begin:
render() {
const {loadPublicChannels, loading, publicChannels, checkedInMountain, selectedMountain} = this.props;
return !loadPublicChannels && publicChannels && !loading
? (
<MessagePanelPublic publicChannels={publicChannels} selectedMountain={selectedMountain}/>
) : (
<LoadingAnimation />
);
}
actions
export const getUserPublicChannels = () => {
return (dispatch, state) => {
dispatch(loadPublicChannels());
// get all mountains within distance specified
let mountainsInRange = state().session.mountainsInRange;
// get the user selected mountain
let selectedMountain = state().session.selectedMountain;
// see if the selected mountain is in range to add on additional channels
let currentMountain;
mountainsInRange
? (currentMountain =
mountainsInRange.filter(mountain => mountain.id === selectedMountain)
.length === 1
? true
: false)
: (currentMountain = false);
// mountain public channels (don't need to be within distance)
let currentMountainPublicChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Public");
// mountain private channels- only can see if within range (geolocation)
let currentMountainPrivateChannelsRef = FIREBASE_REF_CHANNEL_INFO.child(
"Public"
)
.child(`${selectedMountain}`)
.child("Private");
// get public channels
return currentMountainPublicChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
let publicChannelsToDownload = [];
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
// add the channel ID to the download list
publicChannelsToDownload.push({ id: channelId, info: channelInfo });
});
// if mountain exists then get private channels/ if in range
if (currentMountain) {
currentMountainPrivateChannelsRef
.orderByChild("key")
.once("value")
.then(snapshot => {
snapshot.forEach(channelSnapshot => {
let channelId = channelSnapshot.key;
let channelInfo = channelSnapshot.val();
publicChannelsToDownload.push({
id: channelId,
info: channelInfo
});
});
});
}
return publicChannelsToDownload;
})
.then(data => {
setTimeout(function(){ dispatch(loadPublicChannelsSuccess(data)); }, 10);
});
};
};
reducer related to public channels
case types.LOAD_PUBLIC_CHANNELS:
return {
...state,
loadPublicChannels: true
};
case types.LOAD_PUBLIC_CHANNELS_SUCCESS:
console.log("PUBLIC");
console.log(action.publicChannels);
console.log(action);
return {
...state,
publicChannels: action.publicChannels,
loadPublicChannels: false,
messages: {}
};
case types.LOAD_PUBLIC_CHANNELS_ERROR:
return {
...state,
channelsPublicError: action.error,
loadPublicChannels: false
};
container which calls mapStateToProps and mapDispatchToProps
class MessagePanelPublicContainer extends Component {
constructor(props) {
super(props);
}
// get public and private channels from redux
async componentDidMount() {
this.props.getUserPrivateChannels();
this.props.loadCurrentUser();
// this.props.getUserPublicChannels();
}
componentDidUpdate(prevProps) {
if (this.props.mountainsInRange && prevProps.mountainsInRange !== this.props.mountainsInRange || prevProps.selectedMountain !== this.props.selectedMountain) {
this.props.getUserPublicChannels();
}
}
lessThan12HourAgo = (date) => {
return moment(date).isAfter(moment().subtract(12, 'hours'));
}
render() {
const {loadPublicChannels, loading, publicChannels, checkedInMountain, selectedMountain} = this.props;
return !loadPublicChannels && publicChannels && !loading
? (
<MessagePanelPublic publicChannels={publicChannels} selectedMountain={selectedMountain}/>
) : (
<LoadingAnimation />
);
}
}
const mapStateToProps = state => {
return {
publicChannels: state.chat.publicChannels,
loadPublicChannels: state.chat.loadPublicChannels,
currentUser: state.chat.currentUser,
loading: state.chat.loadCurrentUser,
mountainsInRange: state.session.mountainsInRange,
selectedMountain: state.session.selectedMountain,
};
}
const mapDispatchToProps = {
loadCurrentUser,
getUserPublicChannels,
};
export default connect(
mapStateToProps,
mapDispatchToProps
)(MessagePanelPublicContainer);
component
import React, { Component } from "react";
import { View, Text, FlatList, ImageComponent } from "react-native";
import { connect } from "react-redux";
import PropTypes from "prop-types";
import { ListItem, Icon, Button } from "react-native-elements";
import { withNavigation } from "react-navigation";
import LinearGradient from "react-native-linear-gradient";
import styles from "./Styles";
import moment from 'moment';
import FastImage from 'react-native-fast-image';
class MessagePanelPublicComponent extends Component {
render() {
// rendering all public channels
const renderPublicChannels = ({ item }) => {
return (
<ListItem
leftAvatar={{
source: { uri: item.info.ChannelPicture },
rounded: false,
overlayContainerStyle: { backgroundColor: "white" },
ImageComponent: FastImage
}}
title={item.info.Name}
titleStyle={styles.title}
chevron={true}
bottomDivider={true}
id={item.Name}
containerStyle={styles.listItemStyle}
/>
);
};
const renderText = () => {
return (
<View style={styles.extraTextContainer}>
<Text style={styles.extraText}>
Get within 100 miles from resort or select a closer resort to see more channels...
</Text>
<Icon
name="map-marker"
type="font-awesome"
size={40}
iconStyle={styles.extraIcon}
/>
</View>
);
};
return (
<View style={styles.container}>
<View style={styles.channelList}>
<FlatList
data={this.props.publicChannels}
renderItem={renderPublicChannels}
// keyExtractor={item => item.Name}
keyExtractor={(item, index) => index.toString()}
extraData={this.props.publicChannels}
removeClippedSubviews={false}
/>
{this.props.publicChannels.length < 3 ? renderText() : null}
</View>
</View>
);
}
}

Angular2 ControlValueAccessor

i'm trying to understand how ControlValueAccessor work precisely.
I have studied the behavior of it with two different control component:
The first is suppose to provide a primitive value: a single number.
The second provide a complex object.
So in short:
class FirstControlComponent implements ControlValueAccessor {
// ...
value:number = 10;
writeValue(value: number) {
this.value = value;
}
// ...
}
class SecondControlComponent implements ControlValueAccessor {
// ...
value:any = {};
writeValue(value: any) {
this.value = value;
}
// ...
}
The ControlValueAccessor interface only specify a 'setter': writeValue, but no 'getter'.
So when i bind a Control to SecondControlComponent, something like:
this.form = this.builder.group({
controlName: this.builder.control(this.theObject) });
and later in the template:
<second-component ngControl='controlName'> <second-component>
Everything works just fine, because writeValue is called on init with a reference to the existing theObject object, so the control modify the same instance of the object (hope i'm clear)
BUT: if i do exactly the same thing with FirstControlComponent, because the value is not passed as a reference (cause it's a primitive), and because ControlValueAccessor do not provide a 'setter' the value in my control and the value in my host component are NOT kept in sync ...
Does this mean that we HAVE to pass Object and not primitive to custom control implementing the ControlValueAccessor? I Guess no, so i guess i must be misunderstanding something .. :)
I'm using it the right way ?
Any hints are welcome !
Thanks !
It's not clear to me what you try to do here but ControlValueAccessor is an entity that you need to register for your element. Something like that:
const CUSTOM_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => LabelsValueAccessor), multi: true});
#Directive({
(...)
providers: [CUSTOM_VALUE_ACCESSOR]
})
export class LabelsValueAccessor implements ControlValueAccessor {
(...)
}
It will then take part of the value updates (from component and from the template). When you set a value within the component on the input (for example), the writeValue method is called in your value accessor. If you want to update the input value from the value accessor, you need to leverage the registered onChange callback by Angular2
See this article (section "NgModel-compatible component") for more details:
http://restlet.com/blog/2016/02/17/implementing-angular2-forms-beyond-basics-part-2/
In Parent Component I want output of form like this {someOtherControlName: any, controlName: number} so output of Child form must be number.also in update form (example) parentform.patchValue({someOtherControlName: '', controlNmae: 3}) value of formControl must set properly
#Component({
selector: 'parent',
template: `
<div [formGroup]="form">
<second-component [formControl]='form.controlName'> <second-component>
<div formControlName="someOtherControlName"></div>
export class ParentComponent {
parentForm = new FormGroup({
controlName: new FormControl(), <<<<<======important
someOtherControlName: new FormControl()
})
}
In ControlValueAccessor:
#Component({
selector: 'counter',
template: `
<div class="number-input" [formGroup]="form">
<input type="text" class="form-control text-center" formControlName="counter" />
</div>
`,
styleUrls: ['counter.component.scss'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: CounterComponent,
multi: true,
},
],
})
export class CounterComponent implements ControlValueAccessor, OnDestroy, OnInit {
form = new FormGroup({
counter: new FormControl(0, Validators.required),
})
private onChange: (value: any) => void = value => {}
private onTouched: () => void = () => {}
myValue: number
private onDestroy$: Subject<void> = new Subject()
ngOnInit() {
this.form.valueChanges
.pipe(
tap(value => {
this.onChange(typeof value === 'number' ? value : value.counter) <<<< important
this.onTouched()
}),
takeUntil(this.onDestroy$)
)
.subscribe()
}
}
writeValue(value: number | { counter: number }) {
this.myValue = typeof value === 'number' ? value : value.counter
this.form.setValue({ counter: value > 0 ? value : 0 })
}
registerOnChange(fn: () => {}) {
this.onChange = fn
}
registerOnTouched(fn: () => {}) {
this.onTouched = fn
}
setDisabledState?(isDisabled: boolean): void {
// when the parent updates the
// state of the form control
if (isDisabled) {
this.form.disable()
} else {
this.form.enable()
}
}
ngOnDestroy(): void {
this.onDestroy$.next()
this.onDestroy$.complete()
}

React Native - Navigate after an async action

I'm developing a mobile app with React Native and Redux and I'm facing a software design problem.
I want to call a REST API (async operation) for login and navigate to main view if that operation was successful.
I'm using redux and thunk so I already have the async actions implemented so my main doubt is: Where should I put the logic to navigate to main view?
Can I access the navigator object directly from an action and perform the navigation there?
Should I do that in the Login Component? (As I'm doing it already - check the code below).
componentWillReceiveProps(nextProps){
if(nextProps.errorLoginMsg){
Alert.alert("Login Failed", nextProps.errorLoginMsg);
}
else if(!nextProps.user.isNull()){
this.props.navigator.replace({name: 'main'});
}
}
I'm not confident of having that logic in the component. Does not seem a good practice. Are there any other way to do this?
Thanks
Here is the code how I do it:
const resetAction = NavigationActions.reset( {
index : 0,
actions: [
NavigationActions.navigate( { routeName: 'Home' } )
]
} );
this.props.requestDeleteEvent( {
id: eventID
} ).then( () => {
this.props.navigation.dispatch( resetAction );
} );
And inside function requestDeleteEvent:
export function requestDeleteEvent(requestData) {
const id = requestData.id;
return (dispatch, getState) => {
return fetch(Config.event + id, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json; charset=UTF-8',
},
})
.then((response) => getResponseJson(response))
.then((responseJson) => {
if (responseJson.isSuccess) {
return dispatch(deleteEvent(id));
}
else {
return dispatch(triggerToast({type: 'alert', content: ERROR}));
}
}
);
}
}
This is one of the hardest problems in react native with the current Navigator API. I would suggest having a route store which holds the current route and have the component which includes the Navigator connected to this store and having a navigation triggered on componentWillReceiveProps.

Resources