I wanted to recursively add a react component from within its own component. I saw this example of a tree component which was mapping through the child TreeNodes and adding child nodes in the same way. Unfortunately it doesn't work at all for me. The idea was to have a simple comment component, and the replies would reuse the same component.
var Comment = React.createClass({
render: function() {
return (
<div className="comment">
{/* text and author */}
<div className="comment-text">
<span className="author">{this.props.author}</span>
<span className="body" dangerouslySetInnerHTML={{__html: this.props.body}} />
</div>
{/* replies */}
<div className="replies">
{
this.props.replies.map(function(reply) {
<Comment body={reply.body} author={reply.author} />
}.bind(this))
}
</div>
</div>
);
}
});
I get the following error message:
Uncaught TypeError: Failed to construct 'Comment': Please use the 'new' operator, this DOM object constructor cannot be called as a function.
here is an example of the JSON data passed to the component.
{ "author" : "Some user",
"body" : "<div>Great work</div>",
"replies" : [ { "author" : "A user replying",
"body" : "<div Yes it was great work</div>"
},
{ "author" : "Another user replying",
"body" : "<div It really was great work!</div>"
}
]
}
Here's an alternative in ES6:
import React, { Component, PropTypes } from 'react'
export default class Comments extends Component {
render() {
const { children } = this.props
return (
<div className="comments">
{children.map(comment =>
<div key={comment.id} className="comment">
<span>{comment.content}</span>
{comment.children && <Comments children={comment.children}/>}
</div>
)}
</div>
)
}
}
Comments.propTypes = {
children: PropTypes.array.isRequired
}
And is some other component:
<Comments children={post.comments}/>
If I create the child nodes as an object at the top of the render method, it works fine.
export default class extends React.Component {
let replies = null
if(this.props.replies){
replies = this.props.replies.map((reply) => {
return (
<Comment author={reply.author} body={reply.body} />
)
})
}
render() {
return (
<div className="comment">
<div className="replies">{ replies }</div>
</div>
)
}
}
The easiest way is to create a function in the class which returns an instance of your class:
RecursiveComponent.rt.js:
var RecursiveComponent = React.createClass({
render: function() {
// JSX
....
},
renderRecursive: function(param1)
return React.createElement(RecursiveComponent, {param1: param1});
});
if you use react-templates library:
RecursiveComponent.rt:
<div>
...
<div rt-repeat="recursiveChild in this.props.recursiveItem.recursiveChilds">
{this.renderRecursive(recursiveChild)}
</div>
</div>
Related
Here's my structure :
Main.js (Parent)
MainContainer.js
|
|_ Article.js
|
|__ Comments.js
Now i want to set click handler on comment component (recursive component) and dispatch an action.
here's my code on comment.js
class Comment extends Component {
deleteComment = (id) => {
this.props.handleDelete(id)
}
render() {
var comment = this.props.comment
return (
<div className={styles.commentsWrapper}>
<ul>
<li>
<div className={styles.commentsName}>
<a onClick={() => this.deleteComment(comment.id)} className={styles.commentsNameRight}>
</a>
</div>
<p>{comment.body}</p>
{comment.children.length > 0 && comment.children.map(function(child) {
return <Comment comment={child} key={child.id}/>
})}
</li>
</ul>
</div>
);
}
}
export default Comment;
and Article.js :
class Article extends Component {
handleDeleteComment = (id) => {
this.props.deleteComment(id)
}
render() {
return (
<article className={styles.articleItem}>
{this.props.comments.map(item =>
<Comment handleDelete={this.handleDeleteComment} comment={item} key={item.id}/>)}
</article>
);
}
}
export default Article;
And the Main.js
class Main extends Component {
deleteComment = (id) => {
this.props.deleteCommentRequest(id)
}
render() {
return (
<div className="">
<Header />
<section className="container">
<div>
{
!this.props.articles.loading && this.props.articles.articles? (
<div>
{this.props.articles.articles.map(item =>
<Article
bodytext={item.selftext}
key={item.id}
comments={item.finalComments}
deleteComment={this.deleteComment}
/>)}
</div>
) : (
<div className={styles.loading}> <Spin /> </div>
)
}
</div>
</section>
</div>
);
}
}
export default Main;
so what i did here is: pass deleteComment as props from main to article and pass again handleDelete from article to comment.
not sure if it's a good way of doing this ?
Thanks in advance
Nothing wrong with this pattern for 2 - 3 depth of components, as that is how data should flow from children to ancestors. But if your application is getting heavier with several layers, consider a different state management such as redux where a global state is maintained and any component can subscribe to it and dispatch actions. More on that here.
Alternatively you can also achieve the same with React Hooks with useContext where you can set the context and any child component can subscribe to it. Example:
const MyContext = React.createContext();
export default function App({ children }) {
const [items, setItems] = React.useState([]);
return (
<MyContext.Provider value={{ items, setItems }}>
{children}
</MyContext.Provider>
);
}
export { MyContext };
Now in any child at any level of depth as long as it is within App component's children, you can do this:
import {MyContext} from './filename';
function TodoItem() {
const { items, setItems } = React.useContext(MyContext);
return (
<div onClick={() => setItems(1)}>
</div>
);
}
you can use context API to have the props in the wrapper and easily accessible from child component.
there is a great tutorial from wesbos on youtube
class App extends Component {
render() {
return (
<MyProvider>
<div>
<p>I am the app</p>
<Family />
</div>
</MyProvider>
);
}
}
class MyProvider extends Component {
state = {
name: 'Wes',
age: 100,
cool: true
}
render() {
return (
<MyContext.Provider value={{
state: this.state,
growAYearOlder: () => this.setState({
age: this.state.age + 1
})
}}>
{this.props.children}
</MyContext.Provider>
)
}
}
I have a class called: QuestionList, which creates Questions children, along with (nested) Alternatives:
QuestionList render:
<Question wording="wording...">
<Alternative letter="a" text="bla ..." />
<Alternative letter="b" text="ble ..." />
<Alternative letter="c" text="bli ..." />
<Alternative letter="d" text="blo ..." />
</Question>
Who is "alternatives" parent? Question (because it is nested) or QuestionList (because it created)?
How can pass a Question event handler to Alternative?
If I use
<Alternative onClick={this.handleClick} (...) />
It will pass QuestionList's handler (and not Question's handler - the desired behavior).
QuestionList
import React, { Component } from 'react';
import InfiniteScroll from 'react-infinite-scroller';
import Loader from 'react-loaders';
import Question from './Question';
import Alternative from './Alternative';
export default class QuestionList extends Component {
constructor(props) {
super(props);
this.state = {
questions: []
};
}
loadItems(page) {
let questions = this.state.questions;
axios.get('https://jsonplaceholder.typicode.com/photos?_start='+ page * 5 +'&_limit=5')
.then(response => {
console.log(response.data);
response.data.map(p => {
questions.push(p);
});
this.setState({questions});
});
}
handleClick() {
alert("QuestionList");
}
render() {
let items = [];
const loader = <Loader type="ball-scale-multiple" />;
this.state.questions.map((p, i) => {
items.push(
<Question
title={p.title}
key={i}
id={p.id}
>
<Alternative onClick={this.props.handleClick} key={1} text={ p.title } letter="a" />
<Alternative onClick={this.props.handleClick} key={2} text={ p.title } letter="b" />
<Alternative onClick={this.props.handleClick} key={3} text={ p.title } letter="c" />
<Alternative onClick={this.props.handleClick} key={4} text={ p.title } letter="d" />
<Alternative onClick={this.props.handleClick} key={5} text={ p.title } letter="e" />
</Question>
)
});
return (
<InfiniteScroll
key={1}
pageStart={0}
loadMore={this.loadItems.bind(this)}
hasMore={true}
loader={loader}
>
<div className="justify-content-center" id="react-app-questions-list">
{items}
</div>
</InfiniteScroll>
);
}
}
Question
import React, { Component } from 'react';
export default class Question extends Component {
constructor(props) {
super(props);
this.state = {
answer_class: "unanswered"
};
}
handleClick(isCorrect, e) {
// alert(this.props.id + ": " + isCorrect);
alert("Question");
}
render() {
return (
<div className={"list-group list-group-bordered mb-3 " + this.state.answer_class}>
<div className="list-group-item">
<div className="list-group-item-body">
<h4 className="list-group-item-title">
{ this.props.title }
</h4>
</div>
</div>
{ this.props.children }
</div>
);
}
}
Alternative
import React, { Component } from 'react';
class Alternative extends Component {
render() {
return (
<a className="list-group-item list-group-item-action react-app-alternative">
<div className="list-group-item-figure">
<div className="tile tile-circle bg-primary">{ this.props.letter }</div>
</div>
<div className="list-group-item-body"> { this.props.text }</div>
</a>
);
}
}
export default Alternative;
Who is Alternatives parent? Question (because it is nested) or QuestionList (because it created)?
Alternative parent is Question. If you check Question.props.children array (remember that Question is just an object), you will see Alternative types there.
function Question({ children }) {
console.log(children); // children[0].type === Alternative
return children;
}
Read more about React elements as objects here.
How can pass a Question event handler to Alternative?
You can inject props to Question children, for example:
function Question({ children }) {
console.log(children);
const injectChildren = React.Children.map(children, child =>
React.cloneElement(child, { letter: `${child.props.letter}-injected` })
);
return injectChildren;
}
For this you need to read about React Top-Level API and refer to React.Children API and cloneElement().
Check out the example:
Handlers are intended to work on context ... where state is managed .. then in <QuestionList/>. Prepare specific, parametrized handlers and use them to update common state.
Chaining 'desired' (more granular or more specific) handlers to pass values through the structure can't be practical. It won't be efficient, either.
Take a look at data/state flow in 'Formik` project - form, validations, fields. It can be a good source of inspiration for this problem.
<Question/> and <Alternative/> should be stateless, functional components - you don't need them to be statefull. KISS, YAGNI...
Could you tell me please. How can I make React component which I can use with props or with array of these props.
For example I have this component:
import React, { Component } from 'react'
export default class Links extends Component {
render () {
return (
<a
href={ this.props.link }
>
{ this.props.name }
</a>
)
}
}
And I want to use this component here:
import React, { Component } from 'react'
import Links from './Links'
export default class Block extends Component {
render () {
const social = [{
name: 'Twitter',
link: 'https://twitter.com',
}, {
name: 'FaceBook',
link: 'https://fb.com',
}]
return (
<div>
<div>
<Links someword={ social }>
</div>
<Links name={ 'Google' } link={ 'https://google.com' }>
</div>
)
}
}
Loop through your social array and map each value to your Links component and then put it in the render function of your Block component.
export default class Block extends Component {
render () {
const social = [{
name: 'Twitter',
link: 'https://twitter.com',
}, {
name: 'FaceBook',
link: 'https://fb.com',
}]
const linkComps = social.map(e =>
<Links name={ e.name } link={e.link} key={e.name} />;
);
return (
<div>
<div>
{ linkComps }
</div>
<Links name={ 'Google' } link={ 'https://google.com' }>
</div>
)
}
}
You can use map to iterate the array and create the Links component. Since your social variable is having the const value, so instead of defining that inside render method, define it outside in the starting of the file.
Write it like this:
const social = [{
name: 'Twitter',
link: 'https://twitter.com',
}, {
name: 'FaceBook',
link: 'https://fb.com',
}
]
class Block extends React.Component {
render () {
return (
<div>
<div>
{
social.map((el,i) => <Links
key={i}
name={el.name}
link={el.link} />)
}
</div>
<Links name={ 'Google' } link={ 'https://google.com' }/>
</div>
)
}
}
class Links extends React.Component {
render () {
return (
<a
href={ this.props.link }
>
{ this.props.name }
</a>
)
}
}
ReactDOM.render(<Block/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id='app'/>
If you want to handle that inside Links component, you can write it like this, You can pass either an array by name array or pass the individual value by name and Link.
export default class Links extends Component {
_renderLinks(){
if(this.props.array && Array.isArray(this.props.array)){
return this.props.array.map((el,i) => <a
key={i}
href={ el.link}
>
{el.name}
</a>
}else{
return <a href={ this.props.link}> {this.props.name} </a>
}
}
render () {
return (
<div>
{this._renderLinks()}
</div>
)
}
}
The usual way to render multiple components from an array of props is with map:
render() {
// ...
return (
<div>
{social.map(props => (
<Link key={props.link} {...props}/>
))}
</div>
);
}
That said, having a single component that takes two different kinds of props is, generally speaking, a bad idea. That is to say, a component should take e.g. name and link props or it should take an array of objects with those properties. It should not do both.
A clean way to solve your problem is to have two components: A <Link> component that takes name and link props and renders a single link, and a <Links> (plural) component that takes an array of objects with those properties and renders a <Link> (singular) component for each one.
A basic implementation looks like the below. Click on βΈβ Run code snippet below to see it in action (note that I added some CSS just to show the boundaries of each component).
const Link = ({name, link}) => (
<a href={link}>{name}</a>
);
const Links = ({links}) => (
<div>
{links.map(props => <Link key={props.link} {...props}/>)}
</div>
);
class Block extends React.Component {
render() {
return (
<div>
{/* Render an array of links with <Links> */}
<Links links={this.props.social}/>
{/* Render a single link with <Link> */}
<Link name="Google" link="https://google.com"/>
</div>
);
}
}
const social = [
{ name: 'Twitter',
link: 'https://twitter.com',
},
{ name: 'Facebook',
link: 'https://facebook.com',
}
];
ReactDOM.render(<Block social={social}/>, document.querySelector('div'));
a {display: block;}
div div {border: 1px dotted gray; padding: 5px; margin-bottom: 5px;}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div></div>
I'm trying to return multiple React elements from a helper method. I could solve it simply by moving around some code, but I'm wondering if there's a cleaner way to solve it. I have a method that returns part of the render method, and that functions needs to return both a React element and some text. It's clearer through an example:
class Foo extends React.Component {
_renderAuthor() {
if (!this.props.author) {
return null;
}
return [
' by ',
<a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
]; // Triggers warning: Each child in an array or iterator should have a unique "key" prop.
render() {
return (
<div>
{this.props.title}
{this._renderAuthor()}
</div>
);
}
}
I know the render method has to return exactly 1 React element. Using a helper method like this would trigger a warning, and fixing the warning (by adding keys) would make the code too convoluted. Is there a clean way to do this without triggering a warning?
Edit:
Another use case:
render() {
return (
<div>
{user
? <h2>{user.name}</h2>
<p>{user.info}</p>
: <p>User not found</p>}
</div>
);
}
Edit 2:
Turns out this isn't possible yet, I wrote about 2 workarounds here: https://www.wptutor.io/web/js/react-multiple-elements-without-wrapper
Support has been added using the Fragment component. This is a first-class component.
So you can now use:
render() {
return (
<React.Fragment>
<ChildA />
<ChildB />
<ChildC />
</React.Fragment>
);
}
For more information visit: https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html
The error message tells you exactly how to solve this:
Each child in an array or iterator should have a unique "key" prop.
Instead of this:
return [
' by ',
<a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];
Do this:
return [
<span key="by"> by </span>,
<a key="author" href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];
Yes, you need to wrap the text node ("by") in a span in order to give it a key. Such are the breaks. As you can see, I've just given each element a static key, since there's nothing dynamic about them. You could just as well use key="1" and key="2" if you wanted.
Alternatively, you could do this:
return <span> by <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a></span>;
...which obviates the need for keys.
Here's the former solution in a working snippet:
const getAuthorUrl = author => `/${author.toLowerCase()}`;
class Foo extends React.Component {
_renderAuthor() {
if (!this.props.author) {
return null;
}
return [
<span key="by"> by </span>,
<a key="author" href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];
}
render() {
return (
<div>
{this.props.datePosted}
{this._renderAuthor()}
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me"/>, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
It's not currently possible to do this without some sort of workaround like wrapping everything in another component, since it ends up with the underlying React code trying to return multiple objects.
See this active Github issue where support for this is being considered for a future version though.
Edit: You can now do this with Fragments in React 16, see:
https://reactjs.org/blog/2017/11/28/react-v16.2.0-fragment-support.html
There is another way to solve this. I will suggest you create another component Author.js:
function Author(props) {
return (<span>
<span> by </span>
<a href={props.getAuthorUrl(props.author)}>{props.author}</a>
</span>)
}
class Foo extends React.Component {
render() {
return (
<div>
{this.props.title}
{this.props.author && <Author author={this.props.author} getAuthorUrl={this.getAuthorUrl} />}
</div>
);
}
}
I didn't test this code though. But it will look more cleaner I think. Hope it helps.
I like to have an If-component around for such things, and I have wrapped everything into a span, as it doesn't really break anything and makes the need for keys go away...
const getAuthorUrl = author => `/${author.toLowerCase()}`;
function If({condition,children}) {
return condition ? React.Children.only(children) : null;
}
class Foo extends React.Component {
render() {
return (
<div>
{this.props.datePosted}
<If condition={this.props.author}>
<span> by
<a key="author" href={getAuthorUrl(this.props.author)}>
{this.props.author}
</a>
</span>
</If>
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me"/>, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
...skipping the array thing altogether?
This is a bit hacky but it doesn't have unnecessary jsx as you wished.
var author = 'Daniel';
var title = 'Hello';
var Hello = React.createClass({
_renderAutho0r: function() {
if (!author) {
return null;
}
return {author}
},
render: function() {
var by = author ? ' by ' : null;
return (
<div>
{title}
{by}
{this._renderAutho0r()}
</div>
);
}
});
React.render(<Hello name="World" />, document.body);
my JSFiddle
You can return fragments from sub-rendering functions but not from the main render function, at least before React 16. In order to do so, return an array of components. You don't need to set keys manually unless your fragment children will change (arrays are keyed with indices by default).
For creating fragments you may also use createFragment.
For inline usage, you may use an array or leverage immediately invoked arrow function.
See the example below:
const getAuthorUrl = author => `/${author.toLowerCase()}`;
class Foo extends React.Component {
constructor() {
super();
this._renderAuthor = this._renderAuthor.bind(this);
this._renderUser = this._renderUser.bind(this);
}
_renderAuthor() {
if (!this.props.author) {
return null;
}
return [
' by ',
<a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];
}
_renderUser() {
return [
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
]
}
render() {
return (
<div>
{this.props.datePosted}
{this._renderAuthor()}
<div>
{this.props.user
? this._renderUser()
: <p>User not found</p>}
</div>
<div>
{this.props.user
? [
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
]
: <p>User not found</p>}
</div>
<div>
{this.props.user
? (() => [
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
])()
: <p>User not found</p>}
</div>
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me" user={{name: 'test', info: 'info'}} />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
In order to not get warnings each child must be assigned a key. In order to do so, instead of returning an array please use helper function fragment(...children) to assign index-based keys automatically. Please note that strings must be converted to spans or other nodes that can be assigned with a key:
const fragment = (...children) =>
children.map((child, index) =>
React.cloneElement(
typeof child === 'string'
? <span>{child}</span>
: child
, { key: index }
)
)
const getAuthorUrl = author => `/${author.toLowerCase()}`;
const fragment = (...children) =>
children.map((child, index) =>
React.cloneElement(
typeof child === 'string'
? <span>{child}</span>
: child
, { key: index }
)
)
class Foo extends React.Component {
constructor() {
super();
this._renderAuthor = this._renderAuthor.bind(this);
this._renderUser = this._renderUser.bind(this);
}
_renderAuthor() {
if (!this.props.author) {
return null;
}
return fragment(
' by ',
<a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>
);
}
_renderUser() {
return fragment(
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
)
}
render() {
return (
<div>
{this.props.datePosted}
{this._renderAuthor()}
<div>
{this.props.user
? this._renderUser()
: <p>User not found</p>}
</div>
<div>
{this.props.user
? fragment(
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
)
: <p>User not found</p>}
</div>
<div>
{this.props.user
? (() => fragment(
<h2>{this.props.user.name}</h2>,
<p>{this.props.user.info}</p>
))()
: <p>User not found</p>}
</div>
</div>
);
}
}
ReactDOM.render(<Foo datePosted="Today" author="Me" user={{name: 'test', info: 'info'}} />, document.getElementById('container'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
Try this:
class Foo extends React.Component {
_renderAuthor() {
return <a href={getAuthorUrl(this.props.author)}>{this.props.author}</a>
}
render() {
return (
<div>
{this.props.title}
{this.props.author && " by "}
{this.props.author && this._renderAuthor()}
</div>
);
}
}
Perhaps a more simple way would be to rethink how you're architecting your application. However, in a more simple way.
You're triggering the warning because you're trying to render from an array and not react elements but directly html. In order to approach this, you would have to do
{this._renderAuthor().map(
(k,i) => (React.addons.createFragment({k}))
) }
React addons createFragment function basically does that, it reduces your html elements into react fragments that you can render.
React createFragment documentation
Alternatively, in a much better approach, you can create an AuthorLink stateless component like this..
function AuthorLink(props) {
return (
<div className="author-link">
<span> by </span>
<a href={props.authorUrl}> {props.author} </a>
</div>
});
}
and use this in your main component's render
render() {
const { author } = this.props;
return (
<div>
{this.props.datePosted}
<AuthorLink url={getAuthorUrl(author)} author={author} />
</div>
);
}
Try this approach on your array:
return [
<span key={'prefix-'+random_string_generator()}>' by '</span>,
<a key={'prefix-'+random_string_generator()} href={getAuthorUrl(this.props.author)}>{this.props.author}</a>,
];
Basically new to React, I'm a bit confused on how to properly pass states between components. I found a similar question already React β the right way to pass form element state to sibling/parent elements?
but I wonder if you can give me a specific answer for the code below.
Currently the structure of the app includes:
parent component - App
2 childs: SearchBar and RecipesList
The goal is to make an async search on my Meteor collection and display only the recipes that match the search term.
Right now, I'm just showing all the recipes in my Meteor collection.
I've created a stateful component named SearchBar which holds the input value as this.state.term. The idea is to pass the state to RecipesList but I'm not sure if it's the right thing to do. Alternatively I'd let App deal with the state and passing it to the childs. I believe this is a very common scenario, how do you do it?
App
class App extends Component {
render( ) {
return (
<div>
<Navbar/>
<SearchBar/>
<RecipesList/>
</div>
);
}
}
SearchBar
export default class SearchBar extends Component {
constructor( props ) {
super( props );
this.state = {
term: ''
};
}
onInputChange( term ) {
this.setState({ term });
}
render( ) {
return (
<div className=" container-fluid search-bar">
<input value={this.state.term} onChange={event => this.onInputChange(event.target.value.substr( 0, 50 ))}/>
Value: {this.state.term}
</div>
);
}
}
RecipesList
const PER_CLICK = 5;
class RecipesList extends Component {
componentWillMount( ) {
this.click = 1;
}
handleButtonClick( ) {
Meteor.subscribe('recipes', PER_CLICK * ( this.click + 1 ));
this.click++;
}
renderList( ) {
return this.props.recipes.map(recipe => {
return (
<div key={recipe._id} className="thumbnail">
<img src={recipe.image} alt="recipes snapshot"/>
<div className="caption">
<h2 className="text-center">{recipe.recipeName}</h2>
</div>
</div>
);
});
}
render( ) {
return (
<ul className="list-group">
{this.renderList( )}
<div className="container-fluid">
<button onClick={this.handleButtonClick.bind( this )} className="btn btn-default">More</button>
</div>
</ul>
);
}
}
// Create Container and subscribe to `recipes` collection
export default createContainer( ( ) => {
Meteor.subscribe( 'recipes', PER_CLICK );
return {recipes: Recipes.find({ }).fetch( )};
}, RecipesList );
App
class App extends Component {
constructor(props, ctx){
super(props, ctx)
this.state = {
searchQuery: ''
}
this.searchInputChange = this.searchInputChange.bind(this)
}
searchInputChange(event) {
this.setState({
searchQuery: event.target.value.substr( 0, 50 )
})
}
render( ) {
const { searchQuery } = this.state
return (
<div>
<Navbar/>
<SearchBar onChange={this.searchInputChange} value={searchQuery}/>
<RecipesList searchQuery={searchQuery}/>
</div>
)
}
}
The App component takes care of the state and this is then passed down to the children as props the seach term is available to RecipesList through props.searchQuery.
The searchInputChange handler is passed down to the SearchBar as props.
SearchBar
export default const SearchBar = ({value, onChange}) => (
<div className=" container-fluid search-bar">
<input value={value} onChange={onChange}/>
Value: {value}
</div>
)
Since the SearchBar delegated state to the parent component, we can use a stateless react component as we only need information from the props to render it.
In general it is always best to have a logical or stateful or controller component take care of state and the logic, this component then passes down state and methods to presentational or view components which take care of what the user sees and interacts with.
Define the state term up in to the App component.
Also write the handleInput function and pass it to the SearchBar component as porps
handleInput(val) {
this.setState({
term: val,
});
}
When something in the search bar is typed(onKeyUp) add the listener handleInput.
Also create <RecipesList searchQuery={this.state.term}/>
now in the render function RecipesList filter out the recipes you want to display from your list