React Ref for sibling component - reactjs

I have Component named Home which contains LeftNav, BookSearch and HomeCart Component. BookSearch Component has a input field and HomeCart component has a button. When the button in HomeCart component clicked, I need to focus the input field in BookSearch Component.
I researched a bit and I could able to only focus an element from the same Component only. How can I achieve the desired behaviour this using React Ref?
Home.jsx
class Home extends React.Component {
render() {
return (
<React.Fragment>
<div className="row no-gutters app">
<div className="col-sm-12 col-md-12 col-lg-2">
<LeftNav />
</div>
<div className="col-sm-12 col-md-12 col-lg-7 books-panel">
<BookSearch test={this.test}/>
</div>
<div className="col-sm-12 col-md-12 col-lg-3 cart-panel">
<HomeCart onClick={thi} />
</div>
</div>
</React.Fragment>
);
}
}
BookSearch.jsx
class BookSearch extends React.Component {
render() {
return (
<React.Fragment>
<button onClick={this.test}>Focus</button>
<div className="row text-center">
<div className="col-12">
<form onSubmit={this.handleSubmit}>
<h2 className="mt-5">Start searching your favorite books</h2>
<label htmlFor="search-query" className="sr-only">
Search by book name
</label>
<input
name="search-query"
onChange={this.handleChange}
type="search"
className="book-search"
value={this.state.query}
placeholder="Start searching...."
/>
<button type="submit" className="btn btn-primary btn-lg">
Search
</button>
</form>
</div>
</div>
</React.Fragment>
);
}
}
HomeCart.jsx
class HomeCart extends React.Component {
focusSearchBookInput(){
this.props.focusSearchBookInput()
}
render() {
const { cart_items } = this.props;
const cartTable = ();
const noCartItems = (
<React.Fragment>
<p>No items currently available in your cart. </p>
<button onClick={this.focusSearchBookInput} className="btn btn-success mt-5">
<ion-icon name="search" /> Start Searching
</button>
</React.Fragment>
);
return (
<React.Fragment>
<div className="row text-center">
<div className="col-12">
<h2 className="mt-5 mb-5">Books in your cart</h2>
{cart_items.length > 0 ? cartTable : noCartItems}
</div>
</div>
</React.Fragment>
);
}
}

One solution could be...
Create the ref and a click handler that sets focus on the ref in Home.jsx
class Home extends React.Component {
inputRef = React.createRef();
focusHandler = () => {
this.inputRef.current.focus();
}
render() {
return (
<React.Fragment>
<div className="row no-gutters app">
<div className="col-sm-12 col-md-12 col-lg-2">
<LeftNav />
</div>
<div className="col-sm-12 col-md-12 col-lg-7 books-panel">
<BookSearch inputRef={this.inputRef} />
</div>
<div className="col-sm-12 col-md-12 col-lg-3 cart-panel">
<HomeCart focusHandler={this.focusHandler} />
</div>
</div>
</React.Fragment>
);
}
}
Add ref={this.props.inputRef} to the input in BookSearch.jsx
Add onClick={this.props.focusHandler} to the button in HomeCart.jsx

Related

How can I get my button to align right inside in a card in react?

I made a button (entered as {this.props.children}), and I can align it in the center of the card footer, but for some reason, it will not align right (it just aligns on the left side).
class Product extends Component {
state = {
product: this.props.product
}
render() {
return (
<div className='col-lg-6'>
<div className='card m-2'>
<div className='card-body'>
<div className='text-muted'># {this.state.product.id}</div>
<h5 className='pt-2 border-top'>{this.state.product.productName}</h5>
<div>${this.state.product.price}</div>
</div>
{/*card body ends here*/}
<div className='card-footer text-center'>{this.props.children}</div>
</div>
</div>
);
}
}
Here is the button code:
render() {
return (
<div>
<div className="container-fluid">
<h4>Shopping Cart</h4>
<div className="row">
{this.state.products.map((prod) => {
return (
<Product
key={prod.id}
product={prod}
>
<button className="btn btn-primary">Buy Now</button>
</Product>
);
})}
</div>
</div>
</div>
);
}
Thanks in advance for any help offered!

