React: Use props or array of these - javascript

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>

Related

React loop through array and render instances of multiple child component

I am trying to iterate over an array and assign fields to corresponding child components.
The way I am currently doing it looks like this:
class CardExtension extends React.Component {
render() {
return (
<div>
{ this.props.value }
</div>
);
}
}
class Card extends React.Component {
render() {
return (
<div>
{ this.props.title }
</div>
);
}
}
Once child components are defined and imported, I do push new instances of these classes to a completely new array:
class CardContainer extends React.Component {
render() {
var arr = [
{
'id':1,
'title':'title',
'value':'test_value'
}
]
var elements=[];
for(var i=0;i<arr.length;i++){
elements.push(<Card title={ arr[i].value } />);
elements.push(<CardExtension value={ arr[i].title } />);
}
return (
<div>
{elements}
</div>
);
}
}
Is there any way to accomplish the same using the following format
class CardContainer extends React.Component {
render() {
var arr = [
{
'id':1,
'title':'title',
'value':'test_value'
}
]
return (
<div>
{arr.map((el, idx) => (
<Card title={ el.value } />
<CardExtension value={ el.title } />
))}
</div>
);
}
}
#update
The problem is that whenever I do use the latest solution, I do receive following error message: Adjacent JSX elements must be wrapped in an enclosing tag (39:24)
Working solution:
import React, { Component, Fragment } from 'react';
export default class CardContainer extends React.Component {
render() {
var arr = [
{
'id':1,
'title':'title',
'value':'test_value'
}
]
return (
<div>
{arr.map((el, idx) => (
<Fragment>
<Card title={ el.value } />
<CardExtension value={ el.title } />
</Fragment>
))}
</div>
);
}
}
The error message "Adjacent JSX elements must be wrapped in an enclosing tag" means just that: the <Card> and <CardExtension> elements need to be wrapped in a parent tag.
Now, you probably don't want to wrap the elements in a <div> (since it would create unnecessary DOM nodes), but React has a nifty thing called "Fragments", letting you group elements without extra nodes.
Here is a working solution for your example, using the fragment short syntax:
class CardContainer extends React.Component {
render() {
var arr = [{
'id':1,
'title':'title',
'value':'test_value'
}]
return (
<div>
{arr.map((el, idx) => (
<>
<Card title={ el.value } />
<CardExtension value={ el.title } />
</>
))}
</div>
);
}
}

Passing props to nested child component

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>
)
}
}

React sibling communication with ONLY access to parent

