Keeping track of selected div in grid of divs with React state - arrays

I am new to React/JS and have been trying for a few days to figure out how to keep track of which Collection below is selected.
I want the user to be able to click on the collection div and trigger the selected badge (commented out in the code, but I've included it in the screenshots) for that specific collection div. Upon clicking on a new div, the original div should have the badge remove and the newly clicked upon div should have the new badge. In other words, only one collection should have the badge at any point in time. I want to keep track of this in the Collection component's state (unless there is a better way).
For more reference, each collection div is a card that is highlighted upon hover. The site is also deployed at https://gatsby-client.rishipr.now.sh/practice.
Collection.js
import React, { Component } from "react"
class Collection extends Component {
render() {
return (
<div className="collection">
<div className="collection-name">
{this.props.name}{" "}
{/* <span className="collection-selected-badge">SELECTED</span> */}
</div>
<div className="collection-description">{this.props.description}</div>
<div className="collection-description">
{this.props.numQuestions} questions
</div>
</div>
)
}
}
export default Collection
CollectionList.js
import React from "react"
import Collection from "./collection"
const CollectionList = props => {
return (
<div className="collections">
<Collection
name="All Questions"
description="All questions (technical & behavioral)"
numQuestions="400"
/>
<Collection
name="Top Behavioral"
description="The most common behavioral questions to prepare for"
numQuestions="21"
/>
<Collection
name="Outside the Guide"
description="Questions from outside BIWS 400 and WSO"
numQuestions="12"
/>
<Collection
name="20 Must Know Questions"
description="The 20 must-know interview questions"
numQuestions="20"
/>
<Collection
name="Real IB Interview Questions"
description="Questions from real interviews at various banks"
numQuestions="16"
/>
<Collection
name="Brain Teasers"
description="Common brain teasers"
numQuestions="4"
/>
</div>
)
}
export default CollectionList

Add state to your CollectionList component, storing the name of the selected Collection:
const CollectionList = props => {
const [selected, setSelected] = useState(null);
return (
// ...
)
}
Pass the selected and setSelected props to all of your Collection component:
<Collection
name="All Questions"
description="All questions (technical & behavioral)"
numQuestions="400"
selected={selected}
setSelected={setSelected}
/>
Add an onClick method to the outer div of your Collection component, which sets the selected state value to the name of the current Collection:
<div
className="collection"
onClick={() => this.props.setSelected(this.props.name)}
>
// ...
</div>
Check if the selected prop is the same as the current collection's name in your Collection component. If true display the selected badge:
{this.props.selected === this.props.name && (
<span className="collection-selected-badge">SELECTED</span>
)}
Here's a codesandbox example, though not as pretty as the image you provided.

Related

NextJS: Replace

I have a div in a Next JS application that displays the currency and price of a product once a user enters a product page.
<div className="flex">
<Image src={EuroCurrency} alt="Euro Sign} />
<h1 className="ml-5>9.800,00</h1>
</div>
Through an onClick event on a button, I want to exchange this div with another, evenly formatted but contextually different, div.
<div>
<Image src={DollarCurrency} alt="Dollar Sign} />
<h1 className="ml-5>9,500.00</h1>
</div>
This div would need to be hidden until the user clicks the aforementioned button.
I'm aware that this would be achieved through a state - but I'm uncertain on how to hide (and exchange) a complete div.
this is how you can use conditional rendering and useState together to switch the component
import React from 'react'
export default function App() {
const [toggle, setToggle] = React.useState(false);
return (
<div className="App">
{toggle ? ( <div >
<h1 >9.800,00</h1>
</div>): (<div><h1>9,500.00</h1></div>)
}
<button onClick={()=>{setToggle(!toggle)}}>Toogle</button>
</div>
);
}
Here you need to change the conditional rendering that suits you like
{toggle ? ( COMPONENT 1 ): ( COMPONENT 2 )}
I'm making a few guesses at what you're asking, but hopefully this example helps.
This is an example of a functional component that uses state to switch between rendering euro and dollars components:
import { useState } from 'react';
function MyAwesomeComponent(){
// expected state values are "dollar" or "euro"
// if you use TypeScript, you can enforce that,
// but I'm just a TypeScript shill :P
// "dollar" is the initial state
const [currency, setCurrency] = useState("dollar");
return(
<div className="flex">
{ currency === "dollar"
? <Image src={DollarCurrency} alt="Dollar Sign" />
: <Image src={EuroCurrency} alt="Euro Sign" />
}
<h1 className="ml-5">9.800,00</h1>
<button onClick={
() => currency === "dollar" ? setCurrency("euro") : setCurrency("dollar")
}
>
Click to Switch Currency
</button>
</div>
)
}
There are plenty of other ways to do it. But notice how, instead of having the whole div rendered conditionally and duplicating code, I just render the specific Image component conditionally. Depending on your use case, you could conditionally render divs instead, any component can go in the two slots of the terney expression.

How to NOT render/ hide a React component when no prop is passed?

TLDR: Cannot figure out why component is still being rendered while no props are passed.
So I have been building a NextJS application, and I have this banner component that is shown on every page of my website. It has some header text, buttons and an image:
const Banner = (props) => {
return (
<div className={bannerStyles.wrapper}>
<div className={classnames(bannerStyles.banner, "wrap", "center")}>
<div className={bannerStyles.banner_left}>
<h1>{props.header}</h1>
<div className={bannerStyles.button_wrapper}>
<div className={bannerStyles.button}>
<Button>{props.button || null}</Button>
</div>
<div className={bannerStyles.button}>
<Button>{props.scnd_button || null}</Button>
</div>
</div>
</div>
<div className={bannerStyles.banner_right}>
<Image src={props.image} alt=""></Image>
</div>
</div>
</div>
);
};
Inside of this, as you can see I have two Button components (The MDEast thing is an arrow icon):
const Button = ({children}) => {
return (
<div className={buttonStyles.button}>
<Link href="/"><a>{children} <MdEast /></a></Link>
</div>
)
}
Now I want the option that if no prop is passed, that the Button component(s) do(es) not render/ is hidden from the page, so that it is optional per page. Yet the Button does still render, even though I am not passing any props on my About page. My about page:
const About = () => {
return (
<>
<Banner
header="Hello this is my code"
image={banner_placeholder}
/>
</>
)
}
PS. I am fairly new to React and NextJS, so this might be a beginner mistake, or I am not understanding the fundamentals well enough, but could someone point me in the right direction please?
To conditionally render the button you can use:
props.button && <Button>{props.button}</Button>
When props.button is falsy, then button will not get rendered.

Prevent Component/PureComponent with styled-components from re-rendering all items after state change

After adding styled-components to this example, we've noticed that our list component updates everything when only one item in state is modified.
The list rendering highlights (from React dev-tools) are excessive when adding/removing a single entry. One item is removed/added, then all items get highlighted.
code samples
github: https://github.com/chancesmith/reactjs-optimize-list-updates/tree/master/src
codesandbox: https://codesandbox.io/s/wn45qw44z5
Here is an example of the right list component (CategorizedList.js)
import styled from "styled-components";
const Item = styled.li`
color: #444;
`;
class CategorizedList extends PureComponent {
render() {
return (
<div>
{this.props.categories.map(category => (
<ul>
<li key={this.props.catStrings[category]}>
{this.props.catStrings[category]}
</li>
<ul>
{this.props.items.map((item, index) =>
item.category === category ? (
<div key={item.label + index}>
<Item>{item.label}</Item>
</div>
) : null
)}
</ul>
</ul>
))}
</div>
);
}
}
Preference
I'd prefer to use PureComponent so that shouldComponentUpdate() gets handled automatically.
Question
How can we make sure only the modified objects in items state are re-rendered?
If the data changes , the view will re-render. It shouldn't be an expensive process since it happens once on add/remove action. If you find performance issues it might be caused from something else.
In general this would be the way you can have some control on pure-components re-render:
https://reactjs.org/docs/react-api.html#reactmemo

React router not rendering the component on click of a Array item

I'm creating a component which has array of items and showing them on one page using map function.
Need help on how I can show a detail view of a item by updating route value dynamically based on the item I clicked.
Currently when I click on a item know more though the url change to some wrong value I don't see any change in the page.
Component:
{location[area].map((item, index) =>
<div key={index} className="col-md-4 mt-3">
<div className="card">
<div className="card-body">
<h5 className="card-title">{item.title}</h5>
<p className="card-text">{item.description}</p>
<Link to={'/view/${item.title}'} onClick={() => addDetail(item)} className="btn btn-primary">Know more</Link>
</div>
</div>
</div>
)}
Router:
<Route path='/view/:id' exact component={DetailPage} />
DetailPage Component:
class DetailPage extends Component {
render() {
const selectedItem = this.props.selectedItem;
return(
<div>
<DetailedView selectedItem={this.props.selectedItem} />
</div>
);
}
}
Result Anchor Tag:
<a class="btn btn-primary" href="/view/${item.title}">Know more</a>
Onclick result url:
http://localhost:3000/view/$%7Bitem.title%7D
You need to use backticks for the to prop of your Link components so that template literals will be used and the variable will be inserted into the string.
<Link
to={`/view/${item.title}`}
onClick={() => addDetail(item)}
className="btn btn-primary"
>
Know more
</Link>
Well,As #tholle suggested use template literal.Your route seems fine, just make use of react life cycle method componentWillReceiveProps in the Detail Page Component.Here is the code
class DetailPage extends Component {
componentWillReceiveProps(nextProps){
if(this.props.match.params.id !== nextProps.match.params.id){
//make a call with updated nextProps.match.id or filter the existing data store
with nextProps.match.id
}
}
render() {
const selectedItem = this.props.selectedItem;
return(
<div>
<DetailedView selectedItem={this.props.selectedItem} />
</div>
);
}
}

React access Dom Nodes from this.props.children

Let's say I have a Card that contains a login Form
<Card>
<LoginForm/>
</Card>
How do I access the nodes from the Form within the Card render function?
<Form >
<input type="text" name="email"/>
<input type="password" name="password"/>
<input type="submit"/>
</Form>
Because what i´d like to do is to render the submitbutton not within the props.children context but render it wrapped outside of the given child!
render () {
return (
<div className="card">
<div className="inner">
{/* render Children */}
{this.props.children != undefined ?
<div className="childrenWrapper">
{this.props.children}
</div>
: ""
}
</div>
{/* render submit from login form here, not above */
</div>)
There are some components which actually do what I want. For example the Tabs component from react-toolbox. They somehow manage to render what's within the Tab (children) somewhere else
Just for instance
<Tabs index={this.state.inverseIndex} onChange={this.handleInverseTabChange} inverse>
<Tab label='First'><small>First Content</small></Tab>
<Tab label='Second'><small>Second Content</small></Tab>
<Tab label='Third'><small>Third Content</small></Tab>
<Tab label='Disabled' disabled><small>Disabled Content</small></Tab>
</Tabs>
Which will lead to the following html
As you can see the children from the tab where rendered within their own section
I do not want to change anything on the Form to solve this problem, I would like to pass the Form into the Card and let the Card decide how the Form will be rendered within the card render function.
Since I'm trying to implement the Google Material Design Card component and just use it as a template there are more elements coming which will need to be split up and placed at the positions I want them to be. The thing is I could actually place the relevant HTML around the Form to get it as the Card I want it to be, but then I wouldn't need the component at all.
There are some decent answers here, but none of them directly answer your question. Therefore, even though you should refactor your code (as elucidated below), I am going to provide you a working solution:
class Card extends React.Component {
constructor() {
super();
this.state = {};
}
render() {
console.log(typeof this.props.children)
return (
<div>
{typeof this.props.children === 'object'
? React.cloneElement(this.props.children, { ref: (n) => this.form = n })
: null}
<button onClick={(e) => console.log(this.form.data)}>submit</button>
</div>
);
}
}
class Form extends React.Component {
constructor() {
super();
this.onChange = this.onChange.bind(this);
this.state = {};
}
onChange(e) {
this.data = e.target.value;
}
render() {
return (
<form>
<input type="text" onChange={this.onChange} />
</form>
);
}
}
ReactDOM.render(
<Card><Form /></Card>,
document.getElementById('container')
);
https://jsbin.com/fohehogozo/edit?js,console,output
By setting a property on the instance, you can then access that property from children by using a ref. I checked for typeof === object here, because there was only one child.
WARNING: this code is NOT PRODUCTION READY. Do not ever run this in production. The code I have demonstrated is a terrible hack, and you should never try this at home.
If you are trying to submit a form, maybe look at passing down an onChange event and storing the value (based on the name of the field) in the state of the Card. Then attach the onChange event on the inputs so as soon as they're updated, the data will be passed back up to the container for you to submit.
If you would like to split up the childrens passed, you can simply filter the children array to split up the children, however your childrens seem to be nested.
Why dont you let the cards children handle the separation between your inner container and other content?
I think restructuring in this case is more suitable than modifying the passed children property.
Also, pulling the submit button out of the actual form tags, would break your form as it would no longer submit without some custom connection between the button and the actual form.
Don't try to manipulate the DOM; it's generally an anti-pattern in React (though there are a few valid use cases). In your case, rather than literally trying to move the elements, I'd simply hide the button in the form and add it to the parent.
Assuming you have access to the internals of <LoginForm>, you can add a prop to hide the button:
const button =
<div class="flatbuttonWrapper">
<input type="submit"/>
</div>;
<Form>
<input type="text" name="email"/>
<input type="password" name="password"/>
{!this.props.hideButton && button}
</Form>
Add the button to the Card component:
render() {
return (
<div className="card">
<div className="inner">
{this.props.children != undefined ?
<div className="childrenWrapper">
{this.props.children}
</div>
: ""
}
</div>
<div class="flatbuttonWrapper">
<input type="submit"/>
</div>
</div>
);
}
Finally, in your parent:
<Card>
<LoginForm hideButton />
</Card>
All that said, it really feels like you need to structure your code better and break some of these components up into smaller, more reusable pieces. For example, the Card component probably shouldn't be affecting the button's style or conditionally rendering children; it should just add a frame around any children. Then you can create a more complex component that composes these simpler sub-components to to whatever you need.

Resources