setState() block the UI when two setState() run - reactjs

Here is my componentDidMount() method :
componentDidMount() {
const subscription = accelerometer.subscribe(({ x, y, z, timestamp }) => {
x = Math.trunc(x*100);
this.setState({x})
});
}
In above method, every 100 millisecond state is changing. I used that state in my render() method as below :
render() {
const animatedImageStyle = StyleSheet.flatten([
styles.captureButton,
{
transform: [{rotateZ:this.state.x + 'deg'}]
}
])
return (
<SideMenu
menu={leftMenu}
isOpen={this.state.isOpenLeftMenu}
menuPosition={'left'}
bounceBackOnOverdraw={false}
onChange={(isOpenLeftMenu) => this.updateLeftMenuState(isOpenLeftMenu)}
>
<View>
<TouchableOpacity
activeOpacity={0.5}
onPress={(this.state.recordingMode == 'camera')?() => this.takePicture():() => this.toggleRecording()}
>
<Image
source={require('../assets/imgs/forRotate.png')}
style={animatedImageStyle}
/>
</TouchableOpacity>
</View>
</SideMenu>
)
}
Now, the problem is that when I trying to open sidemenu, it is not opening, I mean it opening but hanging too much. My whole app hanging too much.
I think that's because of below method :
updateLeftMenuState(isMenuOpen) {
this.setState({
isOpenLeftMenu:isMenuOpen
})
}
Notice that I am updating another state called isOpenLeftMenu, which may blocked during I update state x.
Can anyone tell me what't going wrong here ?

you can move the animation view in a separate component along with subscription logic. So the state update of that component won't affect the SideMenu component.

Related

Encountering stale state when trying to pass state to sibling component

