Trying to remove a view index above child count error - react-native

What does this mean?
This happens when I update a list of iterated views something like
<View style={{ flexDirection: 'row', padding: 20, backgroundColor: '#fff' }}>
<Ionicons name={jobIcon} color={theme.iconColor} size={30} />
<Text>{jobService}</Text>
<Text>{jobDate}</Text>
</View>
mapped inside a scrollview.
this error pops up when I modify the array from child scene.
scene1 - is where the ScrollView with job list array of views
sence2 - is where I delete a job and should update scene1 when I do remove a job

If you want the LayoutAnimation to work with ScrollView, replace
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut)
with
LayoutAnimation.configureNext({
duration: 300,
create:
{
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
update:
{
type: LayoutAnimation.Types.easeInEaseOut,
}
});
This works on both Android and iOS without any crashes.

In my case I was using LayoutAnimation for my ScrollView. Inside it a map of Items. When an Item is removed from the list this happens. Not using LayoutAnimation seems to be working fine.

It's happening when you call layout animation when it already in process. iOS will show an warning while Android will explode with this error.
You can use this easy pattern to fix it when you're using LayoutAnimation from same component.
layoutAnimation() {
if (!this.layoutAnimationActive) {
this.layoutAnimationActive = true;
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInOut, () => { this.layoutAnimationActive = false; });
}
}

This happens when a component only has x amount of children, but you are trying to remove a child with index greater than x. Like an index out of bounds exception that is common with arrays. Can cause lots of frustration because often times the child you are trying to remove DOES INFACT EXIST. But might be happening because you are using a third party component that only expects a certain amount of children.
For me, it happened when I put an extra child into air-bnb MapView. I fixed the issue by making this element the child of it's grandparent (it was absolutely positioned so it didn't affect styling).

I encountered this issue when creating a react-native application using Expo.
This error was often getting raised when I was rendering new Markers onto a Map (which was rendered via a MapView component).
What fixed the issue for me was to add a key prop to the Marker component e.g.
<Marker coordinate={coordinate} key={`${coordinate.latitude}_${coordinate.longitude}`} />
Hope this helps anyone who also encounters this issue when dealing with MapView and Marker components!

In my case, I was deleting an item in a virtualized list. I was using redux to manage the data of the list and updated the list with the new data. However, the component for the item that was deleted caused this error.
I solved this by adding a boolean state property on this item component, example "hideItem". My item was a class component but a setState hook could also be used for a functional component. I set this to true after deletion. The post hid with layout animation correctly, and when the page refreshed it no longer rendered the deleted item. Hence, the error went away on android without needing to not use LayoutAnimation.

Evidently, it seems this bug can show its ugly face in various conditions. After a couple of hours of debugging, I found my root cause to be a key prop being passed to a component that didn't require it. I'm still unsure of why this was causing a crash, however, I suspect it has to do with the fact that the component accepts the key as a unique way to identify the view, but was also using the componentWillReceiveProps(props: *) lifecycle method that updated the component's state.

This is likely to happen if you're using some native components, where some ViewManager returns a LayoutShadowNode in createShadowNodeInstance of ViewGroupManager or something extending ReactShadowNode in createShadowNodeInstance of ViewManager on Android, and a RCTShadowView in the shadowView method of RCTViewManager on iOS. But, returns null/nil for some other View in some other ViewManager.
Then, if you combine children of both types in the same parent, and any of the elements without shadowViews/Nodes come before the changing number of elements which do have shadowViews/Nodes, then the indices won't match up, and the RCTUIManager on iOS and NativeViewHierarchyManager on Android will choke and produce these exceptions.
I solved a similar issue in react-native-svg recently, by making all the ViewManagers return values rather than null/nil. https://github.com/facebook/react-native/issues/23350
So, try upgrading react-native-svg to v9.2.4 and the issue might be fixed. Or, try moving the IonIcons to the end of your children.

I had the same issue, because I had an Array.map inside a condition which I was updating using LayoutAnimation.
I fixed it by moving the state variable inside the map function.

I am using vitalized flatlist with swipe left right delete option, and when list is more than 200 items and I use LayoutAnimation then the above problem comes out on arbitrary based, I figured out that deleted item still exist because of LayoutAnimation so you can play around with timings of update using the following code
LayoutAnimation.configureNext({
duration: 300,
create:
{
duration:500
type: LayoutAnimation.Types.easeInEaseOut,
property: LayoutAnimation.Properties.opacity,
},
update:
{
duration: 500,
type: LayoutAnimation.Types.easeInEaseOut,
}
});
this.refs['task'].setNativeProps({
style: {transform: [{translateX: Dimensions.get('window').width}]},
});
one more thing, I am also hiding the item suddenly after it's deletion occurs
here is code snippet
this.props.handleDeleteTask(this.props.item.id);
this.setState({hideItem:true}) // here I am hiding through state function, so the item should disappear right after it's deletion.

