React native state not updating - react-native

I feel like Im going crazy here.....
I type in "x" to the searchbar but this.setState({ filter: text }); is not updating state.... console.log(this.state.filter); gives me a value of '' (its initial value set in constructor).
I can see the value of text variable is x...but its just not updating the filter state.
constructor(props) {
super(props);
this.state = {
timer: 30 * 60,
lat: 0,
lng: 0,
loading: false,
data: [],
pagetoken: null,
refreshing: false,
waiting4answer: false,
placeName: null,
placeUri: null,
filter: '',
};
}
renderHeader = () => {
if (this.state.waiting4answer === false) return null;
return (
<SearchBar
placeholder="Type Here..."
lightTheme
round
onChangeText={(text) => {
this.setState({ filter: text });
console.log(this.state.filter);
this.searchFilterFunction();
}}
autoCorrect={false}
/>
);
};

The state is updating, it's just the way that you are checking and using the updated state is flawed.
State is Asynchronous
State is asynchronous, it takes time for the state to set. The problem you are encountering is that you are calling setState and then immediately logging the value from state. Unfortunately when you do this the state will not have updated by this time you log it, so you are getting the previous value.
State takes a callback
If you want to use the value of state that you have just set then you need to use the callback function that setState has. this.setState({key: value}, callback)
This callback allows you to run functions after the state has been set. You can use it in the following way:
this.setState({filter: text}, () => {
// put the things you wish to occur after you have set your state.
console.log(this.state.filter);
// I am guessing your searchFilterFunction requires the updated filter value in state
// so put that here too
this.searchFilterFunction();
});
This means that once you set the value of filter in the state and the state has updated to reflect that value it will then run the callback, so it will log the value of filter that is now in state, and then it will run the searchFilterFunction.
Further reading
You can read more about state in this series of great articles by Michael Chan
https://medium.learnreact.com/setstate-is-asynchronous-52ead919a3f0
https://medium.learnreact.com/setstate-takes-a-callback-1f71ad5d2296
https://medium.learnreact.com/setstate-takes-a-function-56eb940f84b6

use the setState callback to get the updated state immediately after setting it
this.setState({ filter: text }, () => {
console.log(this.state.filter);
});
react setState is asynchronous

Related

setState in React Native not causing a re-render

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.

Is there a cleaner way to organize this recoilJS state?

I've just learned about recoilJS and have been playing around with it a bit and have a question about whether what I've done is considered "correct." My code works, but it feels weird.
I've got the following React function component:
export const TimerPanel: FC = () => {
const gameState = useRecoilValue<GameState>(HeaderAtoms.gameState);
const timerCount = useRecoilValue(HeaderAtoms.timerCount);
const setTimerCounter = useSetRecoilState(HeaderAtoms.timerCounter);
useEffect(() => {
if (gameState === GameState.IN_PROGRESS && timerCount < 1000) {
window.setTimeout(() => {
setTimerCounter(timerCount + 1);
}, 1000);
}
});
return <NumberPanel num={timerCount} />;
};
where the relevant atom and selector are defined as:
export const timerCount = atom<number>({
key: 'Header.timerCount',
default: 0
});
export const timerCounter = selector<number>({
key: 'Header.timerCounter',
get: ({ get }) => {
return get(timerCount);
},
set: ({ get, set }, newCount) => {
if (get(gameState) === GameState.NEW) {
set(timerCount, 0);
} else if (get(gameState) === GameState.IN_PROGRESS) {
set(timerCount, newCount);
}
}
});
Basically, when the game starts, the TimerPanel increments the timer display by 1 every second the game is in progress. If the user resets the game (GameState.NEW), timerCount resets back to zero. If the atom/selector aren't done properly, there's a race condition in that the game state and timer count will reset, but the timer is still going and will still update the TimerPanel one more time. This is why I have the if blocks in my selector's set prop.
Basically, I'm concerned that my timerCounter selector is a glorified filter/pass-thru entity for the timerCount state and am wondering if there's a better way to handle this use case.
If your concern is the race condition, in your code, I don't see how your timer would click. setTimeout will tick once.
Anyway, you have 2 states, game state and timer state. You want to reset timer when game state changes. How about doing it in a game state selector? or move the logic into a custom hook and operate on these 2 states directly.

How to toggle a boolean value using Context and Hooks?

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

Need assistance fixing maximum update depth exceeded issue

I'm trying to write a hacky fix to ScrollableTabView since it isn't playing nice with the function that triggers when there's a tab switch. When I replace the setState with console.log I see that it only triggers once with every tab switch so it's not looping infinitely like the error is complaining.
Parent container
state = {
headerName: 'Loading',
}
setHeader = (header) => {
this.setState({'headerName': header})
}
render () {
return (
<ScrollableTabView
renderTabBar={() => <BottomTabBar setHeader={this.setHeader} headerNames={['A','B','C']} />}
>
)
}
BottomTabBar
render() {
this.props.setHeader(this.props.headerNames[this.props.activeTab])
...
}

My State value is not Changing ,when button is pressed

onButtonPress = () => {
min = 1000;
max = 9999;
randomotp = min + (Math.random() * (max - min));
console.log(Math.round(randomotp));
this.setState({
otpfield:'#48A23A'
});
console.log('result',this.state.otpfield);
}
}
I have been executing this function in the Button on click , but the corresponding (i,e Otpfield) value is not changing
React does not update the state immediately, it's an asynchronous operation. Therefore your console.log() call is invoked too fast and the state is not yet changed.
Use the "afterUpdated" callback instead (second parameter of the setState method):
this.setState(
{property: true},
() => console.log(this.state.property)
);
The state will not change. onButtonPress function must finish executing first before changing the state.
use componentDidUpdate lifecycle method to check if the state changed
componentDidUpdate(prevProps, prevState) {
console.log('result',this.state.otpfield);
//new State
console.log('result',prevState.otpfield);
// prev State
}