Conditional rendering question with ReactJS - reactjs

I'm trying to use a <Conditional if={condition}> component in React, that renders its content only if the condition is true.
I've used the code from here, as mentioned in Broda Noel's response of this question. You also see the code here.
import * as React from 'react';
const Conditional = props => (
!!props.if && props.children
);
class Item extends React.Component {
render() {
const item = { id: 2 };
return (
<>
<h2>Item detail</h2>
{/* Display item.foo.name if `foo` property exists */}
<Conditional if={item.foo}>
{item.foo.name}
</Conditional>
</>);
}
}
export default Item;
It fails with the error:
Uncaught TypeError: Cannot read property 'name' of undefined
I know I can also use {item.foo && item.foo.name}, but when the code becomes more complex, I find the use of Conditional more readable. And most important, I would really like to understand what happens here.
Why does React render the content of the Conditional, even is the condition is false?
Is there a way to change the Conditional component to make it work in this case?
I'm using React 16.4.1, with typescript.

The code:
<Conditional if={item.foo}>
{item.foo.name}
</Conditional>
is transpiled to:
React.createElement(Condition, { if: item.foo }, item.foo.name)
So even if the condition is false it will try to access the property name.
I suggest that you use the code below when the code becomes more complex:
import * as React from 'react';
class Item extends React.Component {
renderConditional (item) {
if (!item.foo) {
return null;
}
return item.foo.name; // Or some jsx
}
render() {
const item = { id: 2 };
return (
<>
<h2>Item detail</h2>
{this.renderConditional(item)}
</>
);
}
}
export default Item;
You could also create another component to present the data and check the object inside the new component.

The problem is that your component <Conditional> have no logical expression in the if prop. The "React way" to do this is using conditional rendering as is:
render() {
const item = { id: 2 };
return (
<>
<h2>Item detail</h2>
{ item.foo !== undefined && // React Way to Conditional Rendering
{item.foo.name}
}
</>
);
}
I recommend you to read the docs for Conditional Rendering

Related

Reactjs IF statement

I have a component I call that is a passed a recordID and returns the text associated to the Id. 33 should = Tower
will render "Tower" on the screen. All good, but...
When I try to use the component in the following IF statement it does not work.
...
if (<GetAssetTypeNameComponent datafromparent = {assettype_assettypeId}/> === "Tower")
{
this.props.history.push(`/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}`);
}
Using the passed parameter does work if I change the code to:
...
if (assettype_assettypeId === "33")
{
this.props.history.push(`/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}`);
}
...
What am I doing wrong?
Rob
Component Code that needs to be a Function....
...
class GetAssetTypeNameComponent extends Component {
constructor (props){
super(props)
this.state = {
assettype:[]
}
}
componentDidMount()
{
AssetTypeService.getAssetTypeById(this.props.datafromparent).then( (res) =>{
let assettype = res.data;
this.setState({isLoading:false});
this.setState({
assettypeName: assettype.assettypeName,
assettypeType: assettype.assettypeType
});
});
}
render() {
return (
<div>
{this.state.assettypeName}
</div>
);
}
}
export default GetAssetTypeNameComponent;
...
Following Function code compiles:
...
import React, { useState} from 'react';
import AssetTypeService from './AssetTypeService'
const GetAssetTypeNameFunction = (props) =>{
// destructuring
const { assettype_assettypeId } = props;
const [assetType,setAssetType] = useState()
AssetTypeService.getAssetTypeById(assettype_assettypeId).then( (res) =>
setAssetType(res.data));
const arrayMap = assetType.map((post)=>{
return(
<ul>
{post.assettypeName}
</ul>
);})
return (
{arrayMap}
);
}
export default GetAssetTypeNameFunction;
...
Get execution error:
I think because I calling the function from within an eventHandler:
...
editAssets(assetsid,assettype_assettypeId){ if (GetAssetTypeNameFunction(assettype_assettypeId) === "Tower") { this.props.history.push(/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}); }]
...
----- Error: Invalid hook call. Hooks can only be called inside of the body of a function component. I am responding to a onClick in a list to route to a specific component based on the function $
How do I get around this?
A component renders content to be displayed in the page. The retuned value of rendering a component is a tree of nodes that contain your content. All this means that <GetAssetTypeNameComponent> may contain the text content Tower, but it is not equal to the string "Tower". It just doesn't make any sense to render a component as the test for a conditional like this.
In React you want to use logic to tell react how to render. You do not want to render and then use the result in your logic.
It's hard to give advice on the best way to fix that with so little code, but maybe you want a a simple function to coverts the id into some text for you.
function getAssetName(id) {
return someLogicSomewhere(id).result.orWhatever
}
And now you can do something like:
if (getAssetName(assettype_assettypeId) === 'Tower')
{
this.props.history.push(
`/add-assetstower/${assetsid}/${this.props.match.params.sitemasterid}`
);
}

