React Native Pass data to another component - react-native

I am new to React Native and trying to build a Messenger app and I have 2 components Search and Messenger​. I am struggling to pass the data I got from Search to Messenger.
Search component finds user (receiver) and me being sender I want to communicate but after finding user in Search I want to pass that user to Messenger so that I can chat with that specific user that found in <Search> component.
In addition, Search component has Views that will display user calendar etc.. so ideally I don't want to use <Messenger> in render() method of Search as it will include Messenger component features inside the Search component which destroys the purpose of <Search> component.
So my code is :
'use strict';
var Search = React.cerateClasss({
getDefaultProps: function () {
return {
date: new Date(),
singerName:''
};
},
getInitialState: function () {
return {
date: this.props.date,
artistName: '',
artistUserId: 1,
maxNoArtist: 0,
imagePath: '../common/images/1.png',
user: null
}
},
getArtistName: function () {
var artist = [];
var query = new Parse.Query(Parse.User);
query.equalTo('userId', this.state.artistUserId);
return query.first({
success: (result) => {
this.setState({artistName: result.get('name')});
this.props.singerName= result.get('name');
this.setState({imagePath: result.get('image').url()});
},
error: (data, error) => {
console.log('Error occured : ' + error.message())
}
});
},
render: function () {
if (!this.state.user) {
return <View style={styles.container}>
<Text style={styles.label}> Loading.... </Text>
</View>
}
var username = this.state.user.get('username');
return (
<View style={styles.container}>
<ResponsiveImage source={{uri:this.state.imagePath}} initHeight="200" initWidth="400"/>
<Text style={styles.label}>
{this.state.artistName}
</Text>
<View style={styles.innerButtonView}>
<Button text={'Onki'} onPress={this.getPreviousArtistName}/>
<Button text={'Indiki'} onPress={this.getNextArtistName}/>
</View>
<CalendarPicker
selectedDate={this.state.date}
onDateChange={this.onDateChange}
/>
<View style={styles.innerButtonView}>
<Button text={'Cyk'} onPress={this.onLogoutPress}/>
<Button text={'Habarlas'} onPress={this.onPress}/>
</View>
<Messenger singerName={this.props.singerName}></Messenger> // BREAKS SEARCH COMPONENT PURPOSE - INCLUDES MESSENGER FEATURES IN TO SEARCH COMPONENT
</View>
);
},
})
var Messenger = React.createClass({
getInitialState: function () {
return {
greeting: 'Salam',
date: new Date(),
errorMessage: '',
user: null,
olderMessageTextFrom: [],
olderMessageTextTo: [],
olderMessageDateFrom: [],
olderMessageDateTo: [],
earlierMessages: []
}
},
componentWillMount: function () {
Parse.User.currentAsync().then((user) => {
this.setState({user: user})
}
)
},
getMessages() {
return [
{
text: this.state.greeting,
name: this.props.singerName,
image: require('../common/images/1.png'),
position: 'left',
date: new Date()
},

I am late to answer but I did in different way using props.
I have two components.
Splash.js
Home.js
I am passing the data (Let's take String) from Splash.js to Home.js.
First component (Sender)
this.props.navigation.navigate('Home', {user_name: userName})
Second component (Receiver)
this.props.navigation.state.params.user_name
Hope this would help you.

OK, so based on your infos, I think the issue is that you don't get the singerName in the Messenger component.
First, I'd change your getArtistName method to this :
getArtistName: function () {
var artist = [];
var query = new Parse.Query(Parse.User);
query.equalTo('userId', this.state.artistUserId);
return query.first({
success: (result) => {
this.setState({artistName: result.get('name')});
// Removed the this.props.singerName = ...
this.setState({imagePath: result.get('image').url()});
},
error: (data, error) => {
console.log('Error occured : ' + error.message())
}
});
}
then in your render method :
<Messenger singerName={this.state.artistName} />
Inside a component you need to use setState and not change props :
that is to say that this.props.singerName = 'singer' is a wrong way of doing things, you should do this.setState({singerName: 'singer'}); then access it with this.state.singerName
Inside your messenger component, you access it with this.props.singerName

Related

react-native webview accessing variabels and functions directly

i have a simple website with a toggle function that toggles some data.
<body>
<h1>customerType: <span id="h1_element"></span></h1>
<script>
let customerType = "Public"
function toggle(){
customerType = (customerType === "Public") ? "Private" : "Public"
document.getElementById("h1_element").innerText = customerType;
}
toggle()
</script>
</body>
i then have a react-native app that can toggle the data and display the new data.
export default function Inject() {
const [customer, setCustomer] = React.useState('-');
const viewRef = React.useRef();
const postCustomer = () => viewRef.current.injectJavaScript('window.ReactNativeWebView.postMessage(customerType)');
const toggleCustomer = () => {
viewRef.current.injectJavaScript('toggle()');
}
return (
<SafeAreaView style={{ flex: 1, top: 50 }}>
<Text>WebView data: {customer}</Text>
<Button onPress={toggleCustomer} title="toggle webView data" />
<WebView
ref={viewRef}
source={{ uri: 'localhost' }}
onMessage={ event => setCustomer(event.nativeEvent.data) }
javaScriptEnabledAndroid={ true }
onLoadEnd={ postCustomer }
/>
</SafeAreaView>
);
}
I can access the function by simply using refName.current.injectJavaScript('funcName()'), but how could you access the function big project with many modules with their own script files and maybe even same function names?
I guess one way is to make the function global and then access it by window.funcName(), or bind it to a button element and then find the button with a queryselector, but is there a more direct way?
Create objects in window per functionality. Something like this:
window.test = { foo: 'bar', print: () => console.log('foobar') }
or maybe one top level object for the app to keep things clean
window.myApp = {
first: { foo: 'bar', print: () => console.log('foobar') },
second: { foo: 'bar', print: () => console.log('foobar') }
}

React Hook Form with Children in React Native

I have a form with ~15 fields where each section is a unique child component. I want to know how to pass data between the parent form and child components(using control because this is react native)
Right now, I see the proper value for testResult in onSubmit logs but data is undefined for some reason. This means my parent form is somehow not picking up the value in the child.
Parent Form:
const Stepper = () => {
const form = useForm({ defaultValues: {
testResult: "",
}
});
const { control, handleSubmit, formState: { errors }, } = form;
const testResult = useWatch({ control, name: "testResult" });
const onSubmit = (data) => {
console.log("watched testResult value: ", testResult);
console.log("form submission data: ", data);
};
return (
<WaterStep form={form} />
<Button onSubmit={handleSubmit(onSubmit())} />
)
}
Child component:
const WaterStep = ({ form }) => {
const { control, formState: { errors }, } = form;
return (
<Controller
name="testResult"
control={control}
rules={{
maxLength: 3,
required: true,
}}
render={({ field: onBlue, onChange, value }) => (
<TextInput
keyboardType="number-pad"
maxLength={3}
onBlur={onBlur}
onChangeText={onChange}
value={value}
/>
)}
/>
)}
Here I'm trying the first approach this answer suggests, but I've also tried the second with useFormContext() in child https://stackoverflow.com/a/70603480/8561357
Additionally, must we use control in React Native? The examples that use register appear simpler, but the official docs are limited for React Native and only show use of control
Update: From Abe's answer, you can see that I'm getting undefined because I'm calling onSubmit callback in my submit button. I mistakenly did this because I wasn't seeing any data getting logged when passing onSubmit properly like this handleSubmit(onSubmit). I still think my issue is that my child component's data isn't being tracked properly by the form in parent
The problem is most likely in this line:
<Button onSubmit={handleSubmit(onSubmit())} />
Since you're executing the onSubmit callback, you're not allowing react-hook-forms to pass in the data from the form. Try replacing it with the following
<Button onSubmit={handleSubmit(onSubmit)} />
For anyone still looking for guidance on using react-hook-form with child components, here's what I found out to work well:
Parent Component:
const Stepper = (props) => {
const { ...methods } = useForm({
defaultValues: {
testResult: "",
},
});
const onSubmit = (data) => {
console.log("form submission data: ", data);
};
const onError = (errors, e) => {
return console.log("form submission errors: ", errors);
};
return (
<FormProvider {...methods}>
<WaterStep
name="testResult"
rules={{
maxLength: 3,
required: true,
}}
/>
<Button onSubmit={handleSubmit(onSubmit)} />
)
}
Child:
import { useFormContext, useController } from "react-hook-form";
const WaterStep = (props) => {
const formContext = useFormContext();
const { formState } = formContext;
const { name, label, rules, defaultValue, ...inputProps } = props;
const { field } = useController({ name, rules, defaultValue });
if (!formContext || !name) {
const msg = !formContext
? "Test Input must be wrapped by the FormProvider"
: "Name must be defined";
console.error(msg);
return null;
}
return (
<View>
<Text>
Test Input
{formState.errors.testResult && <Text color="#F01313">*</Text>}
</Text>
<TextInput
style={{
...(formState.errors.phTestResult && {
borderColor: "#f009",
}),
}}
placeholder="Test Value"
keyboardType="number-pad"
maxLength={3}
onBlur={field.onBlur}
onChangeText={field.onChange}
value={field.value}
/>
</View>
);
};
Here's what we're doing:
Define useForm() in parent and de-structure all its methods
Wrap child in <FormProvider> component and pass useForm's methods to this provider
Make sure to define name and rules as props for your child component so it can pass these to useController()
In your child component, define useFormContext() and de-structure your props
Get access to the field methods like onChange, onBlur, value by creating a controller. Pass those de-structured props to useController()
You can go to an arbitrary level of nested child, just wrap parents in a <FormProvider> component and pass formContext as prop.
In Ancestor:
...
const { ...methods } = useForm({
defaultValues: {
testResult: "",
},
});
const onSubmit = (data) => {
console.log("form submission data: ", data);
};
...
<FormProvider {...methods}>
<ChildOne/>
</FormProvider>
In Parent:
const ChecklistSection = (props) => {
const formContext = useFormContext();
const { formState } = formContext;
return (
<FormProvider {...formContext}>
<WaterStep
name="testResult"
rules={{
maxLength: 3,
required: true,
}}
/>
</FormProvider>
)}
Thanks to https://echobind.com/post/react-hook-form-for-react-native (one of the only resources I found on using react-hook-form with nested components in react-native)
....
And a further evaluation of my blank submission data problem, if you missed it:
As Abe pointed out, the reason I didn't see data or errors being logged upon form submission was because onSubmit was not being called. This was because my custom submission button, which I didn't include in my original question for simplicity's sake, had a broken callback for a completion gesture. I thought I solved onSubmit not being called by passing it as a call onSubmit(), but I was going down the wrong track.

React native contacts in list works really slow

I'm trying to add a feature to my app which is add your contacts to your profile. I'm using a package for this but it works slow (in my case 470 contact record I got in my phone).
The package
import Contacts from 'react-native-contacts';
My code
componentDidMount() {
this.getContacts();
}
getContacts() {
PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.READ_CONTACTS, {
title: 'Contacts',
message: 'This app would like to view your contacts.',
buttonPositive: 'Please accept bare mortal',
})
.then(
Contacts.getAllWithoutPhotos().then(contacts => {
var contactss = [];
contacts.map(contact => {
/// I'm mapping it for showing them in simple list with checkbox
contactss.push({...contact, checked: false});
});
// then sorting for alphabetical order by displayName
contactss.sort(function (a, b) {
if (a.displayName.toLowerCase() < b.displayName.toLowerCase())
return -1;
if (a.displayName.toLowerCase() > b.displayName.toLowerCase())
return 1;
return 0;
});
this.setState(
{contacts: contactss, loaded: true},
() => {
console.log('contacts', this.state.contacts);
},
);
}),
)
.then(contacts => {});
}
That's all code. Is this normal or should I do what?
Thank you.
I'm just trying to handle with this data. Also I'm giving select option to user which account you want, like this.
//function to select contact
checkContc(contc) {
console.log('checkFriend', contc);
let contactsTemp = this.state.contactsTemp.map(el =>
el.recordID == contc.recordID
? {...el, checked: !el.checked}
: {...el},
);
this.setState({contactsTemp}, () => {
console.log('check frined stateD ', this.state);
});
}
// render in scrollview
<ScrollView>
{this.state.contactsTemp?.map((follower, index) => {
return (
<TouchableOpacity
onPress={() => {
this.checkFriend(follower);
}}>
<FriendsComponent
checked={follower.checked}
nameSurname={follower.displayName}
key={index}
/>
</TouchableOpacity>
);
})}
</ScrollView>
result
I made a research and it looks like it is that slow. I'm not sure if the only solution isn't the native one.

React Native Gifted Chat + Firestore not showing messages correctly?

I am trying to create a chat feature in my react native app. I am using react-native-gifted-chat and saving the messages in firestore. Here is the behavior that is occurring:
When I send a message, ALL the messages re render, some of them are duplicates, as you can see I only have 3 messages sent so far, but all these duplicates are making me wonder why the entire thing is re-rendering and why there are duplicates when it does re-render.
The code:
class Chat extends React.Component {
constructor(props) {
super(props)
this.state = {
messages: [],
currentUser: null,
isLoading: true,
messageID: ""
}
}
//---------------------------------------------------------------
async componentDidMount (){
// get user info from firestore
let userUID = Firebase.auth().currentUser.uid
await Firebase.firestore().collection("users").doc(userUID).get()
.then(doc => {
data = doc.data()
this.setState({
currentUser: {
name: data.username,
avatar: data.profilePic,
_id: doc.id,
},
})
})
const messages = []
await Firebase.firestore().collection("chat")
.orderBy("createdAt", "desc")
.limit(50)
.onSnapshot(querySnapshot => {
querySnapshot.forEach((res) => {
const {
user,
text,
createdAt,
} = res.data();
messages.push({
key: res._id,
user,
text,
createdAt,
});
})
this.setState({
messages,
isLoading: false,
});
})
}
//Load 50 more messages when the user scrolls
//
//Add a message to firestore
onSend = async(message) => {
await Firebase.firestore().collection("chat")
.add({
user: {
_id: this.state.currentUser._id,
name: this.state.currentUser.name,
avatar: this.state.currentUser.avatar,
},
})
.then(ref => this.setState({messageID: ref.id}))
await Firebase.firestore().collection("chat")
.doc(this.state.messageID)
.set({
_id: this.state.messageID,
text: message[0].text,
createdAt: message[0].createdAt
}, { merge: true })
}
render() {
if(this.state.isLoading){
return(
<View style = {{backgroundColor: '#000000', flex: 1}}>
<ActivityIndicator size="large" color="#9E9E9E"/>
</View>
)
}
return (
<View style={{backgroundColor: '#000000', flex: 1}}>
<GiftedChat
showUserAvatar={true}
renderUsernameOnMessage={true}
messages={this.state.messages}
onSend={message => this.onSend(message)}
scrollToBottom
/>
</View>
)
}
}
Some notes:
Every time the component mounts, the messages array pushes the messages to the state array.
The component mounts when I send a message, thus re-rendering the array of messages
Each message ID is unique and generated by firebase using "Add"
Let me know how I can fix this issue! thanks
Duplication is because of just single line
const messages = []
Move this line inside listener, i.e.onSnapShot()
await Firebase.firestore().collection("chat")
.orderBy("createdAt", "desc")
.limit(50)
.onSnapshot(querySnapshot => {
const messages = []
// rest of your code which is having forEach loop
});
The issue was that messages object was created only once when the component loaded, and you were pushing elements to that object only.

In React Native, how can I access methods of one component from another component?

I'm trying to access a method of a React Native component from a different component. It is passed through props. Unfortunately, it seems like the components aren't providing their methods publicly. How can I get access to the method?
Have a look at the following, you'll see InsideView has this.props.myModal, which is a ShowMyModal component. However, it doesn't have access to the .openModal() method.
'use strict';
var React = require('react-native');
var {
AppRegistry,
ActionSheetIOS,
StyleSheet,
Text,
View,
} = React;
var InsideView = React.createClass({
makeItOpen: function() {
debugger;
this.props.myModal.openModal();
},
render: function() {
return (
<View>
<Text onPress={() => this.makeItOpen()}>Click me!</Text>
</View>
);
}
});
var ShowMyModal = React.createClass({
getInitialState: function() {
return {
isModalOpen: false,
}
},
openModal() {
this.setState({isModalOpen: true});
},
closeModal() {
this.setState({isModalOpen: false});
},
render: function() {
return (
<Text>isModalOpen = {String(this.state.isModalOpen)}</Text>
);
}
});
var AwesomeProject = React.createClass({
getInitialState: function() {
return {
myModal: <ShowMyModal />,
}
},
render: function() {
return (
<View style={{padding: 30}}>
<InsideView myModal={this.state.myModal}/>
{this.state.myModal}
</View>
);
},
});
AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);
Something like this should work:
'use strict';
var React = require('react-native');
var {
AppRegistry,
ActionSheetIOS,
StyleSheet,
Text,
TouchableOpacity,
View,
} = React;
var InsideView = React.createClass({
render: function() {
return (
<View>
<TouchableOpacity onPress={() => this.props.openModal()}><Text>Open modal!</Text></TouchableOpacity>
<TouchableOpacity onPress={() => this.props.closeModal()}><Text>Close modal!</Text></TouchableOpacity>
</View>
);
}
});
var ShowMyModal = React.createClass({
render: function() {
return (
<Text>isModalOpen = {String(this.props.isVisible)}</Text>
);
}
});
var SampleApp = React.createClass({
getInitialState: function() {
return {
isModalOpen: false
}
},
_openModal: function() {
this.setState({
isModalOpen: true
});
},
_closeModal() {
this.setState({
isModalOpen: false
});
},
render: function() {
return (
<View style={{padding: 30}}>
<InsideView openModal={this._openModal} closeModal={this._closeModal}/>
<ShowMyModal isVisible={this.state.isModalOpen}/>
</View>
);
},
});
AppRegistry.registerComponent('SampleApp', () => SampleApp);
I don't think it's a good idea to store the components in state. State should really be used for component's data rather than sub-components. Dave's solution above is good approach but it could be done a bit better as it moves the state of modal to the application (which is not very good to separate concerns). It's good if modal can keep it's own state and know if it's visible or not. Then openModal() and closeModal() can do some extra stuff as needed (as opposed to somehow reacting to change in visibility of ShowModal). You can also avoid those extra _openModal and _closeModal which are boilerplate.
I think it's best to use refs. Refs is standard way to refer to other components. See here for more details about refs https://facebook.github.io/react/docs/more-about-refs.html You can use refs as strings and refer to the component by that strings but it's kind of ugly as introduces global names which contradict the component approach of react. But you can also use callbacks as refs to set your internal components as fields. There is a nice and simple example of this is react's documentation: http://facebook.github.io/react-native/docs/direct-manipulation.html#forward-setnativeprops-to-a-child. I copy it here in case the documentation gets updated:
var MyButton = React.createClass({
setNativeProps(nativeProps) {
this._root.setNativeProps(nativeProps);
},
render() {
return (
<View ref={component => this._root = component} {...this.props}>
<Text>{this.props.label}</Text>
</View>
)
},
});
What happens here - the view in question has callback ref which sets this._root as the view's backing component. Then in any other place in the component you can use this._root to refer to it.
So in your case it could look like below (note that you need those anonymous arrow functions rather than passing the openModal / closeModal methods because at the time of rendering _modal is not yet set, you can only refer to it later using the anonymous methods).
// ...
// InsideView render (same as in Dave's solution)
<View>
<TouchableOpacity onPress={() => this.props.openModal()}><Text>Open modal!</Text></TouchableOpacity>
<TouchableOpacity onPress={() => this.props.closeModal()}><Text>Close modal!</Text></TouchableOpacity>
</View>
// ...
// Sample App render ...
<View style={{padding: 30}}>
<InsideView openModal={ () => this._modal.openModal() } closeModal={ () => this._modal.closeModal() } />
<ShowMyModal ref={component => this._modal = component} />
</View>
Then your initial ShowModal implementation can stay as it is - with it's own state and own openModal and showModal functions.