I am having trouble using bootstrap to create a grid from a looping function. In the code below, cities_index.jsx has an unordered list(ul) and uses a mapping loop for each item in the array, while cities_index_item is a separate file that creates the li for each item. This operation is happening across two files.
The trouble I'm having is I cannot figure out how to create a row for two li's at a time with a loop. Currently, it is giving each li its own row -- ideally, I would like a have one row for every two li's. I could hardcode them to be that way, but I think it'd be more efficient to have the grid be outputted from the looping function somehow.
Could anyone help? Any assistance would be appreciated!
//cities_index.jsx
...
render() {
return (
<div>
<ul>
{
this.props.cities.map((city, id) => (
<CitiesIndexItem key={id} city={city} />
))
}
</ul>
{this.props.children}
</div>
);
}
}
export default CitiesIndex;
//cities_index_item.jsx
import React from 'react';
import { Link } from 'react-router';
const CitiesIndexItem = ({ city, router }) => (
<li className="row">
<Link to={`/cities/${city.id}`}>
{city.name}
</Link>
</li>
);
export default CitiesIndexItem;
I'd suggest looking at the Grid components instead of doing a list, unless you have some specific reason for a list.
With Grids, you can set the class of the div you create in your loop to "col-md-6", which will give each div a width of 50% of it's container, thus keeping it to two components per row.
So you'd have a container div, and iterate to create the CitiesIndexItem components, which would be returning another div with the appropriate bootstrap class names.
Something like:
//cities_index.jsx
...
render() {
return (
<div className="container-fluid">
{
this.props.cities.map((city, id) => (
<CitiesIndexItem key={id} city={city} />
))
}
{this.props.children}
</div>
);
}
}
export default CitiesIndex;
//cities_index_item.jsx
import React from 'react';
import { Link } from 'react-router';
const CitiesIndexItem = ({ city, router }) => (
<div className="col-md-6">
<Link to={`/cities/${city.id}`}>
{city.name}
</Link>
</div>
);
export default CitiesIndexItem;
Also might be worth checking out react-bootstrap too.
Related
I want to render multiple instances of the same component. Here is the component that will be repeated (not my actual component, just an example):
import React from 'react';
function Repeated () {
return (
<div></div>
)
}
export default Repeated;
And the component that will repeat the other (again, just an example):
import React from 'react';
import Repeated from './Component1';
function OtherComp () {
return (
<div>
<Repeated />
<Repeated />
<Repeated />
<Repeated />
</div>
)
}
export default OtherComp;
Is there any way I can add the "Repeated" component multiple times through something like a loop, instead of having to copy and paste the component multiple times? Thanks :)
You can create a new array of desired length and map it to the components. However, you have to add a key to every one of them or ignore a warning:
With warning:
return (
<div>
{Array(4).fill(<Repeated />)}
</div>
)
Mapping to keys:
return (
<div>
{Array(4).fill(true).map((_, i) => <Repeated key={i} />)}
</div>
)
If you have an array for example and want to render each element as Repeat you can write
import React from 'react';
import Repeated from './Component1';
function OtherComp () {
// get array via fetch or something else
return (
<div>
{ array.map(item => <Repeated key={a-unique-key} item={item} />) }
</div>
)
}
export default OtherComp;
You can loop through the array of props you want to render via the Repeated components, provided that each of the rendered components has a unique key
The solution provided by #Jakub should work fine
What's the proper React way of toggling a class on a tag outside of the specific component?
So currently I have the following working example:
import React, { useState, useEffect } from "react";
export default function Header() {
const [toggled, setToggled] = useState(0);
useEffect(() => {
document.body.classList.toggle("toggled");
});
return (
<nav>
<ul>
<li>
<a onClick={() => setToggled(!toggled)} href="#">
Open menu
</a>
</li>
</ul>
</nav>
);
}
This feels wrong for some reason.
First of all I'm using document.body.classList.toggle instead of the useState variable. I know when toggling a class inside this Header component I would just use useState instead of useEffect. But when I need to toggle some class outside the component, like in my case, is that possible with useState alone?
Second of all, should I even be using document.body.classList.toggle in the useEffect, or is there some more specific way of doing it? What if I wanted to target some other element without any id or class?
Lastly, if anyone is wondering, I'm toggling the class on the body instead of directly inside the header component because I want to modify many different elements down the line, not just the menu.
Looks like an X/Y problem - if you want an app-wide UI change, you should change the status on the main component (that holds the parent HTMLElement of the whole app ideally), like:
function App() {
const [showNav, setShowNav] = useState(false);
function toggleNav() {
setShowNav(!showNav);
}
return (
<main className={showNav ? 'show-nav' : ''}>
<Header onButtonClick={toggleNav}/>
...
</main>
);
}
function Header({ onButtonClick }) {
return (
<header>
<button onClick={onButtonClick}>Menu</button>
...
</header>
);
}
If you need to pass the function various levels of hierarchy down the component tree, I recommend to use a state container like redux.
I'm building a webpage and realized a common style shared by each component (same background, border, and title style). So I thought I should make an HOC which accepts the inner content of each component as well as a title, and returns an outer component which wraps this inner component and heading.
At first I ran into a lot of issues trying to get this to work, being new to React, but now it's finally working but I still don't understand how.
Here is my HOC
const BaseBlock = (WrappedComponent) => {
return class BaseBlock extends Component {
render () {
return (
<div className={styles['base-block']}>
<div className={styles['container']}>
<div className={styles['base-block-head']}>
{ this.props.title }
</div>
<div className={styles['base-block-body']}>
<WrappedComponent {...this.props} />
</div>
</div>
</div>
)
}
}
}
export default BaseBlock
This is the WrappedComponent:
const HighlightsBlock = (props) => {
return <ListsComponent items={props.items} />
}
export default BaseBlock(HighlightsBlock)
And this is the ListsComponent
const ListsComponent = (props) => {
if (props.items) {
return (
<ul className={styles['styled-list']}>
{props.items.map((item, idx) => {
return (
<li key={idx} className={styles['styled-list-item']}>{item}</li>
)
})}
</ul>
)
} else return (
<h3>No highlights</h3>
)
}
export default ListsComponent
And this is how I'm using the component in my app:
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Now, I can see the HighlightsBlock component receiving props twice (Once when I'm using it in my App with props, and once inside the HOC Baseblock as WrappedComponent ). If I remove props from either of these places it stops working. I don't understand how this is working.
When you render <HighlightsBlock items={this.getHighlights()} title='Highlights' /> you are actually rendering the component returned by HOC which in turn renders your actually HighlightsBlock component as <WrappedComponent {...this.props} />
You can think of HighlightsBlock component to be nested two level deep and hence you need to pass on the props to it, firstly as {...this.props} from within HOC and then receive it as props in functional component
This is because of this.getHighlights() in this line,
<HighlightsBlock items={this.getHighlights()} title='Highlights' />
Every time you pass props to child component this function is getting executed.
To solve this issue, maintain a state value in your parent component and set that value in getHighlights function like,
getHighlights(){
//you logic to get data
this.setState({items:data.items}); //considering `data` is object which has `items`
}
Now you can pass items like,
<HighlightsBlock items={this.state.items} title='Highlights' />
I'm kind of new to React and am following some video tutorials as also am I reading some books about it. (None older than 2016 - because It changes a lot)
During my journey, I passed by two ways to do the array.map() loop in React.
1. Do the map loop inside the parent component and just return the elements from the child component
class ItemsList extends React.Component {
render() {
const items = this.props.items.map((item) =>(
<Item
id={item.id}
title={item.title}
/>
));
return (
<div id='items'>
{item}
</div>
);
} //render
}
class Item extends React.Component {
render() {
return (
<li key={this.props.id}>
{this.props.title}
</li>
);
} //render
}
2. Do the map loop inside the child component and return the whole to the parent component
class ItemsList extends React.Component {
render() {
));
return (
<div id='items'>
<Item
id={item.id}
title={item.title}
/>
</div>
);
} //render
}
class Item extends React.Component {
render() {
const item = this.props.items.map((item) =>(
<li key={item.id}>
{item}
</li>
));
return(item);
}
}
If i understand it correctly, one of the advantages of React is that it's a more "Team-friendly" way to work on an app because everyone can work on one component at a time without really interfering with somebody else work.
But based on this, I would opt for option 2, because we touch the parent component as little as possible and focus more on the child component.
But I also believe it's more smart to choose option 1, because we have all the data and bindings inside the parent component which makes it more structurized and less binding needy for the child component.
Sorry if I'm still missing some vocabulary to explain this better for the experienced React developers - but I hope I made myself clear about this.
Thanks for your time taken!
As react is component based you should group your props to the component it belongs to. Option 2 doesn't make much sense as you have an ItemList component which accepts a list of items and obviously renders and maintains that list. The Item component is specific to one item and shouldn't know about other items. Additionally, with option 1 you are decoupling the item list from the actual Item.
The better approach is to have a parent component to render a collection of child items.
In my opinion the Item component should be as much independent as possible of the ItemList component. I would suggest the following structure:
ItemList component:
const ItemList = ({ items }) => (
<div>
{items.map(item => (
<Item key={item.id} item={item} />
))}
</div>
);
Item component:
const Item = ({ item }) => (
<p>The ID of the item is {item.id}</p>
);
It seems similar to your 1st approach except the key should belongs to the ItemList component and also I would avoid using <li> tags to make it reusable but it really depends on your usecases.
The 2nd approach is logically wrong and I agree what #ztadic91 mentioned, that the Item component shouldn't know about the others.
I'm having a problem with my React component. The nested children of my component ControlPanel don't seem to be rendering. Here is my code:
class App extends Component {
render() {
return (
<div className="App">
<ControlPanel>
<CustomerDisplay />
</ControlPanel>
</div>
);
}
}
I have the following two lines at the top of this file:
import ControlPanel from './components/control_panel';
import CustomerDisplay from './components/customer_display';
And here is my ControlPanel Component:
import React from 'react';
import CSSModules from 'react-css-modules';
import styles from './styles.scss';
const ControlPanel = () => {
return (
<div className="control_panel" id="control_panel">
</div>
);
}
export default CSSModules(ControlPanel, styles);
I have tried:
Calling the component as a full HTML tag (opening & closing)
Nesting the CustomerDisplay component in the ControlPanel component (in the ControlPanel's index.jsx file)
I know that nesting component's is possible. I've seen it done. For some reason it just won't work for me.
To allow components to contain children and render them correctly, you have to use this.props.children. This is passed to all components with children as a prop and contains the children of the component, as explained by the React documentation:
Containment
Some components don't know their children ahead of time. This is especially common for components like Sidebar or Dialog that represent generic "boxes".
We recommend that such components use the special children prop to pass children elements directly into their output:
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
This lets other components pass arbitrary children to them by nesting the JSX
function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
As described in the documentation, some components don't know their children ahead of time -- they may be generic wrappers or boxes of content that vary, which is what your ControlPanel is. So, to render the children of your component, you must render the children from the children prop explicitly in the parent's render method. Thus, apply it like this to your code:
const ControlPanel = (props) => {
return (
<div className="control_panel" id="control_panel">
{props.children}
</div>
);
}
Notice how props.children is rendered (not this.props.children because it is a function component).
You can access the nested elements through props. So in your case do this:
const ControlPanel = (props) => {
return (
<div className="control_panel" id="control_panel">
{props.children}
</div>
);
}
You need to render the children in ControlPanel
const ControlPanel = ({ children }) => {
return (
<div className="control_panel" id="control_panel">
{children}
</div>
);
}
function FancyBorder(props) {
return (
<div className={'FancyBorder FancyBorder-' + props.color}>
{props.children}
</div>
);
}
export default function WelcomeDialog() {
return (
<FancyBorder color="blue">
<h1 className="Dialog-title">
Welcome
</h1>
<p className="Dialog-message">
Thank you for visiting our spacecraft!
</p>
</FancyBorder>
);
}
Anything inside the<FancyBorder>JSX tag gets passed into the FancyBorder component as childrenprop. Since FancyBorder renders {props.children} inside a <div>, the passed elements appear in the final output.
This is what I was looking after, check it out here
https://reactjs.org/docs/composition-vs-inheritance.html
Your App.js (I understand that it is your JSX Index) should be:
import React from 'react';
import ReactDOM from 'react-dom';
export default class App extends Component {
render() {
return (
<div className="App">
<ControlPanel>
<CustomerDisplay />
</ControlPanel>
</div>
);
}
}
ReactDOM.render(<App/>, document.getElementById('YOUR_ROOT_ID'));
Try to add export default before class (in all your components).