React Native, how to update async-storage immediately? - react-native

I'm making a simple drinking game. When a playing card shows, it's corresponding rule shows below it. I have a settings.js file where the rules are, and the user can see and modify the rules, and they update on the game.js file. I'm using async-storage to store the rules.
I wanted to add a button in the settings.js file, which would return the original rules when pressed. The only problem is, that the original rules don't update immediately on the settings screen. When the button is pressed the original rules do update on the game, but they update on the settings screen only when the user goes back in the game and then back in the settings screen.
The code for updating the rules:
initialState = async () => {
try {
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
...
await AsyncStorage.setItem('rule13', 'theoriginalrule13')
catch(err) {
console.log(err)
}
}
I have the following line of code to update the async-storage when the screen is entered, but as said, it only works when the screen is re-entered:
componentDidMount() {
const { navigation } = this.props;
this.focusListener = navigation.addListener('didFocus', () => {
this.getData();
});
}

To answer your question here, not in a comment.
Try this :
componentDidMount() {
const { navigation } = this.props;
this.getData();
this.focusListener = navigation.addListener('didFocus', () => {
this.getData();
});
}

I would suggest you to use ,
State driven UI
means your ui will change only when state is changed , now suppose you are changing your asyncStorage, using
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
so I would suggest your state will also update after updating your aysncStorage like.
//Initial state
this.state = { score: 0 };
async storeValues(){
await AsyncStorage.setItem('rule1', 'theoriginalrule1')
let newScoreValue = await AsyncStorage.getItem('rule1')
this.setState({score:newScoreValue})
}
// UI will be like
render(){
return(<Text>{this.state.score}</Text>)
}

Related

How to update the previous Screen's useState value and refresh the screen on click of goBack()

I want to update the previous screen's useState value and refresh the screen on click of goBack(), I know there is a useIsFocused() to do that but using this screen it is refreshing every time i goBack to the screen, suppose there is a ScreenA and ScreenB, So when user is performing any action in ScreeB then I am dispatching a value using redux and on goBack I am updating the useState value and refreshing the screenA, But it not working, I don't know what's the problem, Please help.
ScreenB
const leaveGroup = () => {
hideMenu();
callLikeMindsApi(`URL`, httpMethods.HTTP_PUT)
.then(response => {
if (response && response.data && response.data.success) {
hideMenu();
dispatch(updateHomeScreen(true));
props.navigation.goBack();
}
})
.catch(error => {
Alert.alert('FAILED', error.response.data.error_message);
});
};
And In ScreenA
const {updateValue} = useSelector(state => state.homeReducer);
useEffect(() => {
console.log('updateValue-1234', updateValue);
}); // it is printing true in console if (updateValue) { setOffset(1); setTotalPages(1); setMyChatRooms([]); getUserData(); } }, [isFocused]);
But
setOffset(1);
setTotalPages(1);
setMyChatRooms([]);
values are not updating, if I remove the if(updateValue){} and writing like this
useEffect(() => {
setOffset(1);
setTotalPages(1);
setMyChatRooms([]);
getUserData();
}, [isFocused]);
then code is working as expected, But it is refreshing every time I come back to the screenA and I want to refresh conditionally.
instead of props.navigation.goBack(); do
props.navigation.navigate('ScreenA',{details});
with the prop that you want pass
in screen A get that value in state from route params
ScreenA = (props) => {
const [navigateState] = useState(props.route.params.details|}{});
---while navigating to Screen B---
props.navigation.navigate('ScreenB',{details: navigateState});
...
I can't get full your code and understand the full logic but if you have already implemented redux you should use it like this:
const {updateValue} = useSelector(state => state.homeReducer);
useEffect(()=>{
if (updateValue) {
// update your screen juse by setting some states on the current component.
} else {
// don't update
}
}, [updateValue])
if you dispatch true value for updateValue whereever in your app then screen-A would be updated regardless it's getting focus or not. if you want to update screen-A only when it's on focus you should add focus condition in useEffect.

React Native useEffect() re-renders too much

i have a useEffect function where a redux action is called and data is written to prop. My Problem is that useEffect loop many times and flooded the server with requests.
const { loescherData, navigation } = props;
useEffect(() => {
AsyncStorage.getItem('userdata').then((userdata) => {
if (userdata) {
console.log(new Date());
console.log(userdata);
var user = JSON.parse(userdata);
props.fetchLoescherDetails(user.standort);
setData(props.loescherData);
}
});
}, [loescherData]);
if i leave it blank the rendering is finished before receiving data and the content would not updated.
is there another way to work with this function?
loescherData won't be available right after calling your redux-action fetchLoescherDetails ... and changing component by setData will cause an infinite rendering cause your current useEffect has a dependency on loescherData
So I'd suggest you exec your redux-action onComponentDidMount by passing an empty-deps [] to your effect ... and then consume the output of you action in a different effect
useEffect(() => {
AsyncStorage.getItem('userdata').then((userdata) => {
if (userdata) {
console.log(new Date());
console.log(userdata);
var user = JSON.parse(userdata);
props.fetchLoescherDetails(user.standort);
// setData(props.loescherData);
}
});
}, []);
useEffect(() => {
if (loescherData) {
// do some with loescherData like setState
}
}, [loescherData]);

OnDidFocus event not working when you navigate back from the stack

I'm trying to test the OnDidFocus event in my React Native app using react navigation 4 and using the following event listener:
useEffect(() => {
const willFocusSub = props.navigation.addListener(
"onDidFocus",
console.log("testing onDidFocus")
);
return () => {
willFocusSub.remove();
};
});
When I first load the page it works fine but when I move away and then come back to the same screen through the Back button it does not seem to perceive the focus event.
This is my stack
const MovieNavigator = createStackNavigator(
{
MoviesList: HomeMovies,
MovieDetail: MovieDetailScreen,
PopularMovies: PopularMoviesScreen,
CrewMember: CastDetailScreen,
GenreSearch: GenreSearchScreen,
MovieSearch: MovieSearchScreen,
},
I'm in MoviesList and the event is triggered fine, then I move to MovieDetail. If I hit Back and return to MoviesList the event onDidFocus is not triggered at all.
I think you could try "willFocus" instead.
Like this:
const willFocusSub = props.navigation.addListener(
"willFocus",
()=>{console.log("testing willFocus")}
);
Try modyfying your useEffect call to this!
useEffect(() => {
const willFocusSub = props.navigation.addListener(
"onDidFocus",
console.log("testing onDidFocus")
);
return () => {
willFocusSub.remove();
};
},[]);
I found another way to detect the focus and blur event and seems the only way to track an event when using the Back button.
Instead of subscribing to events, I'm check the focus status of the screen using the useIsFocused() hooks available from react-navigation-hooks library.
import { useIsFocused } from "react-navigation-hooks";
...
const [showGallery, setShowGallery] = useState(true);
...
useEffect(() => {
if (isFocused) {
setShowGallery(true);
} else {
setShowGallery(false);
}
console.log("isFocused: " + isFocused);
}, [isFocused]);
Basically I'm checking the status of the screen using isFocused hook every time it changes (when it leaves and returns only same as didFocus and didBlur) and setting the state setShowGallery accordingly to run the carousel when focused and stop it when blurred.
Hope it helps others!

a state mutation was detected inside a dispatch (NOT "in between" dispatch)

...inside a dispatch, in the path: "chat.messages.0' Take a look at the reducer(s) handling the action {'type': 'chat', 'payload': {'sender': 'you', 'body': 'foo'}}`
My reducer:
chatMessage.js:
export default (state, action) => {
return Object.assign({}, {
...state,
messages: [...state.messages, action.payload]
})
}
Interestingly, if I use messages=[] in my Chat component, I don't get this error.
export default class Chat extends React.Component {
state = {...this.props.data, input: "", toggle: false}
_renderItem = ({item}) => {
return <ChatMessageRow data={item} />
}
render() {
// var {messages} = this.state
var messages = []
return (
<View style={styles.container}>
<FlatList
inverted
renderItem={this._renderItem}
style={styles.logWrapper}
data={messages.reverse()}
extraData={this.state.toggle}
/>
from my main view, which is bound to Redux:
return (...other views... check if tabIndex == 1, show <Chat data={this.props.chat} ... />
I ran into an issue earlier with the FlatList rendering in Chat not updating and I had to do a
this.setState({messages: messages.push(newMessage), toggle: !this.state.toggle})
to update the state so the Flat List would recognize it's changed.
Now I am loading my data from a Chat store using connect and redux, then passing that data into the component.
I get the error when I try to reload the Chat component after adding chat messages. Weirdly I can get one to update but after adding one it will not show others.
At first I think this is a problem in my reducer so I rewrite the reducer to use the Object assign and array spread operator.
Now I think it is related to the rendering which is unexpected.
How do I debug this?
EDIT:
It's not "it works with adding 1 message". It's - I can bring the view out of focus and back into focus one time. So..
Open Chat Tab
Observer 1 message
Add N messages to store using redux actions
Change tabs, revert back to Chat
Observer N messages added
Add M messages
Change tabs, click back to Chat tab
Error shows
EDIT:
I tried
using ...spread, but redux still throws warning about state mutation 's suggestion doing
export default (state, action) => {
return Object.assign({}, {
...state,
messages: state.messages.map(value => Object.assign({}, value)).concat(action.payload)
})
}
in my reducer, same error.
EDIT:
My reducer is update to chatMessage.js
I think the issue is in how I'm calling this.
I am writing a websocket controller. I don't need to wait for a response when I send a message like I would with an HTTP response.
My websocket controller:
onMessage = ({data}) => {
const json = JSON.parse(data)
if (json) {
if (json.status) {
const reducer = this.stateFilters[json.status]
if (reducer) {
reducer(json.body)
} else {
console.log("No reducer")
}
}
}
}
the websocket controller is created in my View component:
my main view
import {ChatStateFilters} from '../../reducers/chat'
const mapDispatch = { chatMessage }
this.wsController = new WebSocketController({
stateFilters: {chatMessage: this.props.chatMessage},
from the chat reducer file (that contains the chatMessage reducer)
Those state filters get created when I create my slice.
The value of stateFilter['chatMessage'] -- which is the value of what gets bound to my View's prop in mapDispatchToProp
is this function:
let fn = (payload) => {
return dispatch => {
dispatch(stateFilters[actionName](payload))
}
}
exportedStateFilters[actionName] = fn
I think the problem is somewhere in here^... somehow the dispatch is firing, updating state but redux doesn't know the dispatch is finished
EDIT 2:
I thought the lack of async may have been an issue. So I changed the stateFilter (the fn that gets bound to my prop in the view) to this:
let fn = (payload) => {
return dispatch => {
post("", {}, true)
.then(response => {
dispatch(stateFilters[actionName](payload))
})
.catch(error => {
dispatch(stateFilters[actionName](payload))
})
}
}
and now it works a few more times.. then gives me the error "A state mutation was detected between dispatches".
My chat message reducer is still
export default (state, action) => {
let messages2 = Object.assign([], [...state.messages, action.payload])
return Object.assign({}, {...state, messages: messages2 })
}
so why is this error happening?

How to avoid navigating to other screen multiple times

When press on any button on my React Native App to navigate to a different screen multiple times, then it will redirected to the next screen multiple times.
My sample code is:
// This is my button click event
myMethod()
{
this.props.navigation.navigate("ScreenName")
}
I am using react-navigation to navigate through my app.
How can I fix this behaviour?
I think there are a few ways this could be done. Perhaps recording when the navigation has occurred and preventing it from navigating multiple times.
You may also want to consider resetting hasNavigated after an amount of time etc as well.
// Somewhere outside of the myMethod scope
let hasNavigated = false
// This is my button click event
myMethod()
{
if (!hasNavigated) {
this.props.navigation.navigate("ScreenName")
hasNavigated = true
}
}
This react-navigation issue contains a discussion about this very topic, where two solutions were proposed.
The first, is to use a debouncing function such as Lodash's debounce that would prevent the navigation from happening more than once in a given time.
The second approach, which is the one I used, is to check on a navigation action, whether it is trying to navigate to the same route with the same params, and if so to drop it.
However, the second approach can only be done if you're handling the state of the navigation yourself, for example by using something like Redux.
Also see: Redux integration.
One of solution is custom custom components with adds debounce to onPress:
class DebounceTouchableOpacity extends Component {
constructor(props) {
super(props);
this.debounce = false;
}
_onPress = () => {
if (typeof this.props.onPress !== "function" || this.debounce)
return;
this.debounce = true;
this.props.onPress();
this.timeoutId = setTimeout(() => {
this.debounce = false;
}, 2000);
};
componentWillUnmount() {
this.timeoutId && clearTimeout(this.timeoutId)
}
render() {
const {children, onPress, ...rest} = this.props;
return (
<TouchableOpacity {...rest} onPress={this._onPress}>
{children}
</TouchableOpacity>
);
}
}
another: wrap onPress function into wrapper with similar behavior
const debounceOnPress = (onPress, time) => {
let skipCall = false;
return (...args) => {
if (skipCall) {
return
} else {
skipCall = true;
setTimeout(() => {
skipCall = false;
}, time)
onPress(...args)
}
}
}