Autocomplete dropdown input selection not triggering ngModelChange - google-maps

When I choose a google maps address autocompleted result from the dropdown, it doesn't pass to my parent component, but it does when I manually enter the address.
I'll start typing, and it's passing the info, and once I select an address it fills in the rest for me, but doesn't pass that data, even though it's showing in the input box.
I'm using this input (in the child template) to get addresses with a google map div:
<input
[(ngModel)]="address"
(ngModelChange)="addressChange.emit($event)"
id="pac-input"
name='address'
class="mapControls"
type="text"
placeholder="Enter Address"
>
in my child component:
#Component({
selector: 'gmap',
})
export class gMap{
#Input() address;
#Output() addressChange = new EventEmitter();
}
in my parent template:
<gmap [(address)]="address"></gmap>
and parent component:
address: string;
Obviously there's more code, but this is the important stuff I think. Anyone know how to make it so when I select an autocomplete result from the dropdown it triggers ngModelChange with the data chosen from the selection?

I sort of fixed it, I did this:
So since we're using typescript I changed google's all of
function()
to
() =>
then, under these functions of the google map code
searchBox.addListener('place_changed', () => {
places.forEach((place) => {
I called the emitter
this.addressChange.emit(place.formatted_address);
there's a huge delay, sometimes like 20 seconds, but it works!
Anyone know why there's a few-second delay between when I choose from the drop-down and the emitter goes off? I'd really like to remove that delay
If you use promise object then the response will be very fast.
return new Promise((resolve, reject) =>{
autocomplete.addListener('place_changed', () => {
var place = autocomplete.getPlace();
console.log("place "+place);
resolve(place);
});
})

Related

Prevent form rerendering in conrolled react inputs

I am new to React.js and recently I learned about controlled inputs in React.
Code:
Here's a sample implementation that I made:
import React, { useState } from 'react';
const MyForm = () => {
console.log('rendered'); // Line 5
const [text1, setText1] = useState('');
const [text2, setText2] = useState('');
const onSubmit = (evt) => {
evt.preventDefault();
console.log(text1, text2);
}
return (<form onSubmit={onSubmit}>
<input type="text" value={text1} onChange={ev => setText1(ev.target.value)} />
<input type="text" value={text2} onChange={ev => setText2(ev.target.value)} />
<input type="submit"/>
</form>);
};
Problem:
Performance.
Above implementation works correctly, but I noticed that every time one of the field changes, console.log('rendered'); at line 5 is called again, and the entire form seems to be re-rendered. I guess that this could cause some problem especially for more advanced forms with many input fields and heavy pre-processing, etc. Ideally only the field that has changed should be re-rendered.
So I was wondering if my understanding of controlled inputs and form is okay. If not what is more scalable way of implementing this?
Since the state changes, the component will re-render. this is normal. if you dont want that, you need to "export" your input fields to new components with their own state, but then you have to somehow ref these components back to your parent form component in order to get their current values when you are going to submit the form.
Check this link on how to use ref, but I think that the form should be way too heavy in order for you to consider such a senario of creating for each input its own state in order to avoid parent component re-rendering on every input change, or even change to uncontrolled component, which is not usually recommended.

Keyup event fire multipletime

Currently, I am working on Angular 4 app. In my component Html, I have one textbox. Whenever user first type anything I want to make an API call to get some data.
The issue is if User type 'A' then it is working fine and calling API. But when user type "ABC" it is making API call 3 times. Instead of making API call for every letter, only one call should be made.
Please suggest any solution.
Component's HTML :
<input id="inputbox" (keyup)="keyUp($event)"/>
Component :
data: string[]
keyUp(event: any) {
this.loadDataApiCall();
}
loadDataApiCall() {
// calling api to load data.
//fill data into
}
Can I solve this issue with help of RXjs in angular 4
Observable.fromEvent(yourDomElement, 'keyup').auditTime(100).subscribe(()=>{
doSomething();
});
You should probably add a timeout to your call and clear it every time it is triggered so only the last call is called.
data: string[]
keyUp(event: any) {
window.clearTimeout(window.apiCallTimeout);
window.apiCallTimeout = window.setTimeout(this.loadDataApiCall, 100);
}
loadDataApiCall() {
// calling api to load data.
//fill data into
}
This means of course that the call will be done 100ms after the user stops typing. Also if he types "a" and after a while he types "bc", then two calls will be made. Of course you can increase the delay to meet your requirements.
If you only want one API call you can use the blur event, which is emitted when the control loses focus:
<input id="inputbox" (blur)="keyUp($event)"/>
Try this:
keyUp(event: any) {
this.loadDataApiCall();
event.stopImmediatePropagation();
}
the right way to implement this is by registering the event and calling the API after sometime while saving the latest value and checking that the last registered value matches the latest registered value
so in your keyup
keyUp(event: any) {
this.latestValue = event.target.value;
this.registerApiCall(event.target.value);
}
register func
registerApiCall(value){
setTimeout(this.loadDataApiCall.bind(this), 500, value)
}
api call
loadDataApiCall(value) {
if (this.latestValue == value ){
// calling api to load data.
//fill data into
}
}
see working example in this plnk
EDIT:
Observable.fromEvent(yourDomElement, 'keyup').auditTime(100).subscribe(()=>{
doSomething();
});
by é™ˆæšćŽ is the RxJs implementation that looks much better, and here is a working plnkr
If you're willing to change your form to Reactive Forms this would be extremely easy
this.form.get("input").valueChanges.debounceTime(1000).subscribe((value) => {});
Reactive Forms gives you access to observables of value changes and status changes. We're basically subscribing to that observable which emits the value any time it changes and we add a delay of one second so that if the user is still typing and changing the value then it will not execute the code in our subscribe.
#Component({
selector: 'my-app',
template: `
<div>
<input type="text" (keyup)='keyUp.next($event)'>
</div>
,
})
export class App {
name:string;
public keyUp = new Subject<string>();
constructor() {
const subscription = this.keyUp
.map(event => event.target.value)
.debounceTime(1000)
.distinctUntilChanged()
.flatMap(search => Observable.of(search).delay(500))
.subscribe(console.log);
}
}

Refresh previous screen on goBack()

I am new to React Native. How can we refresh/reload previous screen when returning to it by calling goBack()?
Lets say we have 3 screens A, B, C:
A -> B -> C
When we run goBack() from screen C it goes back to screen B but with old state/data. How can we refresh it? The constructor doesn't get called 2nd time.
Adding an Api Call in a focus callBack in the screen you're returning to solves the issue.
componentDidMount() {
this.props.fetchData();
this.willFocusSubscription = this.props.navigation.addListener(
'willFocus',
() => {
this.props.fetchData();
}
);
}
componentWillUnmount() {
this.willFocusSubscription.remove();
}
UPDATE 2023: willFocus event was renamed to focus
componentDidMount() {
this.props.fetchData();
this.focusSubscription = this.props.navigation.addListener(
'focus',
() => {
this.props.fetchData();
}
);
}
componentWillUnmount() {
this.focusSubscription();
}
How about using useIsFocused hook?
https://reactnavigation.org/docs/function-after-focusing-screen/#re-rendering-screen-with-the-useisfocused-hook
const componentB = (props) => {
// check if screen is focused
const isFocused = useIsFocused();
// listen for isFocused, if useFocused changes
// call the function that you use to mount the component.
useEffect(() => {
isFocused && updateSomeFunction()
},[isFocused]);
}
For react-navigation 5.x use
5.x
use
componentDidMount() {
this.loadData();
this.focusListener = this.props.navigation.addListener('focus', () => {
this.loadData();
//Put your Data loading function here instead of my this.loadData()
});
}
For functional component
function Home({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
loadData();
//Put your Data loading function here instead of my loadData()
});
return unsubscribe;
}, [navigation]);
return <HomeContent />;
}
On your screen B constructor will work like magic :)
this.props.navigation.addListener(
'didFocus',
payload => {
this.setState({is_updated:true});
}
);
Yes, constructor is called only for the first time and you can't call it twice.
First: But you can separate the data getter/setter from the constructor and put it in a function, this way you can pass the function down to the next Scene and whenever you're going back you may simply recall the function.
Better: You can make a go back function in your first scene which also updates the scene while going back and pass the go back function down. This way the second scene would not be aware of your update function which is reasonable.
Best: You can use redux and dispatch a go-back action in your second scene. Then in your reducer you take care of going back & refreshing your scene.
The built in listener function which comes with React-Navigation would be the easiest solution. Whenever a component is 'focused' on a again by navigating back, the listener will fire off. By writing a loadData function that can be called both when loading the Component AND when the listener is notified, you can easily reload data when navigating back.
componentWillMount(){
this._subscribe = this.props.navigation.addListener('didFocus', () => {
this.LoadData();
//Put your Data loading function here instead of my this.LoadData()
});}
Easy! insert the function inside useFocusEffect(func)
import { useFocusEffect } from '#react-navigation/native'
I have a similar situation and the way i refreshed was to reset the route when the back button is pressed. So, what happens is when the back button is pressed the screen is re-pushed into the stack and the useEffect on my screen loads the data
navigation.reset({
index: 0,
routes: [{ name: "SCREEN WHERE THE GOBACK BUTTON SHOULD GO" }],
});
Update for react-navigation v5 and use the React Hooks. Actually, the use is the same with react base class. For more detail, please checkout the documentation here
Here is the sample code:
function Profile({ navigation }) {
React.useEffect(() => {
const unsubscribe = navigation.addListener('focus', () => {
// do something
});
return unsubscribe;
}, [navigation]);
return <ProfileContent />;
}
As above code, We add the event listener while the variable navigation change then We do something like call function refresh() and finally, we return the function for removing the event listener. Simple!
I think we have a very easy way (which works in 2021) to do so. Instead of using goBack or navigate, you should use push
this.props.navigation.push('your_route_B').
You can also pass params in the same way as we pass in navigate.
The only difference b/w navigate and push is that navigate checks if the route which we are passing exists in the stack. Thus taking us to the older one but, push just sends us there without checking whether that is in the stack or not (i.e, whether the route was visited earlier or not.)
This can be achived by useFocusEffect from '#react-navigation/native'
useFocusEffect will effect every time when screen is focus
Ref: https://reactnavigation.org/docs/use-focus-effect/
import { useFocusEffect } from '#react-navigation/native';
function Profile({ }) {
useFocusEffect(
React.useCallback(() => {
//Below alert will fire every time when profile screen is focused
alert('Hi from profile')
}, [])
);
return // ...code ;
}
You can use this event: navigation.addListener('focus'
And you can implement like this:
const Cards = ({ navigation }) => {
...
useEffect(() => {
const load =async ()=>{
const a = await selectGlobalCards()
}
navigation.addListener('focus',() =>{
load();
});
}, [])
or you can use useIsFocused, and you can use that as a dependecy for useEffect
import { useIsFocused } from '#react-navigation/native'
const Cards = ({ navigation }) => {
const isFocused = useIsFocused()
useEffect(() => {
const load =async ()=>{
const a = await selectGlobalCards()
}
load()
}, [isFocused])
For react navigation (5.x), you just need to add a focus subscription and put your component initializing logic in a separate function like so:
componentDidMount() {
this.init();
this.didFocusSubscription = this.props.navigation.addListener(
'focus',
() => {
this.init();
}
);
}
init = async () => {
//fetch some data and set state here
}
If you're trying to get new data into a previous view, and it isn't working, you may want to revisit the way you're piping data into that view to begin with. Calling goBack shouldn't effect the mounting of a previous component, and likely won't call its constructor again as you've noted.
As a first step, I would ask if you're using a Component, PureComponent, or Functional Component. Based on your constructor comment it sounds like you're extending a Component class.
If you're using a component, the render method is subject to shouldComponentUpdate and the value of your state is in your control.
I would recommend using componentWillReceiveProps to validate the component is receiving the new data, and ensuring its state has been updated to reflect the new data.
If you're using the constructor to call an API or async function of some kind, consider moving that function into a parent component of both the route you're calling goBack from and the component you're wanting to update with the most recent data. Then you can ask your parent component to re-query the API, or update its state from a child component.
If Route C updates the "state/data" of the application, that update should be propagated to a shared parent of routes A, B and C, and then passsed down as a prop.
Alternatively, you can use a state management solution like Redux to maintain that state independent of parent/child components - you would wrap your components in a connect higher-order component to get the latest updates any time the application state changes.
TL;DR Ultimately it sounds like the answer to your question is rooted in where your application state is being stored. It should be stored high enough in your component hierarchy that each route always receives the latest data as a prop, passed from its parent.
Thanks to #Bat.
I have spent a lot of hours on finding the answer and finally, I got a basic solution which is working according to my needs. I was quite worried though.
Simply make a function like this in your previous activity make sure to bind it.
changeData(){
var mydata= salesmanActions.retrieveAllSalesman();
this.setState({dataListFill: mydata});
alert('' + mydata.length);
}
Simple, then in constructor bind this,
this.changeData= this.changeData.bind(this);
After that, as I am using react native navigation, so I will simply pass this function to the second screen just like the code below:
onPress={() => this.props.navigation.navigate('Add Salesman', {doChange:
this.changeData} )}
So when the new screen registered as "Add Salesman" will be called, a parameter named "doChange" which is assigned a function will also be transfered to other screen.
Now, in other screen call this method anywhere, by :
this.props.route.params.doChange();
It works for me. I hope works for you too, THANKS for the idea #Bat.
let we have 2 screen A and B , screen A showing all data . and screen B is responsible for adding that data. we add some data on using screen B and want to show instant changes on Screen A . we use below code in A
componentDidMount(){
this.focusListener = this.props.navigation.addListener('focus', () => {
thi`enter code here`s.startData();
//Put your Data loading function here
});
}
This is what you can do with react navigation v6.
Create a separate stack in stack navigator like this:
const PropertyListStack = () => {
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name={ROUTE_PROPERTY_LIST} component={PropertyList}/>
</Stack.Navigator>
)};
Now, whenever you you want to reload your initial screen navigate using this stack. like this:
navigation.navigate(
ROUTE_DASHBOARD_TABS,
{screen: ROUTE_PROPERTY_LIST_STACK}
);
This will reload your base screen. In my case base screen is PropertyList.
If you know the name of the Screen you want to go , then you can use this code.
navigation.navigate("Screen"); navigation.replace("Screen");
This code works fine if you don't have nested routes.
This answer assumes that the react-native-navigation library is being used, which is unlikely because it doesn't actually have a goBack() method...
The constructor doesn't call a second time because screen A and B are still rendered (but hidden behind screen C). If you need to know when screen B is going to be visible again you can listen to navigation events.
class ScreenB extends Component {
constructor(props) {
super(props);
// Listen to all events for screen B
this.props.navigator.setOnNavigatorEvent(this.onNavigatorEvent);
}
onNavigatorEvent = event => {
switch (event.id) {
case 'willAppear':
// refresh your state...
break;
};
}
Other events: willDisappear, didAppear, didDisappear
An alternate solution to your problem is to use a state management solution like Redux to provide the state to all screens whenever it is updated (rather than just on screen transitions. See old react-native-nav/redux example.

React - onChange children component

I'm trying to figure out why onChange wont work on a children component, i have a component named AlertMethod and im calling from a container.
<AlertMethod
onChange={this.methodSelected}
label="Sms"
value="sms"
/>
and methodSelected it's just a console.log() to test if getting called
methodSelected = (event, data) => {
console.log(data);
};
here's an img to explain it better.
so finally that function it wont getting called. that's my problem.
EDIT
On my Container AlertSystem.js i want to update a state if a checkbox it's checked or not, so my checkbox component it's Checkbox.js it's a stateless function, it recive 2 props label and value nothing special. so in my container when my Checkbox Component CHANGED, update my state, so that's why i have a onChange={this.methodSelected} on the children component, because in the methodSelected function ill update my state with this.setState and stuff.
You aren't calling props.onClick in AlertMethod
Your child component AlertMethod is being passed the onSubmit prop but not using it.
Should look more like this:
export const AlertMethod = ({ label, value, onClick }) => {
return (
<div>
<Checkbox value={value} fluid label={label} onClick={onClick} />
</div>
)
}

In a <form onSubmit={}>, is the event data automatically passed?

I'm following along with a React tutorial on Thinkster and noticed that the form has an onSubmit tag, which automatically passes on the event to be intercepted.
I did some quick research and couldn't seem to find any indication that this is normally what happens on an onSubmit. Am I missing something here? I just found it rather curious.
From CommentInput.js
...
this.createComment = ev => {
ev.preventDefault();
const payload = agent.Comments.create(this.props.slug, {body: this.state.body});
this.setState({body: ''});
this.props.onSubmit(payload);
};
}
render() {
return (
<form className="card comment-form" onSubmit={this.createComment}>
...
}
Thanks!
inside constructor() method declare
this.createComment= this.createComment.bind(this); to remove auto submiting if it is the case of your question. But if you are asking that, are the data passed when you click submit button, then yes. They should be sent.