React arrow function produced different debugging experience - javascript

I am currently working on a react UI project, and involves lots of debugging with browsers. The strange thing is that when I do not use arrow function on button click event, the browser debugger shows this.state as undefined when mouse over it. When I use arrow function
{() => this.alert()}
the debugger console shows the correct state when mouse over and both these logging correct output when I do console.log(). Also I tested this behavior in Chrome and it is same as FF.
Any one has any idea will be much appreciated, since this brings really big issue when debugging without seeing the state, and it is not realistic to console.log for every state change.
Could not show this.state when mouse over it
Can show this.state when mouse over

Please note that when you do:
somehandler = () => { ... }
in your component, it's already bound to the component instance, i.e. this = your component instance, so you don't need to do onClick={() => this.somehandler() }, which binds it yet again to the component instance.
Stick to the former and you'll be good to go!
Edit: I now realize that it's actually onClick={this.somehandler} that's causing you console.log grief. It may be related to either how the Babel transform or the Dev Tools are implemented... You can try a classic bind like this, to see if it helps:
constructor() {
this.somehandler = this.somehandler.bind(this);
}
somehandler() {
// ....
}
render() {
return <button onClick={this.somehandler}>...</button>
}

Related

When is it necessary to use `rerender` with the React Testing Library?

In times past, my colleagues and I would typically write React Testing Library (RTL) tests for the main parent components, which often have many nested child components. That testing made sense and worked well. Btw the child components in question are very much dedicated to that parent component and not of the reusable variety.
But now we're trying to write RTL tests for every single component. Today I was trying to build tests for an Alerts component, which is the parent of an Alert component and about 4 levels down from the top-level component. Here's some sample code in my test file:
function renderDom(component, store) {
return {
...render(<Provider store={store}>{component}</Provider>),
store,
};
}
let store = configureStore(_initialState);
const spy = jest.spyOn(store, 'dispatch');
const { queryByTestId, queryByText, debug } = renderDom(
<Alerts question={store.getState().pageBuilder.userForm.steps[0].tasks[0].questions[1]} />,
store
);
I then started writing the typical RTL code to get the Alerts component to do its thing. One of these was to click on a button which would trigger an ADD_ALERT action. I stepped through all of the code and the Redux reducer was apparently working correctly with a new alert, as I intended, yet back in the Alerts component, question.alerts remained null whereas in the production code it was definitely being updated properly with a new alert.
I spoke with a colleague and he said that for this type of test, I would need to artificially rerender the component like this:
rerender(<Provider store={store}><Alerts question={store.getState().pageBuilder.userForm.steps[0].tasks[0].questions[1]} /></Provider>);
I tried this and it appears to be a solution. I don't fully understand why I have to do this and thought I'd reach out to the community to see if there was a way I could avoid using rerender.
It's hard to be certain without seeing more of your code, but my typical approach with RTL is to take the fireEvent call that simulates clicking the button and wrap it in an act call. This should cause React to finish processing any events from your event, update states, rerender, etc.
Alternatively, if you know that a particular DOM change should occur as a result of firing the event, you can use waitFor. An example from the React Testing Library intro:
render(<Fetch url="/greeting" />)
fireEvent.click(screen.getByText('Load Greeting'))
await waitFor(() => screen.getByRole('alert'))

if I add anything inside componentWillUnmount nothing happening

I am trying to learn react myself.
I am trying to set session inside my componentWillUnmount.
but if I add anything inside componentWillUnmount nothing happening.
i debugged by adding console and debugger still nothing happening.
can you guys tell how to make my componentWillUnmount to work
so that in future I will fix it myself.
providing my relevant code snippet and sandbox below.
all my code is in RecipeReviewCard.js
https://codesandbox.io/s/1vqkz1own7
componentDidMount() {
console.log("componentDidMount---->");
}
componentWillUnmount() {
console.log("componentwillUnmount---->");
debugger;
window.sessionStorage.setItem(
"favoriteValues",
JSON.stringify(this.props.benchMarks)
);
}
As the name suggests, 'componentWillUnmount' will fire when the component is about to be taken out of the DOM (eg. when you switch tabs for example) and in your example the console.log indeed does fire. Use 'componentWillMount' to run the function when the component is loaded into the DOM

