I just began playing around with React Native and I was trying to figure out how to disable and enable a touchableOpacity when the lengths of two inputTexts are > 0.
On my touchableOpacity I have it set to disabled={true} and it has a children Text with opacity set. When a user types into the textInput's I want to set disabled={false} and change the Text opacity to 1.
What's the best way to do this in react native?
Should i put ref's on them?
Should I use a className?
setNativeProps maybe?
If your constructor's state contains a flag for "isDisabled" and "textOpacity", then you can call setState in the onChangeText function that will change the state of isDisabled and textOpacity. The text component can use the opacity from the state's textOpacity and the touchableOpacity can use the state's isDisabled.
Ex:
`class foo extends Component {
constructor(props) {
super(props);
this.state = {
isDisabled: true,
textOpacity: 0.1,
text1: '',
text2: '',
};
onTextChange1(text) {
if (text.length > 0 && this.state.text2.length > 0) {
this.setState({isDisabled: false, textOpacity: 1, text1: text});
}
else {
this.setState({isDisabled: true, textOpacity: 0.1, text1: text});
}
}
onTextChange2(text) {
if (text.length > 0 && this.state.text1.length > 0) {
this.setState({isDisabled: false, textOpacity: 1, text2: text});
}
else {
this.setState({isDisabled: true, textOpacity: 0.1, text2: text});
}
}
render() {
return (
<View>
<TextInput onChangeText={(text) => this.onTextChange1(text)} value={this.state.text1}/>
<TextInput onChangeText={(text) => this.onTextChange2(text)} value={this.state.text2}/>
<TouchableOpacity disabled={this.state.isDisabled}>
<Text style={{opacity:this.state.textOpacity}}>Hello World!</Text>
</TouchableOpacity>
</View>
);
}
}
I have not tested the above, but I believe this is basically what you are looking for. We perform a check in the on text change to see if a certain condition is met, and depending on if it is, we change the parameters of the child components as you stated. Calling setState and modifying props are the only ways to trigger a rerender, so this is how in react-native we work with rerendering components.
if you want your opacity to be able to change depending on user moves, you need to set it(opacity) in state of your parent component.You can do this for example:
export class Parent extends React.Component {
constructor(props) {
super(props);
this.state = { opacity: 0 }
}
render() {
Some components ....
<TextInput style={{opacity: this.state.opacity}} onChangeText={ () => this.setState({opacity: 1}) } ...other props here .../>
Some components ....
}
}
you can also apply other styles to your TextInput component.
Related
I'm using the react-native-reanimated lib together with react-native-gesture-handler to zoom some elements on the screen.
I've added a react-native-slider inside their shared parent component, to give some extra help (if the item is too small it's hard to pinch it).
I can pinch the element and then slide it, but then I can't go back again to the pinchGestureHandler, the image become zoomable only with the slider.
Export default class Zoomable extends Component {
constructor(props) {
super(props);
this.zoomableItem = props.zoomableItem;
this.Z = new Value(1);
const offsetZ = new Value(1*this.moveableItem.lastScale);
this.handleZoom = event([
{
nativeEvent: ({ scale: z, state }) =>
block([
cond(eq(state, State.ACTIVE), set(this.Z, multiply(z, offsetZ))),
cond(eq(state, State.END), [set(offsetZ, multiply(offsetZ, z))]),
]),
},
]);
}
componentDidUpdate(){
props.slider.value ? this.Z = props.slider.value : null
}
render() {
return (
<Animated.View style={[Styles.container,{transform: [{ scale: this.Z }] } ]}>
<PinchGestureHandler
ref={this.pinchRef}
onGestureEvent={this.handleZoom}
onHandlerStateChange={this.handleZoom}>
<Animated.Image style={Styles.image} source={{ uri: '' }}/>
</PinchGestureHandler>
</Animated.View>
);
}
}
this.Z is an animated object so I forgot to assign it a new value with the proper method:
this.Z.setValue(props.slider.value)
I've made a custom Radio Button in React Native. Below is the link to my code.
https://gist.github.com/shubh007-dev/0d8a0ca4d6f7d1530f3e28d223f9199e
What I want is to animate the radio button when I press it, like it's done in this library - https://www.npmjs.com/package/react-native-simple-radio-button
I'm able to animate my radio button for the first time but when I press another radio button, animation doesn't happen.
(Another approach for this question) How can I make sure that the Animated value is different for each Radio Button?
You either have to make a custom component for the radio or use x animated variable for x radio buttons.
Now, making x variable for x buttons is not an efficient solution but it could be used if you got only a few buttons.
You made a custom component which renders a flatlist and that's the problem; can't animate buttons separately in the same component you use to render them.
You should split your code and make the radio button a component itself.
Something like that (didn't test it, but that way it should work) :
export class RadioButton extends Component {
constructor(props) {
super(props);
this.state = {
radioSelected: this.props.selectedItemId,
};
}
radioClick = id => {
this.setState({ radioSelected: id });
this.props.onChange(id);
}
renderRadioButton = item => {
const { radioSelected } = this.state;
const { labelLeftStyle, labelRightStyle, labelOnRight } = this.props;
return (
<AnimatedRadio
{...item}
isSelected={item.id === radioSelected}
labelLeftStyle={labelLeftStyle}
labelRightStyle={labelRightStyle}
labelOnRight={labelOnRight}
radioClick={this.radioClick}
/>
);
};
render() {
return (
<FlatList
data={this.props.radioOptions}
extraData={this.state}
renderItem={({ item }) => this.renderRadioButton(item)}
keyExtractor={(item, index) => index.toString()}
/>
);
}
}
export class AnimatedRadio extends Component {
springValue = new Animated.Value(1.1);
onRadioClick = id => () => {
const { radioClick } = this.props;
radioClick(id);
this.spring();
};
spring = () => {
Animated.spring(this.springValue, {
toValue: 0.95,
friction: 2,
tension: 15,
}).start();
};
render() {
const {
id,
label,
labelLeftStyle,
labelRightStyle,
labelOnRight,
isSelected,
} = this.props;
return (
<View key={id} style={STYLES.radioContainerView}>
<TouchableOpacity
style={STYLES.radioButtonDirectionStyle}
onPress={this.onRadioClick(id)}
>
{labelOnLeft == true ? (
<Text style={[labelLeftStyle]}>{label}</Text>
) : null}
<View
style={[isSelected ? STYLES.selectedView : STYLES.unselectedView]}
>
{isSelected ? (
<Animated.View
style={[
STYLES.radioSelected,
{ transform: [{ scale: this.springValue }] },
]}
/>
) : null}
</View>
{labelOnRight == true ? (
<Text style={[labelRightStyle]}>{label}</Text>
) : null}
</TouchableOpacity>
</View>
);
}
}
This way each component radio will have its own animated value, and won't interfere with others buttons.
I did it using LayoutAnimation like below.
LayoutAnimation.configureNext({
duration: 300,
create: {
type: 'linear',
property: 'scaleXY',
},
update: {
type: 'spring',
springDamping: 0.4,
property: 'opacity',
},
delete: {
type: 'easeOut',
property: 'opacity',
},
});
How can I force reload certain or all react Components when a particular value is selected using Picker?
import React from 'react';
import { Picker, Text, View } from 'react-native';
export default class Test extends React.Component {
render(){
return (
<View>
<TestComponent />
<Text>
{defaultKey + ' from defaultKey from main class'}
</Text>
</View>
);
}
}
let myArr = [
{
"key": "key0",
"value": "value0"
},
{
"key": "key1",
"value": "value1"
}
];
let defaultKey = "key0";
class TestComponent extends React.Component {
constructor (props){
super(props);
this.state = {
selectedValue : "value0",
PickerValueHolder : "value0"
}
}
render(){
return (
<View>
<Picker
selectedValue ={ this.state.PickerValueHolder }
onValueChange ={ (itemValue, itemIndex) => {
this.setState({ PickerValueHolder: itemValue });
defaultKey = myArr[itemIndex].key;
defaultIndex = itemIndex;
}
}
>
{ myArr.map((item, key)=>(
<Picker.Item label={ item.key } value={ item.value } key={ key } />)
)}
</Picker>
<Text>
{defaultKey + ' from defaultKey from TestComponent'}
</Text>
</View>
);
}
}
In this case, the <Text> in the default class is not reloading. How do I make it reload? And how can I force reload Imported components as well?
React Components renders / refresh whenever state / props changes. If you want to re render current component change it's state. If you want to render the child component change it's props. If you want to re render the parent component use a callback and modify the state in callback.
You could use global state management like redux, but if you're unfamiliar with that, you can send a handler into the Test Component as a callback to the Parent via the props.
Changing current component value use
this.state
Changing a child component value use and pass the prop in by
Inserting Props to child:
<ChildComponent prop1={value}/>
Getting the value from the child component:
this.props.prop1
Updating the Parent state is similar to the Above but requires the prop to be a function passed from the parent.
handler () {// some function in parent component}
<ChildComponent prop1={this.handler}/>
It would mean it would look something like below:
import React from 'react';
import { Picker, Text, View } from 'react-native';
export default class Test extends React.Component {
constructor(props) {
super(props)
this.handler = this.handler.bind(this);
this.state = {
defaultKey : "somevalue"
};
}
handler(value) {
this.setState({
defaultKey: value
})
}
render(){
return (
<View>
<TestComponent handler={this.handler}/>
<Text>
{this.state.defaultKey + ' from defaultKey from main class'}
</Text>
</View>
);
}
}
let myArr = [
{
"key": "key0",
"value": "value0"
},
{
"key": "key1",
"value": "value1"
}
];
let defaultKey = "key0";
class TestComponent extends React.Component {
constructor (props){
super(props);
this.state = {
selectedValue : "value0",
PickerValueHolder : "value0"
}
}
render(){
return (
<View>
<Picker
selectedValue ={ this.state.PickerValueHolder }
onValueChange ={ (itemValue, itemIndex) => {
this.setState({ PickerValueHolder: itemValue });
defaultKey = myArr[itemIndex].key;
defaultIndex = itemIndex;
this.props.handler(defaultKey);
}
}
>
{ myArr.map((item, key)=>(
<Picker.Item label={ item.key } value={ item.value } key={ key } />)
)}
</Picker>
<Text>
{defaultKey + ' from defaultKey from TestComponent'}
</Text>
</View>
);
}
}
UPDATE:
The OP has asked about why to use redux (as some of the components are not nested through the Test class.
What is redux?
Redux is a predictable state container for JavaScript apps.It helps you write applications that behave consistently.
Check out the docs here: https://redux.js.org/
Essentially, you've entered an issue a lot of people face when building an application that needs to share state between components in React/React Native. In every component you have a local state (this.state) - Redux includes a global App State, therefore in your classes where you change your default value, you can update the global store (which all components have access to). The value which you display the defaultValue would be from the global store.
Essentially, components which are not related to each other, will not know about each others state you'll need to use a global store such as redux. I suggest you do some research on the best technique for you. One way you could achieve it at present would be to wrap the whole application in a parent component and pass the props down from there, such as
-- ParentComponent (set state here)
-- ChildComponent1 (Pass Props)
-- ChildComponentOf1 (Pass Props)
-- ChildComponent2 (Pass Props)
For some further reading check out some posts here:
How to update parent's state in React?
How to share state among multiple scenes of a Navigator in React-Native
I'm trying to toggle view's opacity with animated value, by handling the button click, but I'm not getting the desired result, except the first time button is clicked, it fades out (opacity = 0) but when I press the button again nothing happens and I can't see my view. Here's the code:
export default class App extends React.Component {
state = {
animation: new Animated.Value(1)
}
startAnimation = () => {
const { animation } = this.state
Animated.timing(animation, {
toValue: animation === 0 ? 1 : 0,
duration: 1000
}).start()
}
render() {
const animatedStyle = {
opacity: this.state.animation
}
return (
<View style={styles.container}>
<Animated.View style={[styles.box, animatedStyle]} />
<Button title="Toggle fade" onPress={this.startAnimation} />
</View>
);
}
} .
Does anybody know what am I doing (understanding) wrong?
Thanks!
I think it is because you don't change the state for your animated values, and this const { animation } = this.state will have always the same value, and toValue: animation === 0 ? 1 : 0, will have the same value too. I try to show you how I did this in my projects, but you have to update it for your needs.
export default class App extends React.Component {
state = {
animation: new Animated.Value(1),
isVisible: false //add a new value to check your state
}
startAnimation = () => {
const { isVisible } = this.state
Animated.timing(animation, {
toValue: isVisible === 0 ? 1 : 0,
duration: 1000
}).start(() => {
this.setState({ isVisible: !this.state.isVisible });//set the new state, so the next click will have different value
})
}
render() {
const animatedStyle = {
opacity: this.state.animation
}
return (
<View style={styles.container}>
<Animated.View style={[styles.box, animatedStyle]} />
<Button title="Toggle fade" onPress={this.startAnimation} />
</View>
);
}
} .
I am using this:
let val = this.state.sliderOpen ? 0.8 : 0;
Animated.timing( // Animate over time
this.state.sliderAnimation, // The animated value to drive
{
toValue: val, // Animate to opacity: 1 (opaque)
duration: 5, // Make it take a while
}
).start();
this.setState({
sliderOpen : !this.state.sliderOpen
})
Maybe try to extract the value to be changed.
Thanks to #oma I was able to get it work, here's the snack:
Toggle opacity in React Native
Besides that, I've found a nice article on this where this feature can be reused:
Animating appearance and disappearance in React Native
And here's the snack of the working example, with slight modification.
Animate opacity
This solution looks pretty well, hope you can benefit from it.
I made a node package react-native-fade-in-out that toggles a view's opacity with an animated value. You can look at the source code to see how it is accomplished, but here's a simplified version:
import React, {PureComponent} from 'react';
import {Animated} from 'react-native';
export default class FadeInOut extends PureComponent {
state = {
fadeAnim: new Animated.Value(this.props.visible ? 1 : 0),
};
componentDidUpdate(prevProps) {
if (prevProps.visible !== this.props.visible) {
Animated.timing(this.state.fadeAnim, {
toValue: prevProps.visible ? 0 : 1,
duration: 300,
}).start();
}
}
render() {
return (
<Animated.View style={{...this.props.style, opacity: this.state.fadeAnim}}>
{this.props.children}
</Animated.View>
);
}
}
I am working with react native.
I have component listing by using
And, when the state to give data to update the list change. It won't update immediately. It take few seconds to re-render.
so, how can I update the component immeidately
//Listcomponent
const ListGlossary = ({glossaries, onPressGlossary, navigation, searchField}) => {
return (
<FlatList
data={glossaries}
keyExtractor={(item) => item.key}
renderItem={({item}) =>
<TouchableHighlight
onPress = {() => navigation.navigate('DetailGlossaryScreen', { searchField: searchField, word: item.word, translate: item.translate})}>
<ListItem
key={`${item.key}`}
title={`${item.word}`}
/>
</TouchableHighlight>
}
/>
}
//And you can find here the home screen component
class HomeScreen extends React.Component {
constructor(props) {
super(props);
this.state = {
isLoading: true,
glossaries: [],
searchField: '',
}
}
componentDidMount () {
Promise.resolve().then(() => {this.setState({glossaries: JSONDataFromFile, isLoading: false})})
}
onSearchChange = (inputText) => {
this.setState({searchField: inputText});
}
render(){
return(
let filteredWords = []
if(this.state.searchField != null) {
let searchField = this.state.searchField.toLowerCase(),
glossaries = this.state.glossaries;
for(let i = 0, l = glossaries.length; i < l; ++i) {
if(glossaries[i].word.toLowerCase().indexOf(searchField) === 0){
filteredWords.push(glossaries[i]);
}
}
}
{this.state.isLoading ?
<View style={{flex: 1, paddingTop: 20}}>
<ActivityIndicator />
</View>
:
<ListGlossary
navigation = {this.props.navigation}
glossaries = {filteredWords}
onPressGlossary={this.onPressGlossary}
searchField = {this.state.searchField}
/>
}
)
}
Please show the whole component, and give the length of the list.
--- Edit
I suspect you're doing too much work in the render function. You're filtering every time it gets called, and since you're passing in the navigation prop (I assume you're using React-Navigation), it'll get called frequently. If you're using a stack navigator, all the other screens are also getting re-rendered every time you navigate to a new screen. Avoid passing navigation as much as possible, or use a HOC composition to ignore it.
You probably don't need to be filtering glossaries every time the user changes the search value. Use the shouldComponentUpdate lifecycle method.