Reactjs - onClick works after first click - javascript

I am new to react so I apologize in advance if there is an obvious solution to this. But I am attempting to create a reusable search component, in this search component I have an input field that I would like to change the border when clicked upon.
Currently, this css onClick function is only working after the second click. I am not getting any errors in my console so I am currently lost on what the solution is. I've attempted to do some research and I've had no luck.
Here is my code :
const searchBorderChange= () => {
var textareas = document.getElementsByTagName('input');
for (let i=0; i<textareas.length; i++){
// you can omit the 'if' if you want to style the parent node regardless of its
// element type
if (textareas[i].parentNode.tagName.toString().toLowerCase() == 'div') {
textareas[i].onfocus = function(){
this.parentNode.style.borderColor = '#FF0000';
}
textareas[i].onblur = function(){
this.parentNode.style.borderColor = '#B9C0D0';
}
}
}
}
const SearchBar= () => (
<div className="search-nav">
<h1 className="padding-bottom">Search
<br /> Here <span className="text-bold">Lookup Keywords</span></h1>
<div>
<div className="content">
<div className="search-bar">
<i className="fas search fa-search"></i>
<input type="text" name="search" placeholder="Search by Keyword" onClick={searchBorderChange} />
</div>
</div>
</div>
</div>
);
export default SearchBar;
I am expecting the border of my search div to change on the first onClick function.

