Change SVG component on press with React Native - react-native

Background
Pretty simple question: I want to create a "like" button in RN. To do this I have to separate components which are SVG files. One is just the outline of a heart, the other one is filled.
The screen in which I'm trying to build this button is a Function component so I should use hooks. I know about state but don't know how to properly use it.
What I need
A Touchable Opacity component which holds an onPress method which changes the image component when pressed.
Thanks a lot in advance!

import React ,{useState} from 'react';
import {TouchableOpacity } from "react-native";
export default function Like() {
const [isLiked,setIsLiked]=useState(false) ;
const handleLikePress=()=>{
setIsLiked(!isLiked)
}
return (
<TouchableOpacity onPress={handleLikePress}>
{isLiked? <FilledHeartSVG/>: <OutlineHeartSVG/> }
</TouchableOpacity>
)
}
by default, we are showing an outline of a heart SVG
when press event trigger we are changing isLiked state value
if isLiked is true then show filled heart SVG
if isLiked is false then show outline of a heart SVG
FilledHeartSVG and OutlineHeartSVG is just example use your SVG there

You can do something like below, here i have made a toggle for the text but you can change it to your image component, also the callback prop can be used if you want to use that outside the LikeButton
const LikeButton = ({callback}) => {
const [liked, setLiked] = React.useState(false);
return (
<TouchableOpacity onPress={()=>{
setLiked(!liked);
if(callback)
{
callback();
}
}}>
<Text>{liked?"Liked":"Like"}</Text>
</TouchableOpacity>
);
};
You can tryout this snack which uses icons
https://snack.expo.io/#guruparan/likebutton

Related

How can I toggle a dark theme for a stack of screens?

I have included a snack expo here that reproduces the exact layout of my project. App.js -> stack of screens. I am trying to figure out the best way to toggle between light theme and dark theme. I do not care for the ios dark theme setting to trigger this, for now, I just want the button to trigger it.
I would like there to be a button displayed on my home screen that when pushed toggles to the theme it is not currently on. I originally wanted to use theme provider to set my colors for each theme and then call that color on each page rather than the hard coded color but have failed to do so.
When you have a stack of screens being called in a tab navigator, what is the best way to use a dark theme?
You need to use the theme prop of the NavigationContainer but your are still responsible for providing the styling for each of your components.
That said, there are a couple of things we need to do here.
Let the button change the theme prop of the NavigationContainer.
Provide a custom styling (colors etc.) for all of your components depending of the theme.
We can solve the first problem by wrapping the NavigationContainer into a ContextProvider as follows.
import React, {useState} from 'react';
import {
DefaultTheme,
DarkTheme,
NavigationContainer,
} from '#react-navigation/native';
export const ThemeContext = React.createContext();
export default function App() {
const [theme, setTheme] = useState(DefaultTheme)
return (
<ThemeContext.Provider value={{theme, setTheme}}>
<NavigationContainer theme={theme}>
</NavigationContainer>
</ThemeContext.Provider>
);
}
In your Toggle component you can then change the theme dynamically as follows.
import {ThemeContext} from '../App'
import {
DefaultTheme,
DarkTheme,
} from '#react-navigation/native';
const Toggle = (props) => {
const { setTheme, theme } = React.useContext(ThemeContext);
return (
<TouchableOpacity onPress={() => setTheme(theme === DefaultTheme ? DarkTheme : DefaultTheme)}>
</TouchableOpacity>
);
}
You will notice that changing the theme will not change anything in your current setup. This is because you need to take care of this yourself.
This could be done using either the context again or the useTheme hook. For your HomeScreen this could be done as follows.
import { useTheme } from '#react-navigation/native';
const Home = () => {
const { colors } = useTheme();
return (
<View style={{justifyContent: 'center', alignItems: 'center', flex: 1, backgroundColor: colors.background}}>
<Toggle/>
<Screen/>
</View>
);
}
Toggling the button changes the theme now. You can access the default theme colors using the useTheme hook but you still need to apply it to your components. You could also provide a custom theme which is done similar.
I would suggest doing something similar to this:
https://www.section.io/engineering-education/how-to-control-dark-mode-in-react-native-using-redux/.
In the article the author uses React Native Paper to control what each of his screens look like. I personally have a theme.js file that has an if statement based on what my Redux state is set to. If it is to light mode, my global theme variables (e.g. background color, text color, etc.) are set to their respective light colors. When set to dark mode, my global theme variables are set to their respective dark colors.

How to make child prevent ScrollView from scrolling

