React Native ref is undefined until page reload - react-native

I am using refs to animate a View, but the refs are throwing an undefined error. However, if I comment-out my attempt to access the ref, load the page, un-comment the code, & reload the page, the animation works fine.
My code:
export const AccountScreen = ({ navigation }) => {
return (
<SafeAreaView style={{ backgroundColor:'#FFFFFF', flex: 1 }}>
<Animatable.View style={styles.container} ref={ref => {this.containerRef = ref}}>
</Animatable.View>
</SafeAreaView>
)
};
launchOpenAnimation()
function launchOpenAnimation () {
this.containerRef.animate(appearContainerAnimation); //If I comment this out, reload, and un-comment, it works
}
What can I do to fix this? It appears that the ref isn't defined at the time that my launchOpenAnimation() is executed, but that it is defined after, thus resulting in the code working if I comment it out, reload, un-comment, and reload again.

First thing first, your question is messed up between calls component and functional component, cuz there is no this in functional component. I'll convert them to function component using useRef and useEffect
In the first run this.containerRef have not be assign to ref of Animatable yet.
Try this
export const AccountScreen = ({ navigation }) => {
const animateRef = useRef()
useEffect(() =>{
launchOpenAnimation()
function launchOpenAnimation () {
// add ? to make sure animate is called only when this.containerRef exists
containerRef?.current.animate(appearContainerAnimation);
}
},[])
return (
<SafeAreaView style={{ backgroundColor:'#FFFFFF', flex: 1 }}>
<Animatable.View style={styles.container} ref={ref => {
ref.animate(appearContainerAnimation); // add this if you need to run when initial render run
animateRef.current = ref
}}>
</Animatable.View>
</SafeAreaView>
)
};

Related

Dimesnion from React/native not working properly on Android device

