I'm trying to pass a custom component into another component, the parent component should works like a wrapper putting the custom component in a special section at the end, but I'm having this error Uncaught Error: Objects are not valid as a React child (found: object with keys {...}). If you meant to render a collection of children, use an array instead. I read a lot of posts but those solutions doesn't work, here the most similar question but doesn't work for me.
Passing an icon component to another component in ReactJS
Wrapper
function Wrapper(props: Props) {
const { title, icon, content } = props
return (
<div className={ Styles.wrapperContainer }>
<h1>{ title }</h1>
{
icon &&
<img src={`/assets/images/${icon || 'notification-bell-icon.svg'}`} alt="" />
}
{ content } // <-
</div>
)
}
calling the Wrapper component
<Wrapper
title="My tittle"
content={<Button text="press me" type={ButtonTypes.DEFAULT} />}
/>
Button Component
function Button(props: Props){
const { type, text, icon } = props
return (
<span className={`${Styles.buttonContainer} ${Styles[type]}`}>
{
type === ButtonTypes.ICON_BUTTON ?
<img src={`/assets/images/${icon}`} alt=""/> :
(
icon ?
<><LineIcon icon={icon}/>{ text }</> :
{ text }
)
}
</span>
)
}
Okay guys, thanks for your help, I found a problem with the <Button /> Component, I changed like this n' it works, adding <></> tag over the { text } works too.
Thanks
function Button(props: Props){
const { type, text, icon } = props
return (
<span className={`${Styles.buttonContainer} ${Styles[type]}`}>
{
type === ButtonTypes.ICON_BUTTON ?
<img src={`/assets/images/${icon}`} alt=""/> :
(
<>
{ icon && <LineIcon icon={icon}/> }
{ text }
</>
)
}
</span>
)
}
Related
I'm getting the error "each child in a list should have a unique 'key' prop" error, but I'm confused because I'm attempting to set the key to a unique ID, and it seems like it's not working.
I'm using the imdbID as the key, which should be a primary key value (unique and stable). Is my syntax wrong, or what am I doing that's incorrect here ?
import React from 'react';
import "./movie.css";
class Movie extends React.Component {
render() {
const {Title, Poster, Year, imdbID} = this.props;
return (
<div className="movie" key={imdbID} id={imdbID}>
<div className="title-year">
<h1 className="title">{Title}</h1>
<h2 className="year">{Year}</h2>
</div>
<div className="poster">
<img src={Poster} alt="my movie poster"/>
</div>
</div>
)
}
}
export default Movie;
And here is the code that's inserting <Movie...> into the application (app.js):
render() {
return (
<div className="App">
<header className="App-header">
<Search handleSendRequest={this.sendRequest}/>
<div className="response">
{
this.state.movies && this.state.movies.length ? this.state.movies.map((movie) => {
return <Movie {...movie}/>
})
: null
}
</div>
</header>
</div>
);
}
Add a key attribute to your Movie tags and set a unique value. As below
{
this.state.movies && this.state.movies.length ? this.state.movies.map((movie, index) => {
return <Movie key={movie.imdbID} { ...movie
}
/>
}) :
null
}
You need the key on <Movie/> itself:
{
this.state.movies && this.state.movies.length ? this.state.movies.map((movie) => {
return <Movie key="HERE" {...movie}/>
})
: null
}
In particular, you need a key every time you iterate over an object. In this case you are iterating over this.state.movies.
You do not need the key inside Movie's render function, where you placed it, because you are not iterating over any object at that time.
The 'key' prop should be inserted at the <Movie/> component, not in the div inside it.
I am working on react project where i am trying to display toast using react-toastify , inside a div section when props ( isNotificationOpen ) is true. I tried an example something like bellow but i dont want the toast be triggered when button press occurs , i want the the tost to be triggered when isNotificationOpen props is set to true , how can i achieve this?
const notify = () => toast("Wow so easy !");
render() {
return (
<div className="d-flex" style={{ margin: "25px" }}>
<div className="mr-auto p-2">
{this.props.isNotificationOpen ? (
<div>
<div>
<button onClick={notify}>Notify !</button>
<ToastContainer />
</div>
//Show toast here...
</div>
) : (
<p />
)} ```
Use a component lifecycle function to respond to the isNotificationOpen prop changing to trigger a notfication.
Class-based component example
notify = () => toast('Wow so easy!');
componentDidMount() {
const { isNotificationOpen } = this.props;
isNotificationOpen && this.notify();
}
componentDidUpdate(prevProps) {
const { isNotificationOpen } = this.props;
if (prevProps.isNotificationOpen !== isNotificationOpen) {
isNotificationOpen && this.notify();
}
}
Functional component example
useEffect(() => {
props.isNotificationOpen && toast('Wow so easy!');
}, [props.isNotificationOpen]);
I have a small part of my new React app which contains a block of text, AllLines, split into line-by-line components called Line. I want to make it work so that when one line is clicked, it will be selected and editable and all other lines will appear as <p> elements. How can I best manage the state here such that only one of the lines is selected at any given time? The part I am struggling with is determining which Line element has been clicked in a way that the parent can change its state.
I know ways that I can make this work, but I'm relatively new to React and trying to get my head into 'thinking in React' by doing things properly so I'm keen to find out what is the best practice in this situation.
class AllLines extends Component {
state = {
selectedLine: 0,
lines: []
};
handleClick = (e) => {
console.log("click");
};
render() {
return (
<Container>
{
this.state.lines.map((subtitle, index) => {
if (index === this.state.selectedLine) {
return (
<div id={"text-line-" + index}>
<TranscriptionLine
lineContent={subtitle.text}
selected={true}
/>
</div>
)
}
return (
<div id={"text-line-" + index}>
<Line
lineContent={subtitle.text}
handleClick={this.handleClick}
/>
</div>
)
})
}
</Container>
);
}
}
class Line extends Component {
render() {
if (this.props.selected === true) {
return (
<input type="text" value={this.props.lineContent} />
)
}
return (
<p id={} onClick={this.props.handleClick}>{this.props.lineContent}</p>
);
}
}
In your case, there is no really simpler way. State of current selected Line is "above" line collection (parent), which is correct (for case where siblings need to know).
However, you could simplify your code a lot:
<Container>
{this.state.lines.map((subtitle, index) => (
<div id={"text-line-" + index}>
<Line
handleClick={this.handleClick}
lineContent={subtitle.text}
selected={index === this.state.selectedLine}
/>
</div>
))}
</Container>
and for Line component, it is good practice to use functional component, since it is stateless and even doesn't use any lifecycle method.
Edit: Added missing close bracket
'Thinking in React' you would want to give up your habit to grab DOM elements by their unique id ;)
From what I see, there're few parts missing from your codebase:
smart click handler that will keep only one line selected at a time
edit line handler that will stick to the callback that will modify line contents within parent state
preferably two separate components for the line capable of editing and line being actually edited as those behave in a different way and appear as different DOM elements
To wrap up the above, I'd slightly rephrase your code into the following:
const { Component } = React,
{ render } = ReactDOM
const linesData = Array.from(
{length:10},
(_,i) => `There goes the line number ${i}`
)
class Line extends Component {
render(){
return (
<p onClick={this.props.onSelect}>{this.props.lineContent}</p>
)
}
}
class TranscriptionLine extends Component {
constructor(props){
super(props)
this.state = {
content: this.props.lineContent
}
this.onEdit = this.onEdit.bind(this)
}
onEdit(value){
this.setState({content:value})
this.props.pushEditUp(value, this.props.lineIndex)
}
render(){
return (
<input
style={{width:200}}
value={this.state.content}
onChange={({target:{value}}) => this.onEdit(value)}
/>
)
}
}
class AllLines extends Component {
constructor (props) {
super(props)
this.state = {
selectedLine: null,
lines: this.props.lines
}
this.handleSelect = this.handleSelect.bind(this)
this.handleEdit = this.handleEdit.bind(this)
}
handleSelect(idx){
this.setState({selectedLine:idx})
}
handleEdit(newLineValue, lineIdx){
const linesShallowCopy = [...this.state.lines]
linesShallowCopy.splice(lineIdx,1,newLineValue)
this.setState({
lines: linesShallowCopy
})
}
render() {
return (
<div>
{
this.state.lines.map((text, index) => {
if(index === this.state.selectedLine) {
return (
<TranscriptionLine
lineContent={text}
lineIndex={index}
pushEditUp={this.handleEdit}
/>
)
}
else
return (
<Line
lineContent={text}
lineIndex={index}
onSelect={() => this.handleSelect(index)}
/>
)
})
}
</div>
)
}
}
render (
<AllLines lines={linesData} />,
document.getElementById('root')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
I'm new to React and I'm puzzled on something kind of basic.
I need to append a component to the DOM after the DOM is rendered, on a click event.
My initial attempt is as follows, and it doesn't work. But it's the best thing I've thought to try. (Apologies in advance for mixing jQuery with React.)
ParentComponent = class ParentComponent extends React.Component {
constructor () {
this.addChild = this.addChild.bind(this);
}
addChild (event) {
event.preventDefault();
$("#children-pane").append(<ChildComponent/>);
}
render () {
return (
<div className="card calculator">
<p><a href="#" onClick={this.addChild}>Add Another Child Component</a></p>
<div id="children-pane">
<ChildComponent/>
</div>
</div>
);
}
};
Hopefully it's clear what I need to do, and I hope you can help me attain an appropriate solution.
Don't use jQuery to manipulate the DOM when you're using React. React components should render a representation of what they should look like given a certain state; what DOM that translates to is taken care of by React itself.
What you want to do is store the "state which determines what gets rendered" higher up the chain, and pass it down. If you are rendering n children, that state should be "owned" by whatever contains your component. eg:
class AppComponent extends React.Component {
state = {
numChildren: 0
}
render () {
const children = [];
for (var i = 0; i < this.state.numChildren; i += 1) {
children.push(<ChildComponent key={i} number={i} />);
};
return (
<ParentComponent addChild={this.onAddChild}>
{children}
</ParentComponent>
);
}
onAddChild = () => {
this.setState({
numChildren: this.state.numChildren + 1
});
}
}
const ParentComponent = props => (
<div className="card calculator">
<p><a href="#" onClick={props.addChild}>Add Another Child Component</a></p>
<div id="children-pane">
{props.children}
</div>
</div>
);
const ChildComponent = props => <div>{"I am child " + props.number}</div>;
As #Alex McMillan mentioned, use state to dictate what should be rendered in the dom.
In the example below I have an input field and I want to add a second one when the user clicks the button, the onClick event handler calls handleAddSecondInput( ) which changes inputLinkClicked to true. I am using a ternary operator to check for the truthy state, which renders the second input field
class HealthConditions extends React.Component {
constructor(props) {
super(props);
this.state = {
inputLinkClicked: false
}
}
handleAddSecondInput() {
this.setState({
inputLinkClicked: true
})
}
render() {
return(
<main id="wrapper" className="" data-reset-cookie-tab>
<div id="content" role="main">
<div className="inner-block">
<H1Heading title="Tell us about any disabilities, illnesses or ongoing conditions"/>
<InputField label="Name of condition"
InputType="text"
InputId="id-condition"
InputName="condition"
/>
{
this.state.inputLinkClicked?
<InputField label=""
InputType="text"
InputId="id-condition2"
InputName="condition2"
/>
:
<div></div>
}
<button
type="button"
className="make-button-link"
data-add-button=""
href="#"
onClick={this.handleAddSecondInput}
>
Add a condition
</button>
<FormButton buttonLabel="Next"
handleSubmit={this.handleSubmit}
linkto={
this.state.illnessOrDisability === 'true' ?
"/404"
:
"/add-your-details"
}
/>
<BackLink backLink="/add-your-details" />
</div>
</div>
</main>
);
}
}
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>