How to toggle a boolean value using Context and Hooks? - react-native

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

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.

react-select Creatable: override default behavior upon pressing Enter key

I'm using react-select Creatable for my project, and I need to override its default behavior. Currently whenever the user types something in and presses Enter key, it gets added as a new option.
Is there a way to change it to "whenever the user types something in and presses Space bar, it gets added as a new option"?
If you just do
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
}
}}
...in your component, that'll stop the Enter key from doing anything.
To get the space bar to create a new entry, I think you might want to make a KeyDown handler:
function handleKeyDown(e) {
if (e.key === "Enter") {
e.preventDefault();
e.stopPropagation();
}
if (e.key === "Space") {
handleChange();
}
// in component:
onKeyDown={handleKeyDown}
But I have not tried it yet.
Edit: Solution to keep Enter key from submitting a form the CreatableSelect is embedded within, while still allowing Enter to do react-select's default "create a new entry" behavior:
export function TagEditor({data, handleSave}) {
const [newValues, setNewValues] = useState(...our data mgr);
const [lastKey, setLastKey] = useState('');
const handleChange = (newValue) => {
setNewValues(newValue);
};
function handleKeyDown(e) {
/* React Select hack for pages where tags are inside a form element:
Default CreatableSelect functionality is that Enter key creates
the new tag. So we let that happen, but if they hit Enter again,
we kill it so the form doesn't submit. Yes, it's hack-y.
*/
switch (e.key) {
case 'Enter':
if (lastKey === 'Enter') {
e.preventDefault();
e.stopPropagation();
}
setLastKey('Enter');
break;
default:
setLastKey('');
}
}
return <CreatableSelect
aria-label="Add Tags"
autoFocus={true}
blurInputOnSelect={false}
defaultValue={...our data manager function}
inputId="tagmanager"
isClearable={false}
isMulti
name="my_field_name"
onBlur={(e) => {
e.preventDefault();
e.stopPropagation();
handleSave(newValues);
}}
onChange={handleChange}
onKeyDown={handleKeyDown}
openMenuOnFocus={true}
options={data}
placeholder="Select or type new..."
searchable
styles={our styles...}
/>;
}
Note this hack is needed because we're using stateless functional components, and react-select is still a state-full class component.

How to differentiate between double tap and single tap on react native?

I need to perform different action on single and double tap on a view. On double tap I need to like the image just like Instagram double tap experience. On single tap I need to open a modal.
For double tap I have used TapGestureHandler which works perfect
<TapGestureHandler
ref={this.doubleTapRef}
maxDelayMs={200}
onHandlerStateChange={this.onProductImageDoubleTap}
numberOfTaps={2}
>
<SomeChildComponent ...
But when I add any Touchable to detect single tap in the
<TouchableWithoutFeedback onPress={this.imageTapped}>
on double tapping the this.imageTapped function is called twice along with this.onProductImageDoubleTap. Is there any way to cancel tap on touchable when two taps are done is quick succession
The best solution is not using state, since setting state is asynchronous.Works like a charm for me on android !
let lastPress = 0;
const functionalComp = () => {
const onDoublePress = () => {
const time = new Date().getTime();
const delta = time - lastPress;
const DOUBLE_PRESS_DELAY = 400;
if (delta < DOUBLE_PRESS_DELAY) {
// Success double press
console.log('double press');
}
lastPress = time;
};
return <View
onStartShouldSetResponder =
{(evt) => onDoublePress()}>
</View>
}
2022 update
This is a performant native solution without any JS thread blocking calculation!
Many more tips here
const tap = Gesture.Tap()
.numberOfTaps(2)
.onStart(() => {
console.log('Yay, double tap!');
});
return (
<GestureDetector gesture={tap}>
{children}
</GestureDetector>
);
The best solution is use react-native-gesture-handler
https://github.com/software-mansion/react-native-gesture-handler
Here is my solution -
import {State, TapGestureHandler} from 'react-native-gesture-handler';
export const DoubleTap = ({children}: any) => {
const doubleTapRef = useRef(null);
const onSingleTapEvent = (event: any) => {
if (event.nativeEvent.state === State.ACTIVE) {
console.log("single tap 1");
}
};
const onDoubleTapEvent = (event: any) => {
if (event.nativeEvent.state === State.ACTIVE) {
console.log("double tap 1");
}
};
return (
<TapGestureHandler
onHandlerStateChange={onSingleTapEvent}
waitFor={doubleTapRef}>
<TapGestureHandler
ref={doubleTapRef}
onHandlerStateChange={onDoubleTapEvent}
numberOfTaps={2}>
{children}
</TapGestureHandler>
</TapGestureHandler>
);
};
Now we will wrap the component where we need to detect the double and single tap : -
<DoubleTap>
<View>
...some view and text
</View>
</DoubleTap>
The package react-native-double-tap seems to be what you are looking for.
since you are asking on handling one tap and double tap, here's a simple code i think should covered your issue
Untested
first defined clickCount:0 in state:
state={clickCount:0, //another state}
then create a function with setTimeout to handling if user tapping once or two times:
handlingTap(){
this.state.clickCount==1 ?
//user tap twice so run this.onProductImageDoubleTap()
this.onProductImageDoubleTap :
//user tap once so run this.imageTapped with setTimeout and setState
//i set 1 sec for double tap, so if user tap twice more than 1 sec, it's count as one tap
this.setState({clickCount:1}, ()=>{
setTimeout(()=>{
this.setState({clickCount:0})
this.imageTapped()
}, 1000)
})
}
<TouchableWithoutFeedback onPress={this.handlingTap()}/>
just used TouchableWithoutFeedback instead of TapGestureHandler
With hooks:
const [lastPressed, setLastPressed] = useState(0);
const handlePress = useCallback(() => {
const time = new Date().getTime();
const delta = time - lastPressed;
setLastPressed(time);
if (lastPressed) {
if (delta < DOUBLE_PRESS_DELAY) {
console.log('double press');
} else {
console.log('single press');
}
}
}, [lastPressed]);
I have modified flix's answer into this. By this way, you can catch one and double click separately. I also changed debounce into 300ms which is fairly well for one and double click.
state={clickCount:0, //another state}
Binding context into the handlingTap method
constructor(props){
super(props)
this.handlingTap = this.handlingTap.bind(this)
}
With this function you can catch them separately
handlingTap() {
this.state.clickCount === 1
? this.doubleClick() // This catches double click
: this.setState(state => ({ clickCount: state.clickCount + 1 }), () => {
setTimeout(() => {
if (this.state.clickCount !== 2) {
this.oneClick() // this catches one click
}
this.setState({ clickCount: 0 })
}, 300)
})
}
In the button you can use this way
<TouchableWithoutFeedback onPress={this.handlingTap}></TouchableWithoutFeedback>

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])
...
}

How to programmatically switch a switch in React Native?

I make us several Switch Components in one view. When I switch on one switch, I want all others to switch off. Currently, I set the boolean value property via the state. This results in changes happen abruptly because the switch is just re-rendered and not transitioned.
So how would you switch them programmatically?
EDIT 2: I just discovered that it runs smoothly on Android so it looks like an iOS-specific problem.
EDIT: part of the code
_onSwitch = (id, switched) => {
let newFilter = { status: null };
if (!switched) {
newFilter = { status: id };
}
this.props.changeFilter(newFilter); // calls the action creator
};
_renderItem = ({ item }) => {
const switched = this.props.currentFilter === item.id; // the state mapped to a prop
return (
<ListItem
switchButton
switched={switched}
onSwitch={() => this._onSwitch(item.id, switched)}
/>
);
};