Context:
I have a graph, where I allow a user to "scrub" their finger over the graph, and see a tooltip
This graph is nested inside a ScrollView
<ScrollView>
<Graph>
</ScrollView>
Problem:
I want to "disable" the scroll view when the touch is happening over that graph
I'm not sure how to do that.
function Graph() {
return (
<View onTouchStart={e => /* prevent ScrollView from scrolling */} />
)
}
I know about scrollEnabled on ScrollView, but it won't be easy for me to thread that prop. Is there a way I can just "stop propagation" for that touch event, inside Graph?
onTouchStart={(e) => e.stopPropagation()} does not do the trick
My current solution is the following:
import React, { createContext, useRef } from "react";
import { ScrollView } from "react-native";
export const ScrollEnabledContext = createContext(null);
export default function StoppableScrollView(props) {
const ref = useRef(null);
const setIsEnabled = (bool) => {
ref.current && ref.current.setNativeProps({ scrollEnabled: bool });
};
return (
<ScrollEnabledContext.Provider value={setIsEnabled}>
<ScrollView ref={ref} {...props} />
</ScrollEnabledContext.Provider>
);
}
By using setNativeProps, I prevent a render. I can reference ScrollEnabledContext in the child component to prevent scrolls. A bit brittle, but gets the job done. Would be fantastic if I didn't have to do this, and could use something like stopPropagation

Is it possible to render two headers on one screen using React Navigation?

I'm writing an iPad / Android Tablet app with React Native and I'm using React Navigation for the navigation.
The mock up I received has a screen which is split in half (in landscape mode) with two different headers. Is is possible to achieve this with react navigation?
What I've tried is setting header: null in the navigation options for the screen, and then rendering two components on that split screen, each of the components has React Navigation's Header component as the first child. This somehow does not work (the header does not get rendered).
Like you can see in the source of Header it does not render if the header option is null:
_renderHeader(props) {
const { options } = props.scene.descriptor;
if (options.header === null) {
return null;
}
...
}
So using the Header component manually will not work.
You could just create your own Header components with a title and a "back" touchable. If a designer gave you a mock up I assume the Header must be styled very differently from original Header component anyway :)
Your component could look like this:
export default class MyCustomHeader extends Component {
handlePress = () => {
const { navigation } = this.props;
navigation.goBack(); // or .navigate() to whatever you want
};
render() {
return (
<View>
<TouchableHighlight onPress={this.handlePress}>
<some-icon>
</TouchableHighlight>
<Text>My title</Text>
</View>
);
}
}
With proper styling of course :)

Pass touch event to view's parent in react native.

I have a <view> touching which will open a collapsible view. I have used react-native-collapse-view for it (https://www.npmjs.com/package/react-native-collapse-view) I have <text> on top of <view> which is covering it full. I am setting some conditions in onPress event of the <text> element.
Now what I want is if I touch the <text> (obviously I cannot touch view as text is covering it fully), along with onPress event of the <text> the underlaying <view> should also be touched so that it opens the collapsible view.
In short I want to pass the touch event to the parent view in order to complete all work in one touch. I searched and found some content related to onStartShouldSetResponder and pointerEvents but I couldn't get the complete grip as I am a newbie to react native.
in short you need pass function to children for handling events
import React from "react";
class ChildComponent extends React.PureComponent {
render() {
const {handleChildPress} = this.props;
return <Text onPress={handleChildPress}/>;
}
}
class ContainerComponent extends React.PureComponent {
// you need pass this func to children
// =()=> means bind it
handleChildPress=()=>{
console.log('child pressed')
}
render() {
return <ChildComponent handleChildPress={this.handleChildPress}/>;
}
}
export default ContainerComponent

Changing Drawer left and right button image dynamically?

react-native-router-flux v3.37.0
react-native v0.42.0
I'm trying to update drawer navigation bar right image dynamically where I have used leftButtonImage, rightButtonImage, where once user read all the notification I want to change the button image.
I could not manage to re-render or update this button image, Is this feature not supported or is there something that I'm missing?
You can call Actions.refresh when you need to refresh the view, example:
Actions.refresh({key: 'profileView', renderRightButton: this.renderRightButton });
and also define renderRightButton:
renderRightButton() {
return (
<TouchableOpacity onPress={ console.log(this) } >
<Text>Logout</Text>
</TouchableOpacity>
)
}
and lastly don't forget to import Actions from react-native-router-flux
import {Actions} from 'react-native-router-flux';
You can re-render using props or states naturally.
Check the states you want, and apply image resource following the states.(or props)
let NotiImage = {
normal: require('../assets/image/notinormal.png');
new: require('../assets/image/new.png');
}
render() {
...
<Image source={ this.state.newNoti ? NotiImage.new : NotiImage.normal } />
...
}