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.
Related
This problem has consumed a lot of time trying to figure out what is wrong. I have a React-Hook-Form (v6.14.1) that needs to populate dynamic data, based on the component state.
On the initial load, everything works fine. If I change the state all updated data are displaying fine, except the dynamic data.
Here is a codesandbox link. If it does not render due to a library error, just hit the preview refresh button.
The goal is that the WAN 1 tab, on initial load displays the dynamic fields (WAN 1 VLAN-1) and WAN2 does not have dynamic fields to display. Hitting the Update Config button, WAN1 should not have dynamic fields to display and WAN2 should display one (WAN 2 VLAN-1). The problem is that WAN2 does not display it.
I have searched for similar questions, but all of them were about the values of the populated fields and not about displaying the fields themselves. I have used the reset method of react-hook-form and the defaltValue for each dynamic field as react-hook-form documentation suggests.
On App.js I have the state, a button that updates the state, and the Form component which has the state as property.
const [configdata, setConfigdata] = useState(config);
return (
<div className="App">
<UpdateConfig onClick={() => setConfigdata(configUpdated)} />
<Form
formData={configdata}
handleFormData={(data) => console.log(data)}
/>
</div>
);
}
On Form.js there is a Rect-hook-form FormProvider and the WanFields component that dynamically populates form fields.
<FormProvider {...methods}>
<form
onSubmit={methods.handleSubmit((data) =>
props.handleFormData(data)
)}
>
<Tab.Content>
{props.formData?.intfs?.length &&
props.formData?.intfs.map((intf, index) => (
<Tab.Pane key={index} eventKey={`wan${index}-tab`}>
<WanFields
key={`wan${index}-fields`}
intfNo={index}
portTypeOptions={props.portTypeOptions}
data={intf}
/>
</Tab.Pane>
))}
</Tab.Content>
</form>
</FormProvider>
Every time the props.formData update, there is a useEffect that reset the forms' default data.
const methods = useForm({ defaultValues: props.formData });
useEffect(() => {
methods.reset(props.formData);
}, [props.formData]);
In WanFields.js, there are all the form fields, and the useFieldArray method, that will populate the dynamic fields based on the forms' default values and a watch field value (watchIntfType ).
const methods = useFormContext();
const { errors, control, watch, register } = methods;
const { fields, append, remove } = useFieldArray({
control,
keyName: "fieldid",
name: `intfs[${intfNo}].subIntfs`
});
const watchIntfStatus = watch(`intfs[${intfNo}].enabledStatus`);
const watchIntfType = watch(`intfs[${intfNo}].enabled`);
Dynamic fields are populated as follows
{watchIntfType?.value >= "2" && (
<>
<div className="form-group">
<div className="btn btn-success" onClick={append}>
Add
</div>
</div>
<div id={`accordion-${intfNo}`}>
<Accordion>
{console.log("FIELDS", fields)}
// This is where the problem starts. fields are empty after updating data
{fields.map((field, index) => {
return (
<Card key={field.fieldid}>
<Accordion.Toggle
as={Card.Header}
variant="link"
eventKey={`${index}`}
style={{ cursor: "pointer" }}
>
<h4>
WAN {parseInt(intfNo) + 1}{" "}
<span style={{ margin: "0px 5px" }}>
<i className="fas fa-angle-right"></i>
</span>{" "}
VLAN-{index + 1}
</h4>
<div className="card-header-action">
<button
type="button"
className="btn btn-danger"
onClick={() => remove(index)}
>
Remove
</button>
</div>
</Accordion.Toggle>
<Accordion.Collapse eventKey={`${index}`}>
<Card.Body>
<div className="form-row">
<div className="form-group col-12 col-md-6">
<label>IP</label>
<input
type="text"
className="form-control"
name={`intfs[${intfNo}].subIntfs[${index}].ipAddress`}
defaultValue={field?.ipAddress}
ref={register()}
/>
</div>
<div className="form-group col-12 col-md-6">
<label>Subnet</label>
<input
type="number"
className="form-control"
min="0"
max="30"
name={`intfs[${intfNo}].subIntfs[${index}].subnet`}
defaultValue={field?.subnet}
ref={register()}
/>
</div>
</div>
</Card.Body>
</Accordion.Collapse>
</Card>
);
})}
</Accordion>
</div>
</>
)}
The problem is that when the state updates, form default values are updated, but the method useFieldArray attribute fields are not updated and stay as an empty array. I really cannot understand, what I am doing wrong. Any help will be much appreciated.
I don't know if is a correct method but i have resolv this probleme with method reset in a useEffect.
https://react-hook-form.com/api/useform/reset
defaultValues:
{
acvDesignOffice: generateRSEnv.acvDesignOffice,
earthQuakeZone: generateRSEnv.earthQuakeZone,
buildings: generateRSEnv.buildings,
},
useEffect(() => {
reset({
acvDesignOffice: generateRSEnv.acvDesignOffice,
earthQuakeZone: generateRSEnv.earthQuakeZone,
buildings: generateRSEnv.buildings,
});
}, [generateRSEnv]);
I have created a component which basically generates a card which includes card title,card description and a button. Now when I map through an array on a different component to generate those cards I want the button to be different on different components. Like on home page the button should say Update, on other page the button should say Delete. How can I acheive that? Here is the component which generates card.
import React from 'react';
import { Card } from 'react-bootstrap';
const InventoryItem = ({ product }) => {
const { productName, productImage, productPrice, productQuantity, productSupplier, productDetails } = product;
return (
<div className='col-12 col-md-6 col-lg-6'>
<Card className='h-100 items-card d-block d-md-block d-lg-flex flex-row align-items-center border border-0'>
<div className='text-center card-image-container'>
<Card.Img variant="top" src={productImage} className='card-image img-fluid' />
</div>
<Card.Body className='card-details'>
<Card.Title>{productName}</Card.Title>
<Card.Text>{productDetails}</Card.Text>
<div className='d-lg-flex align-items-center justify-content-between mb-3'>
<p className='mb-0'>Price: ${productPrice}</p>
<p className='me-2 mb-0'>Stock: {productQuantity}</p>
</div>
<div className='d-flex align-items-center justify-content-between'>
<p className='mb-0'>Supplier:{productSupplier}</p>
<button className='btn btn-dark'>Update</button>
</div>
</Card.Body>
</Card>
</div>
);
};
export default InventoryItem;
Pass the button text as a prop to the component, so consuming code can speficy the text for the button.
Add it as a prop:
const InventoryItem = ({ product, buttonText }) => {
And use it in the button:
<button className='btn btn-dark'>{buttonText}</button>
Then when using the component, pass the prop:
<InventoryItem product={someProductObject} buttonText="Update" />
or with a conditional value:
<InventoryItem product={someProductObject} buttonText={someCondition ? "Update" : "Delete"} />
You can define a condition for the button basaed on the page you are:
window.location.href return the string href.
const MyButton= window.location.href=='Home'
? <button> HOME</button>
: <button> UPDATE</button>
Hope that answer your question,
Mauro
so im trying to pass the value price from this array
const [products, setProducts] = useState(
[{
name: "14K bracelet",
id: "1",
description: "Beautfull 14K gold Bracelet",
price: 100.00,
img: braceletImg,
}]
)
into here
<h1 className="price">{products.price}</h1>{/*this is a prop*/}
I call the prop here in cart
function Cart({ products })
full code of the cart component
function Cart({ products }) {
return(
<div className="Body">
{/* {products.map(pr => <h1 className="price">{pr.price}</h1>)} */}
<div className="Cart-wrapper" >
<div className="cart-header">
<h5 className="Product-name cart-text">Product</h5>
<h5 className="quantity-name cart-text">Quantity</h5>
<h5 className="price-name cart-text">Price</h5>
<Button className="btn btn-plus">+</Button>
<Button className="btn btn-minus">-</Button>
<div className="card-cart">
<img className="braceletimg" src={braceletImg} />
<h1 className="card-body-title">Bracelet title</h1>
<h1 className="card-body-title seemore"><Link className="Link" to="/Bracelets">Learn More</Link></h1>
<hr className="cart-hr"></hr>
</div>
<div className="div-price">
{products.map(pr => <h1 key={pr.id} className="price">{pr.price}</h1>)}
<small className="shippingprice">$5.00 + shipping</small>
</div>
<Button className="btn btn-cart btn-primary"><Link className="Link" to="/Cart/shipping-buynow">Review Cart</Link></Button>
</div>
</div>
</div>
)
}
export default Cart;
hopefully, this gives you a better context of the component
Because products is an array, you either need to use indexing, or you can process them all using .map().
Using indexing:
<h1 className="price">{products[0].price}</h1>
Using .map():
{products.map(pr => <h1 key={pr.id} className="price">{pr.price}</h1>)}
The addition of key={...} is needed so React can optimize the rendering. If you don't add it then React will show warnings in the console output. If the items already have a unique id or key value, then it's best to use that.
Update:
Your Cart component may be getting activated already before products has been initialized, this would explain the undefined errors.
This is quite normal, and to prevent it from being a problem you can check if products is empty:
function Cart({ products }) {
if (!products)
return "Loading...";
return (
// ...
// ...
// ...
);
}
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
I'm new in React and got stuck on how can I achieve this.
After the user fills the field CEP from the first card and click on the button 'Gravar Dados', it shows a second card with all the information from the first card.
Template Picture:
New Result Picture:
What I need is dynamically create another card, under the second card with new info from the first card.
This is what I've done so far.
I have this component DadosPessoais.js which is the second card in the Template Picture:
render() {
return (
<div className="card shadow mt-5" >
<div className="card-header">
<span>Dados Pessoais </span>
</div>
<div className="card-body">
<div className="row">
<div className="col-4">
<div>
<span>Cep: <strong>{this.props.cep}</strong></span>
</div>
<div>
<span>Bairro: <strong>{this.props.bairro}</strong></span>
</div>
</div>
<div className="col-5">
<div>
<span>Rua: <strong>{this.props.rua}</strong></span>
</div>
<div>
<span>Cidade: <strong>{this.props.cidade}</strong></span>
</div>
</div>
<div className="col-3">
<div>
<span>UF: <strong>{this.props.uf}</strong></span>
</div>
</div>
</div>
</div>
</div>
);
}
In my Home.js I have a form with onSubmit that calls mostrarDadosPessoais function:
Function:
mostrarDadosPessoais(e) {
e.preventDefault();
this.setState({
listaDados: this.state.listaDados.concat({
cep: e.target.value,
rua: e.target.value,
bairro: e.target.value,
cidade: e.target.value,
uf: e.target.value,
}),
});
}
And my input components UserInput, Should it be like this?:
<div className="col-3">
<UserInput name="Rua" Label="Rua" Id="Rua" value={this.state.rua} Disabled />
</div>
<div className="col-3">
<UserInput name="Bairro" Label="Bairro" Id="Bairro" value={this.state.bairro} Disabled />
</div>
<div className="col-3">
<UserInput name="Cidade" Label="Cidade" Id="Cidade" value={this.state.cidade} Disabled />
</div>
<div className="col-1">
<UserInput name="UF" Label="UF" Id="UF" value={this.state.uf} Disabled />
</div>
And to show the result card I've done this:
<div className="col-md-4">
{this.state.listaDados.length === 0
? null
: this.state.listaDados.map((lista) => (<DadosPessoais {...lista} />))}
</div>
This is my state:
this.state = {
listaCidade: [],
listaDados: [],
}
Any ideas on how can I create another component and keep the values from the first one?
Thank you.
If I understand well, you need a list of elements, displayed on the right, that will show user-entered data. You need the user to be able to enter many addresses through the form on the left, and you want each address to display on the right.
I would solve this with a component that contains state for all the elements you need to display in an array that you update on a form submit event, because that's an easy event to work with when you have forms with lots of fields. You can grab the values of the components through the event object e.
class StateContainer extends Component {
constructor() {
super(props);
this.state = {
addresses: [],
}
}
mostrarDadosPessoais(e) {
e.preventDefault();
this.setState({
listaDados: this.state.listaDados.concat({
cep: e.target.cep.value,
rua: e.target.rua.value,
// etc, etc
}),
})
}
render() {
return (
<div>
<form handleSubmit={this.mostrarDadosPessoais}>
{/* left-side form */}
<input name="msg" />
<button type="submit"></button>
</form>
<div>
{/* right-side list of elements */}
{this.state.addresses.length === 0
? null
: this.state.addresses.map(
(address) => (<DadosPessoais {...address} />)
)
}
</div>
</div>
);
}
}