ReactJs - how to use multiple children in a component - javascript

I'm prefacing this with the fact I'm VERY new to ReactJs and probably trying to work out something quite basic.
I have this code with a custom piece of HTML:
Sample Component
const Sample = ({ title, children }) => {
return (
<div class="panel">
<div class="title">
{title}
</div>
<div class="body">
{children}
</div>
</div>
);
};
Side question - whats the correct name for the above? A fragment? It doesn't look to be a "component" but just learning the naming conventions for React too
Utilise Component
export default class ExampleComponent extends Component {
render() {
return <div class="page-body">
<div class="row">
<h1> Page 1 </h1>
<Sample title={<h1>Some title!</h1>}>
<p>This is my sample body!</p>
</Sample>
</div>
</div>
}
}
You can see that the content of the "Sample" element is taken automatically as children property but to set title I have to explicitly set "title" property. What I'd ideally like to do is something similar to the below:
Desired way to utilise Sample Component
export default class ExampleComponent extends Component {
render() {
return <div class="page-body">
<div class="row">
<h1> Page 1 </h1>
<Sample title="Some title!">
<Sample.Title>
<h1>This is my new way to do a title</h1>
</Sample.Title>
<Sample.Body>
<p>This is my sample body!</p>
</Sample.Body>
</Sample>
</div>
</div>
}
}
I've used this type of approach before with components others have created but want to do it myself now and finding it hard to get a simple example to do this.
Thanks in advance for any pointers!

I think what you want is called JSX Namespacing? Either way, you can instantiate Sample, and then add more components as properties of Sample (view it as an object):
import React from "react"
const Sample = ({ children }) => (
<div className="panel">
{children}
</div>
)
Sample.Title = (props) => <div className="title">{props.children}</div>
Sample.Body = (props) => <div className="body">{props.children}</div>
export default Sample
Note: React uses className rather than class since we are using JSX.

The Children utils suit can be helpful for your scenario.
import { Children } from 'react'
const Sample = ({ title, children }) => {
let _body, _title
Children.forEach(children, child => {
if (child.type === SampleTitle) {
return _title = child
}
if (child.type === SampleBody) {
return _body = child
}
})
if (!_title) _title = <div className='title'>{title}</div>
if (!_body) _body = <div className='title'>{children}</div>
return (
<div className='panel'>
{_title}
{_body}
</div>
)
}
const SampleTitle = ({ children }) => <div className='title'>{children}</div>
const SampleBody = ({ children }) => <div className='body'>{children}</div>
Sampe.Title = SampleTitle
Sample.Body = SampleBody
Now you can use Sample in multiple ways:
<Sample title="my title">
<div>my body</div>
</Sample>
<Sample title="my title">
<Sample.Body>my body</Sample.Body>
</Sample>
<Sample title="my fallback title">
<Sample.Title>my overriding title</Sample.Title>
<Sample.Body>my body</Sample.Body>
</Sample>

In that case you just have to extract the relevant containers into their own components:
const Sample = ({children }) => (
<div className="panel">{children}</div>
);
const Title = ({children}) => (
<div className="title">{children}</div>
);
const Body = ({children}) => (
<div className="body">{children}</div>
);
Sample.Title = Title;
Sample.Body = Body;
Also note that the correct prop for a css class is className.

Related

React - How to create div and put into it a existing child

