I want to add exported component on TouchableHighlight in react-native.
var MessageBox = require('./GiftedMessengerContainer');
var MyAppName = React.createClass({
_openGiftedMessanger(){
return (<MessageBox style={styles.container}/>);
},
render: function() {
return (
<View style={styles.container}>
<TouchableHighlight
style={styles.imButton}
onPress={this._openGiftedMessanger}>
<Text>Open Chat Room</Text>
</TouchableHighlight>
}
</View>
);
}
AppRegistry.registerComponent('MyAppName', () => AppName);
And my module is,
import React, {
Linking,
Platform,
ActionSheetIOS,
Dimensions,
View,
Text,
//Navigator,
Component,
} from 'react-native';
var GiftedMessenger = require('react-native-gifted-messenger');
var Communications = require('react-native-communications');
// var STATUS_BAR_HEIGHT = Navigator.NavigationBar.Styles.General.StatusBarHeight;
// if (Platform.OS === 'android') {
// var ExtraDimensions = require('react-native-extra-dimensions-android');
// var STATUS_BAR_HEIGHT = ExtraDimensions.get('STATUS_BAR_HEIGHT');
// }
class GiftedMessengerContainer extends Component {
constructor(props) {
super(props);
this._isMounted = false;
this._messages = this.getInitialMessages();
this.state = {
messages: this._messages,
isLoadingEarlierMessages: false,
typingMessage: '',
allLoaded: false,
};
}
componentDidMount() {
this._isMounted = true;
setTimeout(() => {
this.setState({
typingMessage: 'React-Bot is typing a message...',
});
}, 1000); // simulating network
setTimeout(() => {
this.setState({
typingMessage: '',
});
}, 3000); // simulating network
setTimeout(() => {
this.handleReceive({
text: 'Hello Awesome Developer',
name: 'React-Bot',
image: {uri: 'https://facebook.github.io/react/img/logo_og.png'},
position: 'left',
date: new Date(),
uniqueId: Math.round(Math.random() * 10000), // simulating server-side unique id generation
});
}, 3300); // simulating network
}
componentWillUnmount() {
this._isMounted = false;
}
getInitialMessages() {
return [
{
text: 'Are you building a chat app?',
name: 'React-Bot',
image: {uri: 'https://facebook.github.io/react/img/logo_og.png'},
position: 'left',
date: new Date(2016, 3, 14, 13, 0),
uniqueId: Math.round(Math.random() * 10000), // simulating server-side unique id generation
},
{
text: "Yes, and I use Gifted Messenger!",
name: 'Awesome Developer',
image: null,
position: 'right',
date: new Date(2016, 3, 14, 13, 1),
uniqueId: Math.round(Math.random() * 10000), // simulating server-side unique id generation
},
];
}
setMessageStatus(uniqueId, status) {
let messages = [];
let found = false;
for (let i = 0; i < this._messages.length; i++) {
if (this._messages[i].uniqueId === uniqueId) {
let clone = Object.assign({}, this._messages[i]);
clone.status = status;
messages.push(clone);
found = true;
} else {
messages.push(this._messages[i]);
}
}
if (found === true) {
this.setMessages(messages);
}
}
setMessages(messages) {
this._messages = messages;
// append the message
this.setState({
messages: messages,
});
}
handleSend(message = {}) {
// Your logic here
// Send message.text to your server
message.uniqueId = Math.round(Math.random() * 10000); // simulating server-side unique id generation
this.setMessages(this._messages.concat(message));
// mark the sent message as Seen
setTimeout(() => {
this.setMessageStatus(message.uniqueId, 'Seen'); // here you can replace 'Seen' by any string you want
}, 1000);
// if you couldn't send the message to your server :
// this.setMessageStatus(message.uniqueId, 'ErrorButton');
}
onLoadEarlierMessages() {
// display a loader until you retrieve the messages from your server
this.setState({
isLoadingEarlierMessages: true,
});
// Your logic here
// Eg: Retrieve old messages from your server
// IMPORTANT
// Oldest messages have to be at the begining of the array
var earlierMessages = [
{
text: 'React Native enables you to build world-class application experiences on native platforms using a consistent developer experience based on JavaScript and React. https://github.com/facebook/react-native',
name: 'React-Bot',
image: {uri: 'https://facebook.github.io/react/img/logo_og.png'},
position: 'left',
date: new Date(2016, 0, 1, 20, 0),
uniqueId: Math.round(Math.random() * 10000), // simulating server-side unique id generation
}, {
text: 'This is a touchable phone number 0606060606 parsed by taskrabbit/react-native-parsed-text',
name: 'Awesome Developer',
image: null,
position: 'right',
date: new Date(2016, 0, 2, 12, 0),
uniqueId: Math.round(Math.random() * 10000), // simulating server-side unique id generation
},
];
setTimeout(() => {
this.setMessages(earlierMessages.concat(this._messages)); // prepend the earlier messages to your list
this.setState({
isLoadingEarlierMessages: false, // hide the loader
allLoaded: true, // hide the `Load earlier messages` button
});
}, 1000); // simulating network
}
handleReceive(message = {}) {
// make sure that your message contains :
// text, name, image, position: 'left', date, uniqueId
this.setMessages(this._messages.concat(message));
}
onErrorButtonPress(message = {}) {
// Your logic here
// re-send the failed message
// remove the status
this.setMessageStatus(message.uniqueId, '');
}
// will be triggered when the Image of a row is touched
onImagePress(message = {}) {
// Your logic here
// Eg: Navigate to the user profile
}
render() {
return (
<GiftedMessenger
ref={(c) => this._GiftedMessenger = c}
styles={{
bubbleRight: {
marginLeft: 70,
backgroundColor: '#007aff',
},
}}
autoFocus={false}
messages={this.state.messages}
handleSend={this.handleSend.bind(this)}
onErrorButtonPress={this.onErrorButtonPress.bind(this)}
maxHeight={Dimensions.get('window').height} //- Navigator.NavigationBar.Styles.General.NavBarHeight - STATUS_BAR_HEIGHT}
loadEarlierMessagesButton={!this.state.allLoaded}
onLoadEarlierMessages={this.onLoadEarlierMessages.bind(this)}
senderName='Awesome Developer'
senderImage={null}
onImagePress={this.onImagePress}
displayNames={true}
parseText={true} // enable handlePhonePress, handleUrlPress and handleEmailPress
handlePhonePress={this.handlePhonePress}
handleUrlPress={this.handleUrlPress}
handleEmailPress={this.handleEmailPress}
isLoadingEarlierMessages={this.state.isLoadingEarlierMessages}
typingMessage={this.state.typingMessage}
/>
);
}
handleUrlPress(url) {
Linking.openURL(url);
}
// TODO
// make this compatible with Android
handlePhonePress(phone) {
if (Platform.OS !== 'android') {
var BUTTONS = [
'Text message',
'Call',
'Cancel',
];
var CANCEL_INDEX = 2;
ActionSheetIOS.showActionSheetWithOptions({
options: BUTTONS,
cancelButtonIndex: CANCEL_INDEX
},
(buttonIndex) => {
switch (buttonIndex) {
case 0:
Communications.phonecall(phone, true);
break;
case 1:
Communications.text(phone);
break;
}
});
}
}
handleEmailPress(email) {
Communications.email(email, null, null, null, null);
}
}
module.exports = GiftedMessengerContainer;
How to add custom views on my screen?
You need to make use of something called as states (in React terms). When onPress function is invoked you set a state variable to open/close which then can be used to show/hide the custom view. For ex:
var MessageBox = require('./GiftedMessengerContainer');
var MyAppName = React.createClass({
getInitialState: function(){
return {
messageBoxShow: 'false'
}
},
_openGiftedMessanger:function(){
this.setState({
messageBoxShow: 'true'
});
},
render: function() {
return (
<View style={styles.container}>
<TouchableHighlight
style={styles.imButton}
onPress={this._openGiftedMessanger}>
<Text>Open Chat Room</Text>
</TouchableHighlight>
{this.state.messageBoxShow === 'true' ? <MessageBox style={styles.container}/> : null };
</View>
);
}
AppRegistry.registerComponent('MyAppName', () => AppName);
Related
I have been trying to create a simple auto complete using Quasar's select but I'm not sure if this is a bug or if I'm doing something wrong.
Problem
Whenever I click the QSelect component, it doesn't show the dropdown where I can pick the options from.
video of the problem
As soon as I click on the QSelect component, I make a request to fetch a list of 50 tags, then I populate the tags to my QSelect but the dropdown doesn't show.
Code
import type { PropType } from "vue";
import { defineComponent, h, ref } from "vue";
import type { TagCodec } from "#/services/api/resources/tags/codec";
import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from "quasar";
export const TagAutoComplete = defineComponent({
name: "TagAutoComplete",
props: {
modelValue: { type: Array as PropType<TagCodec[]> },
},
emits: ["update:modelValue"],
setup(props, context) {
const loading = ref(false);
const tags = ref<TagCodec[]>([]);
// eslint-disable-next-line #typescript-eslint/ban-types
const onFilterTest = (val: string, doneFn: (update: Function) => void) => {
const parameters = val === "" ? {} : { title: val };
doneFn(async () => {
loading.value = true;
const response = await list(parameters);
if (val) {
const needle = val.toLowerCase();
tags.value = response.data.data.filter(
(tag) => tag.title.toLowerCase().indexOf(needle) > -1
);
} else {
tags.value = response.data.data;
}
loading.value = false;
});
};
const onInput = (values: TagCodec[]) => {
context.emit("update:modelValue", values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: "title",
optionValue: "id",
outlined: true,
useInput: true,
useChips: true,
placeholder: "Start typing to search",
onFilter: onFilterTest,
"onUpdate:modelValue": onInput,
loading: loading.value,
});
};
},
});
What I have tried
I have tried to use the several props that is available for the component but nothing seemed to work.
My understanding is that whenever we want to create an AJAX request using QSelect we should use the onFilter event emitted by QSelect and handle the case from there.
Questions
Is this the way to create a Quasar AJAX Autocomplete? (I have tried to search online but all the answers are in Quasar's forums that are currently returning BAD GATEWAY)
What am I doing wrong that it is not displaying the dropdown as soon as I click on the QSelect?
It seems updateFn may not allow being async. Shift the async action a level up to solve the issue.
const onFilterTest = async (val, update /* abort */) => {
const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await list(parameters);
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
I tested it by the following code and mocked values.
// import type { PropType } from 'vue';
import { defineComponent, h, ref } from 'vue';
// import type { TagCodec } from "#/services/api/resources/tags/codec";
// import { list } from "#/services/api/resources/tags/actions";
import { QSelect } from 'quasar';
export const TagAutoComplete = defineComponent({
name: 'TagAutoComplete',
props: {
modelValue: { type: [] },
},
emits: ['update:modelValue'],
setup(props, context) {
const loading = ref(false);
const tags = ref([]);
const onFilterTest = async (val, update /* abort */) => {
// const parameters = val === '' ? {} : { title: val };
loading.value = true;
const response = await new Promise((resolve) => {
setTimeout(() => {
resolve({
data: {
data: [
{
id: 1,
title: 'Vue',
},
{
id: 2,
title: 'Vuex',
},
{
id: 3,
title: 'Nuxt',
},
{
id: 4,
title: 'SSR',
},
],
},
});
}, 3000);
});
let list = response.data.data;
if (val) {
const needle = val.toLowerCase();
list = response.data.data.filter((x) => x.title.toLowerCase()
.includes(needle));
}
update(() => {
tags.value = list;
loading.value = false;
});
};
const onInput = (values) => {
context.emit('update:modelValue', values);
};
return function render() {
return h(QSelect, {
modelValue: props.modelValue,
multiple: true,
options: tags.value,
dense: true,
optionLabel: 'title',
optionValue: 'id',
outlined: true,
useInput: true,
useChips: true,
placeholder: 'Start typing to search',
onFilter: onFilterTest,
'onUpdate:modelValue': onInput,
loading: loading.value,
});
};
},
});
I need to achieve the tree view (Go JS Tree View). The respective tree view sample source code without React JS is at (Tree View Source Code). I'm trying to do the same using React JS and have the following code written. But somehow I'm missing something and the diagram/tree view is not rendering. Can you please help me to figure out the issue?
import React from 'react';
import * as go from 'gojs';
import { ReactDiagram } from 'gojs-react';
import '../../../App.css';
go.Shape.defineFigureGenerator("ExpandedLine", function(shape, w, h) {
return new go.Geometry()
.add(new go.PathFigure(0, 0.25*h, false)
.add(new go.PathSegment(go.PathSegment.Line, .5 * w, 0.75*h))
.add(new go.PathSegment(go.PathSegment.Line, w, 0.25*h)));
});
// use a sideways V figure instead of PlusLine in the TreeExpanderButton
go.Shape.defineFigureGenerator("CollapsedLine", function(shape, w, h) {
return new go.Geometry()
.add(new go.PathFigure(0.25*w, 0, false)
.add(new go.PathSegment(go.PathSegment.Line, 0.75*w, .5 * h))
.add(new go.PathSegment(go.PathSegment.Line, 0.25*w, h)));
});
let nodeDataArray = [{ key: 0 }];
const initDiagram = () => {
let $ = go.GraphObject.make;
const diagram =
$(go.Diagram, "myDiagramDiv",
{
allowMove: false,
allowCopy: false,
allowDelete: false,
allowHorizontalScroll: false,
layout:
$(go.TreeLayout,
{
alignment: go.TreeLayout.AlignmentStart,
angle: 0,
compaction: go.TreeLayout.CompactionNone,
layerSpacing: 16,
layerSpacingParentOverlap: 1,
nodeIndentPastParent: 1.0,
nodeSpacing: 0,
setsPortSpot: false,
setsChildPortSpot: false
})
});
diagram.nodeTemplate =
$(go.Node,
{ // no Adornment: instead change panel background color by binding to Node.isSelected
selectionAdorned: false,
// a custom function to allow expanding/collapsing on double-click
// this uses similar logic to a TreeExpanderButton
doubleClick: function(e, node) {
let cmd = diagram.commandHandler;
if (node.isTreeExpanded) {
if (!cmd.canCollapseTree(node)) return;
} else {
if (!cmd.canExpandTree(node)) return;
}
e.handled = true;
if (node.isTreeExpanded) {
cmd.collapseTree(node);
} else {
cmd.expandTree(node);
}
}
},
$("TreeExpanderButton",
{ // customize the button's appearance
"_treeExpandedFigure": "ExpandedLine",
"_treeCollapsedFigure": "CollapsedLine",
"ButtonBorder.fill": "whitesmoke",
"ButtonBorder.stroke": null,
"_buttonFillOver": "rgba(0,128,255,0.25)",
"_buttonStrokeOver": null
}),
$(go.Panel, "Horizontal",
{ position: new go.Point(18, 0) },
new go.Binding("background", "isSelected",
s => (s ? 'lightblue' : 'white')).ofObject(),
$(go.Picture,
{
width: 18, height: 18,
margin: new go.Margin(0, 4, 0, 0),
imageStretch: go.GraphObject.Uniform
},
// bind the picture source on two properties of the Node
// to display open folder, closed folder, or document
new go.Binding("source", "isTreeExpanded", imageConverter).ofObject(),
new go.Binding("source", "isTreeLeaf", imageConverter).ofObject()),
$(go.TextBlock,
{ font: '9pt Verdana, sans-serif' },
new go.Binding("text", "key", function(s) { return "item " + s; }))
) // end Horizontal Panel
); // end Node
diagram.linkTemplate = $(go.Link);
let max = 499;
let count = 0;
while (count < max) {
count = makeTree(3, count, max, nodeDataArray, nodeDataArray[0]);
}
diagram.model = new go.TreeModel(nodeDataArray);
return diagram;
}
function makeTree(level, count, max, nodeDataArray, parentData) {
let numChildren = Math.floor(Math.random() * 10);
for (let i = 0; i < numChildren; i++) {
if (count >= max) return count;
count++;
let childData = { key: count, parent: parentData.key };
nodeDataArray.push(childData);
if (level > 0 && Math.random() > 0.5) {
count = makeTree(level - 1, count, max, nodeDataArray, childData);
}
}
return count;
}
function imageConverter(prop, picture) {
let node = picture.part;
if (node.isTreeLeaf) {
return "images/document.svg";
} else {
if (node.isTreeExpanded) {
return "images/openFolder.svg";
} else {
return "images/closedFolder.svg";
}
}
}
window.addEventListener('DOMContentLoaded', initDiagram);
const TreeView = () => {
return (
<>
GO JS
<div id="myDiagramDiv">
<ReactDiagram
initDiagram={initDiagram}
divClassName='diagram-component'
nodeDataArray={nodeDataArray}
skipsDiagramUpdate={false}
/>
</div>
</>
);
}
export default TreeView;
When React start executing, the DOMContentLoaded event have already been fired. Instead try to call initDiagram in a useEffect hook
const TreeView = () => {
useEffect(initDiagram);
return (
<>
GO JS
<div id="myDiagramDiv">
<ReactDiagram
initDiagram={initDiagram}
divClassName='diagram-component'
nodeDataArray={nodeDataArray}
skipsDiagramUpdate={false}
/>
</div>
</>
);
}
I built a small boat visualizer. I am using AISHub APIs. After fetching data from the APIs I am able to obtain a json file with the vessels I am interested in and inject these vessels inside a table.
The user has to manually update the page pushing the refresh button on top left of the page to see the new updated table.
The problem: How to set a state to refresh the google-map content automatically every minute instead of the user doing it manually?
Below the code:
GoogleMap.js
class BoatMap extends Component {
constructor(props) {
super(props);
this.state = {
buttonEnabled: true,
buttonClickedAt: null,
progress: 0,
ships: [],
type: 'All',
shipTypes: [],
activeShipTypes: [],
logoMap: {}
};
this.updateRequest = this.updateRequest.bind(this);
this.countDownInterval = null;
}
async componentDidMount() {
this.countDownInterval = setInterval(() => {
if (!this.state.buttonClickedAt) return;
const date = new Date();
const diff = Math.floor((date.getTime() - this.state.buttonClickedAt.getTime()) / 1000);
if (diff < 90) {
this.setState({
progress: diff,
buttonEnabled: false
});
} else {
this.setState({
progress: 0,
buttonClickedAt: null,
buttonEnabled: true
});
}
}, 500);
await this.updateRequest();
const shipTypeResults = await Client.getEntries({
content_type: 'competitors'
});
console.log(shipTypeResults);
const shipTypes = shipTypeResults.items.map((data) => data.fields);
const logoMap = shipTypes.reduce((acc, type) => {
return {
...acc,
[type.name]: type.images.fields.file.url
};
}, {});
console.log({ shipTypes });
this.setState({
logoMap
});
}
componentDidUpdate(prevProps, prevState) {
if (this.state.type !== prevState.type) {
}
}
componentWillUnmount() {
clearInterval(this.countdownInterval);
}
async updateRequest() {
const url = 'http://localhost:3001/hello';
console.log(url);
const fetchingData = await fetch(url);
const ships = await fetchingData.json();
console.log(ships);
this.setState({
buttonEnabled: false,
buttonClickedAt: new Date(),
progress: 0,
ships
});
setTimeout(() => {
this.setState({ buttonEnabled: true });
});
}
render() {
return (
<div className="google-map">
<GoogleMapReact
bootstrapURLKeys={{ key: 'KEY' }}
center={{
lat: this.props.activeShip ? this.props.activeShip.latitude : 42.4,
lng: this.props.activeShip ? this.props.activeShip.longitude : -71.1
}}
zoom={8}
>
</GoogleMapReact>
</div>
);
}
}
What I have done so far:
A good way would be using a setTimeout() but would that be correct? Where should that be applied and how?
setTimeout(function () {
location.reload();
}, 60 * 1000);
Or maybe setting an interval as a refresh rate?
I am a bit confused on what would the best way to approach this.
On your request function i guess u want to disable the button while the api doesn't return, so maybe move this piece above the requests:
this.setState({
buttonEnabled: false,
buttonClickedAt: new Date(),
progress: 0,
ships
});
If im wrong you could remove the timeout from the second setState and call as a callback on the first like this:
this.setState({
buttonEnabled: false,
buttonClickedAt: new Date(),
progress: 0,
ships
}, () => {
this.setState({ buttonEnabled: true });
});
on the last part instead of location.reload() set a interval calling the update on ur componentDidMount:
let updateInterval = setInterval(() => {
this.updateRequest();
}, 60 * 1000);
this.setState({updateInterval})
then on the componentWillUnmount you clear the interval this.state.updateInterval
I am trying to use React Data Grid in my project and I want to use row drag and drop and Drag Columns to Reorder features together.
I tried to do this by passing draggable: true for ReactDataGrid column property and wrapping the ReactDataGrid and Draggable.Container with DraggableHeader.DraggableContainer.
It makes column header moveable but it does not trigger onHeaderDrop action in DraggableContainer and it gave a console error Uncaught TypeError: Cannot read property 'id' of undefined.
I change example23-row-reordering.js according to the description above.
const ReactDataGrid = require('react-data-grid');
const exampleWrapper = require('../components/exampleWrapper');
const React = require('react');
const {
Draggable: { Container, RowActionsCell, DropTargetRowContainer },
Data: { Selectors },
DraggableHeader: {DraggableContainer}
} = require('react-data-grid-addons');
import PropTypes from 'prop-types';
const RowRenderer = DropTargetRowContainer(ReactDataGrid.Row);
class Example extends React.Component {
static propTypes = {
rowKey: PropTypes.string.isRequired
};
static defaultProps = { rowKey: 'id' };
constructor(props, context) {
super(props, context);
this._columns = [
{
key: 'id',
name: 'ID',
draggable: true
},
{
key: 'task',
name: 'Title',
draggable: true
},
{
key: 'priority',
name: 'Priority',
draggable: true
},
{
key: 'issueType',
name: 'Issue Type',
draggable: true
}
];
this.state = { rows: this.createRows(1000), selectedIds: [1, 2] };
}
getRandomDate = (start, end) => {
return new Date(start.getTime() + Math.random() * (end.getTime() - start.getTime())).toLocaleDateString();
};
createRows = (numberOfRows) => {
let rows = [];
for (let i = 1; i < numberOfRows; i++) {
rows.push({
id: i,
task: 'Task ' + i,
complete: Math.min(100, Math.round(Math.random() * 110)),
priority: ['Critical', 'High', 'Medium', 'Low'][Math.floor((Math.random() * 3) + 1)],
issueType: ['Bug', 'Improvement', 'Epic', 'Story'][Math.floor((Math.random() * 3) + 1)],
startDate: this.getRandomDate(new Date(2015, 3, 1), new Date()),
completeDate: this.getRandomDate(new Date(), new Date(2016, 0, 1))
});
}
return rows;
};
rowGetter = (i) => {
return this.state.rows[i];
};
isDraggedRowSelected = (selectedRows, rowDragSource) => {
if (selectedRows && selectedRows.length > 0) {
let key = this.props.rowKey;
return selectedRows.filter(r => r[key] === rowDragSource.data[key]).length > 0;
}
return false;
};
reorderRows = (e) => {
let selectedRows = Selectors.getSelectedRowsByKey({rowKey: this.props.rowKey, selectedKeys: this.state.selectedIds, rows: this.state.rows});
let draggedRows = this.isDraggedRowSelected(selectedRows, e.rowSource) ? selectedRows : [e.rowSource.data];
let undraggedRows = this.state.rows.filter(function(r) {
return draggedRows.indexOf(r) === -1;
});
let args = [e.rowTarget.idx, 0].concat(draggedRows);
Array.prototype.splice.apply(undraggedRows, args);
this.setState({rows: undraggedRows});
};
onRowsSelected = (rows) => {
this.setState({selectedIds: this.state.selectedIds.concat(rows.map(r => r.row[this.props.rowKey]))});
};
onRowsDeselected = (rows) => {
let rowIds = rows.map(r => r.row[this.props.rowKey]);
this.setState({selectedIds: this.state.selectedIds.filter(i => rowIds.indexOf(i) === -1 )});
};
render() {
return (
<DraggableContainer
onHeaderDrop={()=>{console.log('column dropped');}}
>
<Container>
<ReactDataGrid
enableCellSelection={true}
rowActionsCell={RowActionsCell}
columns={this._columns}
rowGetter={this.rowGetter}
rowsCount={this.state.rows.length}
minHeight={500}
rowRenderer={<RowRenderer onRowDrop={this.reorderRows}/>}
rowSelection={{
showCheckbox: true,
enableShiftSelect: true,
onRowsSelected: this.onRowsSelected,
onRowsDeselected: this.onRowsDeselected,
selectBy: {
keys: {rowKey: this.props.rowKey, values: this.state.selectedIds}
}
}}/>
</Container>
</DraggableContainer>);
}
}
module.exports = exampleWrapper({
WrappedComponent: Example,
exampleName: 'Row Reordering',
exampleDescription: 'This examples demonstrates how single or multiple rows can be dragged to a different positions using components from Draggable React Addons',
examplePath: './scripts/example23-row-reordering.js'
});
I went through their documentation but could not find any place where they say these two features can't use together. But in their examples does not provide any example for this.Any example code for documentation describes how to use these two features together would be greatly appreciated.
I have a simple react-native client for a website, in login page it provides two option, enter login code manually or scan it using barcode scanner. i've tested the app in real device and emulator lot's of times it's working fine.
Actually i test only over ipv4, and for login im using fetch, which i think is supporting ipv6 by default.
They say over ipv6 network when app was offline, i cannot understand what does it mean to be OFFLINE and be on IPV6 network?
When app is offline, i'm showing error to user that there is no connectivity. so i don't know how it can crash.
should adding a timeout to fetch request fix the issue?
But the app being rejected 3 times due to same error :
Performance - 2.1
Thank you for your resubmission.
Your app crashes on iPhone running iOS 9.3.3 connected to an IPv6
network when we:
Specifically, tapping the login still leads the app to crash.
This occurred when your app was used:
Offline
On Wi-Fi
We have attached detailed crash logs to help troubleshoot this issue.
Here is the login.js :
'use strict';
import React, { Component } from 'react';
import {
Text,
View,
Image,
TextInput,
TouchableOpacity,
ActivityIndicatorIOS,
StyleSheet,
Dimensions,
AlertIOS,
NetInfo,
} from 'react-native';
import Camera from 'react-native-camera';
var { width, height } = Dimensions.get('window');
class Login extends Component {
constructor(props){
super(props);
this.state = {
showProgress: false,
showCamera: false,
cameraType: Camera.constants.Type.back,
barcode: true,
isConnected: false,
}
}
componentWillMount(){
NetInfo.isConnected.fetch().done((data) => {
this.setState({
isConnected: data
})
});
}
_onBarCodeRead(e) {
this.setState({
showCamera: false,
barcodeData: e.data,
logincode: e.data,
success: true,
});
this.onLoginPressed();
}
render(){
if(this.state.showCamera) {
return (
<Camera
ref="cam"
style={styles.container}
onBarCodeRead={this._onBarCodeRead.bind(this)}
type={this.state.cameraType}>
</Camera>
);
} else {
var errorCtrl = <View />;
if(!this.state.success){
errorCtrl = <Text style={styles.error}>
{this.state.message}
</Text>;
}
///// Check login type
if(this.state.barcode){
return(
<View style={styles.container}>
<Image style={styles.logo} source={require('image!logo')} />
<Text style={styles.heading}>
Please use QR-Scanner to login,{'\n'}
or enter the Login code manually.
</Text>
<TouchableOpacity
onPress={this.onQrPressed.bind(this)}
style={styles.button}>
<Text style={styles.buttonText}>Use QR-Scanner</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.toManuall.bind(this)}
>
<Text style={styles.change}>
Want to enter code manually?
</Text>
</TouchableOpacity>
{errorCtrl}
<ActivityIndicatorIOS
animating={this.state.showProgress}
size="large"
style={styles.loader}
/>
</View>
);
} else {
return(
<View style={styles.container}>
<Image style={styles.logo} source={require('image!logo')} />
<Text style={styles.heading}>
Please use QR-Scanner to login,{'\n'}
or enter the Login code manually.
</Text>
<TextInput onChangeText={(text)=> this.setState({logincode: text})} style={styles.loginInput} placeholder={this.state.logincode}>
</TextInput>
<TouchableOpacity onPress={this.onLoginPressed.bind(this)} style={styles.button} >
<Text style={styles.buttonText}>Log in</Text>
</TouchableOpacity>
<TouchableOpacity
onPress={this.toBarcode.bind(this)}
>
<Text style={styles.change}>
Want to use Barcode?
</Text>
</TouchableOpacity>
{errorCtrl}
<ActivityIndicatorIOS
animating={this.state.showProgress}
size="large"
style={styles.loader}
/>
</View>
);
}
/////
}
}
onLoginPressed(){
if(this.state.isConnected){
/// do the validation
var valid = false;
if(this.state.logincode != undefined && this.state.logincode.includes('opencampus://') && this.state.logincode.includes('access_token=') && this.state.logincode.includes('refresh_token=') && this.state.logincode.includes('id=') && this.state.logincode.includes('name=') && this.state.logincode.includes('scheme=')){
var valid = true;
}
if(valid){
console.log('Login.ios: Attempting to log in with logincode ' + this.state.logincode);
this.setState({showProgress: true});
console.log('Login.ios: calling AuthService class');
var AuthService = require('./AuthService');
AuthService.login({
logincode: this.state.logincode
}, (results)=> {
this.setState(Object.assign({
showProgress: false
}, results));
console.log('Login.ios: AuthService execution finished.', results);
if(results.success && this.props.onLogin){
this.props.onLogin(results);
}
});
} else {
AlertIOS.alert(
'Invalid Input',
'Login code you entered is not valid. Be sure to paste the whole string starting with opencampus://'
);
}
} else {
AlertIOS.alert(
'No Connection',
'Please check your internet connection.'
);
}
}
onQrPressed(){
this.setState({
showCamera: true,
});
}
toManuall(){
this.setState({
barcode: false,
});
}
toBarcode(){
this.setState({
barcode: true,
});
}
}
var styles = StyleSheet.create({
container: {
backgroundColor: '#00a2dd',
paddingTop: 40,
padding: 10,
alignItems: 'center',
flex: 1,
justifyContent: 'center'
},
logo: {
width: 141,
height: 137,
},
heading: {
fontSize: 18,
margin: 10,
marginBottom: 20,
color: '#FFFFFF',
paddingTop: 50,
},
change: {
fontSize: 12,
color: '#FFFFFF',
marginTop:10,
},
loginInput: {
height: 50,
marginTop: 10,
padding: 4,
fontSize: 18,
borderWidth: 1,
borderColor: '#FFFFFF',
borderRadius: 0,
color: '#FFFFFF'
},
button: {
height: 50,
backgroundColor: '#48BBEC',
borderColor: '#48BBEC',
alignSelf: 'stretch',
marginTop: 10,
justifyContent: 'center',
alignItems: 'center',
borderRadius: 5
},
buttonText: {
color: '#fff',
fontSize: 24
},
loader: {
marginTop: 20
},
error: {
color: 'red',
paddingTop: 10
}
});
module.exports = Login;
Here is AuthService.js :
'use strict';
import React, { Component } from 'react';
var SQLite = require('react-native-sqlite-storage');
var DeviceInfo = require('react-native-device-info');
class AuthService extends Component {
constructor(props) {
super(props);
this.state = {
showProgress: false
}
this.errorCB = this.errorCB.bind(this);
this.successCB = this.successCB.bind(this);
}
errorCB(err) {
console.log("Auth Service: error: ", err);
this.state.progress.push("Error: " + (err.message || err));
return false;
}
successCB() {
}
login(creds, cb){
var db = SQLite.openDatabase({name : "oc.db", location: 'default'}, this.successCB.bind(this), this.errorCB.bind(this));
var sql = 'CREATE TABLE IF NOT EXISTS users ('
+ 'access_token text NOT NULL,'
+ 'refresh_token text NOT NULL,'
+ 'userName text NOT NULL,'
+ 'userId text NOT NULL,'
+ 'userMail text NOT NULL,'
+ 'userSignature text NOT NULL,'
+ 'userSignatureFormat text NOT NULL,'
+ 'userCreated text NOT NULL,'
+ 'userAccess text NOT NULL,'
+ 'userLogin text NOT NULL,'
+ 'userStatus text NOT NULL,'
+ 'userTimezone text NOT NULL,'
+ 'userLanguage text NOT NULL,'
+ 'userRoles text NOT NULL,'
+ 'deviceId text NOT NULL,'
+ 'deviceName text NOT NULL,'
+ 'host text NOT NULL,'
+ 'active text NOT NULL'
+ ');';
db.executeSql(sql, [],
this.successCB.bind(this),
this.errorCB.bind(this)
);
var LCode = creds.logincode;
var codeSplited = LCode.split("://");
var codeSplited2 = codeSplited[1].split("?");
var appName = codeSplited[0];
var serverName = codeSplited2[0];
var splitedVars = codeSplited2[1].split("&");
var access_token = splitedVars[0].split("=");
var access_token = access_token[1];
var refresh_token = splitedVars[1].split("=");
var refresh_token = refresh_token[1];
var uid = splitedVars[2].split("=");
var uid = uid[1];
var uname = splitedVars[3].split("=");
var uname = uname[1];
var scheme = splitedVars[4].split("=");
var scheme = scheme[1];
var device_id = DeviceInfo.getUniqueID();
var device_name = DeviceInfo.getDeviceName();
var locale = DeviceInfo.getDeviceLocale();
console.log('AuthService: Try to fetch from : ', serverName);
console.log('request body: ', JSON.stringify({
uid: uid,
refresh_token: refresh_token,
token: access_token,
device: device_id,
device_name: device_name,
}));
fetch(scheme + '://' + serverName, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'language': locale,
'Authorization': 'Bearer ' + access_token,
},
body: JSON.stringify({
uid: uid,
refresh_token: refresh_token,
token: access_token,
device: device_id,
device_name: device_name,
})
})
.then((response)=> {
return response;
})
.then((response)=> {
return response.json();
})
.then((results)=> {
console.log(results);
if(results['result'] == 1){
console.log('Auth Service: Login was successfull');
// User data
var userName = results['session']['user']['name'];
var userId = results['session']['user']['uid'];
var userMail = results['session']['user']['mail'];
var userSignature = results['session']['user']['signature'];
var userSignatureFormat = results['session']['user']['signature_format'];
var userCreated = results['session']['user']['created'];
var userAccess = results['session']['user']['access'];
var userLogin = results['session']['user']['login'];
var userStatus = results['session']['user']['status'];
var userTimezone = results['session']['user']['timezone'];
var userLanguage = results['session']['user']['language'];
var userRoles = results['session']['user']['roles']['2'];
var host = results['session']['user']['host'];
var active = 'yes';
//var userPicture = results['session']['user']['picture'];
console.log('Auth Service: Lets save user data to database');
var query = "INSERT INTO users (access_token, refresh_token, userName, userId, userMail, userSignature, userSignatureFormat, userCreated, userAccess, userLogin, userStatus, userTimezone, userLanguage, userRoles, deviceId, deviceName, host, active) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
var params = [access_token, refresh_token, userName,userId,userMail,userSignature,userSignatureFormat,userCreated,userAccess,userLogin,userStatus,userTimezone,userLanguage,userRoles,device_id,device_name,host,active];
db.executeSql(query,params,
this.successCB.bind(this),
this.errorCB.bind(this)
);
return cb({
success: true,
userData: results['session']['user']
});
} else if(results['result'] == 0){
console.log('Auth Service: Login failed message is ' + results['message']);
return cb({
success: false,
message: results['message']
});
} else {
console.log('Auth Service: Login failed error is ' + results['error_description']);
return cb({
success: false,
message: results['error_description']
});
}
})
.catch((err)=> {
console.log('AuthService: ' + err);
return cb(err);
})
.done();
}
}
module.exports = new AuthService();
And here is Index.js :
"use strict";
import React, {Component, PropTypes} from 'react';
import {
AppRegistry,
NavigatorIOS,
StyleSheet,
TabBarIOS,
View,
Text,
StatusBar,
} from 'react-native';
var CourseList = require("./app/CourseList");
var Profile = require("./app/Profile");
import Icon from 'react-native-vector-icons/Ionicons';
var SQLite = require('react-native-sqlite-storage');
var Login = require("./app/Login");
var db = SQLite.openDatabase({name : "oc.db", location: 'default'});
StatusBar.setBarStyle('light-content');
class OpenCampus extends Component {
constructor(props) {
super(props);
this.state = {
selectedTab: "Courses",
isLoggedIn: false,
userId: null,
};
}
componentWillMount(){
var query = "SELECT * FROM users WHERE active='yes'";
var params = [];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
var len = results.rows.length;
if(len > 0){
let row = results.rows.item(0);
this.setState({
isLoggedIn: true,
userId: row.userId
});
}
}, function(){
console.log('index: Something went wrong');
});
});
}
onLogin(results) {
this.setState({
isLoggedIn: true,
});
}
logout() {
console.log("Logout called from index");
var query = "DELETE FROM users WHERE userId=?";
var params = [this.state.userId];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
///// check if there is other accounts on database, if yes, make first row active
var query = "SELECT * FROM users WHERE active='yes'";
var params = [];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
var len = results.rows.length;
if(len > 0){
let row = results.rows.item(0);
userId = row.userId;
///// Set new user active
var query = "UPDATE users SET active='yes' WHERE userId=?";
var params = [userId];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
console.log('index: Active Account Changed');
}, function(){
console.log('index: Something went wrong');
});
});
///////
this.setState({
isLoggedIn: true,
userId: userId,
});
} else {
this.setState({
isLoggedIn: false,
userId: null,
});
}
}, function(){
console.log('index: Something went wrong');
});
});
/////
}, function(){
console.log('index: Something went wrong when logging out');
});
});
}
_renderCourses() {
return (
<NavigatorIOS style={styles.wrapper}
barTintColor='#00a2dd'
titleTextColor='#fff'
tintColor='#ffffff'
ref='RCourses'
initialRoute={{
component: CourseList,
title: 'Courses',
passProps: {filter: 'Courses'},
}}
/>
);
}
_renderRegister() {
return (
<NavigatorIOS style={styles.wrapper}
barTintColor='#00a2dd'
titleTextColor='#fff'
tintColor='#ffffff'
ref='RRegister'
initialRoute={{
component: CourseList,
title: 'Register',
passProps: {filter: 'Register'},
}}
/>
);
}
_renderProfile() {
return (
<NavigatorIOS style={styles.wrapper}
barTintColor='#00a2dd'
titleTextColor='#fff'
tintColor='#ffffff'
ref='RProfile'
initialRoute={{
component: Profile,
title: 'Profile',
passProps: {filter: 'Profile'},
rightButtonTitle: 'Logout',
onRightButtonPress: () => this.logout(),
leftButtonTitle: 'Add Account',
onLeftButtonPress: () => this.addnew(),
}}
/>
);
}
addnew() {
console.log('Send user to login page to add new account');
//// Set old user to inactive
var query = "UPDATE users SET active='no' WHERE active='yes'";
var params = [this.state.userId];
db.transaction((tx) => {
tx.executeSql(query,params, (tx, results) => {
//// Set login status to false so login screen will be shown
console.log(results);
this.setState({
isLoggedIn: false,
userId: null,
});
}, function(){
console.log('index: Something went wrong when adding new account');
});
});
}
popAll(){
if(typeof this.refs.RCourses !== typeof undefined){
this.refs.RCourses.popToTop();
}
if(typeof this.refs.RRegister !== typeof undefined){
this.refs.RRegister.popToTop();
}
if(typeof this.refs.RProfile !== typeof undefined){
this.refs.RProfile.popToTop();
}
}
render() {
if(!this.state.isLoggedIn){
console.log('index: User not logged in. redirecting to Login page.');
return(
<Login onLogin={this.onLogin.bind(this)} />
);
} else {
console.log('index: User is logged in lets show the content');
return (
<TabBarIOS tintColor={"#00a2dd"}>
<Icon.TabBarItem
title="Courses"
iconName="ios-list-outline"
selectedIconName="ios-list-outline"
selected={this.state.selectedTab === "Courses"}
onPress={() => {
this.setState({
selectedTab: "Courses",
});
this.popAll();
}}>
{this._renderCourses()}
</Icon.TabBarItem>
<Icon.TabBarItem
title="Register"
iconName="ios-book"
selectedIconName="ios-book"
selected={this.state.selectedTab === "Register"}
onPress={() => {
this.setState({
selectedTab: "Register",
});
this.popAll();
}}>
{this._renderRegister()}
</Icon.TabBarItem>
<Icon.TabBarItem
title="Profile"
iconName="ios-person"
selectedIconName="ios-person"
selected={this.state.selectedTab === "Profile"}
onPress={() => {
this.setState({
selectedTab: "Profile",
});
this.popAll();
}}>
{this._renderProfile()}
</Icon.TabBarItem>
</TabBarIOS>
);
}
}
}
var styles = StyleSheet.create({
tabContent: {
flex: 1,
alignItems: "center",
},
tabText: {
color: "white",
margin: 50,
},
wrapper: {
flex: 1,
backgroundColor: '#00a2dd',
}
});
AppRegistry.registerComponent('OpenCampus', () => OpenCampus);
UPDATE :
here is the crash log by apple : http://www.ataomega.com/temp..suczkfac.crash
http://www.ataomega.com/temp..hsbgdlod.crash
You should test your app for ipv6 compatibility. Here is a tutorial that explains how to do that.
Boot OS X 10.11
Make sure your Mac is connected to the Internet, but not through Wi-Fi.
Launch System Preferences from your Dock, LaunchPad, or the Apple menu.
Press the Option key and click Sharing. Don’t release the Option key yet.
Select Internet Sharing in the list of sharing services.
Release the Option key.
Select the Create NAT64 Network checkbox.
Choose the network interface that provides your Internet connection, such as Thunderbolt Ethernet.
Select the Wi-Fi checkbox.
Click Wi-Fi Options, and configure the network name and security options for your network.
Setting up local Wi-Fi network options
Select the Internet Sharing checkbox to enable your local network.
When prompted to confirm you want to begin sharing, click Start.
Once sharing is active, you should see a green status light and a label that says Internet Sharing: On. In the Wi-Fi menu, you will also see a small, faint arrow pointing up, indicating that Internet Sharing is enabled. You now have an IPv6 NAT64 network and can connect to it from other devices in order to test your app.