what is the difference between passing an object with a variable and a lone variable to a react native component? - react-native

My lack of success in this problem may be due to a lack of proper terminology when Googling it but nonetheless I am completely stumped. I am passing an onPress function to a custom component in react native. When I pass it by itself as:
export const AddMorePlants = ( onPress ) => {
return (
<TouchableHighlight
onPress={onPress}>
.
.
.
}
I get a this2.props.onPress is not a function error but when I have the exact same code except with the onPress passed within curly braces:
export const AddMorePlants = ({ onPress }) => {
return (
<TouchableHighlight
onPress={onPress}>
.
.
.
}
It Works!
Why does the second one work and not the first?
Sorry for a kind of basic question I just have been really Googling and cant figure it out. Thanks in advance and I can provide any more info if needed.

A functional component in React only has one parameter. The props. You can read more about it here
So what your first attempt at passing the onPress function actually looks like is:
export const AddMorePlants = (props) => {
return (
<TouchableHighlight onPress={props}/>
);
}
When the TouchableOpacity tries to execute the method, it hits the is not a function error because props is an object.
When you do:
export const AddMorePlants = ({onPress}) => {
return (
<TouchableHighlight onPress={onPress}/>
);
}
what you are doing is something called destructuring assignment and it's equivalent of doing:
export const AddMorePlants = (props) => {
const {onPress} = props;
return (
<TouchableHighlight onPress={onPress}/>
);
}
By putting the brackets inside the parentheses you are just doing a shorthand version of this destructuring assignment that we have mentioned.
Here's another version that would also work:
export const AddMorePlants = (props) => {
return (
<TouchableHighlight onPress={props.onPress}/>
);
}
As you can see there are many ways to access an object's property.
The important part is to remember that the props object is the only parameter passed into a functional component.
I hope this helps you understand what's going on there.

In the first function you need to pass only one param i.e onPress to your component while in second you are destructuring assignment so you are doing something like onPress = this.props.onPress and passing an object of params.

Related

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.

React Native doesn't re-render on DOM change

I had an array of components inside a ScrollView component. Somehow react native doesn't re-render when the array is modified.
Here's a demonstration of my problem:
const TestApp = () => {
const [arr, setArr] = useState([]);
function pushArr() {
setArr((arr) => {
arr.push(1);
return arr;
});
console.log('pushArr():', arr);
}
function flushArr() {
setArr([]);
console.log('flushArr():', arr);
}
useEffect(() => {
console.log('useEffect():' , arr);
})
return (
<>
<ScrollView style={{flex:1}}>
{arr.map((elem, i) => <Text key={i}>{elem}</Text>)}
</ScrollView>
<Button title="Push" onPress={pushArr}></Button>
<Button title="Flush" onPress={flushArr}></Button>
</>
)
}
The page remains blank, and no updates happen on button press.
I've logged out arr and these are my findings:
pushArr() and flushArr() works as expected
useEffect() gets triggered only on startup and after flushArr()
Can anyone explain this behavior, and what mistakes have I made?
If I remember correctly, you need to make a copy of the array whenever you want it to “react”. The new memory address will let react know it should update. In other words, you shouldn’t mutate the array.
You can use the spread operator to make a copy and then push an element to the end which you can then pass to useArr. Usually I see people just passing the new object inside your useArr function.
I also don’t see you passing anything to your useArr function.

React-Native - View config not found for name "Custom Tag"

I am using react-native-navigation v2 and every component needs to be registered into the navigation by calling registerComponent(). However, I found myself having 500 lines of code where I register every component of my app using the same registerComponent structure with the only difference of using different jsx tag for every component I register, like so:
import ItemsList from './src/components/ItemsList';
import { Navigation } from "react-native-navigation";
import { Provider } from "react-redux";
import reduxStore from "./src/store";
Navigation.registerComponent(
"app.ItemsList",
() => props => (
<Provider store={reduxStore}>
<ItemsList {...props} />
</Provider>
),
() => ItemsList
);
+++ 35 more components almost exactly just like this one
Now, in order to reduce that huge amount of identical code, I've decided to write an IIFE that maps through an array of objects(components) that look like:
[...,
{
name: "ItemsList",
component: ItemsList
},
...]
then calls registerComponent on every item and returns the JSX I need, like so:
(function componentsRegistration() {
return components.map(({ name, component }) => {
const Tag = name;
Navigation.registerComponent(
`app.${name}`,
() => props => (
<Provider store={reduxStore}>
<Tag {...props} />
</Provider>
),
() => component
);
});
})()
After this specific manipulation, my app doesn't render anymore. Instead it throws the "Invariant Violation: View config is not found for name ItemsList". I think I've made all of this respecting the React commandments (capital letter jsx tag, etc.), however can't get it to work. If anyone could, please help.
[SOLVED] I was getting an error because I was trying to pass a string with the component name instead of the component itself.
The right way to do it would be:
const Tag = component;
instead of:
const Tag = name;

Unable to receive props to dump component

I tried all possible ways to get a simple parameter on file MembershipCard.js. My Home component Home.js simply passes props to MembershipList.js where I have done minor Array operations and iterate it to prepare a list. Each item from the list is then pass on to third file MembershipCard.js. I'm getting membership object in this file and able to prepare a card list at Home page. On Home page I have to show a side line whereas I don't want this side line on other pages (which are also accessing MembershipCard.js) hence I'm trying to send a variable on which I will conditionally show side line.
But after so many try out I'm still receiving undefined
This is my React component - Home.js
render () {
return (
<Surface>
<GreetingCard profile={this.props.profile.Profile}/>
<MembershipList props={this.props}/>
</Surface>
)
}
MembershipList.js - this contain only few functions
renderMembershipCard = (membership, i, props, sideLine = true) => {
return (
<TouchableOpacity key={i} style={styles.membership} onPress={() => props.navigation.navigate('Item', { title: membership.gym_name })}>
{/* <MembershipCard {...{membership, sideLine }}/> */}
<MembershipCard {...membership} sideLine={sideLine}/>
</TouchableOpacity>
)
}
const MembershipList = (props) => {
let membership = props.props.profile.Membership
let listArray = [];
Object.keys(membership).forEach(key => listArray.push(this.renderMembershipCard(membership[key], key, props.props)));
return (
<View>
<Text style={styles.ListTitle}>Active Membership ({listArray.length})</Text>
{listArray}
</View>
);
}
MembershipCard.js - this file is part of my presentation layer. It only return a Card design.
const MembershipCard = ({membership,sideLine}) => {
console.log('sideLine', sideLine); // showing undefined
console.log('membership', membership);
return (
<Card>
<Text style={styles.gymTitleText}>{membership.name}</Text>
... JSX code
</Card>
)
Make the following changes to your code and it should work. Change props.props seems to be incorrect way of passing props. Use spread operator for passing all props to children in correct manner.
<MembershipList {...this.props}/>
const MembershipList = (props) => {
let membership = props.profile.Membership
let listArray = [];
Object.keys(membership).forEach(key => listArray.push(this.renderMembershipCard(membership[key], key, props)));
return (
<View>
<Text style={styles.ListTitle}>Active Membership ({listArray.length})</Text>
{listArray}
</View>
)}
<MembershipCard membership={membership} sideLine={sideLine}/>
I solved it using simple trik.
Instead of calling it as a component -
<MembershipCard {...membership} sideLine={sideLine}/>
call it as a simple JS function using curly braces {} -
{ MembershipCard (membership, sideLine) }
This way I can easily pass as many parameters and can easily access all those in called function.

How can I refactor this small piece of code so that I don't call a function inside render

I read on this page that you should not create functions inside the render method. One way around this is to bind.
My constructor looks like this:
constructor(props) {
super(props);
this.idCheckAlert = this.idCheckAlert.bind(this);
}
The function I created:
idCheckAlert = (idForAPI) => () => {
//does some stuff with the idForApi
}
Inside my render:
<TouchableOpacity
style={styles.inputButton}
onPress={() => {
const idForAPI = this.userId;
this.idCheckAlert(idForAPI);
}}
>
My current refactor looks like this:
<TouchableOpacity
style={styles.inputButton}
onPress={this.idCheckAlert(this.userId)}
>
However, I would like to instead create a variable inside onPress, but when I do I get an error message that says unexpected token. Is there a way for me to create a variable inside onPress, without having to create a function?
Is not a good practice create the variable inside onPress, just like you said, but you can do it after the render
const userId = 1;
return (
<TouchableOpacity
style={styles.inputButton}
onPress={this.idCheckAlert(userId)}
>
);