I want to dynamically combine different react elements:
const Addresses = () => {
const addressElement = () => {
return <TextInput
name="addressLine"
/>;
};
const addressElement2 = () => {
return <TextInput
name="addressLine2"
/>;
};
let address = addressElement1 + addressElement2;
return (
<div>
address
</div>);
};
Instead of printing the elements this is printing:
[object Object][object Object];
Here's what you want to do:
return (
<div>
{ addressElement1() }
{ addressElement2() }
</div>
);
or you can make them into an array beforehand:
const address = [ addressElement1(), addressElement2() ];
return (
<div>
{ address }
</div>
);
Hmm there some little mistakes on your implementation. If you want to render variable you need to use {address}. And combining React component can be made with simply [, ]
Your final code would be like this
let address = [<addressElement1/>, <addressElement2/>]
return (
<div>
{address}
</div>
);
edit
let address = [<AddressElement1 key="left"/>, <AddressElement2 key="right"/>]
But you must rename your stateless component width A instead of a
If you don't want to use key you can directly
return (
<div>
<AddressElement1/>
<AddressElement2/>
</div>
);
Related
I have a little problem with a recursion task that I found.
I have three functions that are stored in an array:
const One = () => <div>One</div>
const Two = () => <div>Two</div>
const Three = () => <div>Three</div>
const arr = [One, Two, Three]
Then I pass this array as a prop to another component in which I must write a recursive function to make One a parent of *Two, and Two a parent of Three.
The HTML shopuld look like that:
<div class='App-box'>
One
<div class='App-box'>
Two
<div class='App-box'>
Three
</div>
</div>
</div>
I am able to write a recursive function for an array for objects or nested arrays but this one I cannot figure out.
You can pass your data into a recursive iteration as follows:
const data = {
obj: One,
children: [{
obj: Two,
children: [{
obj: Three
}]
}]
}
export default App = () => {
return (
<RecursiveComp {...data} />
)
}
and the recursive component:
const RecursiveComp = ({ obj, children }) => {
const hasChildren = children && children.length
return (
<div className='App-box'>
{obj}
{hasChildren && children.map((item) => (
<RecursiveComp key={children.obj} {...item} />
))}
</div>
)
}
On the other hand, if you don't want to change the variable "data", you can use this:
const data = [
<div>One</div>,
<div>Two</div>,
<div>Three</div>,
]
export default App = () => {
return (
<RecursiveComp {...data} />
)
}
const RecursiveComp = (data) => {
const children = Object.values(data).slice(1)
return (
<div className="App-class">
{data[0]}
{(children.length > 0) && <RecursiveComp {...children} />}
</div>
)
}
export default RecursiveComp
A non-recursive solution that returns an Array of React Elements, contained in a Fragment.
slice is used as reverse mutates the array.
['One','Two','Three'].slice().reverse().reduce((acc,str)=>
React.createElement('div',{key:str }, [str].concat(acc))
, undefined)
<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>
paragraph() {
var p = document.createElement("P");
p.innerHTML = "This is a paragraph";
document.getElementById("builderSection").appendChild(p);
}
render() {
return (
<button onClick={this.paragraph()}>Paragraph</button>
<div id="builderSection">
</div>
)
}
I'm getting the error of TypeError: Cannot read property 'appendChild' of null because paragraph is running before return or smth related, any idea how can i patch?
To somewhat answer your question the way to create an arbitrary number of elements is to hold them in array.
In your case you need to use useState to hold that array and update it whenever the button is clicked, so:
const [paragraphs, setParagraphs] = useState([]);
const addParagraphOnClick = () => {
setParagraphs(prevState => {
return [...prevState, <p>New Paragraph</p>];
});
};
And then to render:
render() {
return (
<>
<button onClick={addParagraphsOnClick}>Paragraph</button>
{paragraphs.map(paragraph => { return paragraph })}
</>
)
}
Can't manage to make useRef/createRef to get any other div's other then what was added last. How can i make it so when the button is clicked the ref to the div changes.
I've tried with both useRef and createRef. Since I want to make a new instance of ref, i've looked more into createRef rather then useRef.
I've also played around useEffect. But my solution didn't help me with my biggest problem
I have made a small project containing 3 components to help you understand what I'm trying to explain.
I also have a database containing mock data -> in my real project this isn't the problem. It's an array containing objects.
[{'id':'1', 'name':'first'},...]
Main:
const MainComponent = () => {
const dataRef = React.createRef(null)
React.useEffect (() => {
if(dataRef && dataRef.current){
dataRef.current.scrollIntoView({ behavior:'smooth', block:'start' })
}
},[dataRef])
const _onClick = (e) => {
dataRef.current.focus();
}
return(
<>
{data && data.map((entry, index) =>{
return <ButtonList
key={index}
entry={entry}
onClick={_onClick}
/>
})}
{data && data.map((entry, index) =>{
return <ListingAllData
key={index}
dataRef={dataRef}
entry={entry}
index={index}/>
})}
</>
)
}
Button Component
const ButtonList = ({ entry, onClick }) => {
return <button onClick={onClick}>{entry.name}</button>
}
Listing data component
const ListingAllData = (props) => {
const {entry, dataRef } = props;
return (
<div ref={dataRef}>
<p>{entry.id}</p>
<p>{entry.name}</p>
</div>
);
}
I've console logged the data.current, it only fetches the last element. I hoped it would fetch the one for the button I clicked on.
I think the main idea here is to create dynamic refs for each element (array of refs), that's why only the last one is selected when app renders out.
const MainComponent = () => {
const dataRefs = [];
data.forEach(_ => {
dataRefs.push(React.createRef(null));
});
const _onClick = (e, index) => {
dataRefs[index].current.focus();
dataRefs[index].current.scrollIntoView({
behavior: "smooth",
block: "start"
});
};
return (
<>
{data &&
data.map((entry, index) => {
return (
<ButtonList
key={index}
entry={entry}
onClick={e => _onClick(e, index)}
/>
);
})}
{data &&
data.map((entry, index) => {
return (
<>
<ListingAllData
key={index}
dataRef={dataRefs[index]}
entry={entry}
index={index}
/>
</>
);
})}
</>
);
};
Created working example in code sandbox.
https://codesandbox.io/s/dynamic-refs-so25v
Thanks to Janiis for the answer, my solution was:
in MainComponent
...
const refs = data.reduce((acc, value) => {
acc[value.id] = React.createRef();
return entry;
}, {});
const _onClick = id => {
refs[id].current.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
....
then i passed it through to the child and referred like
<div ref={refs[entry.id]}>
I'm trying to do something very simple but its not playing well with my code. I can see it render but only 3 times and not 9
const renderTempBoxes = () => {
for (var i = 0; i < 10; i++) {
console.log('i = ', i);
return <div className={styles.box} key={i} />;
}
};
const Component = () => {
return (
{renderTempBoxes()}
)
}
This doesn't even work, which is overkill to use an array when I just want 9 boxes to render.
UPDATE:
const Component = () => {
return (
<div>
{
[...Array(10)].map((x, i) => {
console.log('i = ', i);
return <div className={styles.box} key={i} />;
})
}
</div>
)
}
The first issue is that you simply cannot return individual elements from within the for loop like that. This is not specific to React, this is simply a JavaScript issue. Instead you can try something like this using Array.from to map an array of elements:
const renderTempBoxes = () => Array.from({ length: 10 }).map((v, i) =>
<div className={styles.box} key={i}>{i}</div>
);
Or simply the for loop with Array.prototype.push to generate an array of elements and return it:
const renderTempBoxes = () => {
let els = [];
for (let i = 0; i < 10; i++) {
els.push(<div className={styles.box} key={i}>{i}</div>);
}
return els;
};
Rendering the elements:
const Component = () => {
return (
<div>
{renderTempBoxes()}
</div>
)
}
Or with React.Fragment to forgo the wrapping extra node:
const Component = () => {
return (
<React.Fragment>
{renderTempBoxes()}
</React.Fragment>
)
}
The second issue with your example is that <div /> isn't going to really render anything, it's not a void/self-closing element such as <meta />. Instead you would need to do return the div element as <div className={styles.box} key={i}>{whatever}</div>.
Regarding the syntax [...Array(10)], there must be an Webpack in terms of how it handles/transpiles Array(10), [...Array(10)], [...new Array(10)], or even `[...new Array(10).keys()]. Either of the approaches described in the answer should solve your issue.
I've created a StackBlitz to demonstrate the functionality.
When trying to render multiple times the same components use an array an map over it.
export default class MyComp extends Component {
constructor(props) {
super(props)
this.state = {
array: [{key: 1, props: {...}}, {key: 2, props: {...}, ...]
}
}
render () {
return (
<div>
{this.state.array.map((element) => {
return <div key={element.key} {...element.props}>
})}
</div>
)
}
}
Remember to always set a unique key to every component you render
Below is my render method.
I want to loop through toptracks only if it is not null.
But I am not able to get this working.
render() {
console.log('galler props',this.props);
const { toptracks } = this.props;
return (
<div>
if(this.props.toptracks !== null){
{toptracks.map((toptracks,k) => {
const trackImg =toptracks.track[0].image[0]['#text'];
return (
<div
key = {k}
className ="track"
>
<img
src={trackImg}
className="trackImg"
alt="track"
/>
<p className="tracl-text">
{track.name}
</p>
</div>
)
}
)
}
}
</div>
)
}
Using an if is rather simple if you decide for an early return:
render() {
const { toptracks } = this.props;
if (!toptracks) { // this will handle both null and undefined
return <div/>;
}
return (
...
);
}
However, there are simpler ways to do that. Note that in your case having no value is the same as having an empty value. Why not default to an empty value instead then?
render() {
const toptracks = this.props.toptracks || [];
// no change needed here
}
The same can be done by defining defaultProps for your component.
static defaultProps = {
toptracks: []
};
You can't have an if statement within your render-return statement. You can make use of ternary statement like
render() {
const { toptracks } = this.props;
return (
<div>
{this.props.toptracks ? toptracks.map((toptracks,k) => {
const trackImg =toptracks.track[0].image[0]['#text'];
return (
<div key = {k} className ="track">
<img
src={trackImg}
className="trackImg"
alt="track"
/>
<p className="tracl-text">
{track.name}
</p>
</div>
)
}) : null}
</div>
)
}
You can add if if you make that a functional component and add if statement inside that.
ShowTopTracks = () =>{
if(this.props.toptracks){
return toptracks.map((toptracks,k) => {
const trackImg =toptracks.track[0].image[0]['#text'];
return (
<div
key = {k}
className ="track"
>
<img
src={trackImg}
className="trackImg"
alt="track"
/>
<p className="tracl-text">
{track.name}
</p>
</div>
)
})
}
else return null;
}
render() {
console.log('galler props',this.props);
const { toptracks } = this.props;
return (
<div>
{this.ShowTopTracks()}
</div>
)
}
As has been mentioned, toptracks by itself is undefined, declare it at the start of the render function as
const { toptracks } = this.props;
then you can use it without referencing this.props.toptracks
Secondly the map function takes arguments of the element and the index but you have declared them as toptracks again which creates aliasing issues (at least in my mind) and is also just plain confusing...
return toptracks.map((toptracks,k) =>
should ideally be
return toptracks.map((toptrack,k) => - then remember to update the toptracks inside the map callback to toptrack to make it clearer which is the collection and which is the element
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/map for more info on the map function.
As already pointed out, if toptracks is not an array then you will need to turn it into an array or use .keys for example if it is an object.