How to handle a button press inside react-navigation titlebar - react-native

I am pretty new to react-native and I am currently handling navigation for my app using react-navigation.
I want to be able to add a button to my app's title bar created by react-navigation and be able to press a button to call a function.
static navigationOptions = ({ navigation }) => {
const { params } = navigation;
return {
title: 'Assign To',
headerRight: (
<Button
title='Back'
onPress={() => params.handlePress()}
/>
),
};
};
This is close to what I read from the react-navigation docs, but it I keep getting an error of undefined is not an object.
componentDidMount() {
this.props.navigation.setParams({ handlePress: () => this.myFunction.bind(this) });
}
I have myFunction defined inside my component as follows
myFuntion() {
//...
}
Any help is highly appreciated

I'm going to answer this, with a little more explanation. The problem as you know was that you were combining the syntax of two similar approaches. You can use either the .bind syntax: handlePress: this.myFunction.bind(this) or the so-called fat-arrow syntax: handlePress: () => this.myFunction. They are more-or-less equivalent and there are opinions that the fat-arrow syntax supersedes the bind syntax (and, as always, opinions to the contrary :) ). There are differences, such as (quoting from this):
Arrow functions are always anonymous, which means you can't, for
instance, reliably call them recursively (since you don't have a
reliable lexical name to use).
Arrow functions actually create
lexical bindings for this, super, and arguments. Only this is bound
by .bind(..).
Arrow functions cannot be used in new expressions,
while bind(..) bound functions can.
Also, given that this is a react native question, it might help to be aware of this interesting article:
https://medium.freecodecamp.org/why-arrow-functions-and-bind-in-reacts-render-are-problematic-f1c08b060e36

For most elements you can use the onPress() function in react-native
onPress={() => { Alert.alert("You clicked me") } }
If you need to bind you can usually do it with the following example:
onPress={this.functionName.bind(this)}

Related

What is the difference between a set state and a set state with an arrow in react native

I am creating a function that handles some logic from a switch change, but the follow doesn't work, but when I add the arrow part it does, but I don't understand what the arrow is doing in this context????
Underlying logic that is getting triggered to call my function
const [verbose, setVerbose] = useState(false)
<Switch
trackColor={{ false: "#767577", true: "#81b0ff" }}
thumbColor={isEnabled ? "#f5dd4b" : "#f4f3f4"}
ios_backgroundColor="#3e3e3e"
onValueChange={userChangedVb}
value={verbose}
/>
What doesn't work:
function userChangedVb(vb) {
//other logic
setVerbose(!vb )
}
What does work:
function userChangedVb(vb) {
//other logic
setVerbose(vb => !vb )
}
Can someone explain why? I guess I don't know what the arrow is doing inside the set state call? I'm still new to react native and usually when I use or see the arrow its something like this () => to generate a new function.
EDIT: This is mostly from the example here, which has the expo emulator so its really easy to try/experiment this code: https://reactnative.dev/docs/switch#onchange
EDIT: part of the problem is the vb parameter comming in has already been changed, so if I use:
function userChangedVb(vb) {
//other logic
setVerbose(vb )
}
It works as intend.

How to create an rxjs Observable from TextInput (either onChange or onTextChange)

I want to create an observable from a change event that gets fired on a React Native TextInput component. TextInput comes with 2 change props that I'm aware of (onChangeText and onChange). From what I gather, you need to use onChange if you want access to the native event you need to use onChange.
I don't know much about the native event object. I am trying to create an rxjs observable using fromEvent.
First I created a ref in my functional component like this:
const sqftRef = useRef().current
Then I attached this ref to the TextInput component like this:
<TextInput
ref={sqftRef} // attach a ref
label='Sqft'
mode='flat'
textContentType='none'
autoCapitalize='none'
keyboardType='numeric'
autoCorrect={false}
value={String(formValues.sqft)}
dense
underlineColor={colors.colorOffWhite}
onChangeText={(text) => setText(text)}
onChange={e => {
// somehow create an observable from this event ???
}}
style={styles.inputStyles}
theme={inputTheme}
/>
I tried to create an Observable using fromEvent like this but it doesn't work. I get undefined is not an object (evaluating target.addEventListener):
fromEvent(sqftRef, 'onChange').subscribe(value => console.log(value))
I know my approach is all wrong. Hoping someone can point me in the correct direction.
I would emit events you need into a subject, then subscribe to the subject in other parts of your code.
Here's a simple React example that should get you started
function App() {
const textChange = new Subject<string>();
useEffect(() => {
// subscribe to
const subscription = textChange.asObservable().subscribe(console.log)
return () => subscription.unsubscribe()
}, [])
// Emit events with a subject
return <textarea onChange={(e) => {
textChange.next(e.target.value)
}}>
</textarea>
}
render(<App />, document.getElementById('root'));
Check out the example here: https://stackblitz.com/edit/react-ts-akoyfv
I think the problem is with assigning the current directly to the sqftRef. Try to define it without current, but use current when creating the Observable, like the following:
const sqftRef = useRef();
Then create the Observable within useEffect to make sure that the DOM is ready:
useEffect(() => {
fromEvent(sqftRef.current, 'onChange').subscribe((value) =>
console.log(value)
);
});
OK, I was able to figure it out with the help of Amer Yousuf and Alex Fallenstedt.
I did something similar to what Alex suggested, modifying his solution for React Native. One reason his solution wasn't working for me is that it is important to use the useRef hook to prevent the Observable from being re-created on each render. If the observable is recreated (on a re-render) and useEffect doesn't run again, then we won't have an active subscription to the newly (re-created) observable (useEffect never runs again). That's why my call to sqft$.next was originally only being called once (the first time until we re-render).
My solution looks like this:
let sqft$ = useRef(new BehaviorSubject(0)).current
useEffect(() => {
const sub = sqft$.subscribe({
next: (val) => {
// just testing stuff out here
updateForm('sqft', val)
updateForm('lot', val * 2)
}
})
// this is only relevant to my use case
if (activeReport) sqft$.next(activeReport.sqft)
return () => sub.unsubscribe()
}, [activeReport])
and of course I call this in onChangeText:
onChangeText={(text) => {
sqft$.next(text)
}}
So this is working right now. I still feel like there may be a better way using onChange(e => ...stuff). I will leave this question open for a little bit in case anyone can break down how to do this using nativeEvent or explain to me how I can access an event off the TextInput component.