It's generally a good idea not to alter the document manually in React if you can avoid it. You could keep the input border color in a state variable and change it when it is focused or blurred.
Example
const { useState } = React;
const defaultColor = "#B9C0D0";
const SearchBar = () => {
const [inputColor, setInputColor] = useState(defaultColor);
return (
<div className="search-nav">
<h1 className="padding-bottom">
Search
<br /> Here <span className="text-bold">Lookup Keywords</span>
</h1>
<div>
<div className="content">
<div className="search-bar">
<i className="fas search fa-search"></i>
<input
type="text"
name="search"
placeholder="Search by Keyword"
style={{ border: `1px solid ${inputColor}` }}
onFocus={() => setInputColor("#FF0000")}
onBlur={() => setInputColor(defaultColor)}
/>
</div>
</div>
</div>
</div>
);
};
ReactDOM.render(<SearchBar/>, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="root"></div>

Related

How to pass data between components in react?

I have been trying to display the form data submitted but the map is throwing an error.
I have two components
NameForm.js
Here is the form input, handlechange and handlesubmit methods are done
function Nameform() {
const [form, setForm] = useState({firstname: "", lastname: ""});
const handleChange = (e) => {
setForm({
...form,
[e.target.id]: (e.target.value),
});
};
const handleSubmit = (e) => {
e.preventDefault();
console.log("hello from handle submit", form );
}
return (
<section>
<div className='card pa-30'>
<form onSubmit={ handleSubmit }>
<div className='layout-column mb-15'>
<label htmlFor='name' className='mb-3'>First Name</label>
<input
type='text'
id='firstname'
placeholder='Enter Your First Name'
data-testid='nameInput'
value={form.firstname}
onChange={handleChange}
/>
</div>
<div className='layout-column mb-15'>
<label htmlFor='name' className='mb-3'>First Name</label>
<input
type='text'
id='firstname'
placeholder='Enter Your First Name'
data-testid='nameInput'
value={form.firstname}
onChange={handleChange}
/>
</div>
<div className='layout-row justify-content-end'>
<button
type='submit'
className='mx-0'
data-testid='addButton'
>
Add Name
</button>
</div>
</form>
</div>
</section>
)
}
export default Nameform
NameList.js
I want to pass the data in handleSubmit in NameForm.js to NameList.js. But the data is not displayed.
function NameList({form}) {
return (
<section>
{form.map(displayName => {
return (
<ul
className='styled w-100 pl-0'
>
<li
className='flex slide-up-fade-in justify-content-between'
>
<div className='layout-column w-40'>
<h3 className='my-3'>{displayName.firstname}</h3>
<p className='my-0'{displayName.lastname}></p>
</div>
</li>
</ul>
)
})}
</section>
)
}
export default NameList;
App.js
In App.js, I want to display both the form and the data.
import { Nameform, Namelist } from './components'
function App() {
return (
<div>
<div className='layout-row justify-content-center mt-100'>
<div className='w-30 mr-75'>
<Nameform />
</div>
<div className='layout-column w-30'>
<NameList />
</div>
</div>
</div>
)
}
export default App;
Thank you for your help!
Pass the data you want to share between parent and children via props (which stands for properties).
In the parent class, when rendering <NameForm> and <ListForm> add the data like that:
//if you want to share count and name for example:
<NameForm
count={this.state.count}
name={this.state.name}
/>
You can add as many props as you want. Furthermore, you can pass a function and its argument using arrow functions:
<NameForm
aFunction={() => this.myFunction( /* anArgument */ )}
/>
To access props in a child class dynamically wherever you need them:
{this.props.count}
{this.props.name}
{this.props.aFucntion}
You can get rid of this.props using a technique called object destructing:
render(
const {count, name, aFunction} = this.props;
//now you can use {count} and {name} and {aFunction} without this.props
);
There are some bugs in your code, first form is an object not an array, so you can't map it, you need to use form.firstname and form.lastname, Also you set both input ids equal firstname you need to modify it, Also you need to move the form state and handleChange function to the App component.
This is a working code of your example.
https://codesandbox.io/s/upbeat-forest-328bon
You can save the state in the parent component and pass it as props to the child components like so.
Here we make use of an outer state called submittedForm to display only the submitted values. The inner form state is being used for handling the values before submitting.
// App.js
function App() {
const [submittedForm, setSubmittedForm] = useState({
firstname: "",
lastname: "",
});
return (
<div>
<div className="layout-row justify-content-center mt-100">
<div className="w-30 mr-75">
<NameForm setSubmittedForm={setSubmittedForm} />
</div>
<div className="layout-column w-30">
<NameList form={submittedForm} />
</div>
</div>
</div>
);
}
export default App;
// NameForm.js
function NameForm({ setSubmittedForm }) {
const [form, setForm] = useState({
firstname: "",
lastname: "",
});
const handleChange = (e) => {
// setActive(true);
setForm({
...form,
[e.target.id]: e.target.value,
});
};
const handleSubmit = (e) => {
e.preventDefault();
setSubmittedForm(form);
};
return (
<section>
<div className="card pa-30">
<form onSubmit={handleSubmit}>
<div className="layout-column mb-15">
<label htmlFor="name" className="mb-3">
First Name
</label>
<input
type="text"
id="firstname"
placeholder="Enter Your First Name"
data-testid="nameInput"
value={form.firstname}
onChange={handleChange}
/>
</div>
<div className="layout-column mb-15">
<label htmlFor="name" className="mb-3">
Last Name
</label>
<input
type="text"
id="lastname"
placeholder="Enter Your Last Name"
data-testid="nameInput"
value={form.lastname}
onChange={handleChange}
/>
</div>
<div className="layout-row justify-content-end">
<button type="submit" className="mx-0" data-testid="addButton">
Add Name
</button>
</div>
</form>
</div>
</section>
);
}
export default NameForm;
// NameList.js
function NameList({ form }) {
return (
<section>
<ul className="styled w-100 pl-0">
<li className="flex slide-up-fade-in justify-content-between">
<div className="layout-column w-40">
<h3 className="my-3">{form.firstname}</h3>
<p className="my-0">{form.lastname}</p>
</div>
</li>
</ul>
</section>
);
}
export default NameList;

How do i pass data values to modals using react hooks?

I am working on using modals to accept form inputs on a react project
here is the plan component
plan/index.js
import React, { useState } from "react";
import Pay from '../Pay';
const Plan = () => {
const [payModal, setPayModal] = useState(false);
const [planMode, setPlanMode] = useState(true);
return (
<main class="main">
{payModal && <Pay setOpenPayModal={setPayModal} />}
<div class="plan">
<div>
<a class="plans" onClick={() => {setPayModal(true);}}>plan A</a>
<div class="plan">
<span class="plan">{!planMode ? "$19.99" : "$9.99"}</span>
<span class="plan">{!planMode ? "month" : "year"}</span>
</div>
</div>
<div>
<a class="plans" onClick={() => {setPayModal(true);}}>plan B</a>
<div class="plan">
<span class="plan">{!planMode ? "$29.99" : "$19.99"}</span>
<span class="plan">{!planMode ? "month" : "year"}</span>
</div>
</div>
</div>
</main>
);
};
export default Plan;
as you can see on the line {payModal && <Pay setOpenPayModal={setPayModal} />} where i call the pay modal component from the plan component and it opens up the modal
here is the pay component which is the modal component
pay/index.js
import React, { useState } from "react";
function Pay({ setOpenPayModal }) {
const [billingDetails, setBillingDetails] = useState({
price: "",
: "",
});
return (
<div class="modal">
<div class="modal">
<div class="close">
<button
onClick={() => {
setOpenPayModal(false);
}}
>
</button>
</div>
<div class="modal">
<form class="form" onSubmit={handleSubmit}>
<fieldset class="form">
<Field
label="Price"
id="price"
type="text"
value={billingDetails.price}
onChange={(e) => {
setBillingDetails({ ...billingDetails, price: e.target.value });
}}
/>
<Field
label="Frequency"
id="frequency"
type="text"
value={billingDetails.frequency}
onChange={(e) => {
setBillingDetails({ ...billingDetails, frequency: e.target.value });
}}
/>
</fieldset>
<button class="pay" onClick={handleSubmitPay}>
Pay
</button>
</form>
</div>
</div>
</div>
);
}
export default Pay;
The issue is I want to be able to pass the values price and frequency from the plan component to the pay modal component
for example for plan A is price="$19.99" and frequency="year", so based on the button clicked on the plan component page, those values get passed to the pay modal component in a dynamic way
how do I achieve this using react hooks?
You can use contexts to pass, but in this case I don't think it's the best option. What I really recommend is passing the state variable through the props.
{payModal && <Pay setOpenPayModal={setPayModal} price={price} frequency={frequency} />}
I usually use the Context (useContext) when I need values and various components, for example:
I need to save the user id that is logged in to various components, and instead of getting it from the server every time I need it, I keep it saved in my context that I created, so I can make the call only once.
Documentation-> https://pt-br.reactjs.org/docs/context.html

using button to increment divs in react

I am fairly new to React/Next and I had a quick question.
I am trying to create a button that will increment the number of divs in real time.
Here is my code:
import React from 'react'
const Clown = () => {
const [clownCounter, setClownCounter] = React.useState(1);
function addClown(event) {
event.preventDefault();
}
return(
<React.Fragment>
<div>
<form>
{Array.from({ length: clownCounter}, (_unused, index) => index + 1).map(
(clownIndex) => {
const clownid = `${clownIndex}`
return (
<div key={clownid } className="clown-box">
<label htmlFor={clownid }>Activity {clownIndex}</label>
<br />
<input type="text" onChange={(e)=> onChangeForm(e)} name={activityId} id={activityId} />
<br />
</div>
)
},
)}
<span className="clown-add">
<button onClick={addClown} onChange={() => { setClownCounter(clownCounter++) }}>Add Clown</button>
</span>
<br />
</form>
</div>
</React.Fragment>
)
}
export default Clown
As you can see the goal is to increase the amount of clown-box divs everytime the button is clicked. I think I am close but it is not currently working. Can anyone help?
There are few small this wrong with your code.
First, you have an extra comma(,) after the return statement in map function
Second, you are updating state clownCounter on onChange event in button, which is incorrect. You should update it on click and also prevent the default behaviour of form submit on click of button or you can define the button type to be type="button"
Lastly, you need to define your onChangeForm function
const Clown = () => {
const [clownCounter, setClownCounter] = React.useState(1);
function onChangeForm() {
}
function addClown(event) {
event.preventDefault();
setClownCounter(prev=> prev+1);
}
console.log(clownCounter);
return(
<div>
<form>
{Array.from({ length: clownCounter}, (_unused, index) => index + 1).map(
(clownIndex) => {
const clownid = `${clownIndex}`;
return (
<div key={clownid } className="clown-box">
<label htmlFor={clownid }>Activity {clownIndex}</label>
<br />
<input type="text" onChange={(e)=> onChangeForm(e)} name={'activityId'} id={'activityId'} />
<br />
</div>
)
})
}
<span className="clown-add">
<button type="button" onClick={addClown}>Add Clown</button>
</span>
<br />
</form>
</div>
)
}
ReactDOM.render(<Clown />, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="app" />
Edit: Thought issue was caused by Array.from, but, it's not. I've removed that part, but kept the example since OP might find it useful
const { useState } = React;
const Clowns = ({ title }) => {
const [clownCounter, setClownCounter] = React.useState(1);
return (
<div>
<button onClick={() => setClownCounter(clownCounter + 1)}>
Add clown
</button>
<div className='clowns'>
{Array.from({ length: clownCounter}, (_unused, index) => index + 1).map((e, i) => (
<div>
<h4>{`Clown #${i + 1}`}</h4>
<img src={`https://placehold.it/150x150&text=Clown%20%23${i + 1}`} />
</div>
))}
</div>
</div>
);
};
ReactDOM.render(<Clowns />, document.getElementById("react") );
.clowns { display: flex; flex-direction: column; }
h4 { margin-bottom: 5px; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.1/umd/react-dom.production.min.js"></script>
<div id="react"></div>

How to properly search in a list in ReactJS

I am trying to set a simple search operation in a user interface as shown below:
I have a total of 70 react-strap cards and each card contain a vessel with name, type and an image. I would like to search the name of the vessel and have the card related to that vessel to pop-up. All my images are currently contained inside the external database Contentful. Below the fields of interests:
The problem is that I don't know how to write a search function that locate a specific value of a list.
Below the code:
SideBar.js
import React from 'react';
import Client from '../Contentful';
import SearchVessel from '../components/SearchVessel';
class Sidebar extends React.Component {
state = {
ships: [],
};
async componentDidMount() {
let response = await Client.getEntries({
content_type: 'cards'
});
const ships = response.items.map((item) => {
const {
name,
slug,
type
} = item.fields;
return {
name,
slug,
type
};
});
this.setState({
ships
});
}
getFilteredShips = () => {
if (!this.props.activeShip) {
return this.state.ships;
}
let targetShip = this.state.ships.filter(
(ship) => this.props.activeShip.name === ship.name
);
let otherShipsArray = this.state.ships.filter((ship) => this.props.activeShip.name !== ship.name);
return targetShip.concat(otherShipsArray);
};
render() {
return (
<div className="map-sidebar">
{this.props.activeShipTypes}
<SearchVessel />
<pre>
{this.getFilteredShips().map((ship) => {
console.log(ship);
return (
<Card className="mb-2">
<CardImg />
<CardBody>
<div className="row">
<img
className="image-sizing-primary"
src={ship.companylogo.fields.file.url}
alt="shipImage"
/>
</div>
<div>
<img
className="image-sizing-secondary"
src={ship.images.fields.file.url}
alt="shipImage"
/>
</div>
<CardTitle>
<h3 className="thick">{ship.name}</h3>
</CardTitle>
<CardSubtitle>{ship.type}</CardSubtitle>
<CardText>
<br />
<h6>Project Details</h6>
<p>For a description of the project view the specification included</p>
</CardText>
<Row style={{ marginTop: '20px' }}>
<div className="buttoncontainer">
<div className="btn btn-cards">
<a
className="buttonLink"
download
href={ship.projectnotes.fields.file.url}
>
Project Notes
</a>
</div>
<div className="btn btn-cards">
<a className="buttonLink" href={ship.abstract.fields.file.url}>
Abstract
</a>
</div>
</div>
</Row>
</CardBody>
</Card>
);
})}
</pre>
</div>
);
}
}
export default Sidebar;
VesselSearch.js
import React, { Component } from 'react';
export default class SearchVessel extends Component {
render() {
const { value, handleSubmit, handleChange } = this.props;
return (
<React.Fragment>
<div className="container">
<div className="row">
<div className="col-10 mx-auto col-md-8 mt-5 text-center">
<h4 className="text-slanted text-capitalize">Search for Vessel</h4>
<form className="mt-4" onSubmit={handleSubmit}>
<label htmlFor="search" className="text-capitalize">
type vessel separated by comma
</label>
<div className="input-group">
<input
type="text"
name="search"
placeholder="Type name of vessel here"
className="form-control"
value={value}
onChange={handleChange}
/>
<div className="input-group-append">
<button type="submit" className="input-group-text bg-primary text-white">
<i className="fas fa-search" />
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</React.Fragment>
);
}
}
What I have done so far:
1) I tried different combination with the filter function and I think I am close. The problem is that when I operate the search nothing happens and in order to find the card of the vessel I want, I have to scroll down until I find it.
I am running out of ideas and if you see something I didn't catch point me in the right direction for solving this issue.
You're close! I would add a field to your state called 'searchText' and then create a method to filter based on that searchText state item.
getFilteredShips = () => this.state.ships.filter(s => s.name.includes(this.state.searchText)
Then just map over those values to render the cards that match the search text. The cards will update each time the searchText value updates.
this.getFilteredShips().map(ship => ..........
React is famous for re-usable component. You will have all the data of these vessels in an array. You will loop through the array and render the items with card component.And when you search for the specific card you want that vessel to pop out on top.
There are two ways to do it:
You have to run through the array, find the index of that vessel and do whatever it takes to manipulate your array and to make that item at top and re-render your list.
Alternatively render one more component on top of your vessel list as user clicks the search button. You just have to find the item index and render it. This way you don't have to deal with array manipulation. It doesn't matter if you have 80 or 1000 cards.
Please checkout official documentation for array methods, for array slicing and splice.
Hope this is what you are looking for. If you need further help, comment please.

Dynamically create dropdown menu options from array with react-bootstrap

I'm building a drupal based e-commerce site right now and got stuck. Still new to react and coding in general, but trying to learn. So I've got all my data pulled in, using redux and rest api, and I'm getting my products, variations and attributes. The product page is setting a specific product based on url, and now I need to be able to select the different attributes via a dropdown menu. Currently I have a place holder dropdown that matches the one shown in react-bootstrap documentation. However, I need to be placing options in that dropdown based off of my array holding the attributes.
I'm sure it's simple but I've been searching around and haven't found an answer yet that works. Hopefully you guys can help.
As you look through the code, keep in mind that sizes = [] is the array I'm looking to place data from as the selectable options in the dropdown.
Here's the product page:
import React, { Component} from 'react';
import '../../css/Home.css';
import MenuBar from "../sub-components/MenuBar";
import LeftMenuBar from "../sub-components/LeftMenuBar";
import "../../css/ProductPage.css"
import WomensWear from "../../media/WomensWear.jpg"
import {
Dropdown,
DropdownToggle,
DropdownMenu,
DropdownItem } from 'reactstrap';
class ProductPage extends Component {
constructor(props) {
super(props);
this.toggle = this.toggle.bind(this);
this.state = {
dropdownOpen: false
};
}
toggle() {
this.setState(prevState => ({
dropdownOpen: !prevState.dropdownOpen
}));
}
getProduct() {
let product = null;
let sizes = [];
if (this.props.products && this.props.products.items.length) {
product = this.props.products.items.find(o => o.path[0].alias === this.props.router.match.url);
if (product && this.props.variations && this.props.attributes) {
product.something = [];
for (let i = 0; i < product.variations.length; i++) {
let varid = product.variations[i].target_id;
let variation = this.props.variations.items.find(o => o.variation_id[0].value === varid);
variation.size = this.props.attributes.items.find(o => o.attribute_value_id[0].value === variation.attribute_size[0].target_id);
sizes.push({value: variation.size.attribute_value_id[0].value, name: variation.size.name[0].value});
product.something.push(variation);
console.log(sizes);
}
}
}
return product;
}
render() {
let style = {
height: this.props.height - 56,
};
let product = this.getProduct();
let body = product && product.body.length ? product.body[0].value : null;
return (
<div className="App" id="default">
<div className='MenuBar'>
<MenuBar/>
</div>
<div>
<div style={style} className="ProductPage row no-gutters">
<div className="col-xs-3 col-md-3">
<LeftMenuBar/>
</div>
<div className="outer col-xs-4 col-md-4">
<div>
<div id="ProductPlacement">
<img src={WomensWear} alt=""/>
<div id="alternate-pics">
<div id="alt-pic">
</div>
<div id="alt-pic">
</div>
<div id="alt-pic">
</div>
</div>
</div>
</div>
</div>
<div className="col-xs-5 col-md-5">
<div id="ImagePlacement">
<div className="ProductTitle">
<h1>First Product</h1>
</div>
<hr/>
<div className="ProductDescription">
<div dangerouslySetInnerHTML={{__html: body}} />
</div>
<div id="options">
<div id="color">
</div>
<div id="color2">
</div>
<div id="color3">
</div>
</div>
<div id="options">
<div>
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.toggle}>
<DropdownToggle caret id="size-dropdown">
Size
</DropdownToggle>
<DropdownMenu>
<DropdownItem>1</DropdownItem>
<DropdownItem>3</DropdownItem>
<DropdownItem>5</DropdownItem>
</DropdownMenu>
</Dropdown>
<div className="AddToCart">
<button className="AddToCart">Add To Cart</button>
<button className="Price">$34.99</button>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
);
}
}
export default ProductPage;
Neat thing about React is that you can use regular JS.
<Dropdown isOpen={this.state.dropdownOpen} toggle={this.toggle}>
<DropdownToggle caret id="size-dropdown">
Size
</DropdownToggle>
<DropdownMenu>
{sizes.map(size => (
<DropdownItem>{size}</DropdownItem>
))}
</DropdownMenu>
</Dropdown>
Sidenote: select seems to be more suitable element for this but that wasn't your question.

Categories

Resources