react dnd nesting level on hover - react-dnd

I want to implement a drop system where if drop targets are nested you can cycle between them using the mouse scroll wheel (or have an automatic cycle happen after a certain amount of time) this will be particularly useful because many of the nested targets occupy the exact same areas on screen.
I'm presently thinking that I could use a callback passed down from the container that could be used by drop targets to register/de-register themselves when their hover function is called/when isOver prop changes but it's very coupled and I have to pass props into the DropTargets from the container, in the real world application there will be an unknown number of levels between container and drop target so I'd likely have to set up some kind of callback system, overall its not an ideal solution. Also I'm not sure how to ensure the correct order when cycling as the drop targets don't know how deeply nested they are (see code below for how I've implemented this). Is there a cleaner way to implement such a system?
#DragDropContext(HTML5Backend)
export default class Container extends Component {
constructor (props) {
super(props);
this.state = {
bins: []
};
this.status = {};
this._cycle = this.cycle.bind(this);
this._register = this.register.bind(this);
this._deregister = this.deregister.bind(this);
}
componentWillUnmount () {
if (this.timer) {
window.clearInterval(this.timer);
}
}
register (name) {
if (this.state.bins.findIndex(e => e === name) === -1) {
this.setState({
bins: this.state.bins.concat(name)
});
if (!this.timer) {
this.cycledBins = [];
this.timer = window.setInterval(this._cycle, 3000);
this._cycle();
}
}
}
deregister (name) {
if (this.state.bins.findIndex(e => e === name) !== -1) {
const bins = this.state.bins.filter((e) => e === name);
this.setState({
bins
});
if (!bins.length) {
window.clearInterval(this.timer);
this.timer = undefined;
}
}
}
cycle () {
this.status = {};
const bins = this.state.bins;
let activeBin = -1;
for (let i = 0; i < bins.length; i += 1) {
if (this.cycledBins.findIndex(bin => bin === bins[i]) === -1) {
activeBin = bins[i];
break;
}
}
if (activeBin === -1) {
activeBin = bins[0];
this.cycledBins = [];
}
this.cycledBins.push(activeBin);
this.activeBin = activeBin;
this.status[activeBin] = {
isActive: true,
isOnlyActive: bins.length === 1
}
this.forceUpdate();
}
render () {
return (
<div>
bins = {JSON.stringify(this.state.bins)}<br />
cycledBins = {JSON.stringify(this.cycledBins)}
<div style={{ overflow: 'hidden', clear: 'both' }}>
<Dustbin name="Outer" register={this._register} deregister={this._deregister} status={this.status["Outer"]} >
<Dustbin name="Middle" register={this._register} deregister={this._deregister} status={this.status["Middle"]} >
<Dustbin name="Inner" register={this._register} deregister={this._deregister} status={this.status["Inner"]} />
</Dustbin>
</Dustbin>
</div>
<div style={{ overflow: 'hidden', clear: 'both' }}>
<Box name='Glass' />
<Box name='Banana' />
<Box name='Paper' />
</div>
</div>
);
}
}
const boxTarget = {
hover(props) {
props.register(props.name);
}
};
#DNDDropTarget('Box', boxTarget, (connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver()//,
//canDrop: monitor.canDrop()
}))
export default class Dustbin extends Component {
static propTypes = {
connectDropTarget: PropTypes.func.isRequired,
isOver: PropTypes.bool.isRequired//,
//canDrop: PropTypes.bool.isRequired
};
componentWIllMount () {
if (!this.props.isOver) {
this.props.deregister(this.props.name);
}
}
componentWillReceiveProps (nextProps) {
if (nextProps.isOver !== this.props.isOver && !nextProps.isOver) {
this.props.deregister(this.props.name);
}
}
render() {
const { canDrop, isOver, connectDropTarget, status } = this.props;
const isOnlyActive = status && status.isOnlyActive;
let isActive = status && status.isActive;
if (!isOver && isActive) {
isActive = false;
}
return connectDropTarget(
<div>
<DropTarget position={BEFORE} shown={isActive} />
{ isActive && !isOnlyActive ? '(' + this.props.name + ')' : undefined }
{ this.props.children ? this.props.children : (
<div style={style}>
{ isActive ?
'Release to drop' :
'Drag a box here'
}
</div>
) }
<DropTarget position={AFTER} shown={isActive} />
</div>
);
}
}