React Hover to conditional render component

this is my code so far
class PortfolioList extends Component{
render(){
const {column , styevariation } = this.props;
const list = PortfolioListContent.slice(0 , this.props.item);
return(
<React.Fragment>
{list.map((value , index) => (
<div className={`${column}`} key={index}>
<div className={`portfolio ${styevariation}`}>
<div className="thumbnail-inner" >
<div className={`thumbnail ${value.image}`}></div>
<div className={`bg-blr-image ${value.image}`}></div>
</div>
<div className="content" >
<div className="inner">
<p>{value.category}</p>
<h4>{value.title}</h4>
<div className="btn-container">
<div className="portfolio-button">
<a className="rn-btn" href="/portfolio-details"><FaGithub /> Git </a>
</div>
<div className="portfolio-button">
<a className="rn-btn" href="/portfolio-details"><FaExternalLinkAlt /> Live </a>
</div>
</div>
</div>
</div>
</div>
</div>
))}
</React.Fragment>
)
}
}
I want to conditionally render the div "content" and the child elements of content div when mouse is hovering over "thumbnail-inner" div. But hide content when mouse is not hovering over thumbnail-inner div.
How can i achieve this?
I didn't test this, but the idea is to add a variable in the state of the component which holds the current hovered item.
When a mouseEnter event enters to your thumbnail-inner, you update that variable with the current component index. And you set it to -1 when a mouseLeave events happens in your thumbnail-inner.
Then you simply render the content conditionally by checking if the this.state.selectedIndex === index.
class PortfolioList extends Component {
state = {
selectedItem: -1,
}
render(){
const {column , styevariation } = this.props;
const list = PortfolioListContent.slice(0 , this.props.item);
return(
<React.Fragment>
{list.map((value , index) => (
<div className={`${column}`} key={index}>
<div className={`portfolio ${styevariation}`}>
<div
className="thumbnail-inner"
onMouseEnter={ () => { this.setState({selectedItem: index}) } }
onMouseLeave={ () => { this.setState({selectedItem: -1}) } }>
<div className={`thumbnail ${value.image}`}></div>
<div className={`bg-blr-image ${value.image}`}></div>
</div>
{
this.state.selectedItem === index &&
<div className="content" >
<div className="inner">
<p>{value.category}</p>
<h4>{value.title}</h4>
<div className="btn-container">
<div className="portfolio-button">
<a className="rn-btn" href="/portfolio-details"><FaGithub /> Git </a>
</div>
<div className="portfolio-button">
<a className="rn-btn" href="/portfolio-details"><FaExternalLinkAlt /> Live </a>
</div>
</div>
</div>
</div>
}
</div>
</div>
))}
</React.Fragment>
)
}
First, you need to add state for condition of hover (ex: "onHover"), the state is for conditional rendering the <div className="content">.
Second you need create function for hover and leaveHover(onMouseEnter & onMouseLeave) we call it handleMouseEnter for onMouseEnter, and we call it handleMouseLeave for onMouseLeave.
class PortfolioList extends Component {
state = {
onHover: false,
}
handleMouseEnter() {
this.setState({onHover: true})
}
handleMouseLeave() {
this.setState({onHover: false})
}
render(){
const {column , styevariation } = this.props;
const list = PortfolioListContent.slice(0 , this.props.item);
return(
<React.Fragment>
{list.map((value , index) => (
<div className={`${column}`} key={index}>
<div className={`portfolio ${styevariation}`}>
<div
className="thumbnail-inner"
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}>
<div className={`thumbnail ${value.image}`}></div>
<div className={`bg-blr-image ${value.image}`}></div>
</div>
{
this.state.onHover &&
<div className="content" >
<div className="inner">
<p>{value.category}</p>
<h4>{value.title}</h4>
<div className="btn-container">
<div className="portfolio-button">
<a className="rn-btn" href="/portfolio-details"><FaGithub /> Git </a>
</div>
<div className="portfolio-button">
<a className="rn-btn" href="/portfolio-details"><FaExternalLinkAlt /> Live </a>
</div>
</div>
</div>
</div>
}
</div>
</div>
))}
</React.Fragment>
)
}

How to change the state of an item affected by onClick in array?

I'm making a page where I need to make multiple selections of buttons (like a filter, which I'll use for the next page).
the information from these buttons is coming from an array and I'm using .map () to mount the button list.
My problem is how do I change the state of only the button that was clicked. The way it is now, when I click, all the buttons are active.
How can I solve this?
Thank you.
import React from 'react';
import { Link } from 'react-router-dom';
import { FormattedMessage } from 'react-intl';
import messages from './messages';
import { getLevel, getDiscipline } from '../../functions';
import template from './index.pug';
export default class ConfigAssessment extends React.PureComponent { // eslint-disable-line react/prefer-stateless-function
constructor(props){
super(props);
this.state = {
level: getLevel(),
discipline: getDiscipline(),
active: '',
first_click: true,
}
}
changeActive = () => {
if (this.state.first_click === true) {
this.setState({
active: 'active',
first_click: false
});
} else {
this.setState({
active: '',
first_click: true,
});
}
}
render() {
return(
<div className="configuration">
<div className="config-title">
<i className="ti-settings" />
<h2>
<FormattedMessage {...messages.configAssessment} />
</h2>
</div>
<div className="config-items">
<div className="form-group">
<label>
<FormattedMessage {...messages.level} />
</label>
<div className="row">
{this.state.level.map((level, i) => (
<div className="col-xs-1 col-md-4 col-lg-3" key={level.id}>
<button
className={`btn btn-light-gray btn-block ${this.state.active}`}
id={level.id}
onClick={this.changeActive}
>
{level.level}
</button>
</div>
))}
</div>
</div>
<div className="form-group">
<label>
<FormattedMessage {...messages.discipline} />
</label>
<div className="row">
{ this.state.discipline.map((discipline, i) => (
<div className="col-xs-1 col-md-4 col-lg-3" key={i}>
<button
className={`btn btn-light-gray btn-block ${this.state.active}`}
onClick={this.changeActive}
>
{discipline.discipline}
</button>
</div>
))}
</div>
</div>
<div className="form-group">
<label>
<FormattedMessage {...messages.selectQuestion} />
</label>
<div className="row">
<div className="col-xs-1 col-md-4 col-lg-3">
<button
className={`btn btn-light-gray btn-block ${this.state.active}`}
onClick={this.changeActive}
>
<FormattedMessage {...messages.typeAutomatic} />
</button>
</div>
<div className="col-xs-1 col-md-4 col-lg-3">
<button
className={`btn btn-light-gray btn-block ${this.state.active}`}
onClick={this.changeActive}
>
<FormattedMessage {...messages.typeManual} />
</button>
</div>
</div>
</div>
<div className="form-group fg-right">
<Link className="btn btn-warning" to="#">
<FormattedMessage {...messages.createAssessment} />
</Link>
</div>
</div>
</div>
);
}
}
Create a separate component for button
class MyButton extends Component {
constructor(props){
super(props);
this.state = {
person: this.props.person
}
}
buttonActiveHandler = () => {
let oldStatus = this.props.person.status;
this.props.person.status = (!oldStatus ? 'active': '');
this.setState({
person:this.props.person
});
}
render() {
return (
<button className={this.state.person.status} onClick={this.buttonActiveHandler}>{this.state.person.name}</button>
);
}
}
export default MyButton;
Then import button component. use map function to for your code block
<div className={classes.Box}>
<h4>Lorem, ipsum.</h4>
{
this.props.survey.map((person, i) => {
return (
<MyButton key={i} person={person}/>
)
})
}
</div>
The easiest solution to this problem is making the component of the content inside the map and then handling the state of that component there. Hence it will maintain individual states.
It depends what you need.
You can create separate component for button with state.
You can also keep state of each button in react state as an array, and then you can get the state of each button by index.
I'd recommend the first solution, it'd easier to manage such state, but it will be harder to get the state of a specific button from the parent component.

