Use image as icon with react-select - reactjs

I would like to add an image as Icon with react-select. I did all right but I have a problem with the fact that, in react, images are written like this :
<img src={require(...)} />
So I use react-select like this :
const IconOption = (props) => (
<Option {... props}>
<div>
<img src={require(props.data.image)} />
</div>
</Option>
);
And then call for the select box :
render() {
return (
<Select
classNamePrefix="react-select"
placeholder={"Search..."}
onChange={this.handleChange}
components={{ DropdownIndicator: () => null, Option: IconOption }}
options={this.state.options}
openMenuOnClick={false}
styles={customStyle}
/>
);
}
I tried to write :
const IconOption = (props) => (
<Option {... props}>
<div>
{props.data.image}
</div>
</Option>
);
Which gives me :
./css/img/PROFILEPICTURE.jpg
It is exactly what I want to get, the path is correct. If I exactly write :
const IconOption = (props) => (
<Option {... props}>
<div>
<img src="./css/img/PROFILEPICTURE.jpg" />
</div>
</Option>
);
Image is correctly displayed.
If I write the first code, which is the one to get different picture for each item in selectbox, I got an error :
Any solution to not use require function for img in react?
Edit :
I also tried :
const IconOption = (props) =>
(
<Option {... props}>
<div>
<img src={props.data.image} />
{props.data.label}
</div>
</Option>
);
And i got not found images :

You just have to remove the require since your image is not part of the compiled react app:
const IconOption = (props) => (
<Option {... props}>
<div>
<img src={props.data.image} />
</div>
</Option>
);

Related

how do i get specific data from the state on a button click in react

