I have an application with multiple components each wrapped in an error boundary. Sometimes, their children components throw errors which bubble up to the error boundary.
Here's a contrived example: https://codesandbox.io/s/throwing-many-errors-yirbgc
Each "Failed to load" is displaying an error boundary. I want to be able to click "refresh" to reset all of them.
Is it possible?
This solution is very crude and far from perfect but you could try something in the lines of remembering the resets of the errors given by the error boundary props
const errorResets = useRef<Function[]>([]);
<button
onClick={() => {
// I want to reset all my error boundaries
alert(`resetting ${errorResets.current.length} errors`);
errorResets.current.forEach((reset) => {
console.log(reset);
reset();
});
errorResets.current = [];
}}
>
Refresh
</button>
<ErrorBoundary
FallbackComponent={({ error, resetErrorBoundary }) => {
errorResets.current = [...errorResets.current, resetErrorBoundary];
return <h3>Failed to load</h3>;
}}
>
<MyRow />
</ErrorBoundary>
Related
General :
TL;DR: async code hangs rendering.
I have this component with a Modal and inside the Modal it renders a list of filters the user can choose from. When pressing a filter the color of the item changes and it adds a simple code(Number) to an array. The problem is that the rendering of the color change hangs until the logic that adds the code to the array finishes.
I don't understand why adding a number to an array takes between a sec and two.
I don't understand why the rendering hangs until the entire logic behind is done.
Notes: I come from a Vue background and this is the first project where I'm using react/react-native. So if I'm doing something wrong it would be much appreciated if someone points that out
Snack that replicates the issue :
Snack Link
My code for reference :
I use react-native with expo managed and I use some native-base components for the UI.
I can't share the whole code source but here are the pieces of logic that contribute to the problem :
Parent : FilterModal.js
The rendering part :
...
<Modal
// style={styles.container}
visible={modalVisible}
animationType="slide"
transparent={false}
onRequestClose={() => {
this.setModalVisible(!modalVisible);
}}
>
<Center>
<Pressable
onPress={() => this.setModalVisible(!modalVisible)}
>
<Icon size="8" as={MaterialCommunityIcons} name="window-close" color="danger.500" />
</Pressable>
</Center>
// I use sectionList because the list of filters is big and takes time to render on the screen
<SectionList
style={styles.container}
sections={[
{ title: "job types", data: job_types },
{ title: "job experience", data: job_experience },
{ title: "education", data: job_formation },
{ title: "sector", data: job_secteur }
]}
keyExtractor={(item) => item.id}
renderItem={({ item, section }) => <BaseBadge
key={item.id}
pressed={this.isPressed(section.title, item.id)}
item={item.name}
code={item.id}
type={section.title}
add={this.addToFilters.bind(this)}
></BaseBadge>}
renderSectionHeader={({ section: { title } }) => (
<Heading color="darkBlue.400">{title}</Heading>
)}
/>
</Modal>
...
The logic part :
...
async addToFilters(type, code) {
switch (type) {
case "job types":
this.addToTypesSelection(code);
break;
case "job experience":
this.addToExperienceSelection(code);
break;
case "formation":
this.addToFormationSelection(code);
break;
case "sector":
this.addToSectorSelection(code);
break;
default:
//TODO
break;
}
}
...
// the add to selection methods look something like this :
async addToTypesSelection(code) {
if (this.state.jobTypesSelection.includes(code)) {
this.setState({ jobTypesSelection: this.state.jobTypesSelection.filter((item) => item != code) })
}
else {
this.setState({ jobTypesSelection: [...this.state.jobTypesSelection, code] })
}
}
...
Child :
The rendering Part
render() {
const { pressed } = this.state;
return (
< Pressable
// This is the source of the problem and read further to know why I used the setTimeout
onPress={async () => {
this.setState({ pressed: !this.state.pressed });
setTimeout(() => {
this.props.add(this.props.type, this.props.code);
});
}}
>
<Badge
bg={pressed ? "primary.300" : "coolGray.200"}
rounded="md"
>
<Text fontSize="md">
{this.props.item}
</Text>
</Badge>
</Pressable >
);
};
Expected outcome :
The setState({pressed:!this.state.pressed}) finishes the rendering of the item happens instantly, the rest of the code happens after and doesn't hang the rendering.
The change in the parent state using the add code to array can happen in the background but I need the filter item ui to change instantly.
Things I tried :
Async methods
I tried making the methods async and not await them so they can happen asynchronously. that didn't change anything and seems like react native ignores that the methods are async. It hangs until everything is done all the way to the method changing the parent state.
Implementing "event emit-listen logic"
This is the first app where I chose to use react/react-native, coming from Vue I got the idea of emitting an event from the child and listening to it on the parent and execute the logic that adds the code to the array.
This didn't change anything, I used eventemitter3 and react-native-event-listeners
Using Timeout
This is the last desperate thing I tried which made the app useable for now until I figure out what am I doing wrong.
basically I add a Timeout after I change the state of the filter component like so :
...
< Pressable
onPress={async () => {
// change the state this changes the color of the item ↓
this.setState({ pressed: !this.state.pressed });
// this is the desperate code to make the logic not hang the rendering ↓
setTimeout(() => {
this.props.add(this.props.type, this.props.code);
});
}}
>
...
Thanks for reading, helpful answers and links to the docs and other articles that can help me understand better are much appreciated.
Again I'm new to react/react-native so please if there is some concept I'm not understanding right point me in the right direction.
For anyone reading this I finally figured out what was the problem and was able to solve the issue for me.
The reason the rendering was getting hang is because the code that pushes to my array took time regardless of me making it async or not it was being executed on the main thread AND that change was triggering screen re-render which needed to wait for the js logic to finish.
The things that contribute to the solution and improvement are :
Make the array (now a map{}) that holds the selected filters stateless, in other words don't use useState to declare the array, instead use good old js which will not trigger any screen re-render. When the user applies the filters then push that plain js object to a state or context like I'm doing and consume it, doing it this way makes sure that the user can spam selecting and deselecting the filters without hanging the interactions.
first thing which is just a better way of doing what I needed is to make the array a map, this doesn't solve the rerender issue.
I am receiving the list of tools data through mapStateToProp. I also looked after other similar issues which addressed issue via only one solution of adding return within map function, but still it doesn't work. Checked in logs the data is present and even used the ternary operator to evaluate if data is actually present, still it doesn't shows the else (code after the ':') portion
const DemoTools=({toolsListData})=>{
const [toolsList, setToolsList] = useState(toolsListData);
return (
<List>
{
toolsList ?
toolsList.map((tool,index)=>{
return (
<ListItem key={index}>
<ListItemText primary={tool.name} />
</ListItem>
:<p>No Tools</p> //It never reaches here though
)})
}
</List>
);
}
The reason you are seeing data in this image, I have a search bar to search a tool, as soon as I enter text to search for a tool, or even clear the search box, the whole list is then visible.
const handleSearch = (value) => {
let matchvalue = toolsListData.length > 0 ? toolsListData.filter(tool => tool.name.toLowerCase().includes(value.toLowerCase())) : [];
setToolsList(matchvalue);
}
Your logic is syntactically incorrect (weird phrase). Here is the way that should work for you :-
toolsList ?
toolsList.map((tool,index)=>{
return (
<ListItem key={index}>
<ListItemText primary={tool.name} />
</ListItem>
)})
:<p>No Tools</p>
Found the exact issue. The tools list which I am fetching here came from GET api call, which is empty initially in first render cycle. So the by the time data is actually fetched the ListItem was already rendered. So I used a useEffect to monitor the toolsListData and then 'set' it thru setToolsList(toolsList);
useEffect(() => {
if (toolsListData && toolsListData.length > 0)
setToolsList(toolsListData);
}, [toolsListData])
As soon as there is change in toolsListData it reflected immediately making it look like it rendered in first render cycle.
I am using Scrollview of react-native in my code.
<ScrollView style={styles.productlist} >
<CarouselComponent images={images}/>
</ScrollView>
Its working fine on an app, with no error, and no warning but in Console I am getting the below message repeatedly:
You specified onScroll on a but not scrollEventThrottle. You will only receive one event. Using 16 you get all the events but be aware that it may cause frame drops, use a bigger number if you don't need as much precision.
Don't know what is missing, I have not passed onScroll props but still getting this message.
Any suggestion...
My Carousel component Code is:
const renderImages = (image, index) => {
return (
<Image
key={index}
style={styles.carouselImage}
source={{ uri: image.large }}
/>
);
}
const CarouselComponent = (props) => {
const { images: { alternate = [] } = {} } = props;
if (alternate.length > 0) {
return (
<Carousel
delay={3000}
autoplay
style={styles.carouselLayout}
bullets
chosenBulletStyle={globalStyle.proDetCarSelectBullet}
bulletStyle={globalStyle.productDetailBullet}
bulletsContainerStyle={globalStyle.proDetCarBullet}
isLooped
currentPage={0}
>
{alternate.map((image, index) => renderImages(image, index))}
</Carousel>
);
}
return null;
}
You are probably using an older version of react-native-looped-carousel. On github there is a issue, which was fixed 27 days ago, describing your problem.
Fix:
Updating to the latest version of react-native-looped-carousel should resolve your issue. As an alternative you can fix the error by manually adding the scrollEventThrottle. See here.
There is nothing wrong in your code, the problem is contained into react-native-looped-carousel as an issue states here: https://github.com/phil-r/react-native-looped-carousel/issues/269
I suggest you to search for another library. React-native's code keep growing in a very fast way and every library should be updated frequently.
Just to learn something new, the scrollEventThrottle prop defines how many times the onScroll event will be fired while you scrolling. The bigger the number is, less times the event will be fired. 16 is most precise value.
Actually, the main ScrollView component needs the following code:
<ScrollView
scrollEventThrottle={16}
~~~
I have a multiple field form, and I'm trying to get a small Form Warning to display when the user inputs wrong information. The problem I'm having is that the form will not display the warning under the desired input correctly. I have been told that this is because the Form warning is positioned absolutely, and thus and parents along that way that are not-staticly-positioned will throw off the alignment.
The recommendation was to use .append or .after in componentWillMount() to put the Form Warning component in the body, so FormWarning component can be positioned absolutely to the window. Makes sense to me, but my attempts only being me an [object Object] at the very bottom of my page.
Can anyone provide any insight into why this is occurring and what I can do to fix it? I've tried multiple different ways of putting this, but nothing works. I always just get [object Object]
componentWillMount(){
this.formWarningToBody();
}
formWarningToBody = () => {
let form =[]
form.push(<FormWarning visible={this.state.formWarning.visible}
invalidInputID={this.state.formWarning.invalidInputID} text=
{this.state.formWarning.text}/>)
document.body.append({form})
}
render() {
if (this.state.isSubmitted) return <Redirect to="/order" />
let CustomTag = this.props.labels ? 'label' : 'span',
{ inputs, saveInputVal, styles, state } = this,
{ formWarning, submitting } = state,
{ invalidInputID, text, visible } = formWarning
return (
<div style={this.styles.formWrapper}>
{
typeof this.props.headerText === 'string'
? ( <h2 style={this.styles.formHeader}>{this.props.headerText}</h2> )
: this.props.headerText.map((text) => {
return <h2 key={text} style={this.styles.formHeader} className={'header'+this.props.headerText.indexOf(text)}>{text}</h2>
})
}
<form onSubmit={this.submit} style={this.styles.form}>
<FormOneInputs inputs={inputs} saveInputVal={saveInputVal} CustomTag={CustomTag} styles={styles} />
<button style={this.styles.button}>{this.props.buttonText}</button>
</form>
<Throbber throbberText='Reserving your order...' showThrobber={submitting} />
</div>
)
}
}
That's not how React works, you can't just push JSX to DOM because a JSX element is indeed an object, which is then rendered to HTML. You can use React Portals.
See docs: https://reactjs.org/docs/portals.html
I'm using a package which has an issue that causes React to console the error "unknown-prop warning", i am looking for a short-term solution to hide the error until something else can be found/used.
Is there a way to hide this?
edit: its more of a general question as to how to overall disable these errors, i want to avoid rollbar flagging it as it will cause a lot of errors in the logs.
but im using reactslick, theres numerous reported issues of this error but i never found a fix, it comes from the code snippet below: ( i believe it passes in every prop even the ones it doesnt need.
SliderButtonNext = () =>
<button className="react-slick-slider">
<FaChevronRight size={24} className="arrow" />
</button>
SliderButtonPrev = () => {
<button className="react-slick-slider">
<FaChevronLeft size={24} className="arrow" />
</button>
}
const settings = {
nextArrow: this.SliderButtonNext(),
prevArrow: this.SliderButtonPrev(),
};