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>;
}
Related
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>
);
}
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.
Child
import React, {Component} from 'react'
class Card extends Component {
render() {
let props = this.props;
return(
<div className="card-main">
<img src={`http://image.tmdb.org/t/p/w342/${props.path}`} alt="Poster" />
<div className="card-deatils">
<h4 className="card-name">{props.name}</h4>
<h4 className="id card-name">{props.id}</h4>
</div>
</div>
);
}
}
export default Card;
I want to get the id stored in with class "id". The problem is that from previous component the amount cards are at minimum 20 in the page and what I ideally want is to pass id back to its parent component. Most of the methods I tried give value undefined.
Parent
import React, {Component} from 'react'
import Card from './Card'
class CardArray extends Component {
render() {
var props = this.props
return (
<div className="pop-movie-container">
<div className="card">
{
props.popNames.map((val,index) => {
return(
<Card
key ={props.popId[index]}
id={props.popId[index]}
name={props.popNames[index]}
path={props.popPath[index]}
/>
);
})
}
</div>
<div className="load-more">
<button className="btn btn-12" onClick={props.change}>LOAD MORE</button>
</div>
</div>
);
}
}
export default CardArray;
popNames is an array containing 20 names at minimum and increases by 20 on click of load more.
So, ideally what I want is that to get id from Card passed to CardArray.
output
So, when someone clicks on card more information can be fetched from the api using id of movie
parent
import React, { Component } from "react";
import Card from "./Card";
class CardArray extends Component {
fetchDetails = (id) => {
// fetching logic goes here
}
render() {
var props = this.props;
return (
<div className="pop-movie-container">
<div className="card">
{props.popNames.map((val, index) => {
return (
<Card
key={props.popId[index]}
id={props.popId[index]}
name={props.popNames[index]}
path={props.popPath[index]}
handelClick={this.fetchDetails}
/>
);
})}
</div>
<div className="load-more">
<button className="btn btn-12" onClick={props.change}>
LOAD MORE
</button>
</div>
</div>
);
}
}
export default CardArray;
child
import React, { Component } from "react";
class Card extends Component {
render() {
let props = this.props;
return (
<div className="card-main" onClick={() => this.props.handelClick(this.props.id)}>
<img
src={`http://image.tmdb.org/t/p/w342/${props.path}`}
alt="Poster"
/>
<div className="card-deatils">
<h4 className="card-name">{props.name}</h4>
<h4 className="id card-name">{props.id}</h4>
</div>
</div>
);
}
}
export default Card;
u can pass an function as props to child and child can call that function with id.
if you need more explanation let me know.
also once quick suggestion you can directly pass
name={val} instead of name={props.popNames[index]}
I edited code that was working perfectly fine, until I added new code to make the button clicking work from video to video. I just can't find the error, and the terminal is not picking it up either.
Can someone tell me why the ./video_list_item.js is not being recognized anymore?
Attached is the parent, and 2 child components, though I have 5 total components the error is definitely only in one of the two.
index.js
import React, {Component } from 'react';
import ReactDOM from 'react-dom';
import YTSearch from 'youtube-api-search';
import SearchBar from './components/searchbar';
import VideoList from './components/video_list';
import VideoDetail from './components/video_detail';
const API_KEY = 'AIzaSyAi1CzVpifuFUDVQf3dzrTu3mwJDP2n8r8';
class App extends Component {
constructor(props){
super(props);
//Do i expect this component to play any type of state? aka pass props
this.state= {
videos: [],
selectedVideo:null
};
// ^proper name can be anything
YTSearch({key: API_KEY, term: 'surfboards'}, (videos) => {
// console.log(data);
this.setState({ videos:videos,
selectedVideo: videos[0]
});
}); // this.setState({videos : vidoos});
}
render (){
return (
<div>
<SearchBar />
<VideoDetail video={this.state.selectedVideo} />
<VideoList
onVideoSelect={selectedVideo => this.setState({selectedVideo}) }
videos={this.state.videos} />
</div>
);
}
}
ReactDOM.render(<App />, document.querySelector('.container'));
video_list.js
//video list file. JS.react
import React from 'react';
import VideoListItem from './video_list_item';
const VideoList = (props) => {
// ^props is made args here because videos var is passed
// in index.js into VideoList function(with state).
const videoItems = props.videos.map((video) => {
return (
<VideoListItem
onVideoSelect={props.onVideoSelect}
key={video.etag}
video ={video} />
);
});
return (
<ul className="col-md-4 list-group">
{videoItems}
</ul>
);
};
export default VideoList;
video_list.item.js
//video list item file. JS.react
import React from 'react';
const VideoListItem = ({video, onVideoSelect}) => {
const imageUrl = video.snippet.thumbnails.default.url;
// can see this in console log
return (
<li onClick={() => onVideoSelect{video} }className="list-group-item">
<div className ="video-list media">
<div className ="media-left">
<img className="media-object" src = {imageUrl}/>
</div>
<div className="media-body">
<div className="media-heading"> {video.snippet.title} </div>
</div>
</div>
</li>
);
};
export default VideoListItem;
I can post the error message I get in the dev tools, but it literally just says one thing. This is the error message => Cannot find module "./video_list_item"
Also, no files were moved around at all, the code was edited and that created the error message. Thanks for anyone who sincerely answers this question!
<li onClick={() => onVideoSelect{video} }className="list-group-item">
The error is the {video} should actually be in parenthesis like so (video).
correct code is:
<li onClick={() => onVideoSelect(video) }className="list-group-item">.
Note: The {} makes it so the child component is not recognized by the parent for some strange reason. Thanks to all those who helped in answering!