ReactJS Conditional rendering is not working - reactjs

I have an input and whenever a user clicks in the box I want to see if the input is greater than 1
updateQuery(e) {
this.setState({ query: e.target.value });
const optionValue = e.target.value.length;
}
Then I want to do the following in render / return:
render() {
const { query } = this.state;
return (
<div>
{optionValue > 1 ? (
<div className="loading">
) : (
<div className="not-loading">
)}
<NewSearch query={query} updateQuery={this.updateQuery} />
</div>
<Debounce ms={1000}>
<BusInfo query={query} />
</Debounce>
</div>
);
}
But I get this issue:
Line 48:6: Parsing error: Unexpected token ;
and my app won't run.
What am I missing here?
I've read this doc:
https://reactjs.org/docs/conditional-rendering.html
Help!

It seems that the only thing you need to change based a certain value of optionValue is the className of the div.
Hence, you can do something like this:
return (
<div>
<div className={optionValue > 1 ? 'loading' : 'not-loading'}>
<NewSearch query={query} updateQuery={this.updateQuery} />
</div>
<Debounce ms={1000}>
<BusInfo query={query} />
</Debounce>
</div>
);
Also, optionValue is a const of updateQuery and cannot be accessed later on your render. You should also add optionValue on your state:
updateQuery(e) {
this.setState({
query: e.target.value,
optionValue: e.target.value.length
});
}
And later on your render:
const { query, optionValue } = this.state;
or just check on your render query.length instead of holding a new value just for this on your state.
In case you have classes that you want to share on both cases, you could use template literals to achieve this:
<div className={`myClass1 myClass2 ${optionValue > 1 ? 'loading' : 'not-loading'}`}>

Don't define html tag like react component
Change this
{optionValue > 1 ? (
<div className="loading">True condition</div>
) : (
<div className="not-loading">False condition</div>
)}

Your opening and closing tags don't match up, you have an unmatched closing </div>
If the conditionally rendered divs don't have any content, that's fine but they do need to be self-closing: <div className="loading" /> (note the closing />)
This should now work fine (although you may need to do this.optionValue)
updateQuery(e) {
this.setState({ query: e.target.value });
this.optionValue = e.target.value.length;
}
render() {
const { query } = this.state;
return (
<div>
<div>
{this.optionValue > 1 ? (
<div className="loading" />
) : (
<div className="not-loading" />
)}
<NewSearch query={query} updateQuery={this.updateQuery} />
</div>
<Debounce ms={1000}>
<BusInfo query={query} />
</Debounce>
</div>
);
}

Related

How on click rerender conmponent in react