Dynamic titles with function components

Recently I started rewriting some of my legacy code that used class components to modern hooks. The issue I have is that I used this to set header title like so:
static navigationOptions = ({ navigation }) => ({
title: someVar,
})
Now, I have to do it like so:
MyScreen.navigationOptions = ({ navigation }) => {
return {
title: navigation.getParam('headerTitle'),
}
}
and then
useEffect(() => {
navigation.setParams({
headerTitle: 'Some title',
})
}, [])
Which works fine with static screen titles. But for dynamic titles it does not. It takes a second to update the title, first it renders with empty title. Which is explainable, given the method. It worked perfectly with class components. Is there a better way to do this?
You are setting the params (setParams) passed to the screen and not the options (setOptions) of the screen itself which is what's causing the odd behaviour you are experiencing.
I am not sure what your use case is so I can't tell you which one to use but there are two ways to set the title in react navigation. Either from the navigator using the options parameter or from inside the component using navigation.setOptions take a look at this https://reactnavigation.org/docs/headers

React Hooks and useEffect – best practices and server issues

I am using React Native with functional components. componentDidMount() etc. are not available in functional components, instead I use Hooks. But Hooks don't act like lifecycle methods. I am wondering what the best practices are.
Assumed that we have a function like this one:
const ABCScreen = () => {
const [someHook, setSomeHook] = useState<any>()
useEffect(() => {
// some code inside this function which is called on every component update
}, [])
server.asyncCall().then(data => {
setSomeHook(data)
})
return (<View>
{someHook ? (<Text> `someHook` was assigned </Text>) : (<Text> `someHook` was not assigned, display some ActivityIndicator instead</Text>)}
</View>)
}
Where to place server.asyncCall()? Inside or outside of useEffect?
I think you have a misunderstanding here. The convention is that all the fetching data is going to be placed inside the componentDidMount lifecycle method. React useEffect hook can replace this easily by placing an empty array of dependencies, which means you can place that call inside the useEffect you already have.
Unlike you mention in your code comment, this hook won't be triggered on each component update. It will be only be triggered once the component is being mounted. So, you should be able to do it as follows:
const ABCScreen = () => {
const [someHook, setSomeHook] = useState<any>()
useEffect(() => {
server.asyncCall().then(setSomeHook)
}, [])//only triggered when component is mounted.
In the future, you might want to check the rules of the hooks.

Using FlatList#onViewableItemsChanged to call a Component function

I'm currently attempting to implement a form of LazyLoading using the FlatList component, which introduces a neat little feature called onViewableItemsChanged which gives you a list of all of the components that are no longer on the screen as well as items that are now on the screen.
This is a custom LazyLoad implementation and as such is more complicated than most LazyLoad open-sourced libraries that are available, which is why I'm working on my own implementation. I'm already looked into react-native-lazy-load and others.
Basically, I need to be able to call a function that's part of the component being rendered in the FlatList, I've tried creating a reference to the item rendered in the FlatList and calling it as such, but it doesn't seem to work.
For example:
<FlatList data={...}
renderItem={(item) => <Example ref={(ref) => this[`swiperRef_${item.key}`] = ref}}
onViewableItemsChanged={this.onViewableItemsChanged}
/>
onViewableItemsChanged = ({viewableItems}) => {
viewableItems.forEach((item) => {
const { isViewable, key } = item;
if(isViewable && !this.cachedKeys.includes(key)) {
const ref = this[`swiperRef_${key}`];
if(!ref) return console.error('Ref not found');
ref.startLoading();
this.cachedKeys.push(key);
}
});
}
Now in the <Example /> component I would have a function called startLoading which should be called when a new visible item is brought onto the screen, however the ref never exists.
I was actually doing everything correctly, but I accidently forgot to deconstruct the parameter returned from the renderItem function, so (item) should have been ({ item })
That's all there was to it.