I am struggling with figuring out how to implement conditional rendering in React. Basically, what I want to do is this: if there is a reviewResponse in the reviewResponses array, I no longer want to render the reviewResponseForm. I only want to render that ReviewResponse. In other words, each review can only have one response in this app.
I am not sure what I am doing wrong when trying to implement this logic. I know I need to implement some kind of conditional statement saying if the length of my reviewResponses array is greater than 0, I need to render the form. Otherwise, I need to render that reviwResponse. Every statement I have written has not worked here. Does anybody have a suggestion?
Here is my code so far:
My review cardDetails component renders my ReviewResponseBox component and passed the specific reviewId as props:
import React from "react";
import { useLocation } from "react-router-dom";
import StarRatings from "react-star-ratings";
import ReviewResponseBox from "../ReviewResponse/ReviewResponseBox";
const ReviewCardDetails = () => {
const location = useLocation();
const { review } = location?.state; // ? - optional chaining
console.log("history location details: ", location);
return (
<div key={review.id} className="card-deck">
<div className="card">
<div>
<h4 className="card-title">{review.place}</h4>
<StarRatings
rating={review.rating}
starRatedColor="gold"
starDimension="20px"
/>
<div className="card-body">{review.content}</div>
<div className="card-footer">
{review.author} - {review.published_at}
</div>
</div>
</div>
<br></br>
{/*add in conditional logic to render form if there is not a response and response if there is one*/}
<ReviewResponseBox review_id={review.id}/>
</div>
);
};
export default ReviewCardDetails;
Then eventually I want this component, ReviewResponseBox, to determine whether to render the responseform or the reviewresponse itself, if it exists already.
import React from 'react';
import ReviewResponse from './ReviewResponse';
import ReviewResponseForm from './ReviewResponseForm';
class ReviewResponseBox extends React.Component {
constructor() {
super()
this.state = {
reviewResponses: []
};
}
render () {
const reviewResponses = this.getResponses();
const reviewResponseNodes = <div className="reviewResponse-list">{reviewResponses}</div>;
return(
<div className="reviewResponse-box">
<ReviewResponseForm addResponse={this.addResponse.bind(this)}/>
<h3>Response</h3>
{reviewResponseNodes}
</div>
);
}
addResponse(review_id, author, body) {
const reviewResponse = {
review_id,
author,
body
};
this.setState({ reviewResponses: this.state.reviewResponses.concat([reviewResponse]) }); // *new array references help React stay fast, so concat works better than push here.
}
getResponses() {
return this.state.reviewResponses.map((reviewResponse) => {
return (
<ReviewResponse
author={reviewResponse.author}
body={reviewResponse.body}
review_id={this.state.review_id} />
);
});
}
}
export default ReviewResponseBox;
Here are the ReviewResponseForm and ReviewResponse components:
import React from "react";
class ReviewResponseForm extends React.Component {
render() {
return (
<form className="response-form" onSubmit={this.handleSubmit.bind(this)}>
<div className="response-form-fields">
<input placeholder="Name" required ref={(input) => this.author = input}></input><br />
<textarea placeholder="Response" rows="4" required ref={(textarea) => this.body = textarea}></textarea>
</div>
<div className="response-form-actions">
<button type="submit">Post Response</button>
</div>
</form>
);
}
handleSubmit(event) {
event.preventDefault(); // prevents page from reloading on submit
let review_id = this.review_id
let author = this.author;
let body = this.body;
this.props.addResponse(review_id, author.value, body.value);
}
}
export default ReviewResponseForm;
import React from 'react';
class ReviewResponse extends React.Component {
render () {
return(
<div className="response">
<p className="response-header">{this.props.author}</p>
<p className="response-body">- {this.props.body}</p>
<div className="response-footer">
</div>
</div>
);
}
}
export default ReviewResponse;
Any advice would be helpful, thank you.
If I understand your question correctly, you want to render ReviewResponseForm if the this.state.reviewResponses state array is empty.
Use the truthy (non-zero)/falsey (zero) array length property to conditionally render either UI element.
render () {
const reviewResponses = this.getResponses();
const reviewResponseNodes = <div className="reviewResponse-list">{reviewResponses}</div>;
return(
<div className="reviewResponse-box">
{reviewResponses.length
? (
<>
<h3>Response</h3>
{reviewResponseNodes}
</>
)
: (
<ReviewResponseForm addResponse={this.addResponse.bind(this)}/>
)}
</div>
);
}
Related
I am new in ReactJs and trying to create a popup window through onclick event.
I am following this resource - https://dev.to/skptricks/create-simple-popup-example-in-react-application-5g7f
File - /src/components/feed.js
import React from 'react';
function feed (props) {
return (
<div className="card-header">
<h2>{props.firstname} {props.middleInitial} {props.lastname}</h2>
<h4 className="card-title">{props.gender}</h4>
</div>
<div className="card-footer">
<button onClick="" className="btn btn-secondary">Click To view Samples</button>
</div>
);
}
export default feed;
File - /src/app.js
import React, { Component } from 'react';
import './App.css';
import Header from './components/header.js';
import fetchfeed from './components/fetchfeed.js';
class App extends Component {
render() {
return (
<div>
<Header />
<div className="d-flex justify-content-center">
<fetchfeed />
</div>
</div>
);
}
}
export default App;
File - /src/components/fetchfeed.js
import React from 'react';
import axios from 'axios';
import Pagination from "react-js-pagination";
import feed from './feed.js';
class fetchfeed extends React.Component {
constructor(props) {
super(props);
this.state = {
feedDetails: []
};
this.fetchURL = this.fetchURL.bind(this);
}
fetchURL() {
axios.get(`/feed/`)
.then( response => {
..............
});
//Fetch the feed url and process the variables and setstate to feedDetails array.
}
componentDidMount () {
this.fetchURL()
}
populateRowsWithData = () => {
const feedData = this.state.feedDetails.map(feed => {
return <feed
key = {feed.id}
firstname = {feed.firstname}
middleInitial = {feed.middleInitial}
lastname = {feed.lastname}
dateOfBirth = {feed.dateString}
gender = {feed.gender}
/>;
});
return feedData
}
render(){
return (
<div >
{this.populateRowsWithData()}
</div>
);
}
}
export default fetchfeed;
I have already created Popup.js under /src/components and the required css for the popup as directed on reference link.
My question is where should I define the onclick function for popup?
Any help is highly appreciated. Thanks in advance.
As it says in the source you should do something like this in the component you want to show the popup in:
//This is the function
togglePopup() {
this.setState({
showPopup: !this.state.showPopup
});
}
// This is what you should do in the render method
{this.state.showPopup ?
<Popup
text='Click "Close Button" to hide popup'
closePopup={this.togglePopup.bind(this)}
/>
: null
}
As per my understanding, you are trying to customize the code in the tutorial according to your requirements. If you want to open the popup on click of the button "click to view samples", you should do two things first.
Define a function to trigger when button is clicked
Attach that function to the button
The following code demonstrates above steps.
import React from 'react';
function feed (props) {
function openPopup(){
//code relevant to open popup
}
return (
<div className="card-header">
<h2>{props.firstname} {props.middleInitial} {props.lastname}</h2>
<h4 className="card-title">{props.gender}</h4>
</div>
<div className="card-footer">
<button onClick={openPopup} className="btn btn-secondary">Click To view Samples</button>
</div>
);
}
export default feed;
I am learning React and I am trying to call a function in a child component, that accesses a property that was passed from parent component and display it.
The props receives a "todo" object that has 2 properties, one of them is text.
I have tried to display the text directly without a function, like {this.props.todo.text} but it does not appear. I also tried like the code shows, by calling a function that returns the text.
This is my App.js
import React, { Component } from "react";
import NavBar from "./components/NavBar";
import "./App.css";
import TodoList from "./components/todoList";
import TodoElement from "./components/todoElement";
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
};
this.addNewTodo = this.addNewTodo.bind(this);
}
addNewTodo(input) {
const newTodo = {
text: input,
done: false
};
const todos = [...this.state.todos];
todos.push(newTodo);
this.setState({ todos });
}
render() {
return (
<div className="App">
<input type="text" id="text" />
<button
onClick={() => this.addNewTodo(document.getElementById("text"))}
>
Add new
</button>
{this.state.todos.map(todo => (
<TodoElement key={todo.text} todo={todo} />
))}
</div>
);
}
}
export default App;
This is my todoElement.jsx
import React, { Component } from "react";
class TodoElement extends Component {
state = {};
writeText() {
const texto = this.props.todo.text;
return texto;
}
render() {
return (
<div className="row">
<input type="checkbox" />
<p id={this.writeText()>{this.writeText()}</p>
<button>x</button>
</div>
);
}
}
export default TodoElement;
I expect that when I write in the input box, and press add, it will display the text.
From documentation
Refs provide a way to access DOM nodes or React elements created in the render method.
I'll write it as:
class App extends Component {
constructor(props) {
super(props);
this.state = {
todos: []
};
this.textRef = React.createRef();
this.addNewTodo = this.addNewTodo.bind(this);
}
addNewTodo() {
const newTodo = {
text: this.textRef.current.value,
done: false
};
const todos = [...this.state.todos, newTodo];
this.setState({ todos });
}
render() {
return (
<div className="App">
<input type="text" id="text" ref={this.textRef} />
<button onClick={this.addNewTodo}>Add new</button>
{this.state.todos.map(todo => (
<TodoElement key={todo.text} todo={todo} />
))}
</div>
);
}
}
In your approach, what you got as an argument to the parameter input of the method addNewTodo is an Element object. It is not the value you entered into the text field. To get the value, you need to call input.value. But this is approach is not we encourage in React, rather we use Ref when need to access the html native dom.
I has not state but it does have logic. A simple mapping. What is the process for making it state-less?
import React from 'react';
import BMFave from './BMFave.jsx';
class BMTag extends React.Component {
constructor(props) {
super(props);
}
render () {
const bookmarks = this.props.bookmarks.map((bookmark) =>
<BMFave bookmark={bookmark} key={bookmark.id} />
);
return (
<div className="bookmark_page" id="{this.props.tag}" >
<div className="bookmark_tag_title">
<p className="bookmark_tag_title_p">
{this.props.tag}
</p>
</div>
{bookmarks}
</div>
)
}
}
export default BMTag;
Stateless doesn't mean no logic, it means no state. So you're already there.
Of course you can simplify:
export default ({bookmarks, tag})=> (
<div className="bookmark_page" id={tag} >
<div className="bookmark_tag_title">
<p className="bookmark_tag_title_p">
{tag}
</p>
</div>
{
bookmarks.map(bm=> <BMFave bookmark={bm} key={bm.id} />)
}
</div>
)
Sure. Just move your mapping function outside the function. You could also leave it in, if you want, but that's not the best practice since it'd get recreated on every render (I think).
import React from 'react';
import BMFave from './BMFave.jsx';
const constructBookmarks = (bookmarks) => {
return bookmarks.map((bookmark) =>
<BMFave bookmark={bookmark} key={bookmark.id} />
);
};
export default (props) => {
return (
<div className="bookmark_page" id={props.tag} >
<div className="bookmark_tag_title">
<p className="bookmark_tag_title_p">
{props.tag}
</p>
</div>
{constructBookmarks(props.bookmarks)}
</div>
);
};
As long as your component does not have a state and does not use React lifecycle methods or refs, it can be a stateless component.
const BMTag = props => {
const bookmarks = props.bookmarks.map((bookmark) =>
<BMFave bookmark={bookmark} key={bookmark.id} />);
return (
<div className="bookmark_page" id="{props.tag}" >
<div className="bookmark_tag_title">
<p className="bookmark_tag_title_p">
{props.tag}
</p>
</div>
{bookmarks}
</div>
)
}
export default BMTag;
For more information, you could check this article about Presentational and Container Components by Dan Abramov.
Currently I am getting data from an API and storing those results into an array. The problem, when I do the array mapping to the child component, it never executes because the array is empty to begin with. How can I execute the array mapping when the array has data in it. I tried inline conditional such as doing {array.length > 0 ? //do array mapping}. I also tried making the array both global and an array that is a state of the parent component.
//React Router
import React from 'react';
import { Route, IndexRoute } from 'react-router';
import Main from '../components/Main';
export default () => {
return <Route path="/" component={Main}/>
};
//Main component
import React, { PropTypes, Component } from 'react';
// import bgImage from './ignasi_pattern_s.png';
import Child1 from './Children/Child1';
import axios from 'axios';
const QUERY_URL = "https://api.nytimes.com/svc/search/v2/articlesearch.json?api-key=";
//****Tried global array declaration and as a state property but both do not work.
// var articles = [];
class Main extends Component {
constructor(props){
super(props);
this.state = {
search: "",
articles: []
}
this.getTopic = this.getTopic.bind(this);
this.executeSearch = this.executeSearch.bind(this);
}
getTopic(event) {
this.setState({
search: event.target.value
});
}
executeSearch(event) {
event.preventDefault();
axios.get(QUERY_URL + "&q=" + this.state.search).then((response) => {
for(var i = 0; i < 5; i++){
this.state.articles.push({
headline: response.data.response.docs[i].lead_paragraph
})
}
});
}
render() {
return (
<div className="Main" style={{backgroundImage: `url(${"http://aiburn.com/files/articles/creating_professional_business_backgrounds/06.gif"})`}}>
<div className="page-header">
<h1>{getNiceName(this.props.routes)}{' '}
<small>page</small>
</h1>
<h1>Search For Something</h1>
<form onSubmit={this.executeSearch}>
<input type="text" value={this.state.search} onChange={this.getTopic}/>
<button type="submit" className="btn btn-default">Search</button>
</form>
</div>
<div className="container Main-content">
//Trouble here mapping the array to the child component.
//This never executes because the array is empty to begin with.
{this.state.articles.length > 0 ?
{this.state.articles.map((item, index) => {
return <Child1
key={index}
headline={item.headline}
/>;
})}
}
</div>
</div>
);
}
}
Main.propTypes = {
children: PropTypes.node,
routes: PropTypes.array
};
export default Main;
//Child Component
import React, { Component } from 'react';
class Child1 extends Component {
constructor(props) {
super(props);
}
render() {
return <div>
<div className="container">
<h1>{this.props.headline}<span><button type="submit" className="btn btn-default">Save</button></span></h1>
</div>
</div>;
}
}
export default Child1;
You should not need to check if this.state.articles.length > 0. You can just proceed to call this.state.articles.map immediately. map, when given an empty array, just returns the empty array - it does nothing with it.
That is, [].map(x => whatever(x)) === [].
Therefore, even if this.state.articles.length <= 0, you will just end up rendering the empty collection (that is, nothing at all).
I am not sure if this might be an issue or not, but seems like there is syntax error with inline conditional. The code should be like this.
<div className="container Main-content">
{this.state.articles.length > 0 ?
this.state.articles.map((item, index) => {
return <Child1
key={index}
headline={item.headline}
/>;
}) : null
}
</div>
Also, as #evocatus mentioned there is no need to put check on length as map already handles this.
If you want to render a different component when the array is empty, you can put that element instead of null.
I am creating a basic blog in react using Flux + React Router + Firebase. I am having trouble trying to get a single blog post to render. When I click on the link to a single post, I try to filter out all of the other posts from a list of all posts and display only a single post from my firebase database.
I attempt to do this by matching the key of the firebase entry with the url params like so if (this.props.routeParams.key===key) . I really do not know what I have to do to make this happen. Any suggestions are welcome.
Below is Blogger.jsx, the page where I allow a user to create a blog post and then beneath the blog post, I display a list of the titles all blog posts.
import AltContainer from 'alt-container';
import React from 'react';
import { Link } from 'react-router';
import List from './List.jsx'
import Firebase from 'firebase'
import BlogStore from '../stores/BlogStore'
import BlogActions from '../actions/BlogActions';
const rootURL = 'https://incandescent-fire-6143.firebaseio.com/';
export default class Blogger extends React.Component {
constructor(props) {
super(props);
BlogStore.getState();
BlogStore.mountFirebase();
{console.log(this.props.location.query)}
};
componentDidMount() {
BlogStore.listen((state) => {
this.setState(state)
})
this.firebaseRef = new Firebase(rootURL + 'items/');
}
componentWillMount() {
BlogStore.unlisten((state) => {
this.setState(state)
})
}
renderList = (key) => {
return (
<Link to={`blogshow/${key}`}> <List key={key} blog={this.state.blog[key]} /> </Link>
)
}
handleInputChange = () => {
BlogStore.setState({
title: this.refs.title.value,
text: this.refs.text.value});
}
handleClick = () => {
BlogStore.handleClick();
}
render() {
return (
<div>
<div className="row panel panel-default">
<div className="col-md-8 col-md-offset-2">
<h2>
Create a New Blog Post
</h2>
</div>
</div>
<h2>Blog Title</h2>
<div className="input-group">
<input
ref="title"
value={BlogStore.state.title}
onChange = {this.handleInputChange}
type="text"
className="form-control"/>
<span className="input-group-btn">
</span>
</div>
<h2>Blog Entry</h2>
<div className="input-group">
<textarea
ref="text"
value={BlogStore.state.text}
onChange = {this.handleInputChange}
type="text"
className="form-control"/>
</div>
<div className="blog-submit input-group-btn">
<button onClick={this.handleClick}
className="btn btn-default" type="button">
Publish Blog Post
</button>
</div>
{/*<List blog={this.state.blog} />*/}
{Object.keys(BlogStore.state.blog)
.map(this.renderList)}
</div>
);
}
}
When a user clicks on a link to a single blog post, they should be transported to a page which shows only that single blog post. I have called this component BlogShow. I can't get BlogShow to render because I keep on getting the error
invariant.js?4599:45 Uncaught Invariant Violation: BlogShow.render(): A
valid React element (or null) must be returned. You may have returned
undefined, an array or some other invalid object.
This is BlogShow.jsx:
import AltContainer from 'alt-container';
import React from 'react';
import { Link } from 'react-router';
import Blogger from './Blogger'
import List from './List'
const rootURL = 'https://incandescent-fire-6143.firebaseio.com/';
import BlogStore from '../stores/BlogStore'
import BlogActions from '../actions/BlogActions';
export default class BlogShow extends React.Component {
constructor(props) {
super(props);
{console.log(this.props.routeParams.key)}
this.filterList = this.filterList.bind(this);
}
filterList(key) {
if (this.props.routeParams.key===key) {
return (<List key={key} blog={BlogStore.state.blog[key]} />)
}
}
render() {
<div> {Object.keys(BlogStore.state.blog).map(this.filterList)} </div>
}
}
You are getting that error because your Component BlogShow is not returning anything.
render() {
<div> {Object.keys(BlogStore.state.blog).map(this.filterList)} </div>
}
Should be:
render() {
return <div> {Object.keys(BlogStore.state.blog).map(this.filterList)} </div>
}
I'm not familiar with React.js at all, but I am familiar with pure JS arrays. To remove elements from an array, you should use .filter(), and then afterwards you can map the items.
Something like this:
filterList(key) {
return this.props.routeParams.key === key; // true if the item should stay in the list
}
mapList(key) {
return <List key={key} blog={BlogStore.state.blog[key]} />;
}
render() {
return <div> {Object.keys(BlogStore.state.blog).filter(this.filterList).map(this.mapList)} </div>;
}