When I click on a card, the loadAboutInfo function works through which I transfer data to another component and display it there. But if I click again on the same card, then it is duplicated. How can I fix it?I have check which take card id and then if it the same it render but I click again it render one more card, but i need if it already exist than new card mustn't render
loadAboutInfo=(pokemonValue,pockemonImg,pokemonId)=>{
this.setState(prevState => ({
pokemonValue:[...prevState.pokemonValue, pokemonValue],
pockemonImg,
pokemonId
}))
}
render() {
return (
<div className="wrapper">
<div className="pokemonlist__inner__cards">
<div className="pokemonlist__cards">
{this.state.pokemonList.map((value,index)=>{
let pokemonImgTemplate = this.state.pokemonImgTemplate;
let pokemonId = value.id;
let pockemonImg = pokemonImgTemplate.replace('{id}',pokemonId);
return(
<div className="pokemonListCard" key={index} onClick={()=>this.loadAboutInfo(value,pockemonImg,pokemonId)}>
<PokemonCard
pockemonImg={pockemonImg}
pokemonName={value.name}
pokemonTypes={value.types}
/>
</div>
)
})}
</div>
<PokemonLoadMore
loadMore={this.loadMore}
currentPage={this.state.currentPage}
/>
</div>
</div>
);
}
}
component where i map get data
render() {
return (
<div className="pokemon__about">
{this.props.pokemonValue.map((value,index)=>{
let totalMoves = value.moves.length;
return(
<div className="pokemon__about__wrapper" key={index}>
{this.props.pokemonId == value.id ?
<div className="pokemon__about__inner" key={index}>
<AboutImage
pockemonImg={this.props.pockemonImg}
/>
<AboutName
pockemonName={value.name}
/>
<div className="pokemon__about__table">
<AboutPokemonTypes
pokemonTypes={value.types}
/>
<table>
<AboutPokemonWeight
pockemonWeight={value.weight}
/>
<AboutPokemonMoves
totalMoves={totalMoves}
/>
</table>
</div>
</div>
:
null
}
</div>
)
})}
</div>
);
On the loadAboutInfo you can check if there is already a pokemon with the same id on pokemonValue array, something like this:
loadAboutInfo = (pokemonValue,pockemonImg,pokemonId) => {
// this will get the first element that matches the id
const exists = this.state.pokemonValue.find(pokemon => pokemon.id === pokemonId)
if (!exists) {
this.setState(prevState => ({
pokemonValue:[...prevState.pokemonValue, pokemonValue],
pockemonImg,
pokemonId
}))
}
}
So it will update the state only if the clicked pokemon isn't in the pokemonValue array

Pass an object value in props to another component

Consider the code below, I need to pass an id which is in object format to another component by props. But I have try many time and it not working. I think I may have some thing mistake, but I'm not sure where is it.
Main page (updated):
render(props){
const data = this.state.data;
return (
<div>
<Header />
<NavigationBar />
<PurchaseInfoView show={this.state.displayModal} closeModal={this.closeModal} value={this.openModal}/>
<div className="purchase-side">
<div className="side-title">
<h1>Purchase Order List</h1>
</div>
<hr class="solid" />
{
Object.keys(data).map((key) =>
<div className="list-item">
<h2 onClick= {() => this.openModal(data[key].id)}> //get id
{ data[key].item_name}
</h2>
</div>
)}
</div>
<div className="dads">
</div>
</div>
);
}
openModal (Updated):
openModal = (id) => {
this.setState(
{
displayModal: true,
id: id
});
console.log(id) // i can get the id from here id=1
};
PurchaseInfoView to get the id (Updated).
class PurchaseInfoView extends Component {
render() {
console.log(this.props.id) // get undefined
return (
<div className="Modal"
style={{
transform: this.props.show ,
opacity: this.props.show ? "1" : "0"
}}
>
<h3>Purchase Order detail</h3>
<p>Id: {this.props.id}</p> //cannot get it
</div>
);
}
}
export default PurchaseInfoView;
console.log result:
If you want to pass an object to props here are the steps:
define the object in your parents state.
pass the object in props when calling components
get the object from props in child.
Here you are missing the second step!
You should try these:
MainPage
render(props){
const { data, modalObject, displayModal } = this.state; //use destructuring is more readable
return (
<div>
<Header />
<NavigationBar />
<PurchaseInfoView show={displayModal} closeModal={this.closeModal} modalObject={modalObject}/> //pass the object from destructuring state as props
<div className="purchase-side">
<div className="side-title">
<h1>Purchase Order List</h1>
</div>
<hr class="solid" />
{
Object.keys(data).map((key) =>
<div className="list-item">
<h2 onClick= {() => this.openModal(data[key].id)}> //get id
{ data[key].item_name}
</h2>
</div>
)}
</div>
<div className="dads">
</div>
</div>
);
}
OpenModal
openModal = (id) => {
this.setState(
{
displayModal: true,
modalObject: {id: id, ...any others key/val pair}
});
};
PurchaseInfoView
class PurchaseInfoView extends Component {
render() {
const { modalObject} = this.props; //here get your object from props
console.log(modalObject.id);// here you have the object
return (
<div className="Modal"
style={{
transform: this.props.show ,
opacity: this.props.show ? "1" : "0"
}}
>
<h3>Purchase Order detail</h3>
<p>Id: {modalObject.id}</p>
</div>
);
}
}
Tell me if you have any question in comment ;)
NB: i did this with an object (aka {} ) if you needed more things in your modal than just id. If just id is needed you just have to replace the modalObject by just the "id" you need
Cheers!
EDIT: for this solution to work you have to either:
initialise your state to this at least:
this.state={ modalObject : { id: ''}}
or make a not null test in your child component before displaying the element like so:
Id: {modalObject && modalObject.id ? modalObject.id : ' '}
These are needed because on first render your state will have the initial state you setted so if you didnt set anythin or didnt test for a value... well... it's undefined! :)
(note if id is null instead of having an undefined error you will have a blank space displaying in your modal)
Guess you are calling it wrongly. It should be {this.props.id}
render() {
console.log(this.props.id);
return (
<div className="Modal">
<h3>Purchase Order detail</h3>
<p>Id: {this.props.id}</p> //Changed line
</div>
);
}
Inside main page pass the id to PurchaseInfoView and access it as a prop
<PurchaseInfoView show={this.state.displayModal} closeModal={this.closeModal} value={this.openModal} id={this.state.id}/>

