React Native conditionally render part of view while input is focused - javascript

I have the following scenario.
function MyComponent() {
return (
<View>
<TextInput ref={ref => (this.input = ref)} style={styles.input} />
{this.input.isFocused() && <Text>Hello World</Text>}
</View>
);
}
This actually works fine, but I get the warning:
MyComponent is accessing findNodeHandle inside its render. render
should be a pure function.
How do I conditionally render The text block and avoid this warning?

You can use component state :
class MyComponent extends React.Component {
state = { isFocused: false }
handleInputFocus = () => this.setState({ isFocused: true })
handleInputBlur = () => this.setState({ isFocused: false })
render() {
const { isFocused } = this.state
return (
<View>
<TextInput
onFocus={this.handleInputFocus}
onBlur={this.handleInputBlur}
/>
{isFocused && <Text>Hello World</Text>}
</View>
)
}
}

You cannot reference refs from the render() method. Read more about the cautions of working with refs here.
As you can see in the image below, the first time the component mounts, refs is undefined, when I change the text (Which changes the State, which re-renders the component) refs is now available.
An optimal solution would be using state. I was going to post a solution but Freez already did. However, I am still posting this so you know why you should be using state instead of refs.

Related

Cannot set property '0' of null in React Native ref

I am trying to set up a React Native ref like here, only in a class component:
https://snack.expo.io/PrrDmZ4pk
Here's my code:
class DetailBody extends Component {
constructor() {
super();
this.myRefs = React.createRef([]);
}
clickText(index) {
this.myRefs.current[index].setNativeProps({ style: { backgroundColor: '#FF0000' } });
}
render() {
if (this.props.article.json.results.length === 0) {
return <Loading />;
}
return (
<View >
<View>
<View ref={this.props.highlight} nativeID="some-id" >
{this.props.article.json.results.map((content, index) => (
<TouchableOpacity onPress={() => this.clickText(index)}>
<View key={index} ref={el => this.myRefs.current[index] = el}>{content}</View>
</TouchableOpacity>
</View>
</View>
</View>
This should theoretically let me add a background colors when my ref is clicked, much like the snack I linked to above.
However what I actually see is this:
This seems to be related to .current inside my ref being null, despite passing a default value.
How do I fix this error?
If the ref callback is defined as an inline function, it will get called twice during updates, first with null and then again with the DOM element.
Haven't really used it this way but I think you might just need to do this in the constructor:
this.myRefs = React.createRef();
this.myRefs.current = [];

React Native FlatList - Re-Renders / Mounts child items more than once [duplicate]

I have this code
class Home extends Component {
constructor(props) {
super(props);
this.state = {
dataSource: []
}
this._handleRenderItem = this._handleRenderItem.bind(this);
this._keyExtractor = this._keyExtractor.bind(this);
}
componentDidMount() {
let success = (response) => {
this.setState({ dataSource: response.data });
};
let error = (err) => {
console.log(err.response);
};
listarProdutos(success, error);
}
_keyExtractor = (item, index) => item._id;
_handleRenderItem = (produto) => {
return (
<ItemAtualizado item={produto.item} />
);
}
render() {
return (
<Container style={styles.container}>
<Content>
<Card>
<CardItem style={{ flexDirection: 'column' }}>
<Text style={{ color: '#323232' }}>Produtos atualizados recentemente</Text>
<View style={{ width: '100%' }}>
<FlatList
showsVerticalScrollIndicator={false}
data={this.state.dataSource}
keyExtractor={this._keyExtractor}
renderItem={this._handleRenderItem}
/>
</View>
</CardItem>
</Card>
</Content>
</Container>
);
}
}
export default Home;
The function _handleRenderItem() is being called twice and I can't find the reason. The first time the values inside my <ItemAtualizado /> are empty, but the second was an object.
This is normal RN behavior. At first, when the component is created you have an empty DataSource ([]) so the FlatList is rendered with that.
After that, componentDidMount triggers and loads the updated data, which updates the DataSource.
Then, you update the state with the setState which triggers a re render to update the FlatList.
All normal here. If you want to try, load the datasource in the constructor and remove the loading in the componentDidMount. It should only trigger once.
If you want to control render actions, you can use shouldComponentUpdate method.
For example:
shouldComponentUpdate(nextProps, nextState){
if(this.state.friends.length === nextState.friends.lenght)
return false;
}
it will break render if friends count not change.
I recreated the issue in this snack. https://snack.expo.io/B1KoX-EUN - I confirmed you can use shouldComponentUpdate(nextProps, nextState) to diff this.state or this.props and return true/false - https://reactjs.org/docs/react-component.html#shouldcomponentupdate docs say this callback should be used only for optimization which is what we're doing here.

React native: rendering conditional component based on state value change in Modal

I have a requirement of showing a tab like behavior inside a react native Modal
I have started by creating a state variable selectedSub which is initialized with value 'from'
Now my modal has 2 TouchableHighlight as below
_availabiltyModal() {
return (
<TouchableHighlight
onPress={() => { this.setState({ selectedSub: 'from' }) }}
activeOpacity={0.9}
style={[styles.tab, this.state.selectedSub == 'from' && styles.active]}>
<RkText rkType="header" style={this.state.selectedSub == 'from' && styles.activeText}>From</RkText>
</TouchableHighlight>
<TouchableHighlight
onPress={() => { this.setState({ selectedSub: 'to' }) }}
activeOpacity={0.9}
style={[styles.tab, this.state.selectedSub == 'to' && styles.active]}>
<RkText rkType="header" style={this.state.selectedSub == 'to' && styles.activeText}>To</RkText>
</TouchableHighlight>
{this._renderPicker()}
)}
These two are responsible to change the state param selectedSub as required.
Based on this state param i am conditionally showing another component i made and imported as below
_renderPicker() {
if (this.state.selectedSub == 'from') {
return <TimePicker screenProps={{ time: this.state.selectedAvailabilty.from }} />
} else {
return <TimePicker screenProps={{ time: this.state.selectedAvailabilty.to }} />
}
}
I have called this function in the Modal below the TouchableHighlight's
Now as per the RN docs when ever the state variable is updated with this.setState() method the component should re-render. Everything else is working fine (TouchableHighlight styles changing) and also the updates to the state are reflecting in the console. But the _renderPicker function does not return the changed view and is always stuck on the view which was set when the parent was initialized as pointed earlier.
Could somebody help me point the problem here which i might have been overlooking. Also on the side note all this calls are actually directly made outside the main render() method. Could that be a possible issue
------update------
here is the complete reference
render() {
return({this._availabiltyModal()}
<View style={appStyles.tagsWrapper}>
{this.state.week.map((day, i) => {
return (
<TouchableHighlight
key={i}
activeOpacity={0.9}
style={[appStyle.mr10, appStyle.mb10]}
onPress={() => {
this.setModalVisible(true, day);
}}>
<Text style={appStyle.tag}>{day}</Text>
</TouchableHighlight>
)
})}
</View>
)
}
Move the _renderPicker method inside the render() method like...
render() {
...
{this._renderPicker()}
...
}
Looking at the code of the MODAL component from react-native
render(): React.Node {
....
const innerChildren = __DEV__ ? (
<AppContainer rootTag={this.context.rootTag}>
{this.props.children}
</AppContainer>
) : (
this.props.children
);
return (
<RCTModalHostView
....>
<View style={[styles.container, containerStyles]}>
{innerChildren}
</View>
</RCTModalHostView>
);
}
The state you are changing are of the component that use the modal component thats render its children through the upper function.. when the state update it only re render the component whose state is updated.. so somewhere down rendering child inside component it does not get re render until force re render is applied.
Here is an helpful article to further explain how exactly this functions, forcefully re-rendering the child component

Why are my items not being sorted (re-rendered) in React?

I have a button that when clicked calls a function that sorts the products by case amount. I am updating the products array so I assumed this would trigger a re-render of the products being mapped in the code below but it is not. Does anyone know how to get this to trigger the products.map to be re-rendered again thus displaying the new sorted products?
render() {
const {products} = this.props;
const cartIcon = (<Icon name="shopping-cart" style={styles.cartIcon} />);
sortCartItems = () => {
products.sort((a, b) => a.cases > b.cases);
}
return (
<View style={styles.itemsInCartContent}>
<View style={styles.cartHeader}>
<TouchableOpacity onPress={sortCartItems}>
{cartIcon}
<Text>Sort</Text>
</TouchableOpacity>
</View>
{products.map((product, index) =>
<CartProductItem
ref="childCartProductItem"
key={product.id}
product={product}
index={index}
activeIndex={this.state.active}
triggerParentUpdate={() => this.collapseAllOtherProducts}
/>
)}
</View>
);
}
A component should not mutate it's own props. If your data changes during the lifetime of a component you need to use state.
Your inline arrow function sortCartItems tries to mutate the products that come from props. Your need to store the products in the components state instead and call setState to change them which will trigger a re-render.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
products: props.products,
}
}
sortCartItems = () => {
this.setState(prevState => ({products: prevState.products.sort((a, b) => a.cases > b.cases);}))
}
render() {...}
}
Note that you need to use a callback in setState whenever you are updating the state based on the previous state. The callback receives the old state as a parameter and returns the new state.
I used a combination of messerbill's and trixn's answers to come up with the following which is now working. And I added a products property to state which receives its data from props.products
render() {
const cartIcon = (<Icon name="shopping-cart" style={styles.cartIcon} />);
sortCartItems = () => {
this.setState({
products: this.state.products.sort((a, b) => a.cases > b.cases)
});
}
return (
<View style={styles.itemsInCartContent}>
<View style={styles.cartHeader}>
<TouchableOpacity onPress={sortCartItems}>
{cartIcon}
<Text>Sort</Text>
</TouchableOpacity>
</View>
{this.state.products.map((product, index) =>
<CartProductItem
ref="childCartProductItem"
key={product.id}
product={product}
index={index}
activeIndex={this.state.active}
triggerParentUpdate={() => this.collapseAllOtherProducts}
/>
)}
</View>
);
}

Is there any chance to use a component as a global ActivityIndicator on React-Native

Is there any chance to use a component as a global ActivityIndicator which has transparent color and had been created by me on React-Native?
Details:
I use a redux store to update the UI. So I intend to show an ActivityIndicator by updating the store.
I've created an ActivityIndicator component with name ActIndicator.
I have a main App component that contains the app.
I have a Root component that wraps the ActIndicator and App components.
The ultimate code of render method of Root component looks like the following:
render() {
if (this.state.showActivityIndicator) {
return(
<ActIndicator>
<App />
</ActIndicator>
)
}
return (</App>)
}
I've tried several methods but I can not be successful.
I guess my brain is stopped.
I also guess there may be a logic mistake.
const withLoadingOverlay = (Component) => class withLoadingOverlayView extends React.Component { props: withLoadingOverlayProps
// Indicator view styles loadingOverlay = (style) => (
<View style={[style, styles.someDefaultStyles]}>
<ActivityIndicator color={someColor} size="large" />
</View> )
render() {
const { pending, ...passProps } = this.props;
const { width, height } = Dimensions.get('window');
return (
<View style={{ flex: 1 }}>
<Component {...passProps} />
{pending && this.loadingOverlay({ width, height })}
</View>
); } };
I used to wrap whole container with HOC and with redux action to set on start pending prop true and on success or fail to set on false so this prop will be consumed by HOC and indicator will be displayed only when pending is set on true.
In container you have to wrap component in connect
export default connect(
(state) => ({
pending: state.reducer.pending, // pending prop should be here
}),
(dispatch) => ({ dispatching redux actions })
)(withLoadingOverlay(WrappedComponent));
I don't think you're supposed to pass App as a child, the way I use it is more like this:
render() {
if (this.state.showActivityIndicator) {
return(
<View>
<ActivityIndicator />
<App />
</View>
)
}
return (<App />)
}
but it would probably be better to set it up like this:
render() {
return (
<View>
<ActivityIndicator animating={this.state.showActivityIndicator} />
<App />
</View>
)
}

Categories

Resources