Related

react native setInterval cannot read property apply

I am new in react native I am trying to render the count of unread notification for that I called my API in HOC it is working fine for initial few seconds but after that, I started to get the below error
func.apply is not a function
below is my code
import React, { Component } from "react";
import PropTypes from "prop-types";
import { Modal, View } from "react-native";
import { themes } from "./constants";
import { AsyncStorage } from "react-native";
export default (OriginalComponent, animationType) =>
class extends Component {
static propTypes = {
handleFail: PropTypes.func,
theme: PropTypes.string,
visible: PropTypes.bool
};
state = {
modalVisible: true
};
static getDerivedStateFromProps({ visible }) {
if (typeof visible === "undefined") {
setInterval(
AsyncStorage.getItem("loginJWT").then(result => {
if (result !== null) {
result = JSON.parse(result);
fetch(serverUrl + "/api/getUnreadNotificationsCount", {
method: "GET",
headers: {
Authorization: "Bearer " + result.data.jwt
}
})
.then(e => e.json())
.then(function(response) {
if (response.status === "1") {
if (response.msg > 0) {
AsyncStorage.setItem(
"unreadNotification",
JSON.stringify(response.msg)
);
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}
})
.catch(error => {
alert(error);
// console.error(error, "ERRRRRORRR");
});
} else {
AsyncStorage.setItem("unreadNotification", 0);
}
}),
5000
);
return null;
}
return { modalVisible: visible };
}
handleOpenModal = () => {
this.setState({ modalVisible: true });
};
handleCloseModal = () => {
const { handleFail } = this.props;
this.setState({ modalVisible: false }, handleFail);
};
render() {
const { modalVisible } = this.state;
const { theme } = this.props;
return (
<View>
<Modal
animationType={animationType ? animationType : "fade"}
transparent={true}
visible={modalVisible}
onRequestClose={this.handleCloseModal}
>
<View style={themes[theme] ? themes[theme] : themes.transparent}>
<OriginalComponent
handleCloseModal={this.handleCloseModal}
{...this.props}
/>
</View>
</Modal>
</View>
);
}
};
I have not used getDerivedStateFromProps but, according to the docs, it is called on initial component mount and before each render update.
Thus your code is creating a new interval timer on each update without clearing any of the earlier timers, which could be causing a race condition of some sort.
You may want to consider using the simpler alternatives listed in the docs, or at a minimum, insure that you cancel an interval before creating a new one.

Change specific buttons background onPress (React Native)

I'm trying to build a simple quiz. I would like to change the background of a button to green if it's correct and to red if it's incorrect onPress. My questions is how do I select only ONE of the buttons? Currently I can only get all of the buttons to turn colour.
export default class TestScreen extends React.Component {
constructor(props){
super(props);
this.qno = 0
this.score = 0
const jdata = jsonData.quiz.quiz1
arrnew = Object.keys(jdata).map( function(k) { return jdata[k] });
this.state = {
question : arrnew[this.qno].question,
options : arrnew[this.qno].options,
correctoption : arrnew[this.qno].correctoption,
countCheck : 0,
back_colour: 'gray',
answered: false,
}
}
change_colour(ans) {
if(this.state.answered == false){
if(ans == this.state.correctoption){
Alert.alert('Correct!');
this.setState({ back_colour: 'green'})
}
else {
Alert.alert('Wrong!');
this.setState({ back_colour: 'red'})
}
this.setState({ answered: true })
}
else {
// Do nothing
}
}
render() {
let _this = this
const currentOptions = this.state.options
const options = Object.keys(currentOptions).map( function(k) {
return ( <View key={k} style={{margin:10}}>
<Button color={_this.state.back_colour} onPress={() => _this.change_colour(k)} title={currentOptions[k]} />
</View>)
});
}
}
yeah its great and easy with react . You can use ids for this purpose.
example ..
validateUser = id => {
const newItems = items.map(item => {
if(item.id === id ) {
then check anwer here
and trun status true or false ..
}
})
}
items.map(item function() {
<Button color={item.status ? 'red' : 'black' } onPress={() =>
this.validateAnswer(item.id)}>
})
and your item object in your array should be like this ..
{
id: '0',
status: true/false
}
i think this will help .

React Native FlatList extraData not rerendering

I am not able to re-render my FlatList component. I have tried everything that I can think of. I have tried updating a boolean value for extraData with this.setState, I have passed Immutable.Map to extraData. But nothing seems to work although I know my component has updated correctly and everything is in place when component re-render, but FlatList does not re-render although I have passed changed data to extraData. I just cant figure out what I am doing wrong.
Here is my component without imports and styles:
class DetailsScreen extends Component {
constructor(props) {
super(props);
const {
data: { list, letterIndexes, alphabet },
savedList
} = this.props.navigation.state.params;
this.state = {
alphabet,
letter: "A",
letterIndexes,
letterIndexesValues: Object.values(letterIndexes),
savedList: savedList || [],
list,
updated: false
};
}
componentWillMount() {
const { list, savedList, refreshFlatList, updated } = this.state;
this.setState({
list: this.updateListActiveState(list, savedList),
updated: !updated
});
}
static navigationOptions = ({ navigation }) => ({
title: `${navigation.state.params.title}`
});
/**
* Adds active prop to each object in array.
* If they exists in savedList then we set active prop as true
*
* #param {Array<Object>} list - Names list
* #param {Array<Object>} savedList - Saved names list
* #returns {Array<Object>} list
*/
updateListActiveState(list, savedList) {
return list.map(item => {
item.active = savedList.some(s => item.name === s.name);
return item;
});
}
/**
* Updates list names state with new state
*/
onClick = savedList => {
const { list, updated } = this.state;
this.setState({
list: this.updateListActiveState(list, savedList),
updated: !updated
});
};
/**
* Renders FlatList single item
*/
renderItem = ({ item }) => {
return <NameItem key={item.id} item={item} onClick={this.onClick} />;
};
onLetterPress(letter) {
const { letterIndexes } = this.state;
this.setState({ letter });
try {
this.flatListRef.scrollToIndex({
animated: true,
index: letterIndexes[letter]
});
} catch (e) {}
}
getItemLayout = (data, index) => {
return { length: ROW_HEIGHT, offset: ROW_HEIGHT * index, index };
};
handleScroll = event => {
const { letterIndexes, letterIndexesValues } = this.state;
const y = event.nativeEvent.contentOffset.y + ROW_HEIGHT;
const index = parseInt(y / ROW_HEIGHT);
let letter = "A";
let cnt = 0;
for (const key in letterIndexes) {
if (letterIndexes.hasOwnProperty(key)) {
const startIndex = letterIndexes[key];
const endIndex =
letterIndexesValues[cnt + 1] !== undefined
? letterIndexesValues[cnt + 1]
: startIndex;
if (startIndex <= index && index <= endIndex) {
letter = key;
break;
}
}
cnt += 1;
}
if (letter !== this.state.letter) {
this.setState({ letter });
}
};
render() {
const { list, letterIndexes, alphabet, savedList, updated } = this.state;
const {
alphabetContainer,
container,
letter,
letterActive,
letterLast
} = styles;
console.log(updated);
return (
<View className={container}>
<FlatList
data={list}
keyExtractor={item => item.id}
renderItem={this.renderItem}
getItemLayout={this.getItemLayout}
initialNumToRender={20}
onScrollEndDrag={this.handleScroll}
extraData={updated}
ref={ref => {
this.flatListRef = ref;
}}
/>
<ScrollView
style={alphabetContainer}
showsHorizontalScrollIndicator={false}
>
<View>
{alphabet.map((l, i) => {
const active = l === this.state.letter ? letterActive : "";
const last = i === alphabet.length - 1 ? letterLast : "";
return (
<Text
key={i}
style={[letter, active, last]}
onPress={() => this.onLetterPress(l)}
>
{l}
</Text>
);
})}
</View>
</ScrollView>
</View>
);
}
}

Scrolling issues with FlatList when rows are variable height

I'm using a FlatList where each row can be of different height (and may contain a mix of both text and zero or more images from a remote server).
I cannot use getItemLayout because I don't know the height of each row (nor the previous ones) to be able to calculate.
The problem I'm facing is that I cannot scroll to the end of the list (it jumps back few rows when I try) and I'm having issues when trying to use scrollToIndex (I'm guessing due to the fact I'm missing getItemLayout).
I wrote a sample project to demonstrate the problem:
import React, { Component } from 'react';
import { AppRegistry, StyleSheet, Text, View, Image, FlatList } from 'react-native';
import autobind from 'autobind-decorator';
const items = count => [...Array(count)].map((v, i) => ({
key: i,
index: i,
image: 'https://dummyimage.com/600x' + (((i % 4) + 1) * 50) + '/000/fff',
}));
class RemoteImage extends Component {
constructor(props) {
super(props);
this.state = {
style: { flex: 1, height: 0 },
};
}
componentDidMount() {
Image.getSize(this.props.src, (width, height) => {
this.image = { width, height };
this.onLayout();
});
}
#autobind
onLayout(event) {
if (event) {
this.layout = {
width: event.nativeEvent.layout.width,
height: event.nativeEvent.layout.height,
};
}
if (!this.layout || !this.image || !this.image.width)
return;
this.setState({
style: {
flex: 1,
height: Math.min(this.image.height,
Math.floor(this.layout.width * this.image.height / this.image.width)),
},
});
}
render() {
return (
<Image
onLayout={this.onLayout}
source={{ uri: this.props.src }}
style={this.state.style}
resizeMode='contain'
/>
);
}
}
class Row extends Component {
#autobind
onLayout({ nativeEvent }) {
let { index, item, onItemLayout } = this.props;
let height = Math.max(nativeEvent.layout.height, item.height || 0);
if (height != item.height)
onItemLayout(index, { height });
}
render() {
let { index, image } = this.props.item;
return (
<View style={[styles.row, this.props.style]}>
<Text>Header {index}</Text>
<RemoteImage src = { image } />
<Text>Footer {index}</Text>
</View>
);
}
}
export default class FlatListTest extends Component {
constructor(props) {
super(props);
this.state = { items: items(50) };
}
#autobind
renderItem({ item, index }) {
return <Row
item={item}
style={index&1 && styles.row_alternate || null}
onItemLayout={this.onItemLayout}
/>;
}
#autobind
onItemLayout(index, props) {
let items = [...this.state.items];
let item = { ...items[index], ...props };
items[index] = { ...item, key: [item.height, item.index].join('_') };
this.setState({ items });
}
render() {
return (
<FlatList
ref={ref => this.list = ref}
data={this.state.items}
renderItem={this.renderItem}
/>
);
}
}
const styles = StyleSheet.create({
row: {
padding: 5,
},
row_alternate: {
backgroundColor: '#bbbbbb',
},
});
AppRegistry.registerComponent('FlatListTest', () => FlatListTest);
Use scrollToOffset() instead:
export default class List extends React.PureComponent {
// Gets the total height of the elements that come before
// element with passed index
getOffsetByIndex(index) {
let offset = 0;
for (let i = 0; i < index; i += 1) {
const elementLayout = this._layouts[i];
if (elementLayout && elementLayout.height) {
offset += this._layouts[i].height;
}
}
return offset;
}
// Gets the comment object and if it is a comment
// is in the list, then scrolls to it
scrollToComment(comment) {
const { list } = this.props;
const commentIndex = list.findIndex(({ id }) => id === comment.id);
if (commentIndex !== -1) {
const offset = this.getOffsetByIndex(commentIndex);
this._flatList.current.scrollToOffset({ offset, animated: true });
}
}
// Fill the list of objects with element sizes
addToLayoutsMap(layout, index) {
this._layouts[index] = layout;
}
render() {
const { list } = this.props;
return (
<FlatList
data={list}
keyExtractor={item => item.id}
renderItem={({ item, index }) => {
return (
<View
onLayout={({ nativeEvent: { layout } }) => {
this.addToLayoutsMap(layout, index);
}}
>
<Comment id={item.id} />
</View>
);
}}
ref={this._flatList}
/>
);
}
}
When rendering, I get the size of each element of the list and write it into an array:
onLayout={({ nativeEvent: { layout } }) => this._layouts[index] = layout}
When it is necessary to scroll the screen to the element, I summarize the heights of all the elements in front of it and get the amount to which to scroll the screen (getOffsetByIndex method).
I use the scrollToOffset method:
this._flatList.current.scrollToOffset({ offset, animated: true });
(this._flatList is ref of FlatList)
So what I think you can do and what you already have the outlets for is to store a collection by the index of the rows layouts onLayout. You'll want to store the attributes that's returned by getItemLayout: {length: number, offset: number, index: number}.
Then when you implement getItemLayout which passes an index you can return the layout that you've stored. This should resolve the issues with scrollToIndex. Haven't tested this, but this seems like the right approach.
Have you tried scrollToEnd?
http://facebook.github.io/react-native/docs/flatlist.html#scrolltoend
As the documentation states, it may be janky without getItemLayout but for me it does work without it
I did not find any way to use getItemLayout when the rows have variable heights , So you can not use initialScrollIndex .
But I have a solution that may be a bit slow:
You can use scrollToIndex , but when your item is rendered . So you need initialNumToRender .
You have to wait for the item to be rendered and after use scrollToIndex so you can not use scrollToIndex in componentDidMount .
The only solution that comes to my mind is using scrollToIndex in onViewableItemsChanged . Take note of the example below :
In this example, we want to go to item this.props.index as soon as this component is run
constructor(props){
this.goToIndex = true;
}
render() {
return (
<FlatList
ref={component => {this.myFlatList = component;}}
data={data}
renderItem={({item})=>this._renderItem(item)}
keyExtractor={(item,index)=>index.toString()}
initialNumToRender={this.props.index+1}
onViewableItemsChanged={({ viewableItems }) => {
if (this.goToIndex){
this.goToIndex = false;
setTimeout(() => { this.myFlatList.scrollToIndex({index:this.props.index}); }, 10);
}
}}
/>
);
}
You can use onScrollToIndexFailed to avoid getItemLayout
onScrollToIndexFailed={info => {
const wait = new Promise(resolve => setTimeout(resolve, 100));
wait.then(() => {
refContainer.current?.scrollToIndex({
index: pinPosition || 0,
animated: true
});
});
}}

React-Native pass Textinputvalue to other js

i'm a very newbie to react-native, so sry for this kind of question.
I have to implement a app with that i can log into our website. More details later.
First problem:
LoginScreen.js
var Animated = require('Animated');
var Dimensions = require('Dimensions');
var Image = require('Image');
var React = require('React');
var StatusBar = require('StatusBar');
var StyleSheet = require('StyleSheet');
var View = require('View');
var {
Text
} = require('OnTrackText');
var LoginButton = require('../common/LoginButton');
var TouchableOpacity = require('TouchableOpacity');
var TextInput = require('TextInput');
var {
skipLogin
} = require('../actions');
var {
connect
} = require('react-redux');
class LoginScreen extends React.Component {
state = {
anim: new Animated.Value(0),
name: '',
password: ''
};
componentDidMount() {
Animated.timing(this.state.anim, {
toValue: 3000,
duration: 3000
}).start();
}
render() {
return ( < Image style = {
styles.container
}
source = {
require('./img/login-background.png')
} >
< StatusBar barStyle = "default" / >
< TouchableOpacity accessibilityLabel = "Skip login"
accessibilityTraits = "button"
style = {
styles.skip
}
onPress = {
() => this.props.dispatch(skipLogin())
} >
< Animated.Image style = {
this.fadeIn(2800)
}
source = {
require('./img/x.png')
}
/>
</TouchableOpacity >
< View style = {
styles.section
} >
< Animated.Image style = {
this.fadeIn(0)
}
source = {
require('./img/ontrack-logo#3x.png')
}
/>
</View >
< View style = {
styles.section
} >
< Animated.Text style = {
[styles.h1, this.fadeIn(700, -20)]
} >
Willkommen zur < /Animated.Text>
<Animated.Text style={[styles.h1, {marginTop: -10}, this.fadeIn(700, 20)]}>
OnTrack App
</Animated.Text >
< /View>
<View style={styles.section}>
<TextInput
style={styles.input}
onChangeText={(text) => this.setState({ name: text }) }
value={this.state.name}
placeholder={"Benutzername"}
/ >
< TextInput style = {
styles.input
}
onChangeText = {
(text) => this.setState({
password: text
})
}
value = {
this.state.password
}
secureTextEntry = {
true
}
placeholder = {
"Password"
}
/>
</View >
< Animated.View style = {
[styles.section, styles.last, this.fadeIn(2500, 20)]
} >
< LoginButton name = {
this.state.name
}
password = {
this.state.password
}
source = "First screen" / >
< /Animated.View>
</Image >
);
}
fadeIn(delay, from = 0) {
....
}
const scale = Dimensions.get('window').width / 375;
var styles = StyleSheet.create({
....
}
});
module.exports = connect()(LoginScreen);
As you can see i would like to enter the name and password into the textinput.
Than
the LoginButton.js
'use strict';
const React = require('react');
const {StyleSheet} = require('react-native');
const { logInToWeb } = require('../actions');
const {connect} = require('react-redux');
class LoginButton extends React.Component {
props: {
style: any;
source?: string; // For Analytics
dispatch: (action: any) => Promise;
onLoggedIn: ?() => void;
};
state: {
isLoading: boolean;
};
_isMounted: boolean;
constructor() {
super();
this.state = { isLoading: false };
}
componentDidMount() {
this._isMounted = true;
}
componentWillUnmount() {
this._isMounted = false;
}
render() {
if (this.state.isLoading) {
return (
<OnTrackButton
style={[styles.button, this.props.style]}
caption="Please wait..."
onPress={() => {}}
/>
);
}
return (
<OnTrackButton
style={[styles.button, this.props.style]}
// icon={require('../login/img/f-logo.png')}
caption="Login to OnTrack"
// onPress={this.props.onpress}
onPress={() => this.logIn()}
/>
);
}
async logIn() {
const {dispatch, onLoggedIn, name, password} = this.props;
this.setState({isLoading: true});
try {
await Promise.race([
dispatch(logInToWeb(name,password)),
timeout(15000),
]);
} catch (e) {
const message = e.message || e;
if (message !== 'Timed out' && message !== 'Canceled by user') {
alert(message);
console.warn(e);
}
return;
} finally {
this._isMounted && this.setState({isLoading: false});
}
onLoggedIn && onLoggedIn();
}
}
async function timeout(ms: number): Promise {
return new Promise((resolve, reject) => {
setTimeout(() => reject(new Error('Timed out')), ms);
});
}
var styles = StyleSheet.create({
...
});
module.exports = connect()(LoginButton);
Than
the dispatch(logInToWeb)method in ./action/login.js looks like this:
'use strict';
import type { Action, ThunkAction } from './types';
const Parse = require('parse/react-native');
const {Platform} = require('react-native');
const Alert = require('Alert');
function logInToWeb(data): ThunkAction {
const {name, password} = data
Alert.alert(
`Hi, ${name} & ${password}`,
'möchten Sie sich ausloggen?',
[
{ text: 'Abbruch' },
{ text: 'Ausloggen' },
]
)
}
function skipLogin(): Action {
return {
type: 'SKIPPED_LOGIN',
};
}
function logOut(): ThunkAction {
...
}
function logOutWithPrompt(): ThunkAction {
....
}
module.exports = {logInToWeb, skipLogin, logOut, logOutWithPrompt};
So the Question is:
how can i pass the value of the Textinput from the LoginScreen.js on ButtonClick To the logInToWeb-Method in the login.js
How can i get the name and password in the alert that i called in login.js
Thats it. Later i will ask more about bearer-auth and loggin to server :)
I think what you're asking is how to send the name and password to your logIn() method? Maybe something like this would work:
// Login Button
<LoginButton
name={this.state.name}
password={this.state.password}
source="First screen" />
// Login method
async logIn() {
const {dispatch, onLoggedIn, name, password} = this.props;
this.setState({isLoading: true});
try {
await Promise.race([
dispatch(logInToWebk({name, password})),
timeout(15000),
]);
}
}
then
function logInToWebk(data): ThunkAction {
const { name, password } = data
// do something with name + password
}