JSX not updating to binded State after await

I have a login form, which loads a loader when logging in. When login is unsucesful the loader should disappear and the buttons should reappear.
The loader is binded to this.state.loading.
if !this.state.loading it should show buttons (works on first load)
when this.state.loading = true then it should show loader (this works too)
I then run a promise using await async.
then I run this.state.loading = false and it does not update.
login.js:
import React, { Component } from "react";
import { connect } from "react-redux";
import * as actions from "../../actions";
import RoundLoader from "../elements/RoundLoader";
import Logo from "../img/image-center.png";
import "./login.css";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
errorMsg: "",
loading: false
};
this.loginClicked = this.loginClicked.bind(this);
console.log("loggedIn:", this.props.login);
}
async loginClicked() {
try {
var self = this;
self.state.loading = true;
console.log("this1", this);
await this.props.loginUser(
this.refs.email.value,
this.refs.password.value
);
self.state.loading = false; //This has no effect on JSX
if (this.props.login.success) {
this.props.history.push("/");
}
} catch (ex) {
this.state.loading = false;
this.state.errorMsg = "Unable to connect to server";
console.log("error", ex);
}
}
render() {
return (
<div className="login-bg">
<div className="container signForm">
<div className="row back">
<i className="material-icons">arrow_back</i>
Back to Site
</div>
<div className="row login-container z-depth-4">
<div className="col s12 l6 image-bg">
<img className="hbi-logo" src={Logo} alt={"HBI Logo"} />
<h1 className="center-align">
Insurance <br /> Building Company
</h1>
</div>
<div className="col s12 l6 login-form">
<p className="center-align">Login to Trade Portal</p>
<form>
<div className="row">
<div className="input-field">
<input
id="email"
type="email"
className="validate"
ref="email"
/>
<label htmlFor="email">Email</label>
</div>
</div>
<div className="row">
<div className="input-field">
<input
id="password"
type="password"
className="validate"
ref="password"
/>
<label htmlFor="password">Password</label>
</div>
</div>
{!this.state.loading ? ( {/* HERE IS THE IF STATEMENT */}
/* If Not Loading then show Login buttons */
<div>
<div className="row">
<button
className="btn waves-effect waves-light"
type="button"
name="action"
onClick={this.loginClicked}
>
Submit
</button>
<a href="/subcontractor" className="register">
Register here
</a>
</div>
</div>
) : (
/* If Loading then show Loader not Login buttons */
<div className="row">
<div className="col s12 center">
<RoundLoader />
</div>
</div>
)}
<div className="row">
<div style={{ textAlign: "center", color: "red" }}>
{this.props.login.message}
</div>
</div>
</form>
</div>
</div>
</div>
</div>
);
}
}
function mapStateToProps({ login }) {
return { login };
}
export default connect(mapStateToProps, actions)(Login);
try
this.setState({loading: false})
instead of
this.state.loading = false;
https://reactjs.org/docs/react-component.html#setstate
From https://reactjs.org/docs/react-component.html#state :
Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.

