Setting State on react native alert's onPress error - reactjs

I find myself in a situation where I am needed to set the state of app upon the onPress of alert in react native. I am implemented the code below
Alert.alert(
'Delete Program',
'Are you sure you want to delete this program?',
[
{text: 'OK', onPress: () => this.doRefreshState()},
],
{ cancelable: false }
)
doRefreshState = () =>{
console.warn("i made it here")
this.setState({})
}
The error response is:
"_this2.doRefreshState is not a function.(in '_this2.doRefreshState()','_this2,doRefreshState; is undefined).
Please note that the alert is in another function

First, as a comment in general I would strongly suggest giving a more complete example so we have more information (including your version of React Native and the target OS).
My guess is that your method is not actually bound to the class instance, or in the case of a pure function, is not accessible. Using ES6 syntax you might try something like this:
class YourComponent extends React.Component {
doRefreshState() {
console.warn('method called') //personally I would use an alert here to verify
this.setState({})
}
renderAlertContainer() {
const onPressHandler = () => { //Bind to "this" with the lambda
this.doRefreshState()
}
return (
<Button onPress={()=>{
Alert.alert(
'Delete Program',
'Are you sure you want to delete this program?',
[
//{text: 'OK', onPress: () => this.doRefreshState()},
{ text: 'OK', onPress: onPressHandler }
],
{ cancelable: false }
) }} />
)
}
render() {...}
}
The reason I would format it this way is for (1) readability, it shows what "this" I meant, and (2) because I'm not actually sure what "this" refers to when it is transpiled by babel/webpack/whatever (and "this" has been a problematic language feature in JavaScript for many developers). You could probably jump into the debugger and figure out what "this2" actually is, but my guess is that it isn't bound to the object you think it is.
Secondly, depending on how your class/object/function is constructed you might need to explicitly bind the function to the prototype rather than the instance. In that case the easiest solution (in my opinion) is to simply add it in the constructor:
class.....
{
constructor(props) {
super(props)
this.doRefreshState = this.doRefrestState.bind(this) //annoying but now this method is bound to the class and I can act on it no matter the lifecycle state.
}
}
Without a clear picture of how the React.Component is initialized I can't really say for sure but I suspect either the method needs to be bound to the instance (prototype) or that the this object in the alert method is, in fact, executing in the context of the alert and not your class and, thus, is undefined.
My preferred technique to fix that category of error is to pull the handler out into a variable and then assign it so that whenever it is called the correct context is explicit rather than implicit (implied).
I should note: I just pushed out an Android React Native App to a client and I ran into problems very similar to this more often than I should have. Pulling the handlers out of the methods solved the problem in my Third party calls almost every time. Frankly, I'm just guessing at the cause of this issue as I didn't crawl into the debugger and figure it out, but I suspect one of my two solutions here will fix it.

Try binding the function instead of calling it.
Example:
Alert.alert(
'Delete Program',
'Are you sure you want to delete this program?',
[
{text: 'OK', onPress: this.doRefreshState},
],
{ cancelable: false }
)
doRefreshState = () =>{
console.warn("i made it here")
this.setState({})
}
Hopefully this should work as you are using arrow functions. If this does not work try binding the function like.
Alert.alert(
'Delete Program',
'Are you sure you want to delete this program?',
[
{text: 'OK', onPress: this.doRefreshState.bind(this)},
],
{ cancelable: false }
)
doRefreshState() {
console.warn("i made it here")
this.setState({})
}
Hope that helped.

Would check your doRefreshState function is outside the react's lifecycle method? Let's say i am calling this from componentWillMount
componentWillMount(){
Alert.alert(
"Delete Program",
"Are you sure you want to delete this program?",
[{ text: "OK", onPress: () => this.doRefreshState() }],
{ cancelable: false }
);
}
doRefreshState = () => {
alert("i made it here");
this.setState({});
};
render(){}

Related

Is it possible to listen to actions?

For example
There is a store:
const DataStore = observable({
data: [],
getDataAction() {}
})
And there are also other stores.
All of them are used in:
injectStores({ store, storeY })
in order to MobX DevToolsPro work.
Official documentation says, that you can use methods:
autorun()
reaction()
when()
All examples are given with classes.
And if I understood it right, you can listen only to state changes.
For example, if
data != []
you can call
console.log("data is not empty")
or anything you want.
Question Section:
It is possible to listen not to
data != []
, but to
getDataAction()
and, if getDataAction was called, then
console.log("getDataAction was called")
?
If it is not possible, can I create then store not with classes, but also with observable( { } ) for using together with autorun(), reaction() and when()? Or all this methods only work with stores, which were created with classes syntax?
Thank You
You can use spy for that, for example:
import { action, makeAutoObservable, spy } from 'mobx';
const DataStore = makeAutoObservable(
{
data: [],
getDataAction() {
console.log('inside of getDataAction');
}
},
{
getDataAction: action('getDataAction')
}
);
spy((event) => {
if (event.type === 'action' && event.name === 'getDataAction') {
console.log('getDataAction spy callback');
}
});
DataStore.getDataAction();
I don't know what you are trying to do with it, but if are just starting with MobX and you want to use this approach in your app code then it's probably a bad practice and not a right thing to do and you should rethink your approach.

