I have the following < CardHeader /> component inside a < Card />.
<CardHeader
title={card.title}
subheader={`${moment(card.createdAt).startOf('minute').fromNow()}` + ' by ' + <div>ABC</div>}/>
It renders the following way:
18 minutes ago by [object Object].
What is the right way of doing this? Thanks in advance!
You can pass on a node to CardHeader's subHeader props, just don't mix string and html
<CardHeader
title={card.title}
subheader={<div>{moment(card.createdAt).startOf('minute').fromNow()} by ABC</div>}/>
Sample DEMO
Wrap your heading in a fragment
You can separate out part of your logic to its own level. This will make reuse easier, and testing also easier.
const MyHeading = ({card}) => {
return (
<React.Fragment>
{`${moment(card.createdAt).startOf('minute').fromNow()} by`
<div>ABC</div>
</React.Fragment>
)
}
In the above I'm only retrieving card, but you can pass all sorts of data to your heading component.
<CardHeader
title={card.title}
subheader={<MyHeading {...props} />
/>
Related
I have this string:
export default function App() {
const string = 'test data,cars,colors,demo';
return (
<div className="App">
<h1>Hello {string.replaceAll(',','<br>')}</h1>
</div>
);
}
I expect:
test data<br>cars<br>colors<br>demo
But i get one string without breack inside my string. How to achieve what i expect using replaceAll()?
In order to display html from string you have to use dangerouslySetInnerHTML
export default function App() {
const string = 'test data,cars,colors,demo';
return (
<div className="App">
<h1 dangerouslySetInnerHTML={{ __html: `Hello ${string.replaceAll(',','<br>')}`}}></h1>
</div>
);
}
Assuming this is a React application you need to use dangerouslySetInnerHTML to add the new HTML to the page.
function Example() {
const string = 'test data,cars,colors,demo';
const html = `Hello ${string.replaceAll(',', '<br>')}</h1>`;
return (
<div className="App">
<h1 dangerouslySetInnerHTML={{ __html: html }} />
</div>
);
}
// Render it
ReactDOM.render(
<Example />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>
I don't suggest you dangerouslySetInnerHTML. It introduces both security and performance issues, and even if you are working on learning project, you better avoid such an approach.
The reason why your code does not work, JSX does not magically convert string "<br>" into appropriate element (until there is dangerouslySetInnerHTML, sure).
But JSX can render arrays just fine. It allows us to split initial string into elements: string.split(', ') and then inject JSX's <br /> with the help of .flatMap()(think about it as if .join() could return array with additional elements in between elements of source array).
{
string.
split(', ').
flatMap((el, index) => index ? [<br />, el]: el)
}
This approach is way more powerful than dangerouslySetInnerHTML since instead of simple BR you may use any JSX tree with React custom components, context and event handlers.
Another approach is to replace ', ' with newlines and apply CSS style white-space: pre-wrap(check white-space docs on all values available)
I have a route like this: <Route exact path="/product/:id" component={Product} /> inside a Switch. Inside the component Product I have a custom hook that makes a request to my api. I also have another component inside Product called: Configurator.
Inside the Configurator component I make another api request to get a specific choice. Then inside the render of the Configurator component I check which component I need to render.
{loading ? <Loading /> :
choices.map((choice, index) => {
return (
<div
key={choice.id}
id={choice?.id}
ref={choice?.id == activeChoice ? myRef : empty}
className={`${choice?.id == activeChoice ? "active" : "inactive"} configurator__choice`}
onClick={() => enableChoice(choice?.id)}
>
{choice?.type === "parent_size" ? <ChoiceSize key={choice.id} number={index + 1} choiceData={choice} availableChoices={availableChoices} activeChoice={activeChoice} onChange={onChange} choices={choices} fetchNewChoiceData={fetchNewChoiceData} onSubmit={onSubmit} /> : <div></div>}
{choice?.type === "parent" ? <ChoiceOption key={choice.id} number={index + 1} choiceData={choice} availableChoices={availableChoices} activeChoice={activeChoice} choiceNameId={choice?.id} choiceId={choice?.choices[0]?.next.id} onClick={onClick} /> : <div></div>}
</div>
)
})
}
So when choice.type is equal to parent_size it loads the ChoiceSize component and adds data as props.
The problem I have is that whenever I am for example on /product/1 and I want to switch to /product/2 my old components (ChoiceSize and/or ChoiceOption) won't go away. It will add new choices with the data from product 2 but the old choices from product 1 are still there.
Does anyone know how I can fix this? Maybe that the whole component gets unmounted and the old choices will get removed and the new choices will get added
I have created a component and it's running well in local server. But I am getting below warning
Warning: Each child in a list should have a unique "key" prop.
Getting this warning means we need to fix the key index props? as given here.
below is some snippets of my component code..
render() {
return (
<div>
<Container>
<Row>
<Col className="col-12">
{this.state.client.map((val,index)=>{
if(index == this.state.colaborators.length -1)
return <a href={"/users/"+val}>{val}</a>
return <a href={"/users/"+val}>{val} ,</a>
})}
</Col>
</Row>
</Container>
</div>
</div>
</div >
);
}
}
export default App;
I checked some solution from here
As I told my code is working well. Can we use some fake key props? for example
key={fake index}
And we are using will this affect in my working code?
If this.state.client ever changes, don't just use the index (which is sadly common); see this article for why and its demo of what can go wrong. You can only do that with a list that never changes, or only grows/shrinks (and not at the same time), not with one where the order changes (you insert at the beginning, or sort, or...) More in the docs.
I'm guessing val will be unique in the list, so use that as the key:
{this.state.client.map((val, index) => {
const href = "/users/" + val;
const display = index == this.state.colaborators.length - 1 ? val : `${val} ,`;
return <a key={val} href={href} >{display}</a>;
})}
If your lists order is not going to change, simply use:
return <a key={index} href={"/users/"+val}>{val}</a>
return <a key={index} href={"/users/"+val}>{val} ,</a>
It will not affect your code and it will remove the warning.
I have a list of objects photos, from a json data file, that I would like to organize into 3 different <div> columns, but I dont know how to achieve that, here is my broken non-optimized code:
<div className="container">
<div ref={leftColRef} className="left-col" />
<div ref={centreColRef} className="centre-col" />
<div ref={rightColRef} className="right-col" />
{Object.keys(photos).forEach((n, i) => {
const id = photos[n].id;
const thumb = photos[n].thumbnailUrl;
const title = photos[n].title;
const element = (
<Thumbnail id={id} title={title} thumb={thumb} />
);
if (i % 3 === 0) {
leftColRef.current.append(element);
} else if (i % 3 === 1) {
centreColRef.current.append(element);
} else {
rightColRef.current.append(element);
}
// this line works, it idsplays the data but is commented as the data needs to go inside its respective columns
// return <Thumbnail key={id} title={title} thumb={thumb} />;
})}
</div>
The idea is to insert some elements into the left-column when i%3 = 0 and others in the centre-column when i%3 = 1 and so on ...
And a link to my codesandbox
Any help/advise will be much appreciated.
Easiest is probably to prepare the data outside the render function and to render the column one by one.
You should not manipulate the DOM like it's done in jQuery using JSX
Example:
const Component = (props) => {
const filterPhotos = (column) => {
return props.photos.filter((photo,index)=> index%3==column);
}
return <>
<MyColumn photos={filterPhotos(0)}/>
<MyColumn photos={filterPhotos(1)}/>
<MyColumn photos={filterPhotos(2)}/>
</>;
}
First, using ref on div to inject stuff on it is wrong. It's the opposite of how react works.
Like charlies said, I would split the photos in 3 different arrays before the render. Then, you'll be able to do something like this :
<div ref={leftColRef} className="left-col" />
{ photosLeft.map(photo => <Thumbnail key={photo.id} {...photo} />)
</div>
when preparing your data, try to use the same object properties and component props name so you can spread it easily ( {...photo} ).
Note: Also, when rendering an array in react, each child must have a unique key props. It will help react to render on that part of dom if your data change.
Is it possible to put JSX inside a template string that is being used as a React render prop?
This is what I'm trying to do, but it leads to the link rendering as [object Object]
const Container = ({ message }) => <div className="from line 4"> {message}</div>;
const Link = () => juan;
const App = () => (
<div>
<Container message={`My message with a ${<Link />}`} />
</div>
);
One thing I tried was to put JSX instead of a template string inside message. This works, but it introduces a new div that isn't needed.
<Container
message={<div>My message {<Link />}</div>}
/>
I made this codesandbox to illustrate the problem
You can use a Fragment to render inline like you are trying to do and to prevent adding a new wrapping <div />:
const App = () => (
<div>
<Container
message={<React.Fragment>My message with a <Link /></React.Fragment>}
/>
</div>
);
Here is a forked version of your Codesandbox using React.Fragment: https://codesandbox.io/s/nrmr9l34vl