React Native Conditional Rendering without if statement - javascript

Is it possible to re-render specific components without using if/else statement in the rendersection.
So when a specific statement changed only his specific component(s) will re-render
while the rest remain intact.
Because if I use the componentWillUpdate or shouldComponentUpdate it will re-render the whole app scene again.
I look forward to your answers.

You can try something like -
class MainComponent extends React.Component {
displayMessage() {
if (this.props.isGreeting) {
return <Text> Hello, JSX! </Text>;
} else {
return <Text> Goodbye, JSX! </Text>;
}
}
render() {
return ( <View> { this.displayMessage() } </View> );
}
}
Check this article - https://medium.com/#szholdiyarov/conditional-rendering-in-react-native-286351816db4

You can try with new ES6 enhanced object litrals.
We can access the property of an object using bracket [] notation:
myObj = { "name":"Stan", "age":26, "car": "Lamborghini"};
x = myObj["name"]; //x will contain Stan
So you can use this approach for conditional rendering
this.state = {
contentToDisplay: "content1",
}
render() {
return (
<section>
{{
content1: <Content1 />,
content2: <Content2 />,
content3: <Content3 />,
}[this.state.contentToDisplay]}
</section>
);
}

Related

How to pass function and data from component class to stateless class in React Native?

I am working with react native and I want to pass function and some data from Component class to another Stateless class, but I could not make to passing function and data part.
Here you can see my Component class:
class Classroom extends Component {
constructor(props, context) {
super(props, context);
};
state = {
isLightOn: false,
title : "Turn light on "
}
onPress() {
this.setState({isLightOn: !this.state.isLightOn})
console.log(this.state.isLightOn)
this.setState({title:this.state.isLightOn===false ?"Turn light off":"Turn light on"})
}
render() {
return (
<View style={styles.blue}>
<LightBulb isLightOn={this.state.isLightOn}> </LightBulb>
<LightButton onPress={this.onPress} isLightOn={this.state.isLightOn} title={this.state.title} > </LightButton>
</View>
);
}
}
Firstly, I want to pass isLightOn and title datas to my LightButton class (which mean to my stateless class). After that, I want to use onPress function inside of my Stateless class, but I cannot use. I am taking that error:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
I also LightButton onPress={this.onPress} remove parenthesis, but still taking error.
Here is my my Stateless class
const LightButton = ({onPress,isLightOn,title}) => (
<View style={styles.red}>
<Button
title= {title}
onPress={() => {}
}
/>
</View>
)
I want to use onPress function and datas inside of the this class.
As a result, How can I pass function and data to that class?
The main issue here is that you need to declare onPress using an arrow function or bind it to the component's this value within the constructor. Otherwise it wouldn't have access to the correct this value. Other than that, the way you were passing props into components is perfectly fine.
I also merged your two set state calls in onPress to one as it's easier.
In LightButton, I set it up like this to pass the onPress function down to the button:
const LightButton = ({ onPress, isLightOn, title }) => (
<div style={{backgroundColor:'red'}}>
<Button title={title} onPress={onPress} />
</div>
);
(I set it up using react, but the issues at hand are more of a JS issue than a React/ReactNative one, so the advice should still be valid :) )
const { Component } = React;
const View = 'div';
const Button = (({title,onPress})=><button onClick={onPress}>{title}</button>);
const LightBulb = ({ isLightOn }) => {
return <div className={'LightBulb' + (isLightOn ? ' on' : '')} />;
};
const LightButton = ({ onPress, isLightOn, title }) => (
<div style={{backgroundColor:'red'}}>
<Button title={title} onPress={onPress} />
</div>
);
class Classroom extends Component {
state = {
isLightOn: false,
title: 'Turn light on ',
};
onPress=()=> {
console.log(this.state.isLightOn);
this.setState({
title:
this.state.isLightOn === false ? 'Turn light off' : 'Turn light on',
isLightOn: !this.state.isLightOn
});
}
render() {
return (
<div style={{backgroundColor:'blue'}}>
<LightBulb isLightOn={this.state.isLightOn}> </LightBulb>
<LightButton
onPress={this.onPress}
isLightOn={this.state.isLightOn}
title={this.state.title}
>Button</LightButton>
</div>
);
}
}
ReactDOM.render(<Classroom />, document.querySelector('#root'));
.LightBulb {
height: 50px;
width: 50px;
border-radius: 50px;
background-color: black;
}
.LightBulb.on {
background-color: white;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"/>
You can assign it like
const LightButton = ({onPress,isLightOn,title}) => (
...
onPress={onPress}
...
or with an arrow function if you need to pass arg inside
onPress={()=>onPress(someArg)}
do notice that you either don't put () at all, or twice () => func() for not run the function while it is just loads and not clicked.
unrelated directly to your issue but something that you encounter is inside onPress by doing like so
this.setState({isLightOn: !this.state.isLightOn})
console.log(this.state.isLightOn)
this.setState({title:this.state.isLightOn===false ?"Turn light off":"Turn light on"})
setState it is an async call, and therefore second setState usage not guaranteed to refer the state as you expect, use setState({ ... }, () => callback()) or all at one line and accords to prev state
this.setState({isLightOn: !this.state.isLightOn, title: !this.state.isLightOn===false ?"Turn light off":"Turn light on"})
First thing you did wrong is your state instantiating !
you need to instantiate your state in the constructor block like:
constructor(props, context) {
super(props, context);
this.state = { counter: 0 };
}
onPress() you use this for your function which is not recommended in react native or any other language , those are dedicated functions and methods of React Native
For passing a parameter or calling a function it is better to use these patterns ====>
onPress={() => urFunction()} with parenthesis or
onPress={urFunction} without parenthesis
Do the modifications I hope it helps <3

how to fix, error is "Invariant Violation: Text strings must be rendered within a <Text> component."

I am trying to make config() run but it somehow fails.
import React, {Component} from 'react';
import { StyleSheet, Text, View } from 'react-native';
export default class try1 extends Component {
constructor(noofVertices){
super();
this.noofVertices = this.noofVertices
this.edge = {}
this.a = [];}
addVertex(v){
this.edge[v] = {}
}
addEdge(v, w,weight){
if (weight == undefined) {
weight = 0;
}
this.edge[v][w] = weight;
}
config(){
var vertices = [ 'App0', 'App1', 'App2', 'App3', 'App4' ];
// adding vertices
for (var i = 0; i < vertices.length; i++) {
this.addVertex(vertices[i]);
}
// adding edges
this.addEdge('App0', 'App1',1);
this.addEdge('App0', 'App2',1);
this.addEdge('App0', 'App3',1);
this.addEdge('App0', 'App4',1);
this.addEdge('App1', 'App0',1);
this.addEdge('App1', 'App2',1);
this.addEdge('App1', 'App3',1);
this.addEdge('App1', 'App4',1);
this.addEdge('App2', 'App0',1);
this.addEdge('App2', 'App1',1);
this.addEdge('App2', 'App3',1);
this.addEdge('App2', 'App4',1);
this.addEdge('App3', 'App0',1);
this.addEdge('App3', 'App1',1);
this.addEdge('App3', 'App2',1);
this.addEdge('App3', 'App4',1);
this.addEdge('App4', 'App0',1);
this.addEdge('App4', 'App1',1);
this.addEdge('App4', 'App2',1);
this.addEdge('App4', 'App3',1);
this.traverse('App1');
}
traverse(vertex)
{
for(var adj in this.edge[vertex])
this.a.push(this.edge[vertex][adj])
this.a.sort()
//this.updateEdge1('App0');
}
render(){
return (
<View style={styles.container}>
this.config()
<Text>{this.a}</Text>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: 'white',
alignItems: 'center',
justifyContent: 'center',
},
}
);
I am expecting 11111 to be displayed on screen.
It is showing error "Invariant Violation: Text strings must be rendered within a component."
I am trying to deal with graphs, I have tried maps also but that didn't work.
Does react native supports maps?
Notice your this.a property on your try1 component is an array: this.a = []; and you're trying to display this property directly within your render() method like so:<Text>{this.a}</Text>. However, this causes issues since:
The render() method doesn't support rendering a return type of just an array directly. When you call a React component's render() method it must return one of the following:
React elements. Typically created via JSX. For example, <div /> and <MyComponent /> are React elements that instruct React to render a DOM node, or another user-defined component, respectively.
Arrays and fragments. Let you return multiple elements from render. See the documentation on fragments for more details.
Portals. Let you render children into a different DOM subtree. See the documentation on portals for more details.
String and numbers. These are rendered as text nodes in the DOM.
Booleans or null. Render nothing. (Mostly exists to support return test && <Child /> pattern, where test is boolean.)
For more information check out the render() spec.
There are other errors in this code as well:
Custom react components must be named with an uppercase otherwise JSX will think this is an HTML tag instead. Rename try1 to Try1.
Move your config function call before the return statement as the return statement expects the actual view itself to be returned.
With these points in mind, try looping through this.a and giving each element in the array a Text element to be displayed, something like the following:
render() {
this.config();
let aEles = this.a;
return(
<View style={styles.container}>
aEles.map(edge => (
<Text>{edge}</Text>
));
</View>
)
}
Hopefully that helps!
You can't pass function in return, it interprets it as a string value and View can't render text.
You can use lifecycle methods to execute this function.
You are trying to render an array inside <Text></Text> component. You can only render pure string.
If you want to display dynamic text inside component use "state". This may help you:
constructor(props){
super(props);
...
...
this.state = {
a = []
}
}
And then:
render() {
this.config();
return(
<View style={styles.container}>
this.state.a && this.state.a.map(data=> (
<Text>{data}</Text>
));
</View>
)
}
this.config() is a function that you write this in jsx.
if you want to call config function, you must write outside of jsx.
this.config() in return, consider as a string. every string must be inside Text tag. so you see this error.
render(){
this.config()
return (
<View style={styles.container}>
<Text>{this.a}</Text>
</View>
);
}
If you are using some conditional statements then use ternary operators. If you are using && and then passing a component after that then it will through you an error. See below what was my code. Here is Feed is my component:
Before
{userId && <Feed posts = {posts}/> } // this will give you an error
After
{userId ? <Feed posts = {posts}/> : null} // this works fine
So maybe you have such kind of problem with your code. Have a look now.

How can I wrap the Children of a Parent component in a HOC and render them in React?

Let me start by saying that this example is very simple and can be solved with React.cloneElement. But I want more freedom and the project will be more complex, so I'd like to find a solution.
I would also like to understand what I'm missing :/
I want to be able to augment the children of a Parent component with props and methods (hence the HOC). It would start from here:
<Parent>
<aChild />
<anotherChild />
<yetAnotherChild />
</Parent>
And this is the Parent component (called Sequence in my project), so far:
import React, { Component } from 'react';
const withNotification = handler => Component => props => (
<Component onAnimationEnd={handler} {...props} />
);
class Sequence extends Component {
constructor(props) {
super(props);
this.state = {
pointer: 0,
};
this.notifyAnimationEnd = this.notifyAnimationEnd.bind(this);
this.Children = React.Children.map(this.props.children, Child =>
withNotification(this.notifyAnimationEnd)(Child)
);
}
notifyAnimationEnd() {
// do stuff
}
render() {
return (
<div>
{this.Children.map((Child, i) => {
if (i <= this.state.pointer) return <Child />;
return <div>nope</div>;
})}
</div>
);
}
}
export default Sequence;
I get the following error:
You can play with the code here: https://codesandbox.io/s/6w1n5wor9w
Thank you for any help!
This answer will not solve your problem but maybe gives a hint why this is not possible. At first I was surprised why your code does not work, even though I'm not an experienced React developer it seems ok map this.props.children through with React.Children.map and return the desired Component with your HOC. But it did not work.
I tried to debug it a little bit and did some search. I've learned props.children actually contains the elements itself not the instances of components. Even, React.Children.map does not have any effect on this.
Here is a working snippet proves that your problem is not related with the HOC. I've used an array of components instead of mapping through props.children.
class Line1 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 1</div>;
}
}
class Line2 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 2</div>;
}
}
class Line3 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 3</div>;
}
}
const withNotification = handler => Component => props => (
<Component onAnimationEnd={handler} {...props} />
);
class Sequence extends React.Component {
constructor(props) {
super(props);
this.state = {
pointer: 0
};
this.notifyAnimationEnd = this.notifyAnimationEnd.bind(this);
this.Arr = [ Line1, Line2, Line3 ];
this.Children = this.Arr.map(Child =>
withNotification(this.notifyAnimationEnd)(Child)
);
}
notifyAnimationEnd() {
this.next();
}
next() {
// Clearly, render the next element only if there is, a next element
if (this.state.pointer >= this.Arr.length - 1) {
return;
}
this.setState({ pointer: this.state.pointer + 1 });
}
render() {
return (
<div>
{this.Children.map((Child, i) => {
if (i <= this.state.pointer) return <Child />;
return <div>nope</div>;
})}
</div>
);
}
}
ReactDOM.render(
<Sequence />,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
You are returning <Child /> instead of Child in Sequence.js render method. Here is my edited copy - codesandbox

Passing a custom argument to the eventListener in React [duplicate]

We should avoid method binding inside render because during re-rendering it will create the new methods instead of using the old one, that will affect the performance.
So for the scenarios like this:
<input onChange = { this._handleChange.bind(this) } ...../>
We can bind _handleChange method either in constructor:
this._handleChange = this._handleChange.bind(this);
Or we can use property initializer syntax:
_handleChange = () => {....}
Now lets consider the case where we want to pass some extra parameter, lets say in a simple todo app, onclick of item i need to delete the item from array, for that i need to pass either the item index or the todo name in each onClick method:
todos.map(el => <div key={el} onClick={this._deleteTodo.bind(this, el)}> {el} </div>)
For now just assume that todo names are unique.
As per DOC:
The problem with this syntax is that a different callback is created
each time the component renders.
Question:
How to avoid this way of binding inside render method or what are the alternatives of this?
Kindly provide any reference or example, thanks.
First: A simple solution will be to create a component for the content inside a map function and pass the values as props and when you call the function from the child component you can pass the value to the function passed down as props.
Parent
deleteTodo = (val) => {
console.log(val)
}
todos.map(el =>
<MyComponent val={el} onClick={this.deleteTodo}/>
)
MyComponent
class MyComponent extends React.Component {
deleteTodo = () => {
this.props.onClick(this.props.val);
}
render() {
return <div onClick={this.deleteTodo}> {this.props.val} </div>
}
}
Sample snippet
class Parent extends React.Component {
_deleteTodo = (val) => {
console.log(val)
}
render() {
var todos = ['a', 'b', 'c'];
return (
<div>{todos.map(el =>
<MyComponent key={el} val={el} onClick={this._deleteTodo}/>
)}</div>
)
}
}
class MyComponent extends React.Component {
_deleteTodo = () => {
console.log('here'); this.props.onClick(this.props.val);
}
render() {
return <div onClick={this._deleteTodo}> {this.props.val} </div>
}
}
ReactDOM.render(<Parent/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
EDIT:
Second: The other approach to it would be to use memoize and return a function
constructor() {
super();
this._deleteTodoListener = _.memoize(
this._deleteTodo, (element) => {
return element.hashCode();
}
)
}
_deleteTodo = (element) => {
//delete handling here
}
and using it like
todos.map(el => <div key={el} onClick={this._deleteTodoListener(el)}> {el} </div>)
P.S. However this is not a best solution and will still result in
multiple functions being created but is still an improvement over the
initial case.
Third: However a more appropriate solution to this will be to add an attribute to the topmost div and get the value from event like
_deleteTodo = (e) => {
console.log(e.currentTarget.getAttribute('data-value'));
}
todos.map(el => <div key={el} data-value={el} onClick={this._deleteTodo}> {el} </div>)
However, in this case the attributes are converted to string using toString method and hence and object will be converted to [Object Object] and and array like ["1" , "2", "3"] as "1, 2, 3"
How to avoid this way of binding inside render method or what are the
alternatives of this?
If you care about re-rendering then shouldComponentUpdate and PureComponent are your friends and they will help you optimize rendering.
You have to extract "Child" component from the "Parent" and pass always the same props and implement shouldComponentUpdate or use PureComponent. What we want is a case when we remove a child, other children shouldn't be re-rendered.
Example
import React, { Component, PureComponent } from 'react';
import { render } from 'react-dom';
class Product extends PureComponent {
render() {
const { id, name, onDelete } = this.props;
console.log(`<Product id=${id} /> render()`);
return (
<li>
{id} - {name}
<button onClick={() => onDelete(id)}>Delete</button>
</li>
);
}
}
class App extends Component {
constructor(props) {
super(props);
this.state = {
products: [
{ id: 1, name: 'Foo' },
{ id: 2, name: 'Bar' },
],
};
this.handleDelete = this.handleDelete.bind(this);
}
handleDelete(productId) {
this.setState(prevState => ({
products: prevState.products.filter(product => product.id !== productId),
}));
}
render() {
console.log(`<App /> render()`);
return (
<div>
<h1>Products</h1>
<ul>
{
this.state.products.map(product => (
<Product
key={product.id}
onDelete={this.handleDelete}
{...product}
/>
))
}
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
Demo: https://codesandbox.io/s/99nZGlyZ
Expected behaviour
<App /> render()
<Product id=1... render()
<Product id=2... render()
When we remove <Product id=2 ... only <App /> is re-rendered.
render()
To see those messages in demo, open the dev tools console.
The same technique is used and described in article: React is Slow, React is Fast: Optimizing React Apps in Practice by François Zaninotto.
Documentation encourages to use data-attributes and access them from within evt.target.dataset:
_deleteTodo = (evt) => {
const elementToDelete = evt.target.dataset.el;
this.setState(prevState => ({
todos: prevState.todos.filter(el => el !== elementToDelete)
}))
}
// and from render:
todos.map(
el => <div key={el} data-el={el} onClick={this._deleteTodo}> {el} </div>
)
Also note that this makes sense only when you have performance issues:
Is it OK to use arrow functions in render methods?
Generally speaking, yes, it is OK, and it is often the easiest way to
pass parameters to callback functions.
If you do have performance issues, by all means, optimize!
This answer https://stackoverflow.com/a/45053753/2808062 is definitely exhaustive, but I'd say fighting excessive re-renders instead of just re-creating the tiny callback would bring you more performance improvements. That's normally achieved by implementing a proper shouldComponentUpdate in the child component.
Even if the props are exactly the same, the following code will still re-render children unless they prevent it in their own shouldComponentUpdate (they might inherit it from PureComponent):
handleChildClick = itemId => {}
render() {
return this.props.array.map(itemData => <Child onClick={this.handleChildClick} data={itemData})
}
Proof: https://jsfiddle.net/69z2wepo/92281/.
So, in order to avoid re-renders, the child component has to implement shouldComponentUpdate anyway. Now, the only reasonable implementation is completely ignoring onClick regardless of whether it has changed:
shouldComponentUpdate(nextProps) {
return this.props.array !== nextProps.array;
}

How to extract JSX from class render method?

I want to write something like SCSS for React Native: it'll parse your component jsx and the special SCSS-like styles and return a usual RN component with reworked styles and jsx.
Lets say we have this react code:
class MyClass extends Component {
render() {
return (
<View style={styles.container}>
<Text>I remember syrup sandwiches</Text>
</View>
);
}
}
Also I have SCSS-ish styles where every Text component inside the parent with a container "class" will have the same props that we provided.
const styles = StyleSheet.create(
toRNStyles({
container: {
Text: { color: 'red' },
},
})
);
In the end we need the output of something like this:
...
<View style={styles.container}>
<Text style={styles._Text_container}>
I remember syrup sandwiches
</Text>
</View>
...
So how can I get the jsx that's returning from the render method from outside the class?
You might write a plugin for babel, as react-native uses it to transform JSX to plain javascript.
Have a look to the these packages:
babel-helper-builder-react-jsx
babel-plugin-syntax-jsx
babel-plugin-transform-react-jsx
babel-plugin-transform-react-jsx-source
jsx-ast-utils
There doesn't seem to be a standard way of doing this. However, you could import ReactDOMServer and use its renderToStaticMarkup function.
Like this:
class MyApp extends React.Component {
render() {
var myTestComponent = <Test>bar</Test>;
console.dir(ReactDOMServer.renderToStaticMarkup(myTestComponent));
return myTestComponent;
}
}
const Test = (props) => {
return (
<div>
<p>foo</p>
<span>{props.children}</span>
</div>
);
}
ReactDOM.render(<MyApp />, document.getElementById("myApp"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom-server.js"></script>
<div id="myApp"></div>
I think parsing the returned element is the wrong approach. One challenge will be that the value of style will be an object (styles.container === a hash of style key/values) whereas you need a key which can be mapped to the object.
I think the most reusable approach is to leverage React context (which I'm assuming RN supports!) to build a styleName which can be augmented as you got down the component tree.
Here's an initial approach which makes a few assumptions (e.g. that every component will have styleName provided as a prop; you might want to provide that at design-time rather than run-time). In short, you wrap every component you want to participate with this HOC and the provide styleName as a prop to each component. Those styleName values are concatenated to produce contextualized names which are mapped to styles.
This example produces:
<div style="background-color: green; color: red;">
<div style="color: blue;">Some Text</div>
</div>
const CascadingStyle = (styles, Wrapped) => class extends React.Component {
static displayName = 'CascadingStyle';
static contextTypes = {
styleName: React.PropTypes.string
}
static childContextTypes = {
styleName: React.PropTypes.string
}
// pass the current styleName down the component tree
// to other instances of CascadingStyle
getChildContext () {
return {
styleName: this.getStyleName()
};
}
// generate the current style name by either using the
// value from context, joining the context value with
// the current value, or using the current value (in
// that order).
getStyleName () {
const {styleName: contextStyleName} = this.context;
const {styleName: propsStyleName} = this.props;
let styleName = contextStyleName;
if (propsStyleName && contextStyleName) {
styleName = `${contextStyleName}_${propsStyleName}`;
} else if (propsStyleName) {
styleName = propsStyleName;
}
return styleName;
}
// if the component has styleName, find that style object and merge it with other run-time styles
getStyle () {
if (this.props.styleName) {
return Object.assign({}, styles[this.getStyleName()], this.props.styles);
}
return this.props.styles;
}
render () {
return (
<Wrapped {...this.props} style={this.getStyle()} />
);
}
};
const myStyles = {
container: {backgroundColor: 'green', color: 'red'},
container_text: {color: 'blue'}
};
const Container = CascadingStyle(myStyles, (props) => {
return (
<div {...props} />
);
});
const Text = CascadingStyle(myStyles, (props) => {
return (
<div {...props} />
);
});
const Component = () => {
return (
<Container styleName="container">
<Text styleName="text">Some Text</Text>
</Container>
);
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>

Categories

Resources