Why do i have a problem rendering this 'Loading' component in react?

I have 2 components which use the same data which I am fetching from a json-server.
One component uses the data as shown below:
function Home(props) {
return (
<div className='container'>
<div className='row align-items-start'>
<div className='col-12 col-md m-1'>
<RenderCard item={props.dish}
isLoading={props.dishesLoading}
errMess={props.dishErrMess} />
</div>
<div className='col-12 col-md m-1'>
<RenderCard item={props.promotion}
isLoading={props.promoLoading}
errMess={props.promoErrMess} />
</div>
<div className='col-12 col-md m-1'>
<RenderCard item={props.leader}
isLoading={props.leaderLoading}
errMess={props.leaderErrMess} />
</div>
</div>
</div>
);}
The other component uses the data as shown below:
const leaders = props.leaders.map((leader) => {
return (
<RenderLeader leader={leader} isLoading={props.isLoading}
errMess={props.errMess} />
);
});
Both the RenderLeader and RenderCard components have a similar structure with an if-else loop to display loading animation or error messages along with the actual content.
function RenderLeader({ leader, isLoading, errMess }) {
if (isLoading) {
return (
<Loading />
);
}
else if (errMess) {
return (
<h4>{errMess}</h4>
);
}
else
return (CONTENT)
The problem is that the loading animation and the error message are being displayed for the Home component but NOT for the other component which has exactly similar structure. Moreover, the data is being actually fetched for the second component, its just that it wont display the loading animation and error messages. What is wrong with this ?
Edit
This is how i invoke them both:
<Home
dish={this.props.dishes.dishes.filter((dish) => dish.featured)[0]}
dishesLoading={this.props.dishes.isLoading}
dishErrMess={this.props.dishes.errMess}
promotion={this.props.promotions.promotions.filter((promo) => promo.featured)[0]}
promoLoading={this.props.promotions.isLoading}
promoErrMess={this.props.promotions.errMess}
leader={this.props.leaders.leaders.filter((leader) => leader.featured)[0]}
leaderLoading={this.props.leaders.isLoading}
leaderErrMess={this.props.leaders.errMess}
/>
<Route path='/aboutus' component={() => <About leaders={this.props.leaders.leaders}
isLoading={this.props.leaders.isLoading}
errMess={this.props.leaders.errMess} />} />
Home Component works fine since it renders individual cards. Whereas for the below structure,
const leaders = props.leaders.map((leader) => {
return (
<RenderLeader leader={leader} isLoading={props.isLoading}
errMess={props.errMess} />
);
});
if props.leaders is empty, it cannot iterate and hence RenderLeader will never get called. Hence when props.isLoading is true, the props.leaders will be empty and the above arrow function will return nothing. And once the props.leaders is populated, the above function gets called, but props.isLoading will already be set to false.
That is why the loading icon or the error message was not displayed.
You can modify the calling as,
<div className="col-12">
<Media list>
<RenderLeaderList leaders={props.leaders} isLoading=
{props.leadersLoading} errMess={props.leadersErrMess}/>
</Media>
</div>
and create a new function called RenderLeaderList like;
function RenderLeaderList({leaders, isLoading, errMess})
{
if(isLoading)
{
return(
<Loading />
);
}
else if(errMess)
{
return(
<h4>{errMess}</h4>
);
}
else{
if(leaders!=null)
{
const leaders_map = leaders.map((leader) => {
return(
<div key={leader.id} className="col-12 mt-5">
<RenderLeader leader={leader}/>
</div>
);
});
return (
<div>
{leaders_map}
</div>
);
}
}
}
to make it work.
Hope it helps! :D

Elegant way to render props regardless of their type

Considering I have a component which accepts one prop, what would be the easiest and most elegant way for both of these to work:
<MessageBlock message="Some message goes here" />
and
<MessageBlock message={() => (
<>
Some message <strong>goes</strong> here
</>
} />
What first comes to my mind is checking the prop type with typeof and rendering the prop according to that like this:
class MessageBlock extends React.Component {
render() {
const { message: Message } = this.props;
return (
<div className="message-block">
{typeof Message === "function" ? (
<Message />
) : (
<>
{message}
</>
)}
</div>
)
}
}
It depends on what your component should be able to do.
If you want your component to be able to display anything inside of it, I would recommend using its children prop as a render function :
const MessageBlock = props => (
<div className={'add the stuff you want to wrap'}>
{props.children()}
</div>
)
const App = props => (
<React.Fragment>
<MessageBlock>
{() =>
<React.Fragment>
Some message <strong>goes</strong> here
</React.Fragment>
}
</MessageBlock>
<MessageBlock>
{() => <p>Another message</p>}
</MessageBlock>
</React.Fragment>
)
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<div id='root'>
But if you want it to display a string and nothing else, use the propTypes with the first solution you proposed :
MessageBlock.propTypes = {
message: PropTypes.string
};
If you want it to do both, you could put a || condition with it, if the message is defined. The message will be shown by default if it exists, and otherwise the children function will be executed :
const MessageBlock = ({ children, message }) => (
<div className={'add the stuff you want to wrap'}>
{message || children()}
</div>
)
/*MessageBlock.propTypes = { //Proptypes are undefined in SO snippets :(
message: PropTypes.string
};*/
const App = props => (
<React.Fragment>
<MessageBlock>
{() =>
<React.Fragment>
Some message <strong>goes</strong> here
</React.Fragment>
}
</MessageBlock>
<MessageBlock message={'A string message'} />
</React.Fragment>
)
ReactDOM.render(<App/>, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.0/umd/react-dom.production.min.js"></script>
<div id='root'>

Rendering Parameterized Function Results

I have a function that I am using to check if there are results from a page load and if there are, then map the array and return a component and if there aren't then return a string stating there are no results. At the moment I have been able to write the function without any issue, but I can't seem to get return statement to load. Am I following the right path to returning the components or is there a better method?
The console logs return the correct info, but everything in the return() isn't appearing in the view.
export default class BlogKanbanLayout extends React.Component {
constructor(props) {
super(props);
this.resultsCheck = this.resultsCheck.bind(this);
}
resultsCheck(blogs, user) {
console.log("resultsCheck")
console.log(blogs)
console.log(blogs.length)
if(blogs.length === 0) {
<p>There are no results for your filter criteria.</p>
} else {
console.log("There are blog results")
console.log(blogs)
console.log(user)
blogs.map((blog, index) => {
console.log("blog map")
console.log(blog)
return (
<div className="row">
<p>This is a test></p>
<BlogKanbanCard {...blog} key={blog.blogIdHash} user={user} />
</div>
)
})
}
}
render() {
return (
<div className="col-md-12">
{this.resultsCheck(this.props.negativeBlogs, this.props.user)}
</div>
)
}
}
In your resultsCheck you forgot to return the result of your mapping.
Also, the key used in the map function needs to be given to parent element, which here is your div.
And using conditional rendering you can reduce your entire component to the following code for te exact same result :
export default class BlogKanbanLayout extends React.Component {
render() {
const { negativeBlogs, user } = this.props
return (
<div className="col-md-12">
{negativeBlogs.length ?
negativeBlogs.map(blog =>
<div className="row" key={blog.blogIdHash}>
<p>This is a test></p>
<BlogKanbanCard {...blog} key={blog.blogIdHash} user={user} />
</div>
)
:
<p>There are no results for your filter criteria.</p>
}
</div>
)
}
}
And since you are not using the state of your component you could even optimize it to a stateless one :
const BlogKanbanLayout = ({ negativeBlogs, user }) =>
<div className="col-md-12">
{negativeBlogs.length ?
negativeBlogs.map(blog =>
<div className="row" key={blog.blogIdHash}>
<p>This is a test></p>
<BlogKanbanCard {...blog} key={blog.blogIdHash} user={user} />
</div>
)
:
<p>There are no results for your filter criteria.</p>
}
</div>
You resultsCheck method need to return something, so you need to add the return statement before the two results
resultsCheck(blogs, user) {
console.log("resultsCheck")
console.log(blogs)
console.log(blogs.length)
if(blogs.length === 0) {
return <p>There are no results for your filter criteria.</p>
} else {
console.log("There are blog results")
console.log(blogs)
console.log(user)
return blogs.map((blog, index) => {
console.log("blog map")
console.log(blog)
return (
<div className="row">
<p>This is a test></p>
<BlogKanbanCard {...blog} key={blog.blogIdHash} user={user} />
</div>
)
})
}
}

Resources