React-dnd drop failure state

I'm using the react-dnd library to manage drag/drop, and running into what feels like a limitation, but wanting to make sure I'm not just overlooking something.
I've got a React component implementing useDrop with a fairly complicated set of rules inside canDrop. When the user releases the mouse and canDrop returns false, I'd love to be able to provide a detailed error message so they know why they weren't allowed to drop the item, but drop isn't called (expected behavior, per the docs), and there doesn't appear to be any way to provide context back to the useDrag handler's end function either.
I could obviously fire the error message from canDrop itself, but that function is called hundreds of times for each drag, so that introduces issues like debouncing etc. that I'd prefer to avoid.
So my question is, have I just run into a limitation of the library, or am I missing an event/hook somewhere?
I had the same problem and here is my solution:
canDrop contains the complex logic to determine if the item...can drop.
Add the item id (itemId) to the collect function passed to useDrop and use that id to determine what error message to show based on the combination of !canDrop && itemId==='some specific id'.
My useDrop hook:
const [{ isOver, canDrop, itemId }, drop] = useDrop(
() => ({
accept: [{valid ids go here}],
drop: (item, monitor) => {
// omitted
},
canDrop: (item, monitor) => {
// canDrop logic
},
collect: (monitor) => ({
isOver: !!monitor.isOver(),
canDrop: !!monitor.canDrop(),
itemId: monitor.getItem()?.id,
}),
}),
[]
);
Then in your component render:
return (
<>
{canDrop && <span>You're good to drop</span>}
{!canDrop && itemId === 'some condition' && <span>Error message specific to this condition</span>}
{!canDrop && itemId === 'some other condition' && <span>Error message specific to this condition</span>}
</>
)
For my scenario the item's id lets me determine the error message I should show but I think you could also return the entire item from the collect function.

Redux dispatch making screen render and causing warning after Wix Navigation.push() is called

I'm fetching some data from backend, working around with the messages array obtained, and setting it through Redux, synchronously, I execute Navigation.push(). Noticed that since render is being called due to the Redux call, it's throwing a warning on console about the state change on unmount component because it's already rendering the new screen when that happens.
Warning: Can't perform a React state update on an unmounted component. This is a no-op, but it indicates a memory leak in your application. To fix, cancel all subscriptions and asynchronous tasks in the componentWillUnmount method.
This is my function where the magic begins (?
_getGroupMessages(groupData) {
this._chatClient.getMessages({chatGroupId: groupData.key, limit: this.limit, offset: this.offset })
.then(msgs => {
let {userInfo} = this.props;
// set new offset to retrieve more messages later if any
this.offset = this.offset + msgs.length;
let messages = this._handleRetrievedGroupMessages(msgs, groupData);
this.props.onAddChatMessage(messages, true);
Navigation.push(this.props.componentId, {
component: {
name: 'myapp.ChatScreen',
passProps: {
onRefreshReadByGroup: this._onRefreshReadByGroupId,
onHandleTyping: this._onHandleTyping,
isTargetTyping: this.state.isTargetTyping,
reloadMoreMessages: this._getMoreGroupMessages,
userId: userInfo.id,
name: userInfo.fullName || userInfo.name,
chatData: groupData
},
options: {
animated: true,
bottomTabs: {
visible: false,
drawBehind: true,
animate: true
}
}
}
});
})
.catch(error => {
console.log(error);
});
}
I'm not doing any setState in those functions, the only thing executing is the userReducer to set the redux messages state when I call this.props.onAddChatMessage(messages, true);
I know it's this because i commented out the entire Navigation.push call and the warning is not thrown of course, because the screen isn't unmounting.
I attempted to put the Navigation.push within a setTimeout of 2s and it worked. But that is super dirty.
How can I make this work?
It's a chat list group, that whenever I tap on a group, it takes me to the actual chat. Hence, I need to retrieve the group messages by key and take the user to the chat screen.
Any help is appreciated. (I'd prefer not implementing the this._isMounted workaround since it's also very dirty)
Thanks in advance.

Creating new state variables outside of constructor

I am trying to make multiple state variable names from data in an array that is already a state variable. The number of variables needed can change from page load to page load so I can't really initialize them right away
I've set up a function below that I believe should do what I intend for it to, but I can't figure out where to place it in the component.
makeButtons() {
this.state.Buttons.forEach((button) => {
let key = button.fields.Label
this.setState({ [key]: '' })
});
}
I've tried calling it in render(), but that just gives me a infinite loop error, which makes sense. Any ideas?
EDIT: Tried calling in componentDidMount() but that doesn't seem to work either. Not sure what the issue would be there. Code below
base('ButtonInfo').select({view: 'Grid view' }).eachPage(
(Buttons, fetchNextPage) => {
this.setState({
Buttons
});
fetchNextPage();
}
);
this.state.Buttons.forEach((button) => {
let key = button.fields.Label
this.setState({ [key]: '' })
});
EDIT: Answer is in the comments below, I needed to add await before the api call and make componentDidMount() async.
How about doing it in componentDidMount()? This method is called only once as the after the app is rendered for the first time, and is often used to initialize the app with some data.

Can I force a rerender with setState even if the data hasn't changed? My GUI won't update?

So the problem I'm having right now is on first click, my GUI re-renders. But on second click, it does not re-render. I believe it's because I am not updating the state of "graphicLayers" which my render is binding through the "graphicLayers.map". That's my theory anyway (even though it works on first click? but not the 2nd click or anything after).
I tried forcing an setState update of graphicLayers, but it doesn't seem to be working. Like this:
let graphicLayersCopy = Object.assign([], this.state.graphicLayers);
this.setState({graphicLayers: graphicLayersCopy});
but that's not working. I know through the debugger that it's setting the data correctly, and if I refresh (it saves state and reloads the state), the GUI is then rendered correctly.
Is there anyway I can force a re-render of a variable some how even if it doesn't change value?
constructor
constructor(props, context) {
super(props, context);
this.state = {
graphicLayers: [id1, id2, id3],
graphicLayersById: {
id1: { ... },
id2: { ... },
id3: { ... }
}
this.addLayerClick = this.addLayerClick.bind(this);
};
render
render() {
return (
<div>
{this.state.graphicLayers.map((id) =>
<GraphicLayer addLayerClick={this.addLayerClick.bind(this)} />
)}
</div>
);
}
addLayerClick
addLayerClick() {
... change some property in graphicLayersById dictionary ...
self.setState({ graphicLayersById: newDataHere });
}
EDIT: I found the problem on my end, and it's not exactly shown here.
So my addLayerClick() actually calls another functions that is listening to a call and it sets the state inside. it's weird because the setState gets called in the callback function, but i got it to work by putting the setState in the addLayerClick() itself.. still dont know why this doens't work but i will upvote all of you at least
listenFunction() {
let self = this;
this.firebaseWrapper.storage.on('graphicLayersById', function (save) {
if (save) {
self.setState({ graphicLayersById: save }); // FOR SOME REASON THIS DOESN'T UPDATE THE GUI THE 2nd CLICK. The data is correct though and I see it going here on a breakpoint, but GUI won't update unless I setState in the actual button
}
else {
self.setState({ graphicLayersById: undefined });
}
});
}
In addLayerClick() you're only updating graphicLayersById, but rendering depends on graphicLayers. You should be updating the graphicLayers state in addLayerClick() as well.
addLayerClick() {
this.setState({
graphicLayers: ...
graphicLayersById: ....
});
}
On a side note, you shouldn't bind methods inside render() since that creates a brand new function on every render (and could impact performance). Instead of
<GraphicLayer addLayerClick={this.addLayerClick.bind(this)} />
do
<GraphicLayer addLayerClick={this.addLayerClick} />
and leave the binding in your constructor (the way you already have).
Actually, you have bound the addLayerClick() function to the component, so you can use this instead of self
You should revise your code like this: (there are about 2 changes)
constructor(props, context) {
super(props, context);
this.state = {
graphicLayers: [id1, id2, id3],
graphicLayersById: {
id1: { ... },
id2: { ... },
id3: { ... }
}
// don't need to bind here anymore, since you bind it in the click
//this.addLayerClick = this.addLayerClick.bind(this);
};
addLayerClick() {
//... change some property in graphicLayersById dictionary ...
// just use 'this' here
this.setState({ graphicLayersById: newDataHere });
// the below line is NOT recommended, which is force the component to update
// this.forceUpdate(); // this line will update the component even though there's no change
}
If this doesn't work yet, please post here how you handle onCLick function in the child component, and also post some errors if any, thanks
Hope these two possible way will reder your view
this.setState({graphicLayersById: newDataHere} , ()=>{
console.log(this.state.graphicLayersById);
});
OR
var update = require('react-addons-update');
var graphicLayers = update(this.state, {
graphicLayersById: {$set: newDataHere}
});
this.setState(graphicLayers);

Resources