Adding custom component with an onClick icon - react-select

I'm currently using react-select and I want to create a custom Option component that has an icon that allows interaction (e.g. adding to favourites). I've tried doing
const Option = props => {
return <components.Options {...props}>
<p>Text</p>
<IonIcon onClick={console.log("clicked")} />
</components.Options>
}
Doing this does not allow the IonIcon to be clicked and the onClick behaves like the default Option.
I have also tried:
<div>
<p>Text</p>
<IonIcon onClick={console.log("clicked")} />
</div>
as the return. While doing this, the default Option behaviour is removed, it does not allow the IonIcon to be clicked either.
For both methods I've tried, opening the menu will trigger the onClick, i.e. print "clicked", and clicking the IonIcon doesn't trigger anything.
I've set the zIndex of the icon to 10000 as well.
Thanks in advance! It'll be really cool to include the ability to favourite in select!

Before Clarifications
If I didn't understand wrong you want your new Component to have some kind of text (maybe passed by prop?) and the Icon clickable, after the click the Icon should change color.
For the Icon I'm using react-icons .
App.js
import React,{useState} from 'react';
import { IoStar } from "react-icons/io5";
function App() {
return (
<div style={{textAlign:"center"}}>
<ul style={{listStyleType:"none"}}>
<Option value="Location 1"/>
<Option value="Location 2"/>
<Option value="Location 3"/>
<Option value="Location 4"/>
<Option value="Location 5"/>
</ul>
</div>
);
}
const Option = (props) =>
{
let {value} = props; //Value taken from props
const defaultStyle = //Default Logo Style -> Cursor: Pointer is used to make the cursor become a "hand" when "hover" on the icon
{
cursor:"pointer",
fontSize:"40px"
}
const starPressed = () => //Function triggered on the "On Click"
{
if(isYellow)
setIconStyle(defaultStyle); //Color Default
else
setIconStyle({...iconStyle,color : "#f5d442"}); //Color Change
setIsYellow(!isYellow);
}
const [iconStyle,setIconStyle] = useState(defaultStyle); //UseState
const [isYellow,setIsYellow] = useState(false); //UseState
//return
return(
<li>
<span style={{fontSize:"40px"}}>{value}</span> <span><IoStar onClick={starPressed} style={iconStyle}/></span>
</li>
)
}
export default App;
Basically I'm using useState to re-render after the : iconStyle changes.
I believe it is what you were searching for.
EDIT After Clarifications
This is a BIG BIG work-around, hope it would help you.
I'm sure there are better ways to pass data and other things, and I'm still looking for them, but this is my first approach to it.
(Never used react-select).
Hope it helps.
import React,{useState} from 'react';
import { IoStar } from "react-icons/io5";
import Select from "react-select";
function App() {
const [isYellow,setIsYellow] = useState(
[
false,
false
]
); //UseState
const options =
[
{
value:0,
label:"Option 1",
isYellow: isYellow,
setIsYellow: setIsYellow
},
{
value:1,
label:"Option 2",
isYellow: isYellow,
setIsYellow: setIsYellow
}
];
return (
<>
<Select closeMenuOnSelect={false} options={options} formatOptionLabel={FormatOptionLabel}/>
</>
);
}
const FormatOptionLabel = ({ value, label,isYellow,setIsYellow}) =>
{
const style1 =
{
zIndex:"100",
cursor:"pointer",
fontSize:"30px"
}
const style2 =
{
zIndex:"100",
cursor:"pointer",
fontSize:"30px",
color : "#f5d442"
}
//return
return(
<>
<span style={{fontSize:"20px"}} value={value}>{label}</span> <span style={{float:"right"}}><IoStar onClick={(event) => { let array = isYellow; array[value]=!array[value]; setIsYellow(array);}} style={isYellow[value] ? style2 : style1}/></span>
</>
)
}
export default App;

Related

Can't use ref to select swiper methods