display Popup only on selected element

I have rendered the repeated elements in the component. When i click a particular component, a popup should be displayed on the particular component only. Now i got the popup message on all the elements. How to clear this issue?
class Display extends component{
constructor(props){
super(props);
this.state={showResult:false};
}
renderList(){
return this.props.cars.attribute.map((car) => {
return (
<div key={car.attribute_name} className="col-sm-4" >
<div className="panel-body back-img" onClick={()=> this.setState({showResult:true})}>
<div className="row">
<div className="col-sm-6 incrementer">
<span className="titleclass"> Hala </span>
</div>
</div>
</div>
</div> { this.state.showResults ? <Results /> : null }
)}}}
class Results extends Component{
render(){
return(<div id="results" className="search-results">
Some Results
</div>); }}
Identify the clicked node by a unique property, such as attribute_name:
return this.props.cars.attribute.map((car) => {
return (
<div key={car.attribute_name} className="col-sm-4" >
<div className="panel-body back-img" onClick={()=> this.setState({showResult:true, attributeName: car.attribute_name})}> // store the current car.attribute_name in the state
<div className="row">
<div className="col-sm-6 incrementer">
<span className="titleclass"> Hala </span>
</div>
</div>
</div>
</div> { this.state.showResults && this.state.attributeName === car.attribute_name ? <Results /> : null } // only show results if the current attribute name in the state is the same as the item's
)}}}

Resources