I am trying to create an Instagram like reel functionality in react-native app.
I want to display a video element on entire screen available space.
For the purpose of it I am using a FlatList.
This code doesn't work on every device.
'const HomeScreen = ({navigation}) => {
const dataArray=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19];
const renderItem=({item,index})=>{
return(
<View style={[{height:Dimesnions.get('window').height-bottomtabBarHeight,borderBottomColor:'black'},]}>
<Text>{item}</Text>
</View>
)
}
return (
<SafeAreaView style={styles.container}>
<StatusBar />
<FlatList
data={dataArray}
renderItem={renderItem}
pagingEnabled
decelerationRate={'fast'}
/>
</SafeAreaView>
)
}
export default HomeScreen
const styles = StyleSheet.create({
container:{
flex:1,
}
})`
work for me
import dimansion from react native
const {height,width} = dimansion.get('window')
const Myfun=()=>{
return(
<View style={{flex:1}}>
<View style={{height:height}}></View>
</View>
)
}
actually what you need to do is make a state variable and assign dimension's value in it and then in useEffect make a listener which would be triggered every time your device rotates or dimensions of device gets changed
example below:
const [dim, setDim] = useState(Dimensions.get('screen');
useEffect(() => {
const subscription = Dimensions.addEventListener(
'change',
({ screen}) => {
setDim({ screen});
},
);
return () => subscription?.remove(); });
you can use dim.height for height and dim.width for width in your desired component
PS. I have made a complete and comprehensive tutorials regarding Dimensions API if you want to check it out then link is below(Tutorial is in Urdu/Hindi)
https://www.youtube.com/watch?v=mISuyh_8-Cs&t=605s&ab_channel=LearnByDill
I think it is because of typo which you used Dimesnions instead of Dimensions. You can get height or width of device as below:
const ScreenWidth = Dimensions.get('window').width;
const ScreenHeight = Dimensions.get('window').height;

react native textinput lost focus after 1 char type

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

useEffect not working in custom drawer component without refresh

So I am using react-navigation 5 and I have a custom drawer component for my app. I want to display the name of the logged-in user in the drawer for which I am using a state variable and I am updating the state from firestore. I am calling a function in useEffect which accesses firestore and gets the name of the user. But I think the useEffect is not working without refresh because unless I save the project and refresh the application the state is not getting updated in the application and I cannot see the name of the user without refreshing but it is visible after a refresh. Any ideas why this is happening? Any help would be appreciated. Thank you.
Custom drawer
export default function CustomDrawer(props) {
const paperTheme = useTheme();
const [name,setName]=useState('');
useEffect(() => {
doStuff();
}, []);
const doStuff = async () => {
var phone=global.phone;
await firestore().collection("Users").where('Phone Number', '==', phone).get().then(querySnapshot=>{
querySnapshot.forEach(documentSnapshot => {
console.log("in drawer");
console.log(documentSnapshot.data());
setName(documentSnapshot.data().Name);
})
})
};
return(
<View style={{flex:1}}>
<DrawerContentScrollView {...props}>
<View style={styles.drawerContent}>
<View style={styles.userInfoSection}>
<View style={{flexDirection:'row',marginTop: 15}}>
<Avatar.Image
source={{
uri: ''
}}
size={50}
/>
<View style={{marginLeft:15, flexDirection:'column'}}>
<Title style={styles.title}>{name}</Title>
</View>
</View>
</View>
</View>
</DrawerContentScrollView>
</View>
);
}
Looks like you have doStuff function defined outside the useEffects.
Either you need to put it inside useEffects or add it in dependency list
useEffect(() => {
doStuff();
}, [doStuff]);

React native updates state "on its own"

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.

How can I Refresh Web View in React Native?

I have a webview as tab A and a todolist flatlist on tab B. If the user adds an entry to the flatlist on tab B, i want the tab A webview to refresh.
I couldn't find any .refresh() or reload() methods on the webview control https://facebook.github.io/react-native/docs/webview.html
Any ideas how to accomplish this?
You can set a key to the webview
key={this.state.key}
and then you can reload it by updating the state
this.setState({ key: this.state.key + 1 });
Well I reload WebView by doing following:
render() {
let WebViewRef;
return (
<View style={Style1.container}>
<WebView
ref={WEBVIEW_REF => (WebViewRef = WEBVIEW_REF)}
source={{ uri: this.state.site }}
renderLoading={this.ActivityIndicatorLoadingView}
startInLoadingState={true}
/>
<Button title="Reload Me!" onpress={() => { WebViewRef && WebViewRef.reload(); }} />
</View>
)
}
In this code I Declare Reference Variable WebViewRef first then assign this to WebView as ref={WEBVIEW_REF => (WebViewRef = WEBVIEW_REF)} and then call this reference for reload() as ()=>{ WebViewRef && WebViewRef.reload();}
The react-native-community/react-native-webview component has a .reload() method on the ref.
const webViewRef = useRef();
// ...
return (
<WebView ref={(ref) => webViewRef.current = ref} ... />
)
// ...
You can then use the following to reload:
webViewRef.current.reload();
I ended up using a dummy query parameter to signal a refresh of the web view:
In Tab B, I dispatch a change which changes "latestItemId" in the global state.
In Tab A, I use mapStateToProps which maps to <WebView source={{uri:URL?latestItemId=${latestItemId}}} /> in the render method. This causes it to think it's a new url and reload it.
Reload was not working on my end.
If you want to refresh on focus change you can use the hook useFocusEffect of react navigation and in the unmount clean the URL used in the webview. Then in the initialize you need to set that again. Maybe using a a useState.
useFocusEffect(
useCallback(() => {
setUrl(url!);
return () => {
setUrl(undefined);
};
}, [url]),
);
In my case I have source={{html}} so refresh() won't work in that situation. However, in my case I am still able to inject javascript so I can set some properties specifically document.styles.body.color to match dark and light mode.
const fg = colorScheme === "light" ? "black" : "white";
const webViewRef = createRef<WebView>();
useEffect(() => {
webViewRef.current?.injectJavaScript(`
document.body.style.color='${fg}';
true
`);
}, [fg]);
...
<WebView
ref={webViewRef}
originWhitelist={["*"]}
style={{
height: 200,
backgroundColor: "transparent",
}}
onMessage={() => {}}
javaScriptEnabled={true}
injectedJavaScript={`
document.body.style.color='${fg}';
true;`}
source={{ html }}
/>