Here i have 3 products each have it's own addtocart button and it's option like color, size, quantity. so when i click addtocart button after selecting the options it updating the state and giving me exactly i wanted. The problem is when i selelct any product options and then i click on another product addtocart button it shows the selceted option. not the product options of the addtocart button i clicked
for example: i select the 1st product and choose it's options and i did'nt click on the 1st product addtocart button either i clicked 2nd product button but it returns 1st product selected options it should return 2nd product options.
i need to implement which ever the product button i clicks it should only return that product selected options only.it should'nt return any other product selected options.
how do i make this happen. Help me out.
function Card() {
const [items, setItems] = useState({});
const handleChageCategory = (key, event) => {
setItems((oldState) => ({ ...oldState, [key]: event.target.value }));
};
const submitHandler = () => {
console.log(items);
setItems({});
};
return (
<div className="main-container">
<div className="container">
<div className="image-container">
<img
src="https://images.pexels.com/photos/9558601/pexels-photo-9558601.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
alt=""
/>
</div>
<h2> T-Shirt </h2>
</div>
<div className="form-conatiner">
<div className="selectors">
<p>Solid Round Neck T-shirt</p>
<select
id="color"
name="color"
required
onChange={(event) => handleChageCategory("color", event)}
>
<option>Color</option>
<option value="black">Black</option>
<option value="green">Green</option>
<option value="orange">Orange</option>
</select>
<select
name="quantity"
required
onChange={(event) => handleChageCategory("quantity", event)}
>
<option>Quantity</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<select
name="size"
required
onChange={(event) => handleChageCategory("size", event)}
>
<option>Size</option>
<option value="medium">Medium</option>
<option value="large">Large</option>
<option value="small">Small</option>
</select>
<div>
<button onClick={submitHandler}>Add to Cart</button>
</div>
</div>
</div>
{/* second product */}
<div className="container">
<div className="image-container">
<img
src="https://images.pexels.com/photos/440320/pexels-photo-440320.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
alt=""
/>
</div>
<h2> i-Watch </h2>
</div>
<div className="form-conatiner">
<div className="selectors">
<p>Dizo watch with amlod </p>
<select
id="2"
name="color"
required
onChange={(event) => handleChageCategory("brand", event)}
>
<option>Brand</option>
<option value="Apple">Apple</option>
<option value="Samsung">Samsung</option>
<option value="Pixel">Pixel</option>
</select>
<select
name="qantity"
required
onChange={(event) => handleChageCategory("qantity", event)}
>
<option>Quantity</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<select
name="type"
required
onChange={(event) => handleChageCategory("type", event)}
>
<option>type</option>
<option value="29mm">29mm</option>
<option value="34mm">34mm</option>
<option value="42mm">42mm</option>
</select>
<div>
<button onClick={submitHandler}>Add to Cart</button>
</div>
</div>
</div>
{/* third product */}
<div className="container">
<div className="image-container">
<img
src="https://images.pexels.com/photos/1661471/pexels-photo-1661471.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
alt=""
/>
</div>
<h2> Hoodie </h2>
</div>
<div className="form-conatiner">
<div className="selectors">
<p>Adidas hoodie with zip </p>
<select
id="2"
name="color"
required
onChange={(event) => handleChageCategory("color", event)}
>
<option>Color</option>
<option value="Gray">gray</option>
<option value="White">white</option>
<option value="Cyan">cyan</option>
</select>
<select
name="qantity"
required
onChange={(event) => handleChageCategory("qantity", event)}
>
<option>Quantity</option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
</select>
<select
name="size"
required
onChange={(event) => handleChageCategory("size", event)}
>
<option>type</option>
<option value="39(S)">39(S)</option>
<option value="42(M)">42(M)</option>
<option value="46(L)">46(L)</option>
</select>
<div>
<button onClick={submitHandler}>Add to Cart</button>
</div>
</div>
</div>
</div>
);
}
export default Card;
const [items, setItems] = useState([]);
The handleChangeCategory Logic
const handleChageCategory = (key,product, event) => {
const isExist = items.find((item)=>item.id===product.id)
let updatedItem
if(isExist){
updatedItem = {...isExist, [key]: event.target.value}
setItems((oldState)=>(oldState.map(item=>item.id===updatedItem.id ? updatedItem: item)))
}else{
updatedItem = { ...product, [key]: event.target.value };
setItems((oldState)=>([...oldState, updatedItem]))
}
};
Submit handler logic:
const submitHandler = (product) => {
const isExist = items.find(item=>item.id===product.id)
const newCartItem = isExist? isExist: product
console.log(newCartItem);
setItems((oldState)=>(oldState.filter(item=>item.id!==newCartItem.id)))
}
I know this is an expensive operation and it might hamper user experience but you wanted to do it this way. To handle add to cart, I think the good way is to open a modal or new page after clicking Add To Cart button that also solves your problem because in this way the user is not able to update one product value and click another product button.
A more efficient way to implement a product list is to use a product component for each item which accepts the product data through props. There you will implement all the logic related to the product and check the selected fields and so on.
Full code here.
So here I created Product, which has a selectedValues state that contains the values of all the selectors as an object, and also an handleAddToCart function that sends this product to the parent component function and add the product to the cart there.
Product.js:
const Product = (props) => {
const [selectedValues, setSelectedValues] = useState({});
const { name, description, img, selectors } = props;
//(`selectors` is an array of objects. Each object is a selector with an id field and an array of the `options`).
const handleChangeCategory = (e) => {
setSelectedValues((prevState) => {
return { ...prevState, [e.target.name]: e.target.value };
});
};
const handleAddToCart = () => {
props.addToCart({ name, description, img, selectedValues });//calling the function from the props.
};
return (
<div className="product">
<div className="image-container">
<img width="150px" src={img} alt="" />
</div>
<h2> {name}</h2>
<div className="form-conatiner">
<p>{description}</p>
<div className="selectors">
{selectors.map((s) => {
return (
<select
id={s.id}
name={s.id}
required
onChange={handleChangeCategory}
>
{s.options.map((opt) => (
<option value={opt}>{opt}</option>
))}
</select>
);
})}
</div>
<button onClick={handleAddToCart}>Add to Cart</button>
</div>
</div>
);
};
export default Product;
And this is the parent component, where you have the cart and the addToCartHandler function. And in the JSX you simply create a product by calling the Product component and passing the data and the selectors you want to have for this product.
App.js (parent component):
export default function App() {
//Some dummy cart.
const DUMMY_CART = [];
//Function that adds a product to the DUMMY_CART
const addToCartHandler = (product) => {
//Do some logic with the cart of course. I'm just pushing it here to show you the result.
DUMMY_CART.push(product);
console.log('Added product:', product);
console.log('Cart:', DUMMY_CART);
};
return (
<div>
{/* Product 1 */}
<Product
img="https://images.pexels.com/photos/9558601/pexels-photo-9558601.jpeg?auto=compress&cs=tinysrgb&w=1260&h=750&dpr=1"
name="T-Shirt"
description="Solid round neck T-Shirt"
selectors={[
{ id: 'color', options: ['Color', 'blue', 'grin', 'yellow'] },
{ id: 'quantity', options: ['Quantity', 1, 2, 3] },
{ id: 'size', options: ['Size', 'medium', 'large', 'small'] },
]}
addToCart={(product) => addToCartHandler(product)}
/>
</div>
);
}
Hope this helps. Please check the full code to see how it works.

Formatting of cascading dropdown using react select