I am trying to invoke .slideNext() & .slidePrev that come with swiper when the user presses the arrow keys.
I've managed to do this with querySelector like this:
const changeSlide = (event: KeyboardEvent) => {
const slides = document.querySelector('.swiper').swiper
if(event.key === 'ArrowLeft') {
slides.slidePrev();
} else if (event.key === 'ArrowRight') {
slides.slideNext();
}
}
However this method creates warnings and the usage of querySelector is not allowed in general for this project. So instead i wan't to use a ref to select the swiper but i've not yet succeeded at this.
This is what i tried:
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue';
import { Navigation } from 'swiper';
import { Swiper, SwiperSlide } from 'swiper/vue';
import 'swiper/css';
import 'swiper/css/navigation';
// just some imports that are needed
const swiperRef = ref();
// swiper as ref
const changeSlide = (event: KeyboardEvent) => {
const slides = swiperRef.value.swiper
// instead of querySelector i use the ref.value
if(event.key === 'ArrowLeft') {
slides.slidePrev();
} else if (event.key === 'ArrowRight') {
slides.slideNext();
}
}
</script>
<template>
<swiper
ref="swiperRef"
:initial-slide="props.initialSlide"
:slides-per-view="1"
:space-between="0"
:modules="[Navigation]"
navigation
>
<swiper-slide> // left out the attributes since they are a mere distraction
<img/> // left out the attributes since they are a mere distraction
</swiper-slide>
</swiper>
</template>
The error i get from this code is:
From what I'm seeing in the source code, you can't access the swiper instance using ref on the swiper vue component because it's not exposed.
You have others way to do thought:
inside a child component of the <swiper> component, use the useSwiper() component.
on the parent component that instanciate the <swiper> component, use the #swiper event. It sends the swiper object once mounted:
<template>
<swiper #swiper="getRef" />
</template>
<script setup>
const swiper = ref(null)
function getRef (swiperInstance) {
swiper.value = swiperInstance
}
function next () {
swiper.value.slideNext() // should work
}
</script>
You don't have to use this method instead you can just import Keyboard like this:
import { Navigation } from 'swiper';
Add Keyboard to modules and attributes of <swiper> like this:
<swiper :modules="[Navigation, Keyboard]" keyboard />
And add the attribute to <swiper-slide> as well:
<swiper-slide keyboard />

How to store coming data persistently?