I would like to create div and put into it a existing child.
Let's say I am using some UI component which gives me some structure, it looks more or less like this. Now I would like to wrap part of this structure in my own div. In this case I want to wrap content into my own div.
<div class="heading"></div>
<div class="content"></div>
<div class="footer"></div>
So my Idea was:
useEffect(() => {
const wrapper = document.querySelector('.content');
wrapper && React.createElement('div', wrapper);
}, [dep])
But this does not create any div because I still see content without parent above
Just create a wrapper component and reuse it within your app.
export const Layout = ({ children, header, footer }) =>
<div className="layout">
<div className="header">{header}</div>
<div className="content">{children}</div>
<div className="footer">{footer}</div>
</div>
Layout.defaultProps = {
children: <></>,
header: <></>,
footer: <></>
}
enter image description here
<div>
<div className="heading">1</div>
<div className="content">2</div>
<div className="footer"> 3</div>
</div>
useEffect(() => {
const content = document.querySelector('.content');
const newContent = document.querySelector('.wrapper .content');
let chilItem = document.createElement('div');
let newItem = document.createElement('div');
newItem.className = 'wrapper';
chilItem.className = 'content';
newItem.appendChild(chilItem);
!newContent && content.parentNode.replaceChild(newItem, content);
}, []);
The approach in React is different.
You create a Layout component
Layout.jsx
export default function Layout(props) {
return <div>
<div class="heading">{props.heading}</div>
<div class="content">{props.children}</div>
<div class="footer">{props.heading}</div>
</div>
}
In your sub component
ComponentA.jsx
export default function ComponentA() {
return <Layout heading={<h1>Company Name</h1>} footer={<div>footer contents</div>}>
All you contents, this will be displayed in div
with class content. All contents here will be
available in props.childer of Layout component.
</Layout>
}
More details here - https://reactjs.org/docs/composition-vs-inheritance.html

OnClick Saving Only One ID at a time on UseState Array - ReactJS

i am trying to save favorite post ids in an array with button click. The thing is currently it is only saving one ID at a time in array and when you click you on another "click here" button , it removes the previous id and show you the current id. Currently my array is not saving previous saved Ids with the new one. I will appreciate it if someone explain why it is not working and is there something wrong in my code while saving the ids.
Screenshot
Code
import React, { useEffect } from 'react'
import Demopic from "../assets/img/demopic/4.jpg";
import { useState, useRef } from 'react';
import { post } from 'jquery';
export default function Post(props) {
const { id, title, body } = props.data;
const [postid,setPostID] = useState([]);
const [isActive, setActive] = useState(false);
function onHandleOnClick(id) {
setPostID([...postid, id]);
};
useEffect(()=>{
console.log(postid);
},[postid]);
return (
<div className="card">
<div className="row">
<div className="col-md-5 wrapthumbnail">
<a href="post.html">
<div className="thumbnail dd" style={{ backgroundImage: 'url(' + Demopic + ')' }}>
</div>
</a>
</div>
<div className="col-md-7">
<div className="card-block">
<h2 className="card-title"><a href={'/post-detail/'+id} key={id}>{title}</a></h2>
<h4 className="card-text">
{body.length > 100 ? `${body.substring(0, 90)}...` : body }
</h4>
<div className="metafooter">
<div className="wrapfooter text-left">
<span className="meta-footer-thumb">
<a href="author.html">
<img className="author-thumb" src="https://www.gravatar.com/avatar/e56154546cf4be74e393c62d1ae9f9d4?s=250&d=mm&r=x" alt="Sal"/></a>
</span>
<span className="author-meta">
<span className="post-name">Steve</span><br/>
<span className="post-date">22 July 2017</span><span className="dot"></span><span className="post-read">6 min read</span>
</span>
<button onClick={(e)=>onHandleOnClick(id)} value={id}>click here</button>
</div>
</div>
</div>
</div>
</div>
</div>
)
}
<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>
looks like your Post component is a single post, therefore each one of the Post components that you are creating will have their own postId array.
so if you have 10 posts you will have 10 different arrays, to test this, you can click on the same button multiple times and you will see that they array of that component will grow, for example the Id is 4 and you click multiple times you will see [4 ,4, 4, 4.....] and then if you do it on the Id 2 you will see [2, 2, 2, 2....].
to fix this you should have on your parent component the array and then in then pass the update function to your childs, something like this:
const ParentComponent = () => {
const [postid,setPostID] = useState([]);
const [isActive, setActive] = useState(false);
function onHandleOnClick(id) {
setPostID([...postid, id]);
};
useEffect(()=>{
console.log(postid);
},[postid]);
return (<>
<Post clickFavourite={onHandleOnClick} ...other props/>
<Post clickFavourite={onHandleOnClick} ...other props/>
<Post clickFavourite={onHandleOnClick} ...other props/>
<Post clickFavourite={onHandleOnClick} ...other props/>
<Post clickFavourite={onHandleOnClick} ...other props/>
</>)
}
and then in your child component (Post) you should remove the state and the handler and just use the parent function:
function Post(props) {
const { id, title, body } = props.data;
const { clickFavourite } = props;
return (
<button onClick={(e)=>clickFavourite(id)} value={id}>click here</button>
)
}
notice that I deleted a lot of the content of the Post component just to read it easily.
also take into account that this can be done in different ways, this was the first way that came into my mind but you can use global states, states managers, hooks, etc etc. but as far as I see this is the easiest way and will get the job done
Use
const [postid,setPostID] = useState({});
Instead of
const [postid,setPostID] = useState([]);
It will work.