Expected an assignment or function call and instead saw an expression error in react if return is not present inside map statement why?

import React, { Component } from 'react'
export default class DisplayTodo extends Component {
state = {
todo:["eat", "sleep", "repet"]
}
render() {
return (
<div>
{
this.state.todo.map((value)=>{
<p>{value}</p>
})
}
</div>
)
}
}
1) I know if i keep return in map it works but i want know why it failed
2)Moreover i referd to the link in for React
https://reactjs.org/docs/introducing-jsx.html#embedding-expressions-in-jsx
Now the error looks quit opposite to the explanition saying Expected an assignment or function call and instead saw an expression
3)in the react link explanation in above it is said You can put any valid JavaScript expression inside the curly braces
4) Moreover in javascript we can write like this why it is not possible in React
var a = [11,33,99]
a.map((val)=>{
console.log(val*2)
})
So in your code
export default class DisplayTodo extends Component {
state = {
todo:["eat", "sleep", "repet"]
}
render() {
return (
<div>
{ <-- outer expression start
this.state.todo.map((value)=>{
<p>{value}</p> <-- inner expression
})
} <-- outer expression end
</div>
)
}
}
You've got one outer expression, which wraps your JS code, it's completely correct. Your inner expression is also fine, because you use curly braces for JS code inside of JSX element. But, in order for React to render values from todo array, you must return each element from map callback, like that:
export default class DisplayTodo extends Component {
state = {
todo:["eat", "sleep", "repet"]
}
render() {
return (
<div>
{
this.state.todo.map((value)=>{
return <p key={value}>{value}</p>
})
}
</div>
)
}
}
Or you can do:
{
this.state.todo.map(value => <p key={value}>{value}</p>)
}
Note: I also added React keys to each todo in array, so you have some sort of uniqueness(good explanation about React keys)
Hope it helps :)
The curly braces "{}" require a return statement. If you do not want to add a return statement simply use "()" or nothing instead. ex)
this.state.todo.map((value)=>
<p>{value}</p>
)

What is the performance implication of saving a component to a state?

I was wondering what is the performance impact of saving a component to a state and if it is against good practice to do so?
Say for example I have an HOC where I am passing a callback to, and that returns a component that has all the functionality I needed, and would invoke my callback afterwards. Please see example below:
import React, { PureComponent } from "react";
import {
someHOCthatReturnsAComponentClass,
OtherComponent
} from "../some-folder";
export class A extends PureComponent {
state = {
ComponentInsideState: null,
active: false
};
callback() {
this.setState({ active: true });
}
componentDidMount() {
this.setState({
ComponentInsideState: someHOCthatReturnsAComponentClass(this.callback)
});
}
render() {
const { ComponentInsideState, active } = this.state;
return (
ComponentInsideState && (
<ComponentInsideState>
<OtherComponent active={active} />
</ComponentInsideState>
)
);
}
}
I think assigning a component as state won't create any performance implications as the reference to the component is just saved as the state. But my opinion is that there is no need to set a component to state. As we can conditionally render components, i think there is no need to set a component to state.
{condition1 && <component1/>}
{condition2 && <component2/>}
or you can use ternary operator for 'if else' conditional rendering of components.
condition?<component1>:<component2>;

"'this' is a reserved keyword" in React app, why? [duplicate]