I am trying to create something similar to React Bootstrap's dropdown component. My starting skeleton is something like the following:
import React from 'react';
const DropDown = props => {
return <div className="dropdown-container">{props.children}</div>;
};
const DropDownToggle = props => {
return <div className="dropdown-toggle">{props.children}</div>;
};
const DropDownContent = props => {
return <div className="dropdown-content">{props.children}</div>;
};
export { DropDown, DropDownToggle, DropDownContent };
These components would be used like this:
<DropDown>
<DropDownToggle>
{/*
The content inside here should be customizable so the user of
these components can specify whatever they want for the toggle
*/}
<button type="button">
my button
</button>
</DropDownToggle>
<DropDownContent>
{/*
The content inside here should be customizable so the user of
these components can specify whatever they want for the content of
the dropdown
*/}
<ContentComponent/>
</DropDownContent>
</DropDown>
Is there a way I can communicate between the two children components (DropDownContent and DropDownToggle)? I have access to the parent component and it just receives and displays the children so far, but I would like to somehow communicate between the children so that the user can click on the toggle to open/close the content. I don't want to use redux.
Thank you in advance!
EDIT
I ended up going with the method that #Train suggested in his/her comment below. I was originally hoping for the ability to nest components manually, but what was most important to me was having the state be self-contained in the parent component. Being able to define the toggle button's HTML as well as the content's HTML was also a requirement. My final implementation allows for both of these things and looks something like this:
import React from 'react';
import PropTypes from 'prop-types';
export class Dropdown extends React.Component {
state = {
isOpen: false,
};
onDropDownToggleClick = () => {
this.setState({ isOpen: !this.state.isOpen });
};
render() {
let contentClasses = 'dropdown-content';
if (this.state.isOpen) {
contentClasses += ' show';
}
return (
<div className="dropdown-container">
<div className="dropdown-toggle" onClick={this.onDropDownToggleClick}>
{this.props.toggle}
</div>
<div className={contentClasses}>{this.props.content}</div>
</div>
);
}
}
Dropdown.propTypes = {
toggle: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired,
content: PropTypes.oneOfType([PropTypes.string, PropTypes.element])
.isRequired,
};
export default Dropdown;
to use it:
const dropDownToggle = (
<button type="button">
Dropdown
</button>
);
const dropDownContent = 'content';
<DropDown
toggle={dropDownToggle}
content={dropDownContent}
/>
For something like toggling content you can use composition instead of inheritance to pass data around.
From the example of Facebook
This is done with props.children property.
function Dialog(props) {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
{props.title}
</h1>
<p className="Dialog-message">
{props.message}
</p>
{props.children}
</FancyBorder>
);
}
class SignUpDialog extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.handleSignUp = this.handleSignUp.bind(this);
this.state = {login: ''};
}
render() {
return (
<Dialog title="Mars Exploration Program"
message="How should we refer to you?">
<input value={this.state.login}
onChange={this.handleChange} />
<button onClick={this.handleSignUp}>
Sign Me Up!
</button>
</Dialog>
);
}
handleChange(e) {
this.setState({login: e.target.value});
}
handleSignUp() {
alert(`Welcome aboard, ${this.state.login}!`);
}
}
In the render() I am rendering the Dialog component and passing in the props.
the props are .children and the custom props title, message
This lets us pass child elements directly into the output we can even add components from other classes as I did with the SignUpDialog.
Did you have something like this in mind?
const actionTypes = {
TOGGLE: "TOGGLE"
};
const notRedux = {
actionHandlers: Object.keys(actionTypes).reduce(
(acc, val) => ({ [val]: [], ...acc }),
{}
),
dispatchAction(actionType, data) {
this.actionHandlers[actionType].forEach(handler => handler(data));
},
onAction(actionType, actionHandler) {
this.actionHandlers[actionType].push(actionHandler);
}
};
const DropDown = ({ children }) => {
return <div className="dropdown-container">{children}</div>;
};
const DropDownToggle = () => {
const onClick = () =>
notRedux.dispatchAction(actionTypes.TOGGLE, "oh hi Mark");
return (
<div className="dropdown-toggle">
<button type="button" onClick={onClick}>
my button
</button>
</div>
);
};
const DropDownContent = props => {
notRedux.onAction(actionTypes.TOGGLE, data =>
alert(`DropDownToggle said ${data} //DropDownContent`)
);
return <div className="dropdown-content">{props.children}</div>;
};
const App = () => (
<DropDown>
<DropDownToggle></DropDownToggle>
<DropDownContent>
<span>Content goes here</span>
</DropDownContent>
</DropDown>
);
ReactDOM.render(<App />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="app"></app>

Easy communication of image between siblings

I'm new to ReactJS and I would like to communicate between my components.
When I click an image in my "ChildA" I want to update the correct item image in my "ChildB" (type attribute in ChildA can only be "itemone", "itemtwo", "itemthree"
Here is what it looks like
Parent.js
export default class Parent extends Component {
render() {
return (
<div className="mainapp" id="app">
<ChildA/>
<ChildB/>
</div>
);
}
}
if (document.getElementById('page')) {
ReactDOM.render(<Builder />, document.getElementById('page'));
}
ChildA.js
render() {
return _.map(this.state.eq, ecu => {
return (
<img src="../images/misc/ec.png" type={ecu.type_eq} onClick={() => this.changeImage(ecu.img)}/>
);
});
}
ChildB.js
export default class CharacterForm extends Component {
constructor(props) {
super(props);
this.state = {
items: [
{ name: "itemone" image: "defaultone.png"},
{ name: "itemtwo" image: "defaulttwo.png"},
{ name: "itemthree" image: "defaultthree.png"},
]
};
}
render() {
return (
<div className="items-column">
{this.state.items.map(item => (<FrameCharacter key={item.name} item={item} />))}
</div>
);
}
}
I can retrieve the image on my onClick handler in my ChildA but I don't know how to give it to my ChildB. Any hints are welcomed, thanks you!
What you need is for Parent to pass an event handler down to ChildA which ChildA will call when one of the images is clicked. The event handler will call setState in Parent to update its state with the given value, and then Parent will pass the value down to ChildB in its render method.
You can see this working in the below example. Since I don't have any actual images to work with—and to keep it simple—I've used <button>s instead, but the principle is the same.
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
clickedItem: 'none',
};
}
render() {
return (
<div>
<ChildA onClick={this.handleChildClick}/>
<ChildB clickedItem={this.state.clickedItem}/>
</div>
);
}
handleChildClick = clickedItem => {
this.setState({ clickedItem });
}
}
const items = ['item1', 'item2', 'item3'];
const ChildA = ({ onClick }) => (
<div>
{items.map(name => (
<button key={name} type="button" onClick={() => onClick(name)}>
{name}
</button>
))}
</div>
);
const ChildB = ({clickedItem}) => (
<p>Clicked item: {clickedItem}</p>
);
ReactDOM.render(<Parent/>, document.querySelector('div'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.development.js"></script>
<div></div>

how to render child components in react.js recursively

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>

Categories