I have a cascading dropdown which is like below:-
Its code is like below:-
render() {
return (
<div>
<center>
<h5>
Select cluster and server type
<hr />
<select
value={this.state.selectcluster}
onChange={this.selectclusterChange.bind(this)}
>
<option>-- Select --</option>
{this.state.clusterslist.map((x) => {
return <option>{x.label}</option>;
})}
</select>
<select
value={this.state.selectserver}
onChange={this.routeChange}
>
<option>----selection disabled----</option>
{this.state.servertype.map((x) => {
return <option>{x}</option>;
})}
</select>
</h5>
</center>
</div>
);
}
}
Now if i want its formatting like below:-
Here i have used react-select. Is there any way to improve formatting of my above code using react-select?
I have changed the code to work like below:-
render() {
return (
<div className='container pt-5'>
<center>
<h5>
Select cluster and server type
<hr />
<Select className="custom-select"
onChange={this.selectclusterChange.bind(this)}
value={this.state.selectcluster}
options={this.state.clusterslist.map((x) => {
return {label: x.label};
})}
/>
<Select
onChange={this.routeChange}
value={this.state.selectserver}
options={this.state.servertype.map((y) => {
return {label: y};
})}
/>
</h5>
</center>
</div>
);
}
}
Its working like below:-
Thanks for the helpful link and suggestions.

Pagination issue in React.js

I've created Pagination component but have got issues while implementing it into my Characters component.
I'm getting prev and next buttons, but instead of page numbers I'm gettin NaN.
Please advise where is my mistake?
Is there an issue with Pagination props?
Pagination.js
import React, { useState } from "react";
export const Pagination = ({
data,
RenderComponent,
title,
pageLimit,
dataLimit,
}) => {
const [pages] = useState(Math.round(data.length / dataLimit));
const [currentPage, setCurrentPage] = useState(1);
function goToNextPage() {
setCurrentPage((page) => page + 1);
}
function goToPreviousPage() {
setCurrentPage((page) => page - 1);
}
const changePage = (event) => {
console.log(event.target);
const pageNumber = Number(event.target.textContent);
setCurrentPage(pageNumber);
};
const getPaginatedData = () => {
const startIndex = currentPage * dataLimit - dataLimit;
const endIndex = startIndex + dataLimit;
return data.slice(startIndex, endIndex);
};
const getPaginationGroup = () => {
let start = Math.floor((currentPage - 1) / pageLimit) * pageLimit;
return new Array(pageLimit).fill().map((_, idx) => start + idx + 1);
};
return (
<div>
<h1>{title}</h1>
<div>
{getPaginatedData().map((d, idx) => (
<RenderComponent key={idx} data={d} />
))}
</div>
<div className="pagination">
<button
onClick={goToPreviousPage}
className={`prev ${currentPage === 1 ? "disabled" : ""}`}
>
prev
</button>
{/* show page numbers */}
{getPaginationGroup().map((item, index) => (
<button
key={index}
onClick={changePage}
className={`paginationItem ${
currentPage === item ? "active" : null
}`}
>
<span>{item}</span>
</button>
))}
{/* next button */}
<button
onClick={goToNextPage}
className={`next ${currentPage === pages ? "disabled" : ""}`}
>
next
</button>
</div>
</div>
);
};
The file where I want to implement the Pagination component.
Characters.js
import { useState, useEffect } from "react";
import { CHARACTERS_PAGE_URL } from "../../api/rickNMortyApi";
import { Loading } from "../../components/Loading/Loading";
import { Character } from "./Character";
import { Pagination } from "../Pagination/Pagination";
export const Characters = () => {
const [characters, setCharacters] = useState();
useEffect(() => {
try {
fetch(CHARACTERS_PAGE_URL)
.then((res) => res.json())
.then(({ results }) => {
if (results && Array.isArray(results)) {
setCharacters(results);
}
})
.catch((err) => console.log(err));
} catch (e) {
console.log(e);
}
}, []);
if (!characters) {
return <Loading />;
}
return (
<div className="p-4 font-mono text-green-500 ">
<div className="flex flex-row">
<div className="m-4 ">
<label>Species</label>
<select name="species" id="species">
<option value="all">all</option>
<option value="human">human</option>
<option value="alien">alien</option>
</select>
</div>
<div className="m-4">
<label>Status</label>
<select name="status" id="status">
<option value="all">all</option>
<option value="alive">alive</option>
<option value="dead">dead</option>
<option value="unknown">unknown</option>
</select>
</div>
<div className="m-4">
<label>Gender</label>
<select name="gender" id="gender">
<option value="all">all</option>
<option value="female">female</option>
<option value="male">male</option>
<option value="genderless">genderless</option>
<option value="unknown">unknown</option>
</select>
</div>
</div>
<h1 className="text-4xl">Characters</h1>
<Pagination data={characters} />
<div className="grid grid-flow-col grid-cols-5 grid-rows-4 gap-4">
{characters.map((character) => (
<div key={character.id}>
<Character character={character} />
</div>
))}
</div>
</div>
);
};
I've added
and now I can see prev, 1, 2, 3, 4,5, next
but charcters do not change if I click on the page number
When I try to add dataLimit={20} as a props for Pagination I'm getting an arrow below:
Warning: React.jsx: type is invalid -- expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.