setOnNavigatorEvent callback not fired when switching tabs

I am working on an app powered by react-native#0.55.4 and react-native-navigation#1.1.474.
Initially, there is only a login screen (using Navigation.startSingleScreenApp). When the user logs in I call Navigation.startTabBasedApp (one of the tab components is my NavScreen). Whenever the user changes to another tab the root of the tab's stack is supposed to be displayed so I tried something like this:
class NavScreen extends React.PureComponent {
constructor(props) {
super(props)
this.props.navigator.setOnNavigatorEvent(this.toRootOnTabSelect.bind(this))
}
toRootOnTabSelect(event) {
const {id} = event
if (["bottomTabSelected", "bottomTabReselected"].includes(id)) {
this.props.navigator.popToRoot({
animated: true,
animationType: "fade",
})
}
}
render() {
return <Text>Whatever...</Text>
}
}
But for some reason my toRootOnTabSelect event handler method is not being called when I change tabs (by clicking on them - not by calling the switchToTab API method).
There are multiple posts I found online (i.e. https://stackoverflow.com/a/51159091/6928824, https://github.com/wix/react-native-navigation/issues/648) that indicate that it should work so I don't know what I'm missing. :/
Any help is greatly appreciated! :)
One of the reasons that can cause that is using setOnNavigatorEvent in conjuction with addOnNavigatorEvent, if you have a screen wrapper component that implement addOnNavigatorEvent your current listener will not work.
As mentioned in documentation
setOnNavigatorEvent Can not be used in conjuction with addOnNavigatorEvent
Also
Bear in mind that you can't use both addOnNavigatorEvent and setOnNavigatorEvent. addOnNavigatorEvent returns a function, that once called will remove the registered handler.
I would suggest trying addOnNavigatorEvent instead of setOnNavigatorEvent
This seems to be a bug in react-native-navigation#^1.1.474 (note the caret): see my issue on GitHub.
An according pull request has been opened but not yet merged. :(

BackAndroid not unmounting - React Native (Android)

I am working with React Native 0.29 with Android. For a particular view/activity/screen of my app, I want to add an event listener for BackAndroid button, which is available with react native. I already have a global BackAndroid event listener added to my app (in my index.android.js file) which pop out any view from the stack if it's not the main screen.
The event listener is activated with componentDidMount() lifecycle method and it works. It override the global one and works as expected. Now the problem is, it doesn't get removed when componentWillUnmount() lifecycle method get fired. So when back from that particular screen, the event listener still remains and cause trouble. Here is what I did:
componentDidMount() {
BackAndroid.addEventListener('backBtnPressed', this._handleBackBtnPress.bind(this))
}
componentWillUnmount() {
BackAndroid.removeEventListener('backBtnPressed', this._handleBackBtnPress.bind(this))
}
I don't understand why it's not working. Please help me to understand why it's not working and what should I do to solve this issue.
I spend hours to solve this problem. I think it would help other developers if I share this.
The main problem here is with the .bind(this) statement, bind always returns a new function. So in this code, this._handleBackBtnPress.bind(this) are not same functions in addEventListener and removeEventListener. They are different functions referring different first-class objects. That's why removeEventListener is not removing the listener.
To solve this issue, we can add the following statement to our constructor method - this._handleBackBtnPress = this._handleBackBtnPress.bind(this) and remove .bind(this) from both addEventListener and removeEventListener. So our code will look something like this:
constructor(props) {
super(props)
this._handleBackBtnPress = this._handleBackBtnPress.bind(this)
}
componentDidMount() {
BackAndroid.addEventListener('backBtnPressed', this._handleBackBtnPress)
}
componentWillUnmount() {
BackAndroid.removeEventListener('backBtnPressed', this._handleBackBtnPress)
}
Now both of them will refer same function and will work as expected.

Mounting Components in React Native

I am relatively new to JS and RN and I am currently working with an app where I have bumped in to some major issues regarding my handling of Components.
I've tried to run through the following guide: https://facebook.github.io/react/docs/component-specs.html as well as https://facebook.github.io/react/docs/advanced-performance.html but the latter one flies a bit over my head.
However, as I understand: componentWillMount fires whatever piece of code that is within before the render function is executed, and componentWillUnmount erases whatever it sais to forget. Or how can I specify?
My specific problem lies within the fact that I have three functions, one main and within main I have compOne and compTwo, where the two latter are called in the main component when pressing on a certain sub-navigator. This means that I have three instances of getInitialState whereas compOne and compTwo defines basically the same stuff but calls different parts of the server (hence the code is very much the same).
Also this issue resurfaces sometimes when I go between different frames, and return again to my home screen.
In my Home screen I have it like this:
var Home = React.createClass({
getInitialState: function() {
return {
componentSelected: 'One',
userName: "Loading...",
friendFeed: 'Loading...',
loaded: false,
loadedlocal: false,
};
},
componentWillMount: function() {
Method.getFriendFeed(this.props.tokenSupreme)
.then((res) => this.setState({
friendFeed: JSON.parse(res).friendPosts,
loaded: true,
}))
.catch((error) => console.log(error))
.done();
Method.getLocalFeed(this.props.tokenSupreme, )
.then((res) => this.setState({
localFeed: JSON.parse(res).friendPosts,
loadedlocal: true,
}))
.catch((error) => console.log(error))
.done();
},
Where I pass this.state.friedFeed to be a this.props.friendData in one of two components and vice versa for the localFeed.
Picking it up in my CompOne:
var ComponentOne = React.createClass({
getInitialState: function() {
var ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
return {
dataSource: ds.cloneWithRows(this.props.friendData),
};
},
render: function() {
if (!this.props.loaded) {
return this.renderLoadingView();
} else {
return (
<ListView
dataSource={this.state.dataSource}
renderRow={this.renderRow}
style={styles.card} />
)
}
},
Followed by the renderRow function etc and the compTwo function is basically identical.
But my question is: How should I go about to unmount the component? If it even is what I want? Another frequently but not consequently occuring issue is the error of null is not an object (evaluating 'prevComponentInstance._currentElement' with reference to the _updateRenderedComponent hence my belief that I should go about some different method in mounting, unmounting and updating my components, or am I wrong?
After some browsing ill add another question to this, which might be the main question... Is it even possible for a RN app to handle mutiple listviews and mulitple fetchers in mutilple scenes?
In most situations, you do not need to be concerned about unmounting the components. When a React component is no longer needed, React in general just forgets about it, including its contents, props, state, etc. componentWillUnmount is typically reserved for things that are in a global state that, once the component is forgotten about would cause problems if they still existed.
The documentation on the page you linked to mentions cleaning up timers as an example. In Javascript, if you set a timer via setTimeout() / setInterval(), those timers exist in the global space. Now imagine you had a component that set a timer to modify some element on screen or potentially try to interact with a component, let's say 30 seconds in the future. But then the user navigates away from the screen/component, and because it is no longer on screen React forgets about it. However, that timer is still running, and may cause errors if it fires and it can't interact with that now-trashed component. componentWillUnmount gives you a chance to clear out that timer so weird side effects don't occur when it fires to interact with elements that no longer exist.
In your case you probably don't have anything that needs cleanup, as far as I can tell. You might want to clarify your question because you don't say what the trouble behavior is that you're seeing, but note also that getInitialState is only called the first time a component is created, and won't get called if only props change. So if the friendData is changing but the component stays on the screen, you will need to update your ds via a componentWillReceiveProps.
To your last question, yes it is certainly possible for React to handle multiple ListViews/fetches/etc.

Categories

Resources