How to add className to component in react?

I have a navbar component that I created for react.
When I call this component on different pages, I need to change the classNames.
For example : I want my "" component to have a different class on one page and a different class on another page.
const ServicesCiso = () => {
return (
<div className="hero">
<NavBar/>
<div className...
How can i add className in this code ?
You can pass the className as props to this component and pass the preferred className on the page you are rendering it
const ServicesCiso = ({ className }) => {
return (
<div className={className}>
<NavBar/>
<div className...
<ServicesCiso className="my-class-name" />
You should pass the className from out side to your component via props however you also need a default class in init case
I have suggestion this library
https://www.npmjs.com/package/classnames ( combine classes )
example
View
- ServicesCiso.js
- ServicesCiso.css
import ./ServicesCiso.css;
import cn from('classnames');
const ServicesCiso = (props) => {
const {className} = props;
return (
<div className={cn('hello', className}}>
</div>
)
}

Can not set and then modify the state of component

I've read similar error messages but the difference is most of them are centered around event binding.
I get this error: Cannot read property 'setState' of undefined
I get the error message on this.setState(state => {
I've tried to eliminate that, before I moved to that code I was simply using this.state.finalText = translation;
No matter what I do I can't get the translated text to render without state errors.
This is my second day working with react, so a good explanation would be highly appreciated.
import React from 'react';
class CardComponent extends React.Component {
constructor(props) {
super(props);
let data = this.props.data;
this.state = {finalText: ""};
let truncated = data.truncated;
}
componentDidMount() {
let data = this.props.data;
let truncated = data.truncated;
var googleTranslate = require('google-translate')('apikey');
googleTranslate.translate(data.text, 'en', function(err, translation) {
// by calling set state, React will know to render again
console.log(translation);
this.setState(state => {
state.finalText = translation;
return state;
});
});
}
render() {
let data = this.props.data;
let truncated = data.truncated;
var test = new String(truncated);
return (
<div>
<div className="card-panel grey lighten-5 z-depth-3 hoverable thin">
<div className="row valign-wrapper">
<div className="col s2">
{/*} <img src={data.user.profile_image_url} alt={data.user.name} className="circle responsive-img" />-*/}
</div>
<div className="col s10 left-align">
{(() => {
if (test=='true') {
return ( <span>ok</span>
//<span className="black-text"> {data.extended_tweet.full_text}</span>
)
} else {
return (
<span className="black-text">translated text: {this.state.finalText}</span>
)
}
})()}
</div>
</div>
<div className="row valign-wrapper right-align chip hoverable">
{/*new Date(data.created_at).toLocaleTimeString()*/}
</div>
<div className="row valign-wrapper right-align chip hoverable">
{/* {`#${data.user.screen_name}`}*/}
</div>
</div>
</div>
);
}
}
export default CardComponent;
Your problem seems to be this. If you change your callback from function... to an arrow function () => ... then this will be bound to the enclosing lexical context's this instead of execution context's this. So the fix:
googleTranslate.translate(data.text, 'en', (err, translation) => {
this.setState({
finalText: translation
});
});

React access parent's dom node from child component

I have a grid component as follow:
import React, { Component } from 'react';
import Action from './action.jsx';
class Grid extends Component {
constructor(props) {
super(props);
this.maxN = 110;
this.tempArray = [];
this.question;
}
getRandomN() {
var randomN = Math.floor((Math.random() * this.maxN) + 1);
if(this.tempArray.indexOf(randomN) === -1) {
this.tempArray.push(randomN);
}
else {
randomN = this.getRandomN();
}
return randomN;
}
getRandomQuestion() {
this.question = this.props.current.data.questions[this.getRandomN()];
return this.question;
}
render() {
this.getRandomQuestion();
return (
<section className="game">
<div className="grid">
<div className="row">
<div ref="n1"></div>
<div ref="n2"></div>
<div ref="n3"></div>
<div ref="n4"></div>
<div ref="n5"></div>
<div ref="n6"></div>
</div>
<div className="row">
<div ref="n7"></div>
<div ref="n8"></div>
<div ref="n9"></div>
<div ref="n10"></div>
<div ref="n11"></div>
<div ref="n12"></div>
</div>
<div className="row">
<div ref="n13"></div>
<div ref="n14"></div>
<div ref="n15"></div>
<div ref="n16"></div>
<div ref="n17"></div>
<div ref="n18"></div>
</div>
<div className="row">
<div ref="n19"></div>
<div ref="n20"></div>
<div ref="n21"></div>
<div ref="n22"></div>
<div ref="n23"></div>
<div ref="n24"></div>
</div>
</div>
<Action question={this.question} getRandomQuestion={this.getRandomQuestion.bind(this)}/>
</section>
);
}
}
export default Grid;
inside the "Action" component, based on the correct or wrong answer coming from "getNewQuestion" I need to access a random grid element from the grid component. (any random going from "n1" to "n24" as assigned to each ref attribute)
import React, { Component } from 'react';
class Action extends Component {
constructor(props) {
super(props);
this.state = {
question: props.question
}
}
getNewQuestion(e) {
console.log(this.state.question.correct_option);
let answerId = "option_" + this.state.question.correct_option;
if(e.target.getAttribute('data-question') == answerId) {
this.setState({
question: this.props.getRandomQuestion()
});
}
else {
console.log('wrong');
React.findDOMNode(this.refs.n1).classList.add('fdsdfsdfsdfsdfsfsdf');
}
}
render() {
let state = this.state;
return(
<div className="action">
<div className="action-question">
<h3>{state.question.question}</h3>
</div>
<div className="action-answers">
<p data-question="option_1" onClick={this.getNewQuestion.bind(this)}>{state.question.option_1}</p>
<p data-question="option_2" onClick={this.getNewQuestion.bind(this)}>{state.question.option_2}</p>
<p data-question="option_3" onClick={this.getNewQuestion.bind(this)}>{state.question.option_3}</p>
<p data-question="option_4" onClick={this.getNewQuestion.bind(this)}>{state.question.option_4}</p>
</div>
</div>
);
}
}
export default Action;
inside the "if" statment of the "getNewQuestion" I would like to do something like:
n2.classList.addClass('hidden');
I can't figure out how to access a parent's dom node from the "Action" component
Does the child really need to access the parent DOM directly? Shouldn't the parent Component know how to present itself? If so, then you can use callbacks that you pass down to the children, so that the children have the possibility to notify the parent when it should change.
const Child = ({modifyParent}) => (
<div onClick={ modifyParent } >Click me!</div>
);
const Parent = () => {
const modifyMyOwnStyle = event => {
// here you have easy access
// if you want to style the parent.
alert('Modifying style, based on child call');
}
return (
<Child modifyParent={ modifyMyOwnStyle }/>
);
};
ReactDOM.render(<Parent />, document.getElementById('root'));
Runnable JSFiddle demo here
You can get the ref of a component and pass this to its children like so:
render() {
return (
<div ref={node => this.node = node}>
<SomeChild parent={this.node} />
</div>
)
}
read more about it here: https://facebook.github.io/react/docs/refs-and-the-dom.html
However I have to say that doing this is usually a bad idea, and I would reconsider if you really need to pass the node, or if there is another way around the problem.
EDIT: As jonahe's comment shows you can usually get around the problem by passing a callback to the child component that you can fire when something needs to happen in the parent component
Better than accessing parent's DOM node directly, you can use a callback prop that does it for you.
Something like:
class Grid extends Component {
constructor(props) {
super(props)
this.accessRandomElement = this.accessRandomElement.bind(this)
}
accessRandomElement() {
// do you thing
}
render() {
this.getRandomQuestion()
return (
<section className="game">
...
<Action
question={this.question}
onAccessYourRandomElement={this.accessRandomElement}
///
/>
</section>
)
}
}
and then from inside Action you call this.props.onAccessYourRandomElement()

Categories