Related

ScrollToLocation not working on initial mount

I'd like to add a SectionList to my app such that it renders to a specific section (that isn't the first section in the list). Calling scrollToLocation on componentDidMount does not work; however, adding a button that calls scrollToLocation does. Is there a reason for this?
Could this be due to the SectionList reference (I've tried a few approaches for assigning reference, e.g. variable assignment, function assignment, using createRef, etc.)?
Here is a link to a stripped-down Expo snack to illustrate what I mean: https://snack.expo.io/#bobbymoogs/scrolltolocation-on-componentdidmount.
I found the best solution on this thread is to use onLayout to trigger the scroll:
scrollToInitialPosition = () => {
this.scrollViewRef.scrollTo({ y: 100 });
}
...
<ScrollView
ref={(ref) => { this.scrollViewRef = ref; }}
onLayout={this.scrollToInitialPosition}
/>
Note there are lots of other suggestions using setTimeout, componentDidUpdate, InteractionManager, etc. However using onLayout was the only one that worked for me (and also seems to me to be the cleanest).

v-navigation-drawer drops into a runaway loop on window resize

First, let me say that the v-navigation-drawer works as intended, i.e.:
On clicking the hamburger menu the TOGGLE_DRAWER mutation is committed, and it toggles open/closed, updating the state.
On window resize it opens/closes at a designated breakpoint
So it works.
BUT the window resize does not properly toggle the mutation and I keep getting a Vuex mutation error when I resize the window:
I understand why I'm getting this error - the $store.state.ui.drawer is being modified outside of the mutator (it's the v-navigation-drawer's v-model):
<v-navigation-drawer
v-model="$store.state.ui.drawer"
app
clipped
>
I get it's bad form to bind the state to the v-model. But when I try to make a drawer computed property with a get() and set() method that properly gets/commits a mutation, the browser crashes (presumably because the set method triggers an endless loop of commits toggling drawer true/false into infinity):
computed: {
drawer: {
get () {
return this.$store.state.ui.drawer
},
set () {
this.$store.commit('TOGGLE_DRAWER') // <--crashes the browser
}
}
}
I've searched endlessly for a solution to this problem. It's bugging me even though it visually appears to be working.
I've considered running the v-navigation-drawer in stateless mode and handling all the window resize events and state updates manually. I've also considered disabling 'Strict' mode in Vuex (which would hide the errors). But the former is a lot more complexity and the latter is a bandaid that costs me debugging insight in development.
This sounds like a perfect candidate for Lodash's debounce function. If you need to stick with using setter/getter while applying this effect, have a look at this post; otherwise, this one for sequential event subscription on any of the lifecycle hooks.
After spending some time with this, I think I have a solution. Wanted to share for anyone else that may be facing the same issue with VNavigationDrawer using Vuex state to control visibility.
The #input event passes a val parameter, which includes the state of the drawer after the window resizes. I created a new action that is called by the below function:
<v-navigation-drawer
:value="$store.state.ui.drawer"
app
clipped
#input="updateDrawer($event)"
>
Here is the action being dispatched:
methods: {
updateDrawer(event) {
if (event !== this.drawer) { // avoids dispatching duplicate actions; checks for unique window resize event
this.$store.dispatch('updateDrawer',event)
}
}
},
And the action commits the new val to the Vuex store.
Basically, the input event is able to watch for updates to the drawer, and subsequently update the drawer state if it's necessary.
You'll also see above that I stubbornly accepted using :value as the docs suggest, even though I think this should be controlled by a v-model.
Seems to be working - with the right events called and the state being updated appropriately.

When item added, DetailsList doesn't update

I'm using office-ui-fabric-react library. Current version that I'm on is 7.17.0.
My list is super simple:
<DetailsList
columns={this.state.columns}
items={this.state.items}
compact={true}
selectionMode={SelectionMode.none}
enableUpdateAnimations={true}
/>
When I change the items in a separate method (marked async) and do this.setState({items: someNewItems}), the list doesn't get re-rendered. I'm curious is this a known issue, or is the DetailsList needs to be handled in a special way?
To clarify, after executing this.setState nothing changes, but if I resize the page, the new elements appear suddenly.
This is due to shallow comparison, in fact the solution is listed in the documentation here: https://developer.microsoft.com/en-us/fabric#/controls/web/detailslist
From the docs:
My List is not re-rendering when I mutate its items! What should I do?
To determine if the List within DetailsList should re-render its
contents, the component performs a referential equality check within
its shouldComponentUpdate method. This is done to minimize the
performance overhead associating with re-rendering the virtualized
List pages, as recommended by the React documentation.
As a result of this implementation, the inner List will not determine
it should re-render if the array values are mutated. To avoid this
problem, we recommend re-creating the items array backing the
DetailsList by using a method such as Array.prototype.concat or
ES6 spread syntax shown below:
public appendItems(): void {
const { items } = this.state;
this.setState({
items: [...items, ...['Foo', 'Bar']]
})
}
public render(): JSX.Element {
const { items } = this.state;
return <DetailsList items={items} />;
}
By re-creating the items array without mutating the values, the inner
List will correctly determine its contents have changed and that it
should re-render the new values.
There is no issue with DetailsList. The table should update properly, as you indicated that when you resize the page the items show up but before that it doesn't. It's definitely because the render function isn't being called again.
Which means that when you do this.setState you are not updating the states of your present component. As Mirage, in the comments suggested try checking if you have the correct this and you are updating the correct component.

TouchableHighlight and TouchableOpacity get highlighted on render()

I experience a behaviour where TouchableHighlight and TouchableOpacity reacts visually upon render (onPress is not being called).
One thing is that it looks just a little strange, when I enter the page and my button make a small "blink". This is strange but tolerable. The more frustrating part is that if I alter state for the parent component and thus invoke a re-render(), the button will "blink" again, making all buttons blink whenever I alter state.
Pushing the buttons alters page state, and thus pushing a button makes both buttons "blink".
I use react-redux, but this should not affect this behaviour.
The code below is just for illustration.
render()
{
return(
<View>
<ToucableHightlight> //Click here changes state
<Content/>
</ToucableHightlight>
<ToucableHightlight> //Click here changes state
<Content/>
</ToucableHightlight>
<View>
);
}
Add activeOpacity in TouchableOpacity and it will force to not blink.
<TouchableOpacity style={styles.opecity} activeOpacity={1}>
I solved the problem. Earlier during my render function i defined the "Content"-components, resulting in new (but alike) components being defined during each update. Placing the definitions of "Content" outside of the render function fixed it, so that the components no longer flashes when the page is re-rendered.
This explains why my component was rendered as a new component upon each render in the parent component, but it does not explain why a TouchableHighlight blinks during its initial render.
Buttons blinking during initial render is acceptable to me - buttons blinking upon any state-change is not.
So I am sufficiently happy now.
Not sure if it's because I'm running a later version, but I found this blinking behavior happens only on the first click.
My solution was putting the code that triggers rerendering in a setTimeout
<TouchableOpacity
onPress={function() {
setTimeout(function() {
_this.setState({myState: 'someValue'})
});
}}
>

react native route flux re-render component on stack pop

How do you re-run React Component Lifecycle methods to determine if a re-render is necessary when transitioning back to previous screens from stack using React Native Router Flux?
Scenario: Scene A -> B -> A
Lets say, A is the initial scene and we navigate from A to B with a push i.e Actions.SceneB. From B to A with a Pop. When the stack is popped to show Scene-A, none of the React Component methods are called for Scene-A. How do I determine if a re-render is necessary? I searched the Issues listed on the github repo and many seems to have this problem without a solution. How is this problem addressed?
I have the ugly work around of calling Actions.refresh() with a setTimeout.
Work Around that works!
This workaround seems to be hacky. There must be a better way to hook into component lifecycle to determine if a re-render is required. Perhaps a callback into the Component?
<TouchableHighlight underlayColor='#efefef' onPress={() => { Actions.pop(); setTimeout(()=> Actions.refresh(), 500)}} style={styles.cancel}>
Options that do not work
Actions.pop({type: 'reset'})
OR
Actions.refresh({"key" : "sceneA"})
Work Around that works! This workaround seems to be hacky. There must be a better way to hook into component lifecycle to determine if a re-render is required.
<TouchableHighlight underlayColor='#efefef' onPress={() => { Actions.pop(); setTimeout(()=> Actions.refresh(), 500)}} style={styles.cancel}>
This will call shouldComponentUpdate() method which gives an option to determine, if a re-render is required.
I was facing the same issue below code gave me required result.
let count = 0;
moveToMainScreen = () => {
count += 1;
Actions.pop({ refresh: { test: count } });
};
<Button onPress={this.moveToMainScreen}> Click </Button>