onChange event won't fire in .map function React

I cannot get this onChange event to fire? The goal is to render multiple options from a drop down, from some data I have, then console log "hello" when any of the options is clicked...
It doesn't seem to want to let me use onChange or onClick events in my rendered option elements. If i can simply console log first , then i can figure out everything else. I only posted the necessary code but I can post the rest if needed!
const SlideData = [
{
title: "Slide 0",
},
{
title: "Slide 1",
},
{
title: "Slide 2",
},
];
export default SlideData;
let options = SlideData.map((item, index) => (
<option
key={index}
value={index}
onChange={() => {
console.log("hello");
}}
>
{item.title}
</option>
));
<select className={styles.select} onChange={goto}>
<option>--Select--</option>
{options}
</select>
option tag doesn't support onChange. You can use onClick instead.
onClick={() => {
console.log("hello");
}}
goal was to pass the index and console log it technically. so I guess this worked for me
const slideRef = useRef();
const goto = ({ target }) => {
slideRef.current.goTo(parseInt(target.value, 10));
console.log(target.value);
};
const options = SlideData.map((item, index) => (
<option key={index} value={index}>
{item.title}
</option>
));
return (
<div className={styles.container}>
<Slide ref={slideRef} {...properties}>
<div className={styles.slide}>First Slide</div>
<div className={styles.slide}>Second Slide</div>
<div className={styles.slide}>Third Slide</div>
<div className={styles.slide}>Fourth Slide</div>
<div className={styles.slide}>Fifth Slide</div>
</Slide>
<div>
<Button type="button" onClick={back}>
Back
</Button>
<Button type="button" onClick={next}>
Next
</Button>
<select className={styles.select} onChange={goto}>
<option>--Select--</option>
{options}
</select>
</div>
</div>
);

how to pass array values to Formik select

I am using Formik for a bunch of admin console forms that I have in my application. So far I did not have this use case.
My Formik forms use one of 2 custom components, either a Myinputtext(input box) or a MySelect(drop down). I dont have a need for any other components so far. Here is how my Myselect component looks like.
const MySelect = ({ label, ...props }) => {
const [field, meta] = useField(props);
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
<select className={props.className} {...field} {...props} />
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
);
};
Over in the form I am passing values to this component like this
<MySelect className="select-input" label="Losing Player" name="losingPlayer">
<option value="">Select Losing Player</option>
<option value="player1">{state.Player1Name} </option>
<option value="player2">{state.Player2Name} </option>
All of this works for a few forms I have built so far. In the fourth form now, data coming back from the back end is coming as an array and I am trying to pass the array as input to the myselect component
<MySelect className="select-input" label="Losing Player" name="losingPlayer">
<option value="">Select Losing Player</option>
<option value="player1">{name of array object} </option>
This is failing and not providing the right result.
In the formik official docs it says there is a way to handle array objects like this
<Form>
<Field name="friends[0]" />
<Field name="friends[1]" />
<button type="submit">Submit</button>
</Form>
</Formik>
But my array size can be dynamic and I cannot hardcode, 0,1 like above.
I tried rendering the array inside the select component like this,
<MySelect className="select-input" label="Winning Player" name="winningPlayer">
{props.initialValues.map((player) => {
<option key={player} value={player}> {player} </option> })} </MySelect>
this does not throw any errors. but the drop down is displayed empty.
I am basically hoping to have the names in the array displayed as the dropdown. What is the right solution to tackle this?
This finally worked:-
return (
<div>
<label htmlFor={props.id || props.name}>{label}</label>
{!props.player ? <select className={props.className} {...field} {...props} />
:
<select className={props.className}>
{props.player.map((player) => {
return (
<option key={player} value={player}>
{player}
</option>
)
})}
</select>
}
{meta.touched && meta.error ? (
<div className="error">{meta.error}</div>
) : null}
</div>
You need to map your array and render options inside your select like this:
{options?.map(({ value }) => (
<option key={value} value={value}>
{value}
</option>
))}

Resources