React - map over actual HTML children wrapped in HoC - javascript

I have a Component that renders all the children passed to it as list elements. For example:
// more or less the logic of my component
const ListifyChildren = (props) => {
return <ul>{props.children.map(element => <li>{element}</li>)}</ul>
}
// example use
<ListifyChildren>
<div>Some component</div>
<div>Some other component</div>
</ListifyChildren>
Would produce
<ul class="listify">
<li class="listify-list-item">Some component</li>
<li class="listify-list-item">Some other component</li>
</ul>
But the problem is I want to be able to use HoC's that return a list of components and to treat those components as children of my actual list. For example:
const ReturnSomeStuff = () => {
return [someArray].map(element => <div>{element}</div>
}
<ListifyChildren>
<ReturnSomeStuff/>
</ListifyChildren>
//what I get:
<ul class="listify">
<li class="listify-list-item">
<div>something</div>
<div>something</div>
<div>something</div>
</li>
</ul>
//what I want to get:
<ul class="listify">
<li class="listify-list-item">something</li>
<li class="listify-list-item">something</li>
<li class="listify-list-item">something</li>
</ul>
How can I make sure that my component maps over actual html children, not the function calls passed to it?

You can use Fragment to do it: https://reactjs.org/docs/fragments.html
const ReturnSomeStuff = () => {
return [someArray].map(element => <>{element}</>
}

I have found the answer: the key is referring to child elements via React.Children API rather than simple props.children - it returns the list of rendered elements.

Related

How to wrap HTML in JavaScript so as to iterate through a dictionary and add to a flex container?

I have this code:
<ul className="flex-container"> {
Object.keys(this.state.filestest).forEach(function(key) {
<div className="Tile">
*do stuff
</div>
});
}
</ul>
"filestest" is a String to String dictionary. All I want to do is be able to iterate through filestest, and add a div with className "Tile" for each item in the dictionary. The above code I wrote gives an error on the semi-colon, and other ways I try to write it doesn't work. I think this involves jQuery or something but I'm not familiar with jQuery. (I also need access to both the dictionary's keys and values).
In reactjs you'd do something like this (note the map instead of forEach):
// inside your react component:
render() {
return (
<ul className="flex-container"> {
Object.keys(this.state.filestest).map(function(key) {
return (<div className="Tile">
{key}
</div>)
});
}
</ul>
)
}

reactjs parse child state to parent

i want to parse data from child to parent i have try solution from question
How to parse data from child to parent using reactjs?
I print that state and what appears is the state of the previous action, not the state of the last action
I tried to implement this to bring up content based on the menu that was clicked
example:
i have 3 menu
- A
- B
- C
when i click first time at the menu, for example A. the state in console is '', Then Second time i click B, the state in console is A
this is my code
PARENT
changeMenu= (menu) =>{
this.setState({
menu: menu
});
console.log('menu',menu); // Show State
}
render(){
return (
<LeftMenuMycommission active="0" menu = {(value) => this.changeMenu(value)}/>
CHILD
menuClick = (menu_name, active) =>{
this.setState({
menu: menu_name,
})
this.props.menu(this.state.menu);
}
render (){
render (
<ul>
<li ><a onClick={this.menuClick.bind(this, "A")}><i className={"fa fa-circle"}></i> A</a></li>
<li ><a onClick={this.menuClick.bind(this, "B")}><i className={"fa fa-circle"}></i> B</a></li>
<li ><a onClick={this.menuClick.bind(this, "C")}><i className={"fa fa-circle"}></i> C</a></li>
</ul>
Can anyone help me to find the problem?
Any help would be appreciated thank you :)
It's not guaranteed that state is updated immediately. You would need to use callback function and then call your parent method to pass the child component state to the parent correctly:
menuClick = (menu_name, active) =>{
this.setState({
menu: menu_name,
}, () => {
this.props.menu(this.state.menu);
})
}
Or, componentDidUpdate will do the same job:
componentDidUpdate() {
this.props.menu(this.state.menu) // only called after component is updated
}

How do I prevent an entire list from re-rendering when state of one component changes?

I have a list component that fetches data and maps the results over card-like child components to create a list of "cards". Below is a example:
class Card extends Component {
constructor(props) {
super(props);
this.state = {
activeTab: 1
};
}
setActiveTab = (activeTab) => {
this.setState({ activeTab });
}
render() {
const activeTab = this.state.activeTab;
return (
<div className="card">
<ul className="nav nav-tabs card-header-tabs">
<li
className="nav-item"
key={ 1 }
onClick={ () => this.setActiveTab(1) }
>
<a className="nav-link" href="#">Overview</a>
</li>
<li
className="nav-item"
key={ 2 }
onClick={ () => this.setActiveTab(2) }
>
<a className="nav-link" href="#">Data</a>
</li>
<li
className="nav-item"
key={ 3 }
onClick={ () => this.setActiveTab(3) }
>
<a className="nav-link" href="#">Edit</a>
</li>
</ul>
<h3 className="card-title">{ this.props.title }</h3>
{ activeTab === 1 &&
<h5 className="card-text">
{ this.props.body }
</h5>
}
{ activeTab === 2 &&
<div className="card-data">
<a>Yes: { this.props.yes }</a>
<a>No: { this.props.no }</a>
</div>
}
{ activeTab === 3 &&
<div>
<a
href="/cards"
onClick={ this.props.onDelete }
className="delete-card"
>
Delete
</a>
</div>
}
</div>
)
}
}
export default Card;
class CardList extends Component {
componentDidMount() {
this.props.fetchCards();
}
renderCards() {
return this.props.cards.reverse().map(({ _id, title, body, yes, no }) => {
return (
<Card
key={_id}
title={title}
body={body}
yes={yes}
no={no}
onDelete={() => this.props.deleteSurvey(_id)}
/>
);
});
}
render() {
return (
<div className="container">
<div className="row">{this.renderCards()}</div>
</div>
);
}
}
Each card has tabs, clicking a tab determine which text is shown in each particular card. The tab/toggle is working, but my issue is that the entire list is re-rendering when the tab is clicked. So if you are at the bottom of the list, the re-render brings you back to the top of the list.
I've tried implementing componentShouldUpdate, but with no success thus far. I would like for the card to toggle it's content and the list remain in place. Is using the shouldUpdate lifecycle method the correct route? Or is there a better approach?
In your <a> anchor tag you have href="#"` which will always re-direct you to a new link. (# will re-direct to current page re-rendering everything).
Easiest Solution is to remove href="#" which will remove your cursor: pointer; styling but you can re-add that back into your
nav-item classname.
Another simple solution is you can move the onClick into the anchor tag and adding evt.preventDefault() (Which prevents the event handler from doing the default action which is loading the page to the href) However this will make it so that you have to click the anchor tag so if you have padding between the <li> and <a> this might not be your best solution.
<li
className="nav-item"
key={ 3 }
>
<a
className="nav-link"
href="#"
onClick={ (e) => { e.preventDefault(); this.setActiveTab(3); } }
>
Edit
</a>
</li>
However Thai Duong Tran made a good point where you don't want to re-create the function every time.
Best solution is to move your anonymous function into a class function. (This solution also removes the href="#" so you need to add cursor: pointer; into your css if you want that style)
<li
className="nav-item"
data-tab={ 1 }
onClick={this.onNavClick}
>
<a className="nav-link">Overview</a>
</li>
onNavClick = (evt) => {
this.setState({activeTab: evt.target.dataset.tab});
}
Also your delete action has the same issue where you added a href="/card" so you want to be careful with that.
If you want to have route actions to move to different pages/URL. You should look into adding react-router
I think it's the prbolem
onClick={ () => this.setActiveTab(2) }
Essentially, React decides to re-render a component when it detects a change in component state or props, and it only does shallow comparison for object.
As you are using closures, these function is dynamically re-created every time the Card component render function is called. React detects a new object (in JS, function is treated as object) passed to the li element, then it redraws it.
To solve this problem you can do something like:
<li className="nav-item" data-tab={ 1 }
onClick={this.navLinkClickHandler}>
<a className="nav-link" href="#">Overview</a>
</li>
Then have navLinkClickHandler defined in your component:
navLinkClickHandler = (evt) => this.setState({activeTab: evt.target.dataset.tab})
More on React reconcilation can be found here:
https://reactjs.org/docs/reconciliation.html
And about using closure in render function:
https://reda-bx.gitbooks.io/react-bits/content/conventions/08.closures-in-render.html
Try this code inside CARD component (If you have not tried it already). Might work fine.
shouldComponentUpdate(nextProps,nextState){
if(this.state.activeTab === nextState.activeTab ){
return false
} else {
return true
}
}

How do i get refs value from dynamically generated array in React

I got my data from firebase, looped through it and displayed it on the frontend. Now I am trying to get the refs value of the already displayed value when I click on it. For example, when I click on Dino, i should be able to see the value 'Dino' on my console tab of Chrome browser.
Here is a link to the picture of the array list displayed on react frontend
<ul>
<li onClick={this.handleSubmit}>
{
Object.keys(this.props.group).map(function(keyName, keyIndex) {
return(
<div key={keyIndex}>{keyName}</div>
)
})
}
</li>
</ul>
Assuming that those "dino, magic & muu" are the keys in this.props.group you need to add an onClick handler to that div, so:
<div key={keyIndex} onClick={() => console.log(keyName)}>{keyName}</div>
You'll need to bind a click event to each div within the li. I would actually rewrite it like so:
<ul>
{
Object.keys(this.props.group).map( (keyName, keyIndex) => {
return(
<li key={keyIndex} onClick={this.handleSubmit.bind(this, keyName}>
{keyName}
</li>
)
})
}
</ul>
Then your handleSubmit function will get the keyName as a param:
handleSubmit(keyName){
// Do something...
}

How to return multiple list components using react.js

I understand that you can't return multiple elements.
But this limitation is causing me to not be able to solve this problem without help.
I am working with an HTML design from another that uses a 1 depth list to represent a 3 depth list. As a last resort I can change this.
An example of the HTML I am converting to React. The number of Devices, cards and remotes could all be different on every GET request.
<ul class='deviceList'>
<li>Device A`</li>
<li>Cards Connected in Device A</li>
<li>Card 1</li>
<li>Card 2</li>
<li>Remotes Connected to Device A</li>
<li>Remote 1</li>
<li>Device B</li>
<li>Cards Connected to Device B</li>
<li>Card 1</li>
<!-- ... could go on for more ...-->
</ul>
This is how I setup the React code that actually contains the <ul>:
var DeviceList = React.createClass({
render: function() {
var deviceList = this.props.devices.map(function (device) {
return [
<DeviceTitle title='{device.name}' />,
<ComponentTitle title='Cards' />,
<Cards cards={device.cards} />,
<ComponentTitle title='Remotes' />,
<Remotes remotes={device.remotes} />
];
});
return (
<ul className='deviceList'>
{deviceList}
</ul>
);
}
});
So the problem is the Card and Remote components need to return multiple list components. I tried having it return an array of list components but this did not work.
Here is what I have .. this is the part that fails.
var cards = React.createClass({
render: function() {
var cards = this.props.cards.map(function (card) {
return (
<li>{card.name}</li>
);
});
// illegal
return {{cards});
}
});
// Remote is a little different but same problem
Also.. to try to make this simpler I only showed a 3 depth list but really it is 4 depths. Each card and remote could have other components attached to them.
So as other questions here have shown. I can't do it this way. So what should I do?
The problem is that return {{cards}}; isn't valid JavaScript. You only use curly braces like that in JSX. (Also you were missing a closing parenthesis on line 6.) cards is just a regular JavaScript variable, so your function should return it just like any other variable:
var cards = React.createClass({
render: function() {
var cards = this.props.cards.map(function (card) {
return (
<li>{card.name}</li>
);
});
return <ul>{cards}</ul>;
});
Of course, returning a <ul> instead of an array of <li>s isn't exactly what you wanted, but as a matter of generating sensible markup, it makes sense for Cards and Remotes to be child lists instead of dumping them all into a single list.
var cards = React.createClass({
render: function() {
var cards = this.props.cards.map(function (card) {
return (
<li>{card.name}</li>
);
};
return (<ul>{cards}</ul>);
}
});
A solution to solve this. Not saying it is the best but it is the only method I could figure out.
You can use a plain javascript function. Since they have no constraint on what you can return. Then I just used concat on the array that is created for the actual list to add these elements.
function Card(device) {
return device.cards.map(function(card) {
return (<li>{card.name}</li>);
});
}

Categories