I'm building a simple to do list, and the code I'm trying to execute here is that when each task is clicked ('Task' component), it updates the parent state ('taskEdit') with the current task object. When taskEdit changes, a useEffect would run to re-render the whole component so that the parent can pass that object to another component ('EditTask' component) which is a modal that allows the user to edit the task, AND setEditModalVisible(true) would be run so that the Edit Task modal can be shown.
However, the issue is that when I do this, the 'EditTask' component renders with the previous state.
I.e. I click task A, and then Edit Task renders with an empty task object ({}), i.e. the initial state.
Then I click task B, and then Edit Task renders with the task A object.
Code is below. Any help is much appreciated!
Parent Component - taskEdit state is initialized here, and setTaskEdit is passed to the child Task component. When it's updated, I intend to pass the updated taskEdit state to the Edit Task component
When I press the task, in addition to updating the task object, it would update taskEditTrigger to true, which would allow the setEditModalVisible to continue - then I'd set it to false so that any further renders wouldn't trigger editModalVisible
const [editModalVisible, setEditModalVisible] = useState(false);
const [taskEditTrigger, setTaskEditTrigger] = useState(false);
const [taskEdit, setTaskEdit] = useState({});
useEffect(() => {
if (taskEditTrigger) {
setEditModalVisible(true)
setTaskEditTrigger(false)
} else {
setTaskEditTrigger(false)
}
}, [taskEdit]);
const renderItem = ({ item }) => (
<Task
item={item}
setTaskEdit={setTaskEdit}
setTaskEditTrigger={setTaskEditTrigger}
/>
);
return (
<View>
<FlatList
data={toDos}
renderItem={renderItem}
/>
<EditTask
item={taskEdit}
editModalVisible={editModalVisible}
setEditModalVisible={setEditModalVisible}
/>
</View>
Task Component - on pressing the task component, I intended to update the taskEdit state in the parent component with the current task
const Task = ({ item, setTaskEdit, setTaskEditTrigger })
const onPressTask = () => {
console.log(item.text)
console.log('Task is rendering')
if (item.isComplete) {
setIsTaskCompleteAlertVisible(true)
} else {
setTaskEdit(item)
setTaskEditTrigger(true)
}
};
return (
<View>
<TouchableOpacity onPress={onPressTask}>
<Text>{item.text}</Text>
</TouchableOpacity>
</View>
Export default memo(Task, (prevProps, nextProps) => {
If (prevProps.item.id === nextProps.item.id &&
prevProps.item.text === nextProps.item.text)
{
return true
}
return false
})
**Edit Task Component - I then intended to pass the item={taskEdit} into the Edit Task component to show the relevant task information **
function EditTask({
item, editModalVisible, setEditModalVisible}) {
console.log(item)
const [editingText, setEditingText] = useState(item.text);
const onPressEditTask = () => {
setEditModalVisible(false)
setTaskEdit({})
};
return (
<Modal visible={editModalVisible}>
<TouchableOpacity onPress={onPressEditTask}>
<Text>{editingText}</Text>
</TouchableOpacity>
</Modal>
Thanks to all the comments - found the reason for this issue.
I was able to lift the lift the taskEdit state from the Task component and pass it down from the parent component to EditTask successfully.
However, in EditTask I was updating the editingText state using the item.text property - and that was what was stale. When I updated the editingText state directly in Task and passed it by the same way to the Edit Task component, it was no longer stale.

Why is my React Native component not re-rendering on state update?

I'm struggling with this React-Native component for a few days now. You should probably know that React-Native is kind of new to me so... sorry if the solution is obvious to you.
I'm using react-native-maps and I have several markers on my map. Each one of them has some data stored in my state and I want the callout to display a piece of this state on press.
Here are my states :
const [markersDetails, setMarkersDetails] = useState([]);
const [activeMarker, setActiveMarker] = useState({});
My activeMarker is updated by this function :
const markerSearch = (markerId) => {
let stockMarker = markersDetails.find((singleMarker) => {
return Number(singleMarker.idMarker) === markerId;
});
console.log("Stock ", stockMarker);
setActiveMarker(stockMarker);
console.log("State ", activeMarker);
};
And this function is called, inside my return, with the onPress of any marker :
<Marker
key={Number(marker.index)}
coordinate={{
latitude: Number(marker.latitude),
longitude: Number(marker.longitude),
}}
pinColor="blue"
onPress={() => {
markerSearch(Number(marker.index));
}}
>
{activeMarker !== {} && activeMarker.markerName && (
<Callout>
<View>
<Text>{activeMarker.markerName}</Text>
</View>
</Callout>
)}
</Marker>
But whenever I press on a marker, the callout opens immediatly while my state is not yet updated. So the text within the callout refers either to the previous marker or is empty (if it's the first marker I press on).
I've checked with console.log and my state is clearly updated but it takes a little bit more time. And I don't know why my callout is not re-rendering when this state is updating.
I've tried a ton of things to make this works but I can't figure this out...
Try doing something like that:
You can extract the section to a new component
Then inside this use the useEffect hook
export default function CalloutComponent({activeMarker}) {
const [markerName, setMarkerName] = useState('')
useEffect(() => {
setMarkerName(activeMarker?.markerName)
}, [activeMarker?.markerName])
if(!!markerName) return null
return (
<Callout>
<View>
<Text>{markerName}</Text>
</View>
</Callout>
)
}
And use this new component in your Main view
<Marker
...
>
<CalloutComponent activeMarker={activeMarker}/>
</Marker>

Only show redo search button when user has completely moved to a new area

How can i only show the redo button only when the user has changed region?In my code right now it keeps flickering on and off as the region changes, not sure what is missing.
Code:
app.js
onregionchange() {
this.setState({redosearch: !this.state.redosearch })
}
render() {
const Showredo = ({redosearch }) => redosearch ? <View><Text> redo now <Text></View> : null
return(
<View>
{this.state.redosearch ? <ShowRedo redosearch={this.state.redosearch }/> : null}
<View>
<MapView
ref={(map) => (this.map = map)}
style={styles.map}
onRegionChange={this.onregionchange}
>
</MapView>
)
}
There are a few things I notice may be causing an issue here. First, it looks like you may be double checking the state value; inside the Showredo element property and again inside the return value. second, The code has two open View tags and two Text tags with no close. Third, I can't see if the function onregionchange is bound or not. And finally, you're returning two elements in the render function (or actually missing two view closing tags at the end)
try to change your code to this which should correct all of those:
onregionchange = () => {
this.setState({redosearch: !this.state.redosearch })
}
render() {
const { redosearch } = this.state;
return([
<View key="a_key_for_element_1">
{redosearch ? <View><Text> redo now </Text></View> : null}
</View>,
<MapView
key="a_key_for_element_2"
ref={(map) => (this.map = map)}
style={styles.map}
onRegionChange={this.onregionchange}
>
</MapView>]
);
}

Calling a non-returning JSX function inside of render() in React

I got a quick question on calling a function inside of the render method or some potential way to update a method when a user decides to go to the next screen via clicking "Send".
My goal is to change the old created "selectedExchange" to an updated "selectedExchange" as the user taps the "Send" arrow to go to the next screen.
// On Send Functionality
onSend = () => {
const { exchanges } = this.props
// Testing to see if hard coded values get sought out response
// Hard code values work
// this.props.selectExchange updates the selectedExchange
this.props.selectExchange(exchanges.exchanges[0])
this.props.navigation.navigate('selectRecipient', { transition: 'slideToLeft' })
//return ( exchange )
}
// Additional helper method, able to replace the above "this.props.selectExchange()"
// if necessary
updateExchange = (value, exchange) => {
this.props.selectExchange(exchange)
this.setState({ selectedDisplayName: value })
// Render call
render() {
const { navigation, account, exchanges } = this.props
const { selectedExchange } = exchanges
const { status, symbolPriceTicker, dayChangeTicker } = selectedExchange
const loading = status === 'pending' && !symbolPriceTicker && !dayChangeTicker
const avatar = account.profile_img_url ? { uri: account.profile_img_url } : IMAGES.AVATAR
return (
<View style={globalStyles.ROOT}>
<Loading loading={loading} />
<Image style={globalStyles.backgroundImg} source={IMAGES.BACKGROUND} />
<TopArea navigation={navigation} avatar={avatar} />
<ScrollView>
{
exchanges.exchanges.length > 0 &&
exchanges.exchanges.map(exchange => (
<View style={screenStyles.container}>
<ServiceInfo
account={account}
balances={exchange.balances}
displayName={exchange.label}
symbolPriceTicker={symbolPriceTicker}
// onRequest={this.onRequest}
onSend={this.onSend}
/>
...
...
What I've tried:
Passing an exchange prop via "onSend={this.onSend(exchange)}" to see if I could pass the necessary object that would be used to update selectedExchange. This didn't work as it required me to return something from onSend.
Directly calling the helper method in the JSX between various views. Also didn't work as it required I returned some form of JSX.
Not sure how else I could tackle this. Thanks for any help!

How to properly update/re-render a component that is not a child React Native?

I'm using a react-navigation. More specifically, I have a materialTabNavigator nested inside of a drawerNavigator. Each tab is in itself a stackNavigator. I have a button in homeScreen, that navigates to makePost.js. There I take in information and store it to Async storage using a simple wrapper.
In Posts.js there's a FlatList displaying each post as a component. The data for the FlatList is initially set correctly after making a request from Async Storage. The problem is that this only happens when the app is first opened. I have tried many different approaches to solve this. The only way so far I've found is to continuously setState in ComponentDidUpdate() in Posts.js. Obviously this is problematic, because it re-renders constantly. I can set a flag to stop is from rendering, but then it will not re-render again.
Ultimately, what I'd like to happen is that when I hit the user is done entering their information and is ready to make a post, they hit the button in makePost.js, and the data in the FlatList of Posts.js is update.
I've tried to pass parameters using navigation, does not work, parameters get lost somewhere, probably because of the nested navigators.
I could really used some guidance on the proper way to accomplish this.
( Navigators; not sure why this is forcing to one line )
---drawer
--tabNav
-home
homeScreen.js
makePost.js
-posts
posts.js
-messages
--drawer1
--drawer2
//Posts.js
export default class Posts extends React.Component {
state = {
rows: [
{id: 0, text: "dog"},
],
}
componentDidMount() {
this.loadState();
}
loadState = () => {
var value = store.get('posts').then((res => {
if (res === null) {
res = [{id: 0, text: "default"}]
} else {
res = res
}
this.setState({rows: res})
}))
}
componentDidUpdate() {
this.loadState();
}
renderItem = ({item}) => {
return (
<BoardTab style={styles.row} />
)}
render() {
return (
<View style={styles.view}>
<FlatList
ListFooterComponent={this.renderFooter}
style={styles.container}
data={this.state.rows}
renderItem={this.renderItem}
keyExtractor={extractKey}
>
</FlatList>
<BoardScreenFooter />
</View>
);
}
And Posts.js button looks like this:
<TouchableOpacity
onPress={ () => {
this._onPressButton
this.storeFunc(this.state.newPost)
const retval = this.state.rows
this.props.navigation.navigate('Board',
{rowsID: retval});
}
}>
<Icon
reverse
name='md-camera'
type='ionicon'
color='green'
size={12}
/>
</TouchableOpacity>
storeFunc(newObj) {
newObj.id = newObj.id + 1
store.push('posts', newObj)
store.get('posts').then((res) => {
this.setState({rows: res})
})
}
Rapidly, i would say: use Redux. It alloq you to have global state in your app, which mean you can access the state anywhere (And also set them anywhere)
When opening the app, you get the data from the AsyncStore into the Redux store. You listen to the redux state (Which will be a props in your component) and display your list. When modifying your list in the other tab, you need to do 2 things:
Store the new data in the AsyncStorage
Update the state in the redux store. Since Posts.js will be listening at the redux store (as a props), it will re-render each time your data will change
A simple way to re-render a React-Navigation screen view on navigating to it:
All credit goes to Andrei Pfeiffer, Jul 2018, in his article: "Handle Tab changes in React Navigation v2" https://itnext.io/handle-tab-changes-in-react-navigation-v2-faeadc2f2ffe
I will reiterate it here in case the above link goes dead.
Simply add a NavigationEvents component to your render function with the desired listener prop:
render() {
return (
<View style={styles.view}>
<NavigationEvents
onWillFocus={payload => {
console.log("will focus", payload);
this.loadState();
}}
/>
<FlatList
ListFooterComponent={this.renderFooter}
style={styles.container}
data={this.state.rows}
renderItem={this.renderItem}
keyExtractor={extractKey}
>
</FlatList>
<PostScreenFooter />
</View>
);
}

Resources