I have two screens, one list (Flatlist) and one filter screen where I want to be able to set some filters for the list. the list screen has the states "data" and "usedFilters". When I am switching to the filters screen, the states are set as navigation parameters for react navigation and then passed via navigation.navigate, together with the onChange function, as props to the filter screen. There they are read, and the filters screen class' state is set (usually with passed filters from the list screen, if no valid filters has been passed, some are initialized).
After that the filters can be changed. If that happens, the state of the filter screen gets updated.
If then the apply button is clicked the filter screens' state is passed to the onChange function and via that back to the list screen, the onChange function updates the state "usedFilters" state of the list screen. If the cancel button is pressed null is passed to the onChange function and there is no setState call.
Setting new states for the list screen works perfectly fine. the problem is, that when i press the cancel button (or the back button automatically rendered by react navigation) the changes are kept nevertheless. That only happens if the state has been changed before. So if there has never been applied a change and hence the "usedFitlers" state of the list screen is null, this behavior does not occur. Only if I already made some changes and hence the "usedFitlers" state of the list screen has a valid value which is passed to the filters screen the cancel or go back buttons won't work as expected.
I am using expo-cli 3 and tried on my android smartphone as well as the iOS simulator. Same behavior. I looked into it with chrome dev tools as well but i simply couldn't figure out where the "usedFitlers" state was updated.
I am using react native 0.60 and react navigation 3.11.0
My best guess is that for some reason the two states share the same memory or one is pointer to the other or sth like that. (Had problems like that with python some time ago, not knowing the it uses pointers when assigning variables).
Anyone got an idea?
List Screen:
export default class ListScreen extends React.Component {
state = { data: [], usedFilters: null };
static navigationOptions = ({ navigation }) => {
let data = navigation.getParam('data')
let changefilter = navigation.getParam('changeFilter')
let currfilter = navigation.getParam('currFilter')
return {
headerTitle:
<Text style={Styles.headerTitle}>{strings('List')}</Text>,
headerRight: (
<TouchableOpacity
onPress={() => navigation.navigate('FilterScreen', {
dataset: data, onChange: changefilter, activeFilters:
currfilter })} >
<View paddingRight={16}>
<Icon name="settings" size={24} color=
{Colors.headerTintColor} />
</View>
</TouchableOpacity>
),
};
};
_onChangeFilter = (newFilter) => {
if (newFilter) {
this.setState({ usedFilters: newFilter })
this.props.navigation.setParams({ currFilter: newFilter });
} // added for debugging reasons
else {
this.forceUpdate();
let a = this.state.usedFilters;
}
}
_fetchData() {
this.setState({ data: fakedata.results },
() => this.props.navigation.setParams({ data: fakedata.results,
changeFilter: this._onChangeFilter }));
}
componentDidMount() {
this._fetchData();
}
render() {
return (
<ScrollView>
<FlatList/>
// Just data rendering, no problems here
</ScrollView>
);
}
}
Filter Screen:
export default class FilterScreen extends React.Component {
static navigationOptions = () => {
return {
headerTitle: <Text style={Styles.headerTitle}> {strings('filter')}
</Text>
};
};
state = { currentFilters: null }
_onChange = (filter, idx) => {
let tmp = this.state.currentFilters;
tmp[idx] = filter;
this.setState({ currentFilters: tmp })
}
_initFilterElems() {
const filters = this.props.navigation.getParam('activeFilters');
const dataset = this.props.navigation.getParam('dataset');
let filterA = [];
let filterB = [];
let filterC = [];
if (filters) {
// so some checks
} else {
// init filters
}
const filterElements = [filterA, filterB, filterC];
this.setState({ currentFilters: filterElements })
}
componentDidMount() {
this._initFilterElems()
}
render() {
const onChange = this.props.navigation.getParam('onChange');
return (
<ScrollView style={Styles.screenView}>
<FlatList
data={this.state.currentFilters} // Listeneinträge
keyExtractor={(item, index) => 'key' + index}
renderItem={({ item, index }) => (
<FilterCategory filter={item} name={filterNames[index]}
idx={index} onChange={this._onChange} />
)}
ItemSeparatorComponent={() => <View style=
{Styles.listSeperator} />}
/>
<View style={Layout.twoHorizontalButtons}>
<TouchableOpacity onPress={() => {
onChange(this.state.currentFilters);
this.setState({ currentFilters: null });
this.props.navigation.goBack();
}}>
<View style={Styles.smallButton}>
<Text style={Styles.buttonText}>{strings('apply')} </Text>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={() => {
onChange(null);
this.setState({ currentFilters: null });
this.props.navigation.goBack();
}}>
<View style={Styles.smallButton}>
<Text style={Styles.buttonText}>{strings('cancel')}
</Text>
</View>
</TouchableOpacity>
</View>
</ScrollView >
);
}
}
So when I press the cancel button, null is returned to the _onChangeFilter function of the list screen. This part works, and according to console.log and the debugger, the setState is not called. But if i set a breakpoint within the else part, i can see that this.state.usedFilters has changed.
Ok after a while i figured it out. The problem was that the whole filters list was always just referenced since react native (js) seems to always use references, even when changing sub-parts of the lists.
fixed that by using lodash cloneDeep.
Related
im using navigation to pass between screens.
now,im trying to figure out how can i get a value from second screen to the first screen ?
the user needs to pick a color value from the second screen and return selcted color to the first screen.
this is the code im using .
enter code here
<CustomButton
style={styles.buttonPicker}
darkMode={this.props.darkMode}
title={'pick a color'}
onPress={() => {
this.props.navigation.navigate('ColorPickerScreen', {
onSubmit: (namecolor) => {
console.log('55555555555555', { getNameColor })
},
})
}}
></CustomButton>
enter code here
onSelect = (color) => this.props.navigation.navigate('CreatenewtTipul')
render() {
return (
<Image
style={styles.img}
source={require('../components/icons/color-wheel.png')}
/>
<ColorPicker
colors={this.state.colors}
selectedColor={this.state.selectedColor}
onSelect={this.onSelect}
/>
<Text>Selected Color = {this.state.selectedColor}</Text>
</View>
)
}
}
tnx for any help
arik :)
To pass value from screen A to screen B:
navigation.navigate('ScreenB', {
itemId: 86,
otherParam: 'anything you want here',
});
To access that value in Screen A:
const { itemId, otherParam } = route.params;
Where were route here is part of the screen's props, check the guide here for more info
im not trying to pass a value to the second screen.
im trying to get a value from the second screen to the first screen.
You can pass a function as a callback from the first screen to the second screen in params on call that on your second screen.
function Screen1(props) {
const onSelect = (selectedColor) => {
console.log('selectedColor', selectedColor)
}
const navigateToSecondScreen = () => {
props.navigation.navigate('Screen2', {
onColorSelect: onSelect
})
}
return(
<View>
<TouchableOpacity onPress={navigateToSecondScreen}>
<Text>Go to second screen</Text>
</TouchableOpacity>
</View>
)
}
//Second Screen
function Screen2(props) {
const {onColorSelect} = props.route.params;
return(
<View>
<TouchableOpacity onPress={() => {onColorSelect('color value')}}>
<Text>your other code here</Text>
</TouchableOpacity>
</View>
)
}
The idea is just to call the function which you have passed as a param from Screen1
I have this problem with ios but not with android. It only disturb the add task input the task edit and the list name edit. The input addList(It's the one with "What to do?" on the draw) in the header works fine.
UI drawing
Achitecture of components
I console log my component and I can see it rerender everytime I add a letter in the input field.
I checked on google and follow this:(can we link other website here?) https://www.codegrepper.com/code-examples/javascript/react+native+textinput+lost+focus+after+charter+type
Tried the the first solution with onBlurr and onFocus.
I tried to make a TextInput component for add task.
I even try with my component addList but it didn't solve the problem.
Anyone have faced this problem before? Is there anyway to by pass this?
My code without the import/style look like this:
const TaskList: FunctionComponent<TasksListProps> = ({
addTask,
deleteTask,
toggleTask,
editTaskName,
...props
}) => {
console.log('props', props);
const [nameOfTask, setNameOfTask] = useState('');
console.log('name', nameOfTask);
const textHandler = (enteredName: string) => {
setNameOfTask(enteredName);
};
const handleSubmitTask = () => {
if (nameOfTask === '') {
return;
}
addTask(props.listId, nameOfTask);
setNameOfTask('');
};
return (
<View style={styles.tasksListContainer}>
{props.tasks.map(task => (
<SingleTask
key={task.id}
task={task}
listId={props.listId}
deleteTask={deleteTask}
toggleTask={toggleTask}
editTaskName={editTaskName}
/>
))}
<View style={styles.taskInputContainer}>
<TextInput
style={styles.tasksTextInput}
value={nameOfTask}
onChangeText={textHandler}
placeholder="Write a task to do"
/>
<TouchableOpacity onPress={handleSubmitTask}>
<Image source={require('./Img/add-button.png')} />
</TouchableOpacity>
</View>
</View>
);
};
You can create a HOC and wrap your screen width DismissKeyboard
import { Keyboard } from 'react-native';
const DismissKeyboard = ({ children }) => (
<TouchableWithoutFeedback onPress={() => Keyboard.dismiss()}>
{children}
</TouchableWithoutFeedback>
);
That because Re render.
Try to make the input with the main component of the page to test it.
Then check where the error with re-render
I've seen a couple of close questions, but none that really answered my question. I have the following code in React Native.
# activities add/edit screen
# ...
const [activities, setActivities] = useState([]);
useEffect(() => {
const _setup = async () => {
const temp = await fetch(...); // fetching data from server with await
setActivities(temp);
// building save button
navigation.setOptions({
headerRight: () => (
<TouchableOpacity onPress={() => _submit()}>
<Text style={{ color: '#007AFF', fontSize: 18, }}>
Save
</Text>
</TouchableOpacity>
)
});
setReady(true);
};
_setup();
}, []);
So I build the headerRight button in useEffect to pass it an local _submit function, which looks like this.
const _submit = async () => {
console.log(activities);
try {
// fetch to send data to server
} catch (e) {
showError(e);
}
};
There is a FlatList on this screen which gets dynamically extended based on user interaction. All is well until the user presses the save button in the header. It always loads the activities array from the last hot refresh/render. But the FlatList re-renders ok, the array gets extended just as I want it to be. I tried using "useCallback" on the _submit function and set the dependency to "activities", but still, the header button seems to call the "initial" _submit function. The only thing that helped was to split the useEffect into two separates and one handling the re-render of the button.
// first one to fetch data on initial mound
// ...
useEffect(() => {
// I am using react-navigation and react-native-screens 2.7.0
navigation.setOptions({
headerRight: () => (
<TouchableOpacity onPress={() => _submit()}>
<Text style={{ color: '#007AFF', fontSize: 18, }}>
Save
</Text>
</TouchableOpacity>
)
});
}, [activities]);
It works fine, but somehow feels kind of hacky... I was under the impression that normal functions (like the _submit) would get re-constructed with each re-render, which would be triggered with e.g. a new array-element being pushed to the activities, no? For completion, I add elements to activities like this.
const _addActivity = () => {
const temp = [...activities];
const initActivity = {
title: '', // will be set later via TextInput
startTime: new Date(),
endTime: new Date(),
}
temp.push(initActivity);
setActivities(temp);
}
I am showing some Audio data in Flat-list. Flat-list, I am showing in main class, But, RenderItem calling in separate class. So, Once I tapped particular row item, I am playing audio file. But, I have to change pause to play image. But, When I tried to change it, All images are getting changes.
Bydefault, I am showing all cells images with pause icon.
Also Once user taps on play/pause in audio player, Then I have to change flatlist current playing item row images either play/pause.
I am showing audio player in bottom of the screen. Once user tap on flatlist pause icon, I am playing audio player in bottom of the screen.
I have tired but, All cells images getting changing.
Any suggestions?
Note: We have different UI for Audio player, So, I have created customized UI for player instead of default media component.
Main class.js
selectedAudio = (item, index) => {
if (isConnected) {
if (!isEmpty(audioURL)) {
// console.log('selected audio url is', audioURL);
SoundPlayer.playUrl(audioURL);
this.setState({
paused: false,
currentPosition: 0,
currentTime: 0,
audioSelectedIndex: index,
});
}
} else {
}
}
renderItem = ({ item, indexx }) => (
<Cell
item={item}
onSelected={this.selectedAudio}
index={indexx}
audioSelectedIndex={this.state.audioSelectedIndex}
/>
)
render() {
return (
<View some styles>
<FlatList
style={styles.faltList}
showsVerticalScrollIndicator
data={podcast}
extraData={this.state}
ItemSeparatorComponent={this.separator}
renderItem={this.renderItem}
/>
</View>
);
}
Cell.js
export default class Cell extends PureComponent {
render() {
const { item, indexx, audioSelectedIndex } = this.props;
return (
<View style={styles.flatListCell}>
<View style={styles.containerText}>
<Text style={styles.title}>
{item.title}
</Text>
</View>
</View>
<TouchableWithoutFeedback onPress={this.props.onSelected.bind(this, item)}>
<Image
style={styles.playPause}
source={audioSelectedIndex === indexx ? res.images.play : res.images.pause}
/>
</TouchableWithoutFeedback>
</ImageBackground>
</View>
);
}
}
The issue is that you are destructuring ({ item, indexx }), and renderItem doesn't pass indexx but index. Change indexx to index.
renderPodcastItem = ({ item, index }) => (
<Cell
item={item}
onSelected={this.selectedAudio}
index={index}
audioSelectedIndex={this.state.audioSelectedIndex}
/>
)
Second mistake, you are doing this const { item, indexx, audioSelectedIndex } = this.props; but you are not passing indexx but index to Cell. In Cell component change to.
const { item, index, audioSelectedIndex } = this.props;
Third mistake you are passing this.renderItem to renderItem but the function is undefined.
renderItem={this.renderPodcastItem}
DEMO
I'm having trouble keeping the data in my Flatlist after coming back from another page. My scenario is as follows:
User goes to homepage and scrolls through 20 items
User clicks their profile tab changing page using react-native-router-flux
User clicks the home tab taking them back to the list however the list re-renders and starts from the top.
How can I stop this re-rendering and fetching the same data again?
componentDidMount() {
this.makeRemoteRequest();
}
makeRemoteReuest gets my data from firebase in batches of 5 and sets data: []
data: [...this.state.data, ...results]
I've tried using the below but not sure if this is correct, when i navigate away and back the data re-renders. I want to keep the data so the page will be exactly the same as when it was left.
shouldComponentUpdate(nextProps, nextState) {
if (JSON.stringify(this.state.data) !== JSON.stringify(nextState.data)) {
return true;
}
return false;
}
My flatlist:
<View>
<FlatList
scrollsToTop={false}
ref={(ref) => { this.flatListRef = ref; }}
showsHorizontalScrollIndicator={false}
onScroll={this.handleScroll}
data={this.state.data}
keyExtractor={item => item.key}
ListFooterComponent={this.renderFooter()}
onRefresh={this.handleRefresh}
refreshing={this.state.newRefresh}
onEndReached={this.handleEndRefresh}
onEndReachedThreshold={0.05}
getItemLayout={this.getItemLayout}
renderItem={this.renderItem}
/>
{this.state.refreshAvailable ? this.renderRefreshButton() : null}
</View>
Thanks for any help!
Coded long back for the dumb project, maybe this can help you
The View: used onLayout Prop for getting the y-axis
<ScrollView
ref={(ref) => this.scrollTo = ref}
contentContainerStyle={{margin:5,}}
>
<Card onLayout={(event) => this._findHeight(event.nativeEvent.layout, 'personal')}>
<Personal review={true}/>
</Card>
</ScrollView>
The Function: stored the y-axis; here i have used realm db
_findHeight = (e, name) => {
const {x, y, width, height} = e;
this.realm.write(() => {
this.realm.create('yLocation',{names:name,yaxis:y}) :
});
}
The AutoScroll Method: here i have used scrollTo method from ScrollView you can use any method using their ref
_scrollTo = (y) => {
this.scrollTo.scrollTo({x:0,y:y,animated:true});
}
Note : Call _scrollTo method in componentDidMount