I'm stuck on a error for the reserved keyword "this". In my React Component below shows me passing in a state from a my main component "App.js" to my "RecipeList.js" component to then map the data and render each RecipeItem Component. I just don't understand why I get this error
React.js - Syntax error: this is a reserved word
The error is called in RecipeList inside the render return method; If anybody could help that would great!
Thanks
App.js
//main imports
import React, { Component } from 'react';
//helper imports
import {Button} from 'reactstrap'
import RecipeItem from './components/RecipeItem';
import RecipeList from './components/RecipeList';
import './App.css';
const recipes = [
{
recipeName: 'Hamburger',
ingrediants: 'ground meat, seasoning'
},
{
recipeName: 'Crab Legs',
ingrediants: 'crab, Ole Bay seasoning,'
}
];
class App extends Component {
constructor(props){
super(props);
this.state = {
recipes
};
}
render() {
return (
<div className="App">
<div className = "container-fluid">
<h2>Recipe Box</h2>
<div>
<RecipeList recipes = {this.state.recipes}/>
</div>
</div>
<div className = "AddRecipe">
<Button>Add Recipe</Button>
</div>
</div>
);
}
}
export default App;
RecipeLists.js
import React, {Component} from 'react';
import _ from 'lodash';
import RecipeItem from './RecipeItem';
class RecipeList extends Component {
renderRecipeItems() {
return _.map(this.props.recipes, recipeItem => <RecipeItem key = {i} {...recipes} />);
}
render() {
return (
{ this.renderRecipeItems() }
);
}
}
export default RecipeList
All the solutions given here are correct.
The easiest change is to just wrap your function call in a JSX element:
return (
<div>
{ this.renderRecipeItems() }
</div>
)
However, none of the answers are (correctly) telling you why the code was breaking in the first place.
For the sake of an easier example, let's simplify your code a bit
// let's simplify this
return (
{ this.renderRecipeItems() }
)
such that the meaning and behavior are still the same. (remove parenths and move curlies):
// into this
return {
this.renderRecipeItems()
};
What this code does is it contains a block, denoted by {}, within which you're trying to invoke a function.
Because of the return statement, the block {} is treated like an object literal
An object literal is a list of zero or more pairs of property names and associated values of an object, enclosed in curly braces ({}).
which expects either a: b or a (shorthand) syntax for it's property-value pairs.
// valid object
return {
prop: 5
}
// also valid object
const prop = 5;
return {
prop
}
However, you're passing a function call instead, which is invalid.
return {
this.renderRecipeItems() // There's no property:value pair here
}
When going through this code, the engine assumes it will read an object-literal. When it reaches the this., it notices that . is not a valid character for a property name (unless you were wrapping it in a string - see bellow) and the execution breaks here.
function test() {
return {
this.whatever()
// ^ this is invalid object-literal syntax
}
}
test();
For demonstration, if you wrapped your function call in quotes, code would accept the . as part of a property name and would break now because a property value is not provided:
function test() {
return {
'this.whatever()' // <-- missing the value so the `}` bellow is an unexpected token
}
}
test();
If you remove the return statement, the code wouldn't break because that would then just be a function call within a block:
function test() {
/* return */ {
console.log('this is valid')
}
}
test();
Now, an additional issue is that it's not the JS engine that is compiling your code but it's babel, which is why you get the this is a reserved word error instead of Uncaught SyntaxError: Unexpected token ..
The reason is that JSX doesn't accept reserved words from the JavaScript language such as class and this. If you remove this, you can see that the reasoning above still applies - babel tries to parse the code as an object literal that has a property, but no value, meaning babel sees this:
return {
'renderRecipeItems()' // <-- notice the quotes. Babel throws the unexpected token error
}
Wrap the this.renderRecipeItems() part with a div, it will work.
Reason why it was failing, is explained extremely well by #nem035 in this answer.
Like this:
render () {
return (
<div>
{ this.renderRecipeItems() }
</div>
);
}
And i think instead of:
<RecipeItem key = {i} {...recipes} />
It should be:
<RecipeItem key = {i} {...recipeItem} />
These are the changes i can see, may be some others will be required also.
You can avoid this by rewriting RecipeLists.js as a pure stateless component.
As Pure component:
import _ from 'lodash';
import RecipeItem from './RecipeItem';
const RecipeList = props => renderRecipeItems(props);
const renderRecipeItems = ({ recipes }) => _.map(recipes, recipeItem => <RecipeItem key = {i} {...recipes} />);
export default RecipeList;
So now you're component is basically just a function with params.

React map redux state to create multiple components within a component

I am using the react-redux-mapbox-gl library. I have an array of spots that I want to map in order to create multiple overlay components within the Mapbox Component. However on trying to map the array I always get an undefined error. I am new to React/Redux so am not sure what the issue is.
My Component Below:
import React from 'react';
import Mapbox from 'react-redux-mapbox-gl';
import SpotsOverlay from './SpotsOverlay'
const mapStateToProps = state => ({
spots: state.homemap.spots
})
class HomeMap extends React.Component {
render(){
return (
<Mapbox
mapboxgl={mapboxgl}
accessToken={mapAccessToken}
getMap={this.getMap}
style={this.mapStyle}
options={this.mapOptions}
>
{
this.props.spots.map(spot =>{
return (
<SpotsOverlay
overlay={this.overlay}
key={spot.id}/>
);
})
}
</Mapbox>
);
}
}
Do the mapping outside of the return method might help.
class HomeMap extends React.Component {
render(){
let spots = [];
if(this.props.spots) {
spots = this.props.spots.map(spot =>{
return (
<SpotsOverlay
overlay={this.overlay}
key={spot.id}/>
);
});
}
return (
<Mapbox
mapboxgl={mapboxgl}
accessToken={mapAccessToken}
getMap={this.getMap}
style={this.mapStyle}
options={this.mapOptions}
>
{spots}
</Mapbox>
);
}
}
As #MayankShukla said in his comment, the reason why this works better is that
initially reducer state is {}, so state.homemap.spots will be undefined and when you were using map of undefined

Resources