I am creating a blog using React and Firebase. I have a component called Blogger that creates blog posts and then saves them in firebase. Now, I am trying to render a list of all of the blog posts that have been saved into firebase. I can't seem to get a list to render.
So far, I have created a parent component called Blogger and a child component called List. I want List to render a list of blog post titles within the Blogger component. I've passed the prop title to List like this
What am I doing wrong?
My stack is webpack + React + React Router + Flux + Firebase
I get this error:
The error message I recieve
This is my parent object in which the list is created:
import AltContainer from 'alt-container';
import React from 'react';
import { Link } from 'react-router';
import List from './List.jsx'
import Firebase from 'firebase'
const rootURL = 'https://incandescent-fire-6143.firebaseio.com/';
export default class Blogger extends React.Component {
constructor(props) {
super(props);
this.firebaseRef = new Firebase(rootURL + 'items/');
this.state = {
title: '',
text: ''
};
this.firebaseRef.on('value', function(snapshot) {
console.log(snapshot.val());
});
}
handleInputChange = () => {
this.setState({
title: this.refs.title.value,
text: this.refs.text.value});
}
handleClick = () => {
this.firebaseRef.push({
title: this.state.title,
text: this.state.text,
done: false
})
this.setState({title: '',
text: ''
});
}
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={this.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={this.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 title={this.state.title} />
</div>
);
}
}
This is the child object to which I want to pass the props:
import AltContainer from 'alt-container';
import React from 'react';
import { Link } from 'react-router';
import Blogger from './Blogger'
export default class List extends React.Component {
constructor(props) {
super(props);
console.log(Object.keys(this.props.title));
}
render: () => {
return (
if(this.props.title && Object.keys(this.props.title).length === 0) {
return <h4>enter a blog entry to get started</h4>
} else {
var children = [];
for(var key in this.props.title) {
children.push(
<li>
{title.text}
</li>
)
}
}
);
}
}
The error in your screenshot is quite clear. It's a syntax error.
The following is not legal JavaScript:
function foo () {
return ( if (true) return 'hello )
}
Nesting return statements like this will crash.
The pattern you are looking for is more like this:
function foo () {
if (cond) {
return <List />
}
return <SomethingElse />
}
Additionally the way you are writing render is incorrect. Class functions should just be:
render() {
// return stuff
}
Finally your render method should something like this:
render() {
if (this.props.title && Object.keys(this.props.title).length === 0) {
return <h4>enter a blog entry to get started</h4>
}
return Object.keys(this.props.title).map(key =>
<li>{this.props.title[key]}</li>
)
}
Here is the solution
Blogger.jsx
import AltContainer from 'alt-container';
import React from 'react';
import { Link } from 'react-router';
import List from './List.jsx'
import Firebase from 'firebase'
const rootURL = 'https://incandescent-fire-6143.firebaseio.com/';
export default class Blogger extends React.Component {
constructor(props) {
super(props);
this.firebaseRef = new Firebase(rootURL + 'items/');
this.state = {
title: '',
text: ''
};
this.firebaseRef.on('value', function(snapshot) {
console.log(snapshot.val());
});
}
handleInputChange = () => {
this.setState({
title: this.refs.title.value,
text: this.refs.text.value});
}
handleClick = () => {
this.firebaseRef.push({
title: this.state.title,
text: this.state.text,
done: false
})
this.setState({title: '',
text: ''
});
}
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={this.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={this.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 title={this.state.title} />
</div>
);
}
}
List.jsx
import AltContainer from 'alt-container';
import React from 'react';
import { Link } from 'react-router';
import Blogger from './Blogger'
export default class List extends React.Component {
constructor(props) {
super(props);
// console.log(this.props.blog);
}
render() {
console.log(this.props.blog)
// Object.keys(this.props.title[key]).map(key) =>
// <li>{this.props.title}</li>
// )
return (
<div>
<li>{this.props.blog.title}</li>
</div>
)
}
}
Related
I'm new to React. I'm trying to do simple validation of a form elements. I'm stuck at validating input data and setting the 'name_error' state as the error msg inside <p> tag. Leaving my code below
// App.js
import React, { Component } from 'react';
import ValidateName from './component/validation';
import Modal from './modal';
class Home extends Component {
state = {
show: false,
name: "",
name_error: ""
}
handleChanges = (e) => {
const name = e.target.name;
const value = e.target.value;
this.setState({[name] : value})
}
render() {
console.log(this)
return (
<div>
<div className='Main'>
{/* < Main /> */}
<button id='add' onClick={()=>{this.setState({show: true})}}>Add</button>
</div>
{this.state.show &&
<Modal>
<form>
<div className='modalContainer'>
<b className='title'>Register</b>
<button id='xmark' onClick={()=>{this.setState({show: false})}} >×</button>
<label for='name' >Name</label><br />
<input type="text" id='name' placeholder='Enter your name here' name="name" onChange={this.handleChanges}/><br />
< ValidateName content={this.state.name} />
<button type='submit'>Sign Up</button>
<button>Cancel</button>
</div>
</form>
</Modal>}
</div>
);
}
}
export default Home;
// Modal.js
import React, { Component } from 'react';
class Modal extends Component {
render() {
return (
<div>
{this.props.children}
</div>
);
}
}
export default Modal;
//validation.js
class ValidateName extends Component {
err1 ='Please enter any name';
err2 = 'Use only letters in this field';
render() {
const ren = this.props.content.length === 0 ? (<p>{this.err1}</p> ) :
(this.props.content.match(/[a-zA-Z]+$/) ? '' : <p>{this.err2}</p>)
return ren;
}
}
Please suggest an idea to set name_error as 'Please enter any name' or 'Use only letters in this field' when user enters wrong input
State should be like this,
constructor(props){
super(props);
this.state = {
show: false,
name: "",
name_error: ""
}
}
And Also the update state using callback. It would be helpful,
this.setState({[name] : value},() =>{console.log(this.state)})
I have a menu button that when pressed has to add a new component. It seems to work (if I manually call the function to add the components they are shown). The problem is that if I click the button they are not shown, and I suppose because I should use setState to redraw them. I am not sure how to call the setState of another component within another function/component.
This is my index.js
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import Menu from './Menu';
import * as serviceWorker from './serviceWorker';
import Blocks from './Block.js';
ReactDOM.render(
<div className="Main-container">
<Menu />
<Blocks />
</div>
, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers:
serviceWorker.unregister();
Then I have the Menu.js
import React from 'react';
import './Menu.css';
import {blocksHandler} from './Block.js';
class Menu extends React.Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleAdd = this.handleAdd.bind(this);
}
handleAdd(event) {
blocksHandler.add('lol');
console.log(blocksHandler.render());
}
render() {
return (
<div className="Menu">
<header className="Menu-header">
<button className="Menu-button" onClick={this.handleAdd}>Add block</button>
</header>
</div>
);
}
}
export default Menu;
And finally the Block.js
import React from 'react';
import './Block.css';
// this function adds components to an array and returns them
let blocksHandler = (function() {
let blocks = [];
return {
add: function(block) {
blocks.push(block);
},
render: function() {
return blocks;
}
}
})();
class Block extends React.Component {
constructor(props) {
super(props);
this.state = {
title: '',
content: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({[event.target.name]: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.title);
event.preventDefault();
}
render() {
return (
<div className="Block-container">
<form onSubmit={this.handleSubmit}>
<div className="Block-title">
<label>
Block title:
<input type="text" name="title" value={this.state.value} onChange={this.handleChange} />
</label>
</div>
<div className="Block-content">
<label>
Block content:
<input type="text" name="content" value={this.state.value} onChange={this.handleChange} />
</label>
</div>
<input type="submit" value="Save" />
</form>
</div>
);
}
}
class Blocks extends React.Component {
render() {
return (
<div>
{blocksHandler.render().map(i => (
<Block key={i} />
))}
</div>
)
}
}
export default Blocks;
export {blocksHandler};
I am a React complete beginner so I'm not even sure my approach is correct. Thank you for any help you can provide.
Below I've knocked up a really simple Parent / Child type setup,..
The Parent is responsible for rendering the Buttons, I just used a simple numbered array here. When you click any of the buttons, it calls the setState in the Parent, and this in turns causes the Parent to re-render it's Children.
Note: I've also used React Hooks to do this, I just find them more
natural and easier to use. You can use Classes, the same principle
applies.
const {useState} = React;
function Child(props) {
const {caption} = props;
const {lines, setLines} = props.pstate;
return <button onClick={() => {
setLines([...lines, lines.length]);
}}>
{caption}
</button>;
}
function Parent(props) {
const [lines, setLines] = useState([0]);
return lines.map(m => <Child key={m} caption={`Click ${m}`} pstate={{lines, setLines}}/>);
}
ReactDOM.render(<React.Fragment>
<Parent/>
</React.Fragment>, document.querySelector('#mount'));
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="mount"></div>
Instead of creating blocksHandlers as a separate function ,you can have it nside the Menu.js like as follows
*
class Block extends React.Component {
constructor(props) {
super(props);
this.state = {
title: '',
content: ''
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleChange(event) {
this.setState({[event.target.name]: event.target.value});
}
handleSubmit(event) {
alert('A name was submitted: ' + this.state.title);
event.preventDefault();
}
render() {
return (
<div className="Block-container">
<form onSubmit={this.handleSubmit}>
<div className="Block-title">
<label>
Block title:
<input type="text" name="title" value={this.state.value} onChange={this.handleChange} />
</label>
</div>
<div className="Block-content">
<label>
Block content:
<input type="text" name="content" value={this.state.value} onChange={this.handleChange} />
</label>
</div>
<input type="submit" value="Save" />
</form>
</div>
);
}
}
Menu.js
class Menu extends React.Component {
constructor(props) {
super(props);
this.state = {value: '',blocksArray:[]};
this.handleAdd = this.handleAdd.bind(this);
}
handleAdd() {
this.setState({
blocksArray:this.state.blocksArray.push(block)
})
}
renderBlocks = ()=>{
this.state.blocksArray.map(block=> <Block/>)
}
render() {
return (
<div className="Menu">
<header className="Menu-header">
<button className="Menu-button" onClick={()=>this.handleAdd()}>Add block</button>
</header>
{this.renderBlocks()}
</div>
);
}
}
export default Menu;
I have a Parent component, App.js and a Child component, MealModal.js. When a user click on a specific meal card, it raises a modal that should display further information about the meal.
Hence I try to find a way to dynamically change the modals's data, depending on which meal card is clicked
I have tried to pass the id of the meal to the onClick={this.openModal} function and set the state of modalId is the function. But I got the following error:
Warning: Cannot update during an existing state transition (such as
within render or another component's constructor). Render methods
should be a pure function of props and state; constructor side-effects
are an anti-pattern, but can be moved to 'componentWillMount'.
Here are my components so far:
App.js:
import React from 'react';
import MealCard from './MealCard';
import MealsMap from './MealsMap';
import MealsFilters from './MealsFilters';
import MealModal from './MealModal';
export default class App extends React.Component {
constructor() {
super();
this.state = {
modalIsOpen: false,
modalId: 0
}
this.openModal = this.openModal.bind(this);
this.closeModal = this.closeModal.bind(this);
};
openModal() {
this.setState({modalIsOpen: true});
};
closeModal() {
this.setState({modalIsOpen: false});
};
render() {
return (
<div>
<MealsFilters/>
<div className="app-wrapper" style={{display: 'flex'}}>
<div className="container">
<div className="row">
{[...Array(20)].map((x, i) =>
<div className="col-sm-6 col-xs-12 " key={i} onClick={this.openModal}>
<MealCard />
</div>
)}
</div>
</div>
<MealsMap/>
</div>
<MealModal modalIsOpen={this.state.modalIsOpen} closeModal={this.closeModal} modalId={this.state.modalId}/>
</div>
);
}
}
MealModal.js
import React from 'react';
import Modal from 'react-modal';
const customStyles = {
content : {
}
};
Modal.setAppElement('#app')
export default class MealModal extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<Modal
isOpen={this.props.modalIsOpen}
onRequestClose={this.props.closeModal}
style={customStyles}
contentLabel="Meal Modal"
>
<div className="modal-wrapper">
<div className="container text-center">
<h1>Hello</h1>
<h2>ID of this modal is {this.props.modalId}</h2>
<h3>This is an awesome modal.</h3>
<button onClick={this.props.closeModal}>close</button>
</div>
</div>
</Modal>
)
}
}
Any idea on how I could do this ?
Ok so I found the solution:
First, I changed onClick={this.openModal} in the parent comoponent to onClick= () => {this.openModal}
Second, I add the id as a parameter:
onClick= () => {this.openModal(i)}
Finally: update the openModal function:
openModal(modalId) {
this.setState({modalIsOpen: true,
modalId});
};
And it works.
openModal(modalId) {
this.setState({
modalId,
modalIsOpen: true
});
};
and modify the call function as
<div className="col-sm-6 col-xs-12" key={i} onClick={() => this.openModal(x) } >
<MealCard/>
</div>
I'm using React and firebase to create a simplified slack and using MDl for styles. I'm trying to integrate a button that opens up a dialog box to get some user input, in this case the name of a new chat room, and then when submitted it will send the data to firebase and store it in the rooms array and display the new room name in the list. I have already set up the form to get user input and then I tried to refactor it to work in a dialog box and I seem stuck on how to get the dialog box to work. Here is my whole component:
import React, { Component } from 'react';
import './RoomList.css';
import dialogPolyfill from 'dialog-polyfill';
class RoomList extends Component {
constructor(props) {
super(props);
this.state = { rooms: [] };
this.roomsRef = this.props.firebase.database().ref('rooms');
this.handleChange = this.handleChange.bind(this);
this.createRoom = this.createRoom.bind(this);
}
handleChange(e){
this.setState({ name: e.target.value });
}
createRoom(e) {
e.preventDefault();
this.roomsRef.push({ name: this.state.name });
this.setState({ name: "" });
}
componentDidMount() {
this.roomsRef.on('child_added', snapshot => {
const room = snapshot.val();
room.key = snapshot.key;
this.setState({ rooms: this.state.rooms.concat( room ) });
})
}
dialogBox(e){
const dialogButton = document.getElementsByClassName('dialog-
button');
const dialog = document.getElementById('dialog');
if (! dialog.showModal) {
dialogPolyfill.registerDialog(dialog);
}
dialogButton.onClick( (e) => {
dialog.showModal();
});
dialog.document.getElementsByClassName('submit-close').onCLick(
(e) => {
dialog.close();
});
}
render() {
const roomlist = this.state.rooms.map( (room) =>
<span className="mdl-navigation__link" key={room.key}>
{room.name}</span>
);
const newListForm = (
<div id="form">
<button className="mdl-button mdl-js-button mdl-button--
raised mdl-js-ripple-effect dialog-button">Add room</button>
<dialog id="dialog" className="mdl-dialog">
<h3 className="mdl-dialog__title">Create Room name</h3>
<div className="mdl-dialog__content">
<p>Enter a room name</p>
</div>
<div className="mdl-dialog__actions">
<form onSubmit={this.createRoom}>
<div className="mdl-textfield mdl-js-textfield">
<input type="text" value={this.state.name}
className="mdl-textfield__input" id="rooms" onChange=
{this.handleChange} />
<label className="mdl-textfield__label"
htmlFor="rooms">Enter room Name...</label>
<button type="submit" className="mdl-button submit-
close">Submit</button>
<button type="button" className="mdl-button submit-
close">Close</button>
</div>
</form>
</div>
</dialog>
</div>
);
return (
<div className="layout mdl-layout mdl-js-layout mdl-layout--
fixed-drawer mdl-layout--fixed-header">
<header className="header mdl-layout__header mdl-color--
grey-100 mdl-color-text--grey-600">
<div className="mdl-layout__header-row">
<span className="mdl-layout-title">Bloc Chat</span>
<div className="mdl-layout-spacer"></div>
</div>
</header>
<div className="drawer mdl-layout__drawer mdl-color--blue-
grey-900 mdl-color-text--blue-grey-50">
<header className="drawer-header">
<span>{newListForm}</span>
</header>
<nav className="navigation mdl-navigation mdl-color--
blue-grey-800">
<div>{roomlist}</div>
<div className="mdl-layout-spacer"></div>
</nav>
</div>
</div>
);
}
}
export default RoomList;
Here is a simple sample on how to build a modal with the new portal API provided from React v16.xx
Working demo can be found here. Just use the dropdown to navigate to the simple portal demo. A snapshot of the full code base can be found on github.
Working Code
import React, { Component } from "react";
import { createPortal } from "react-dom";
import "./simple-portal.css";
export default class SimplePortal extends Component {
constructor() {
super();
this.state = {
list: [],
input: "",
showDialog: false
};
this._onChange = this._onChange.bind(this);
this._onSubmit = this._onSubmit.bind(this);
}
_onChange(e) {
let input = e.target.value;
this.setState({ input });
}
_onSubmit(e) {
e.preventDefault();
let showDialog = false;
// Dont Mutate the State!!!
let list = this.state.list.slice();
list.push(this.state.input);
this.setState({ showDialog, list, input: "" });
}
render() {
const { showDialog, list, input } = this.state;
return (
<div className="container">
<div>
<button
className="btn"
onClick={e =>
this.setState({
showDialog: !showDialog
})
}
>
Add Item
</button>
</div>
{/* Render Items from List */}
<div>
<ul>
{list.map(item => {
return <li key={item}>{item}</li>;
})}
</ul>
</div>
{/* Show Modal - Renders Outside React Hierarchy Tree via Portal Pattern */}
{showDialog === true ? (
<DialogModal>
<div className="dialog-wrapper">
<h1>New List Item</h1>
<form onSubmit={this._onSubmit}>
<input type="text" value={input} onChange={this._onChange} />
</form>
</div>
</DialogModal>
) : null}
</div>
);
}
}
class DialogModal extends Component {
constructor() {
super();
this.body = document.getElementsByTagName("body")[0];
this.el = document.createElement("div");
this.el.id = "dialog-root";
}
componentDidMount() {
this.body.appendChild(this.el);
}
componentWillUnmount() {
this.body.removeChild(this.el);
}
render() {
return createPortal(this.props.children, this.el);
}
}
I don't see any event listeners on your buttons that would trigger a rendering of the modal. I would approach this by specifying an onClick event that would update the state which would render the modal/dialogue box.
Another solution, which may be the way you are thinking, is to have the state change to render the modal/dialogue box as visible from within the createRoom() function. Remember, updating state or getting new props will trigger a rendering of the component. You are trying to update the state to re-render your component with the modal/dialogue being shown.
Sorry if I misunderstood the question or your goal.
I am developing UI for a search engine using React. For Searching, I am using Elasticsearch. I am able to connect and fetch results from my index, but I'm only able to fetch only those number of results of which size i specified. I want to enable Infinite scroll until the client reaches to the end of results.
I need help in implementing scan & scroll and pagination
Here is my code of app.js component
import React from 'react';
import Header from './header';
import Footer from './footer';
import SearchResults from './searchresults';
import elasticsearch from 'elasticsearch';
let client = new elasticsearch.Client({
host: 'localhost:9200',
log: 'trace'
})
class App extends React.Component {
constructor(props) {
super(props);
this.state = { results: [], search_query: '*' }
this.handleChange = this.handleChange.bind(this);
}
handleChange ( event ) {
var search_query = event.target.value;
var size = 20;
client.search({
index: 'photos',
type: 'photo',
q: search_query,
size: size
}).then(function ( body ) {
this.setState({ results: body.hits.hits })
}.bind(this), function ( error ) {
console.trace( error.message );
});
}
render () {
return (
<div className="main">
<Header />
<div className="row test">
<div className="col-xs-4"></div>
<div className="col-xs-4">
<div className="row">
<div className="col-xs-12">
<input id="search" className="form-control" type="text" placeholder="Start Searching" name="search" onChange={ this.handleChange }></input>
</div>
</div>
</div>
<div className="col-xs-4"></div>
<br />
</div>
<div className="test bclass">
<SearchResults results={ this.state.results } />
<button type="button" className="btn btn-default">Load More</button>
</div>
<Footer />
</div>
)
}
}
export default App;
And here is my code for searchresults.js component:
import React from 'react';
import PropTypes from 'prop-types';
import LazyLoad from 'react-lazy-load';
let i = 1;
class SearchResults extends React.Component {
constructor(props) {
super(props);
this.state = { results: [] }
}
render () {
return (
<div className="search_results">
<hr />
<ul>
{ this.props.results.map((result) => {
return (
<li key={ result._id + i++}>
<LazyLoad className="lazy">
<img className="image" src={result._source.file_name} alt="Search Result" />
</LazyLoad>
</li>
) }) }
</ul>
</div>
)
}
}
SearchResults.propTypes = {
results: PropTypes.array
}
export default SearchResults;
Make use of AJAX call on scroll as in Facebook home page; use a global incrementing counterVariable that increments on each AJAX call for your ElasticSearch with parameters from records of your result documents of size you want in one AJAX call of scroll. Do increment on every ajax call.
For javaScript of calling AJAX on scroll refer Similar Solved Answer .