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') }
}
Related
I'm trying to open a simple page with React Native WebView.
It's a single page web, and when you do a search, it prints out some information about your search.
After that, if you want to search again, press the back button on the device to move to the search box.
Because it is a single page, I cannot use goBack, so I created a function called cancel.
The problem is that when I click the device's back button, the function called cancel defined on the web is not executed.
The cancel function deletes the searched information and returns to the search window.
I will upload my code.
Please advise.
export default function App() {
const webviewRef = useRef(null);
const backAction = () => {
setBackTapping((prev) => prev += 1);
webviewRef.current.injectJavaScript('window.cancel()')
return true;
}
useEffect(() => {
const timer = setInterval(() => {
setBackTapping(0)
}, 1000)
return () => clearInterval(timer);
}, [])
useEffect(() => {
const backHandler = BackHandler.addEventListener('hardwareBackPress',backAction);
return () => backHandler.remove()
}, [])
useEffect(() => {
if(backTapping >= 2){
return BackHandler.exitApp();
}
},[backTapping])
return (
<KeyboardAvoidingView style={{ flex: 1 }}>
<StatusBar hidden />
<WebView
ref={webviewRef}
textZoom={100}
originWhitelist={['*']}
javaScriptEnabled
source={{ uri: 'myhome.com'}}
startInLoadingState={true}
/>
</KeyboardAvoidingView>
);
}
Expected behavior:
The cancel function is executed, all open windows are closed, and you are returned to the search window.
in my case, calling is wrong.
instead of :
webviewRef.current.injectJavaScript('window.cancel()')
use :
const generateOnMessageFunction = (data) => `
(function(){
window.dispatchEvent(new MessageEvent('message',{data: ${JSON.stringify(data)}}));
})()
`;
webviewRef.current.injectJavaScript(generateOnMessageFunction('cancel'));
detail referance :
https://github.com/react-native-webview/react-native-webview/issues/809
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.
I'm using react native with realm db. The realm schema is as follows:
static schema = {
name: 'TodoItem',
primaryKey: 'id',
properties: {
id: {type: 'string'},
value: {type: 'string'},
Category: {type: 'string'},
completed: {type: 'bool', default: false},
createdTimestamp: {type: 'date'}
}
}
export const todoItemDS = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2, sectionHeaderHasChanged: (s1, s2) => s1 !== s2})
const mapStateToProps = (state, props) => ({
dataSource: todoItemDS.cloneWithRowsAndSections(todoItemsResults),
}
The ListView tag is as follows:
<ListView
dataSource={dataSource}
renderRow={this.renderRow.bind(this)}
renderSectionHeader={this.renderSectionHeader.bind(this)}
/>
and renderSectionHeader:
renderSectionHeader(sectionData, category) {
return (
<Text>{category}</Text>
)
}
renderRow(item){
const {dataSource, deleteTodoItem} = this.props
return (
<View style={{ justifyContent: 'space-between',flexDirection: 'row'}}>
<CheckBox onPress={(e) => this.completed(item.id,item.value,e.target.checked)} style={{marginTop: 15 }}checked={item.completed} />
<Text onPress={(e) => this.goToPageTwo(item.id)} style={{ alignSelf: 'center',flex:10}} >{item.value}
</Text>
<Button iconLeft large transparent primary style={{ height: 30 , flex:1 }} onPress={() => deleteTodoItem(item)}>
<Icon name="trash-o" style={{ color: 'red' }} />
</Button>
</View>)
}
I fill todoItems datasource from this function:
export const getTodoItems = () => {
const todoItems = TodoItem.get().sorted('createdTimestamp', true);
return todoItems
}
However, the rows and sections are rendered with empty sections text and empty rows text as shown in the image.
What is missing in this code and how can I render sections and rows correctly?
I added a listener to realm code that fills the data source as follows:
export const getTodoItems = () => {
console.log('create db:', Realm.path)
const itemData = {}
const todoItems = TodoItem.get().sorted('createdTimestamp', true).filtered('completed=false');
todoItems.addListener((items, changes) => {
// Update UI in response to inserted objects
changes.insertions.forEach((index) => {
if(itemData[items[index].Category]) {
itemData[items[index].Category].push(items[index])
} else
itemData[items[index].Category] = []//;
});
// Update UI in response to modified objects
changes.modifications.forEach((index) => {
});
// Update UI in response to deleted objects
changes.deletions.forEach((index) => {
// Deleted objects cannot be accessed directly
// Support for accessing deleted objects coming soon...
});
});;
todoItems.forEach((item) => {
if(itemData[item.Category]) {
itemData[item.Category].push(item)
} else
itemData[item.Category] = []//;
})
return itemData //todoItems
}
However, I can't see added items. The added item only shows up after adding another item. Any ideas?
The rendered SectionHeader is showing integer is because you didn't construct the dataSource in the right format, see the documentation here: link.
You need to construct something like:
const todoItemsData = {
categoryOne: [itemOne, itemTwo],
categoryTwo: [itemThree, itemFour]
}
But right now what you have is just an array of objects [realmItemOne, realmItemTwo], and if you just pass in an array of objects to construct the dataSource that gonna consumed by the cloneWithRowsAndSections, the category param in renderSectionHeader will becomes integer index accroding to here, Object.keys(arrayOfObjects) will return [0, 1, 2, ...]
So you want to map your todoItemsResults and construct the something like this
const itemData = {}
todoItemsResults.forEach((item) => {
if(itemData[item.Category] {
itemData[item.Category].push(item)
} else
itemData[item.Category] = [];
}
});
const mapStateToProps = (state, props) => ({
dataSource: todoItemDS.cloneWithRowsAndSections(itemData),
}
For the rendered row not showing any data issue, I think is due to the same problem, the item param you are calling within renderRow should be a data attribute for one of you todoItem object. You can try replace {item.value} within {item} to see what item is actually is in your setting. I believe this will be solved if you can construct the right data to feed your dataSource.
For your follow up comments regarding listerning to Realm update:
You can do something like this
class DummyPage extends Component {
constructor(props) {
super(props)
this.realmUpdated = this.realmUpdated.bind(this);
realm.objects('todoItem').addListener('change', this.realmUpdated);
}
componentWillUnmount() {
realm.objects('todoItem').removeListener('change', this.realmUpdated);
}
realmUpdated() {
this.forceUpdate();
}
render() {
//build your data source in here and render the sectionList
}
}
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
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.