I use react-native.
And this is the function that I want to use.
The most important point here is editCommentMutation.
In order to execute this mutation, it requires two variables which are id and payload.
const onUpdateComment = React.useCallback((commentId) => {
setEdit(true);
console.log(commentId, comments);
editCommentMutation({
variables: {
id: parseInt(commentId),
payload: comments,
},
});
}, []);
I get these two variables correctly.
For id , I get it from component screen. So It comes thru argument.
For payload, I get it on the same screen.
Problem here is when I press button1, it sends commentId(id) data from component screen to this page.
And when I press button2, it sends comments(payload) data on this page.
On this App, I press button1 and write comments then button2 in order.
So Each data comes not together, but one by one.
So I execute console.log(commentId, comments),
press button1 : 386 undefined
press button2 : undefined Object { "comments": "뭐야", }
It has undefined for sure..
But in order to execute mutation, I need two data together.
For this, I need to save coming data in somewhere, I guess.
I also tried
const [data, setData] = useState("").
After I save coming commentId to here as:
setData(commentId).
But it isn't saved, just disappears. :)
Can you help me?
Here is a minimal verifiable example. Run the code below and
click ✏️ to edit one of the comments
click ✅ to save your edit and view the updated comment
function App() {
const [edit, setEdit] = React.useState(null)
const [comments, setComments] = React.useState([ "hello", "안녕하세요" ])
const onEdit = editable => event => { setEdit(editable) }
const onUpdate = newComment => event => {
/* execute your graphql mutation here */
setComments([
...comments.slice(0, edit.index),
newComment,
...comments.slice(edit.index + 1)
])
setEdit(null)
}
return <div>
{comments.map((comment, index) =>
<p>{comment}
<button
children="✏️"
onClick={onEdit({comment, index})}
disabled={Boolean(edit)}
/>
</p>
)}
{edit ? <EditComment text={edit.comment} onUpdate={onUpdate} /> : null}
</div>
}
function EditComment({ text, onUpdate }) {
const [value, setValue] = React.useState(text)
return <div>
<input value={value} onChange={e => setValue(e.target.value)} />
<button onClick={onUpdate(value)} children="✅" />
</div>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
button[disabled] { opacity: 0.5; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
In your App you will execute your mutation in onUpdate. A cancel button ❌ can easily be defined using const onCancel = event => { setEdit(null) } and pass it to EditComment.

How to do conditional rendering on dynamic props

I'm trying to add conditional rendering on the dynamically passed prop. I have a component called card-item.vue which is passing the prop cta to component profile.vue.
Now in profile.vue I want the prop cta to display on every card-item component except the first one.
Here is my card-item.vue:
<template>
<span v-if="cta">
{{ cta }}
</span>
</template>
<script>
export default {
name: 'card-item',
props: {
cta: {
type: String
}
}
}
</script>
profile.vue:
<template>
<CardItem
v-for="address in addresses.slice(1)"
:key="uniqueKey('address', address)"
:cta="cms.page.cta" // remove cta from the first address only
/>
</template>
<script>
import CardItem from "./card-item";
const data = require('./profile.json');
export default {
name: 'profile',
comonents: {
CardItem,
},
props: {
cms: {
type: Object,
default: () => {
return {
page: data
}
}
}
}
}
</script>
profile.json:
{
"cta": "Report"
}
In my <CardItem /> component I'm rendering addresses. So I want my :cta on every address except the first one.
I was trying something like:
v-if="addresses[0] ? {cta="null"} : "cms.page.cta""
I know this is incorrect syntax but somewhat I'm trying to achieve.
Please any help would be appreciated.
v-for also supports an optional second argument for the index of the
current item. -- vue docs
<CardItem
v-for="(address, index) in addresses.slice(1)"
:key="uniqueKey('address', address)"
:cta="index !== 0 ? cms.page.cta : null" // remove cta from the first address only
/>

How do you implement AsynSelect with isMilti in Typescript? #react-select

I can't seem to get isMulti to work with Aysync select. I've tried a number of iterations but nothing works. Below is my code. Once I uncomment the isMulti line, thats whne things break. I tried to create a new array type to see if that helps but it doesn't.
I also have another issue where the options box doesn't load the options from the promise function but only does so when I delete a character from the input (it's using the cached results to populated the dropdown at that point).
import { useState } from 'react';
import { FunctionComponent } from 'react';
import AsyncSelect from 'react-select/async';
import ValueType from 'react-select';
import { getGoogleAutoComplete } from './services';
import map from '../../assets/map.svg';
import './LocationInput.styles.scss';
type OptionType = {
value: string;
label: string;
}
type OptionTypeArray = Array<OptionType>;
const LocationInput: FunctionComponent = () => {
const [locations, setLocations] = useState<ValueType<OptionType | OptionTypeArray>>();
const [query, setQuery] = useState("");
const handleChange = (option: ValueType<OptionType | OptionTypeArray> | null) => {
if (option != null){setLocations(option)};
console.log('im in handleChange!')
console.log(option)
};
async function promiseOptions(): Promise<any>{
return await getGoogleAutoComplete(query);
}
return (
<div className='location-input-container'>
<div className='map-icon'>
<img src={map} alt=''/>
</div>
<div className='location-input'>
<AsyncSelect
//isMulti={true}
cacheOptions
value={locations}
defaultOptions
placeholder='Enter a City or ZIP code'
onChange={(option) => handleChange(option)}
onInputChange={(value) => setQuery(value)}
closeMenuOnSelect={true}
noOptionsMessage={() => 'No Match Found'}
isClearable={true}
loadOptions={promiseOptions} />
</div>
</div>
)
}
export default LocationInput;
I was able to find a solution. However, I had to use Type Assertion to make it workout which isn't the ideal approach. If anyone has any other suggestions I'd greaty appreacite it.
import { useState, FunctionComponent } from 'react';
import AsyncSelect from 'react-select/async';
import { getGoogleAutoComplete } from './services';
import OptionTypeBase from 'react-select';
import { OptionsType } from 'react-select/src/types';
import makeAnimated from "react-select/animated";
import map from '../../assets/map.svg';
import './LocationInput.styles.scss';
const LocationInput: FunctionComponent = () => {
const [locations, setLocations] = useState<OptionsType<OptionTypeBase>>();
const [query, setQuery] = useState("");
const handleChange = (option: OptionsType<OptionTypeBase>) => {
setLocations(option);
console.log('im in handleChange!')
console.log(option)
};
async function promiseOptions(value:string): Promise<any>{
return new Promise(resolve => resolve(getGoogleAutoComplete(value)));
}
//get animated components wrapper
const animatedComponents = makeAnimated();
return (
<div className='location-input-container'>
<div className='map-icon'>
<img src={map} alt=''/>
</div>
<div className='location-input'>
<AsyncSelect
isMulti={true}
components={animatedComponents}
cacheOptions
placeholder='Enter a City or ZIP code'
onChange={(option) => handleChange(option as OptionsType<OptionTypeBase>)}
closeMenuOnSelect={true}
noOptionsMessage={() => 'No Match Found'}
loadOptions={promiseOptions} />
</div>
</div>
)
}
export default LocationInput;

REACT - defaultChecked don't render check attribute on second load

I got my component who won't check the radio when i go to the /view/:id for the second time. I started in my list component with react-router at the index of the site, i click on the view button of an element, the radio is checked, i return in my list and go to another or the same element and it's not checked anymore. When i inspect the component in the React developer tool, the radio has the defaultChecked=true property.
import React from 'react';
import { connect } from 'react-redux';
class LicenseRadios extends React.Component {
buildRadios() {
let { licenses, activeValue } = this.props;
return licenses.map(license => {
let checked = false;
if(activeValue !== undefined && activeValue === license.id){
checked = true;
}
return (
<div key={license.id} className="col l2">
<p>
<input name="license" type="radio" id={'licenseRdo_' + license.id} value={license.id} defaultChecked={checked} />
<label htmlFor={'licenseRdo_' + license.id}>{license.label}</label>
</p>
</div>
);
});
}
render() {
return (
<div className="row">
{this.buildRadios()}
</div>
);
}
}
export default LicenseRadios;
I tried to change the defaultChecked for the checked attribute, but it require an onChange event. I don't understand this problem. Can anybody help me please?
Thank you
The defaultChecked prop is only used during initial render. If you need to update the value in a subsequent render, you will need to use an onChange function to handle value changes.
Check out controlled components in the docs to better understand the problem you're having.
use "undefined" for initial value for defaultChecked and re-render by setting it to true or false
const Example = () => {
[checked,setChecked] = useState(undefined);
useEffect(()=>{
// fetch data
setChecked(true);
});
return (
<input type="checkbox" defaultChecked={checked} onClick={(e)=> changeValue(e)}/>
);
}