As can be seen in my posted code, I have a React Native class that calls a function ('getKey') in the 'componentDidMount' which is designed to retrieve some variables from storage. This seems to be operative as expected:
export default class Cast extends Component {
constructor(props) {
super(props);
this.state = {
admin: false,
isPublishing: false,
userComment: "",
hasPermission: false,
paused: true,
_email: false,
_name: false,
_pword: false,
_play_ID: false,
_streamkey: false,
_playurl: "",
_streamurl: "",
isLoading : true,
};
}
getKey = async() => {
try {
var value = await AsyncStorage.getItem('email');
this.setState({ _email: value });
value = await AsyncStorage.getItem('playkey');
this.setState({ _play_ID: value });
const playurl = "https://stream.mux.com/" + value + ".m3u8"
this.setState({ _playurl: playurl });
value = await AsyncStorage.getItem('castkey');
this.setState({ _streamkey: value });
const streamurl = "rtmp://global-live.mux.com:5222/app/" + value
this.setState({ _streamurl: streamurl });
this.setState({ isLoading: false });
} catch (error) {
console.log("Error retrieving data" + error);
}
}
componentDidMount(){
this.getKey();
}
renderCameraView = () => {
return (
<NodeCameraView
style={styles.nodeCameraView}
/* eslint-disable */
ref={vb => {
this.vb = vb;
}}
/* eslint-enable */
outputUrl = {this.state._streamurl}
camera={settings.camera}
audio={settings.audio}
video={settings.video}
autopreview
/>
);
};
renderPlayerView = () => {
const { paused } = this.state;
const source = {
uri: this.state._playurl
};
return (
<Video
source={source} // Can be a URL or a local file.
//...stuff
/>
);
};
renderEmptyView = () => {
console.log("ACTUAL _playurl value variable in renderEmptyView: " + this.state._playurl)
const { paused } = this.state;
const source = {
uri: "http://devimages.apple.com/iphone/samples/bipbop/bipbopall.m3u8"
};
return (
<Video
source={source} // Can be a URL or a local file.
//...stuff
/>
);
};
//...
render() {
const { admin, paused, isPublishing } = this.state;
return (
<View style={styles.container}>
{isLoading ? this.renderEmptyView() : !isLoading && admin ? this.renderPlayerView() : !isLoading && !admin ? this.renderCameraView() : null}
//...stuff
</View>
);
Also near the bottom in the 'render' section I have a ternary operator which is designed to conditionally load the initial view screen based upon variable values. When the render first occurs the 'isLoading' = true (as set in the this.state constructor) and therefore 'this.renderEmptyView()' is intended to be called to render the initial screen.
As I understand when 'setState' is used to change the value of a variable (as in my 'getKey' function)...this is supposed to cause a re-render of the environment. However, my issue is that does not seem to happen. I would expect that when the value of 'isLoading' is changed to 'false' (in the 'getKey' function) that should cause an environment render of either 'this.renderPlayerView()' or 'this.renderCameraView()' (based on the value of the 'admin' variable as can be seen in the code).
I know the variables in 'getKey' are being processed, the 'console.log' statement I placed in the 'renderEmptyView()' displays the following:
LOG ACTUAL _playurl value variable in renderEmptyView:
LOG ACTUAL _playurl value variable in renderEmptyView:
LOG ACTUAL _playurl value variable in renderEmptyView:
LOG ACTUAL _playurl value variable in renderEmptyView:
LOG ACTUAL _playurl value variable in renderEmptyView:
LOG ACTUAL _playurl value variable in renderEmptyView:
LOG ACTUAL _playurl value variable in renderEmptyView: https://stream.mux.com/NOk01fhhiudj9YQVGwOHFs6Ovd01Z6VGNWFJKMmEFkfOE.m3u8
As the log indicates, when execution is first initiated the '_playurl' variable is 'empty'...as expected (since it was initialized as 'empty' ("") in the this.state constructor) however after a number of ticks it does get changed to the value retrieved from storage.
This code technically does not throw any errors however the rendering of the environment when executed is all messed up...I just get blank/black non-functional screens and the 'this.renderPlayerView()' does not get called when the 'isLoading' variable becomes 'false'.
What am I doing wrong here? Perhaps my understanding of the 'setState' is incorrect...or perhaps I need to use some other code syntax? Any enlightening answers will be greatly appreciated. I thank you in advance.
Related
I have the following class in React Native. As can be seen I have some defined 'state' variables and a 'componentDidMount' call which is designed to retrieve previously stored variables using the 'AsyncStorage' tool.
export default class Cast extends Component {
state = {
admin: false,
isPublishing: false,
userComment: "",
hasPermission: false,
paused: true,
_email: false,
_name: false,
_pword: false,
};
getKey = async() => {
try {
var value = await AsyncStorage.getItem('email');
console.log("value variable AFTER getKey: " + value);
this.setState({ _email: value });
} catch (error) {
console.log("Error retrieving data" + error);
}
}
componentDidMount(){
this.getKey();
}
onPressBtn = () => {
console.log("EMAIL value variable AFTER getKey: " + this.state._email) //this should show the value AFTER the retrieval from storage...correct?
};
//...
The console.log statement following the 'AsyncStorage.getItem' successfully displays the variable "value" as being retrieved from the storage (example "me#gmail.com"). However I am greatly confused on how to assign this variable and display it. The "this.setState({ _email: value });" is either not functional or I am using incorrect syntax to display the value for the "_email" variable. I have attempted the following:
console.log("_email variable AFTER getKey: " + _email);
console.log("_email variable AFTER getKey: " + this._email);
console.log("_email variable AFTER getKey: " + {_email});
console.log("_email variable AFTER getKey: " + this.state._email);
None of the above correctly return the value of the "_email" variable. What am I doing wrong here? Is the 'setState' assignment not correct...? I simply want to retrieve any values that are in storage (as "value") and then assign them to the appropriate variables defined in the 'state'. Any advice greatly appreciated. I thank you in advance.
It depends on when you have tried to access the state variable. If you did the following:
getKey = async() => {
try {
var value = await AsyncStorage.getItem('email');
console.log("value variable AFTER getKey: " + value);
this.setState({ _email: value });
console.log(this.state._email)
} catch (error) {
console.log("Error retrieving data" + error);
}
}
Then, this will print false, which is the default in your state. Setting the state will cause a rerender. The new value will be available afterwards.
Consider the following snippet.
getKey = async () => {
try {
var value = "test"
console.log("value variable AFTER getKey: " + value)
this.setState({ _email: value })
} catch (error) {
console.log("Error retrieving data" + error)
}
}
...
render() {
console.log("HELLO VALUE", this.state._email)
return <></>
}
We will notice the following outputs printed to the console.
LOG HELLO VALUE false
LOG value variable AFTER getKey: test
LOG HELLO VALUE test
You'll need to declare your state object in the constructor
constructor(props) {
super(props);
this.state = {
admin: false,
isPublishing: false,
userComment: "",
hasPermission: false,
paused: true,
_email: false,
_name: false,
_pword: false,
};
//Other codes
}
Also when referring to a state, you want to use:
eg. you're trying to get the email stored in the state
this.state._email
//setting states
this.setState({_email: "newEmail"})
I am trying to dynamically translate some text to be displayed when a user clicks on the translate button, but I can't get it to save my values outside of the Promise. I haven't worked much with Promises and every example only shows console.log, rather than saving values outside of the Promise. I don't really understand how they work. Here is (most of) the code I am trying to fix:
constructor(props) {
super(props);
this.state = {
dynamicTranslate: this.props.dynamicTranslate,
};
}
// I've tried this method as both sync and async (with await) but neither work
googleTranslate = (key) => {
const translator = TranslatorFactory.createTranslator();
// translate returns a Promise
return translator.translate(key, i18n.locale)
.then((response) => {return response});
}
renderText() {
// getting some values....
// this loops through all the feedback information
for (var i = 0; i < components_feedback.length; i++) {
let label = (some string);
let value = (some string);
// to do: call google translate call here if Boolean(this.state.dynamicTranslate)
if (Boolean(this.state.dynamicTranslate)) {
// I am ultimately trying to save the translation string from googleTranslate()
// in label/value so I can push it into feedbacks
label = this.googleTranslate(label);
value = this.googleTranslate(value);
}
feedbacks.push({label: label, value: value, type: comp.type})
}
return (
// some stuff
feedbacks.map((feedback, index)) => {
// some stuff
<Text>{feedback.label}</Text>
<Text>{feedback.value}</Text>
// some other stuff
});
);
}
render() {
return (
<View>{this.renderText()}</View>
);
}
One of the issues I'm running into is that label/value is a Promise if translation is on. If I try to make renderText() an async method, it is also turned into a Promise which render() can't handle. No idea where to go from here.
Solved this issue. Solution is to put the loop in an async function that (ideally) gets called on construction. This loop was edited to await the returns and push to local arrays of labels and values then saves those in state. You can compare the length of those arrays to the expected length (compare length of last array being used to be positive that it has finished) and that is how you can know if the Promises have returned. Paraphrased code:
constructor(props) {
this.state = {
translatedLabels = []
translatedValues = []
}
this.asyncFunction()
}
asyncFunction = () => {
labels = []
for loop
label = await promise
labels.push(label)
//same for values
end for
this.setState({translatedLabels: labels})
}
//later
renderText() {
if (this.state.translatedLabels.length === whatever) {
// do your stuff as you know the async function has finished
}
}
render() {
return (
{this.renderText()}
);
}
I am using ReactContext and Hooks to show and hide a Modal on click of a button.
Following is my Context code
const setPrivacyPolicyModalVisibility = dispatch => {
return ({visible}) => {
visible
? dispatch({type: 'enablePrivacyPolicyModalVisibility'})
: dispatch({type: 'disablePrivacyPolicyModalVisibility'});
};
};
And the reducer code for the same is something as follows
case 'enablePrivacyPolicyModalVisibility':
return {...state, ...{enablePrivacyPolicy: true}};
case 'disablePrivacyPolicyModalVisibility':
return {...state, ...{enablePrivacyPolicy: false}};
Some setup code in my class
const {state, setPrivacyPolicyModalVisibility} = useContext(Context);
const [privacyVisibility, setPrivacyVisibility] = useState(false);
on click of button I am calling the following code
<TouchableOpacity
onPress={() => {
setPrivacyVisibility(true);
console.log(`${privacyVisibility}`);
setPrivacyPolicyModalVisibility({privacyVisibility});
}}.....
As you can see I am console logging the privacyVisibility value but it is always false which I fail to understand
Following is my code in the component to hide or show the Modal
{state.enablePrivacyPolicy ? (
<SettingsPrivacyModal visible={true} />
) : (
<SettingsPrivacyModal visible={false} />
)}
The Modal code is proper as I have tried setting default value to true just to check if modal is visible then it works, but on click of button press the state value does not change and I am not able to see the modal as the value is always false
The issue seems to be in the onPress callback:
onPress={() => {
const privacyVisibility_new = !privacyVisibility;
console.log( privacyVisibility_new );
setPrivacyVisibility( privacyVisibility_new );
setPrivacyPolicyModalVisibility( privacyVisibility:privacyVisibility_new );
}}
When the cycle reaches the callback privacyVisibility has the default which is false. I think you are assuming that once setPrivacyVisibility is called, the privacyVisibility variable will have the new value in that same cycle; but it won't have the updated value until the component renders again.
setPrivacyPolicyModalVisibility doesn't seem to be correct. I am not sure where is dispatch exactly, but assuming it is at the same level as the function you can simply use it inside.
const setPrivacyPolicyModalVisibility = visible => {
if ( visible ) {
dispatch({ type: "enablePrivacyPolicyModalVisibility" });
} else {
dispatch({ type: "disablePrivacyPolicyModalVisibility" });
}
};
You might want to simplify your reducer and send directly the visible value:
const setPrivacyPolicyModalVisibility = visible =>
dispatch({ type: "setPrivacyPolicyModalVisibility", payload: visible });
.
case 'setPrivacyPolicyModalVisibility':
return { ...state, is_privacyPolicy_visible: action.payload };
Actually the error was simple. I am using the visible parameter as props in setPrivacyPolicyModalVisibility but while setting I am passing prop of different name
Thanks to #Alvaro for pointing me in the right direction
I am trying to implement an infinite scroll in React Native. Below is the source of the component:
var React = require('react-native');
var server = require('../server');
var Post = require('./Post');
var SwipeRefreshLayoutAndroid = require('./SwipeRefreshLayout');
var backEvent = null;
var lastPostId = "";
var isLoadingMore = false;
var isLoadingTop = false;
var onEndReachedActive = false;
var {
StyleSheet,
ListView,
View,
Text,
Image,
ProgressBarAndroid,
BackAndroid
} = React;
class Stream extends React.Component {
constructor(props) {
super(props);
this.ds = new ListView.DataSource({
rowHasChanged: (row1, row2) => {
console.log("rowHasChenged FIRED!!");
return false;
}
});
this.state = {
dataSource: this.ds.cloneWithRows(['loader']),
hasStream: false,
posts: []
};
}
componentDidMount() {
BackAndroid.addEventListener('hardwareBackPress', () => {
this.props.navigator.jumpBack();
return true;
}.bind(this));
server.getStream('', '', 15).then((res) => {
lastPostId = res[res.length-1].m._id;
this.setState({
posts: res,
hasStream: true,
dataSource: this.ds.cloneWithRows(res)
}, () => onEndReachedActive = true);
})
}
onRefresh() {
var posts = this.state.posts;
var firstPost = posts[0].m._id;
console.log(this.state.dataSource._rowHasChanged);
isLoadingTop = true;
server.getStream('', firstPost, 4000)
.then(res => {
console.log(posts.length);
posts = res.concat(posts);
console.log(posts.length);
this.setState({
dataSource: this.ds.cloneWithRows(posts),
posts
}, () => {
this.swipeRefreshLayout && this.swipeRefreshLayout.finishRefresh();
isLoadingTop = false;
});
}).catch((err) => {
isLoadingTop = false;
})
}
onEndReached(event) {
if(!onEndReachedActive) return;
if(this.state.loadingMore || this.state.isLoadingTop)return;
isLoadingMore = true;
var posts = this.state.posts;
server.getStream(posts[posts.length-1].m._id, '', 15)
.then(res => {
console.log('received posts');
posts = posts.concat(res);
lastPostId = posts[posts.length-1].m._id;
this.setState({
dataSource: this.ds.cloneWithRows(posts),
posts
}, ()=>isLoadingMore = false);
})
}
renderHeader() {
return (
<View style={styles.header}>
<Text style={styles.headerText}>Header</Text>
</View>
)
}
renderRow(post) {
if(post === 'loader') {
return (
<ProgressBarAndroid
styleAttr="Large"
style={styles.spinnerBottom}/>
)
}
let hasLoader = post.m._id === lastPostId;
let loader = hasLoader ?
<ProgressBarAndroid
styleAttr="Large"
style={styles.spinnerBottom}/> : null;
return (
<View>
<Post
post={post}/>
{loader}
</View>
)
}
render() {
return (
<ListView
style={styles.mainContainer}
dataSource={this.state.dataSource}
renderRow={this.renderRow.bind(this)}
onEndReached={this.onEndReached.bind(this)}
onEndReachedThreshold={1}
pageSize={15} />
);
}
}
The problem is that whenever I append (or prepend) new data, the rowHasChanged method of the DataSource doesn't fire. It just re-renders every row, even tho nothing has changed (except the new data).
Any idea why the method is bypassed?
Edit: Pass a function to setState to avoid race conditions
I just figured it out. If you are having the same issue, check the point at which you change your state with the new dataSource. Mine was like this:
this.setState({
dataSource: this.ds.cloneWithRows(posts)
});
Instead you should always use the dataSource from the previous state, like this:
this.setState(state => ({
dataSource: state.dataSource.cloneWithRows(posts)
}))
Cheers!
this worked for me, hope this helps. I created a new dataSource and assigned the updated data to it on state change as follows:`
var dataSource = new ListView.DataSource(
{rowHasChanged: (r1, r2) => ( r1 !== r2)});
this.setState({ dataSource : dataSource.cloneWithRows(posts) });
Now, the new data is assigned and the view is rendered correctly. Note that posts array that is assigned now holds the updated data. Still wondering though if it's the best way to do it but it works!
I agree it seems to make sense that you should always use the dataSource from the previous state.
Yet when I setState this way, rowHasChanged gets called for all rows, however, rowHasChanged always returns false and no rows are rendered??? Why?
// This is callback handler that the ListView DetailView will
// call when a ListView item is edited
onChange(waypoint: Object){
console.log('Callback: rowNumber= ', waypoint.rowNumber);
console.log(' length(m)= ', waypoint.distance.meters);
var itemListChanged = this.state.itemList;
itemListChanged[waypoint.rowNumber-1] = waypoint;
this.setState({
dataSource: this.state.dataSource.cloneWithRows(itemListChanged),
});
},
If I setState this way, renderRow is called for all rows unconditionally without rowHasChanged ever being called. Which is correct?
this.setState({
dataSource: ds.cloneWithRows(itemListChanged),
});
ListView, datasource, and react-native are a hard learning curve coming from C#/C/C++.
for anyone still having issue with rowHasChanged called but are still returning false the following snippets might help
the datasource is initialized like usual:
let ds = new ListView.DataSource ({
rowHasChanged: (a, b) => {
const changed = (a !== b)
return changed
}
})
this.data = []
this.state = {
listDataSource: ds.cloneWithRows(this.data)
}
here is the function which will update a row
updateRow = (row, rowId, sectionId) => {
// make a shallow clone from the stored data array
let blob = this.data.concat()
// modify the row, since we are using the triple equal operator, we need to make sure we are giving it a new object (new ref)
blob[rowId] = Object.assign({}, blob[rowId], {label: blob[rowId].label + '..cape..deh'})
// tell react to update the source
this.setState({
listDataSource: this.state.listDataSource.cloneWithRows(blob)
}, () => {
// we need to update our data storage here! after the state is changed
this.data = blob
})
}
Here is an outline of my code (sparing some details). Basically I just want to make two similar API requests when I click a button, then have a function that works with the results of both the requests, but I cannot figure it out.
class myClass extends Component {
constructor(props) {
super(props);
this.API_KEY_ONE = ‘firstapikey’
this.API_KEY_TWO = ‘secondapikey’
this.state = {
city: 'undefined',
state: 'undefined'
}
}
callOne() {
this.REQUEST_URL = 'https://api.wunderground.com/api/' + this.API_KEY_ONE + '/geolookup/conditions/astronomy/forecast/q/.json';
fetch(this.REQUEST_URL).then((response) => response.json()).then((responseData) => {
this.setState({
city: responseData.location.city
});
}).done();
}
callTwo() {
this.REQUEST_URL = 'https://api.DifferentSite.com/api/' + this.API_KEY_TWO + '/geolookup/conditions/astronomy/forecast/q/.json';
fetch(this.REQUEST_URL).then((response) => response.json()).then((responseData) => {
this.setState({
state: responseData.location.state
});
}).done();
}
// where to put this? when both requests finish, pass both to new component
this.props.navigator.push({
title: 'Forecast',
component: Forecast,
passProps: {city: this.state.city, state: this.state.state}
});
getForecast() {
this.callOne();
this.callTwo();
}
<TouchableHighlight onPress={() => this.getForecast()} />
You can continue with .then() so it should be something like this:
callBoth(){
var request_1_url = 'https://api.wunderground.com/api/' + this.API_KEY_ONE + '/geolookup/conditions/astronomy/forecast/q/.json';
var request_2_url = 'https://api.DifferentSite.com/api/' + this.API_KEY_TWO + '/geolookup/conditions/astronomy/forecast/q/.json';
fetch(request_1_url).then((response) => response.json()).then((responseData) => {
this.setState({
city: responseData.location.city
});
}).then(()=>{
fetch(request_2_url).then((response) => response.json()).then((responseData) => {
this.setState({
state: responseData.location.state,
isFinish: true
});
}).done();
}).done();
}
1) It seems you are using city and state as passProps and not going to refresh the currentView, so maybe you should use them as variables of the current component.
2) You can simply use a variable to record the state of fetching. Like set _finish = 0, when city is fetched, _finish = _finish + 1, and validate whether _finish equals 2. When state is fetched, do the same validate.
fetch(...){
// if _finish is equals 2, navigator push.
}
3) Or you can do it without extra variable:
fetch(...){
if (this._city && this._state){
// navigator push
}
}