toggle effect with react - javascript

I'm trying to create a switcher with react that when I click it it change the price of another component and when I click again over it return the original price.
My first approach was this:
I crated the input with the type of a checkbox so when checked is true change the price and when is false return the original price, some kind of toggle all handled with the funcion handleDiscount
<input
checked={toggle}
onChange={handleDiscount}
type="checkbox"
className="switch-input"
/>
I created the handleDiscount function that change the toggle from his initial state which is false to true and and after that a ternary operator that check the condition to set the price.
const handleDiscount = () => {
setToggle(!toggle);
toggle === true ? setPrice(10) : setPrice(20);
};
the problem is that when is click over the checkbox again the price don't change.
I have to work with the useEffect hook? which it's the best approach for this kind of work?
for example I wrote the same code with my knowledge in vanilaJS and work, here is the example:
const switcher = document.querySelector("input");
switcher.addEventListener("click", () => {
const priceBasic = document.querySelector(".priceBasic");
const pricePro = document.querySelector(".pricePro");
const priceMaster = document.querySelector(".priceMaster");
if (switcher.checked == true) {
priceBasic.innerHTML = `<h1>$49.99</h1>`;
pricePro.innerHTML = "$69.99";
priceMaster.innerHTML = "$89.99";
} else {
priceBasic.innerHTML = "$19.99";
pricePro.innerHTML = "$24.99";
priceMaster.innerHTML = "$39.99";
}
});

Issue
The main issue here is that state updates are asynchronous, the toggle value isn't the updated value you just enqueued.
const handleDiscount = () => {
setToggle(!toggle);
toggle === true ? setPrice(10) : setPrice(20);
};
Solution
Use an useEffect to toggle the price state when the toggle state updates.
useEffect(() => {
setPrice(toggle ? 10 : 20);
}, [toggle]);
...
const handleDiscount = () => {
setToggle(toggle => !toggle);
};
Alternatively you can make the checkbox uncontrolled and directly set the price state when the checkbox value updates
const handleDiscount = (e) => {
const { checked } = e.target;
setPrice(checked ? 10 : 20);
};
...
<input
onChange={handleDiscount}
type="checkbox"
className="switch-input"
/>

I am assuming toggle is a state variable and setToggle is s state function, in which case, this won't work:
const handleDiscount = () => {
setToggle(!toggle);
toggle === true ? setPrice(10) : setPrice(20);
};
As setToggle is asynchronous. So toggle does not actually toggle by the time it reaches toggle === true ? setPrice(10) : setPrice(20);
You should instead use useEffect and listen to the change of toggle like so:
useEffect(() => {
toggle === true ? setPrice(10) : setPrice(20);
},[toggle])
And get rid of toggle === true ? setPrice(10) : setPrice(20); inside handleDiscount altogether.

While your specific question of getting the toggle to work has been answered, your example in vanilla javascript hasn't been addressed.
Ideally you wouldn't be hard coding different price options directly into the component, but would be serving an array of products which each held their own specific details including pricing options.
There are various ways of handling this, but in the snippet below your toggle has become a pointer stored in state in individual Product components that can be used to access the appropriate price property of the product object passed to that component. eg.
let product = {
id: 1,
name: 'Product One',
defaultPricePoint: 'red',
pricePoints: {
red: "$49.99",
blue: "$69.99",
}
};
const [pricePoint, setPricePoint] = useState('red');
let price = product.pricePoints[pricePoint];
// "$49.99"
The change handler simply sets the pricePoint state using the value of the input that changed (which were mapped from the keys of the object's prices to begin with). And a useEffect is implemented to set the initial radio selection based on a default set in the product object.
The result is two fairly simple components and decent separation between data and logic.
(note the use of key values that are unique for each mapped element, and not index values)
const App = ({ products }) => {
return (
<div>
{products.map(product =>
<Product key={product.id} product={product} />
)}
</div>
)
}
const Product = ({ product }) => {
const [pricePoint, setPricePoint] = React.useState();
React.useEffect(() => {
setPricePoint(product.defaultPricePoint)
}, [product]);
const handlePricePoint = (e) => {
setPricePoint(e.target.value);
}
return (
<div>
<h3>{product.name}</h3>
<p>{product.pricePoints[pricePoint]}</p>
<form>
{Object.keys(product.pricePoints).map(p =>
<div key={product.id + p}>
<input
type="radio"
name="price-point"
value={p}
onChange={handlePricePoint}
checked={pricePoint === p}
/>
<label for={p}>{p}</label>
</div>
)}
</form>
</div>
);
}
ReactDOM.render(
<App products={products} />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<script>
const products = [
{
id: 1,
name: 'Product One',
defaultPricePoint: 'red',
pricePoints: {
red: "$49.99",
blue: "$69.99",
}
},
{
id: 2,
name: 'Product Two',
defaultPricePoint: 'pro',
pricePoints: {
basic: "$19.99",
pro: "$24.99",
master: "$39.99",
}
}
]
</script>
<div id="root"></div>

#codemonkey's answer is correct but if you want something a little simpler:
function YourComponent() {
const [price, setPrice] = useState(20);
const [toggle, setToggle] = useState(false);
const handleDiscount = () => {
const newValue = !toggle;
setToggle(newValue);
setPrice(newValue ? 10 : 20);
}
// ...
}
Or if price is only computed based on toggle:
function YourComponent() {
const [toggle, setToggle] = useState(false);
const price = useMemo(
() => toggle ? 10 : 20,
[toggle]
);
const handleDiscount = () => {
setToggle(!toggle);
}
// ...
}

Related

Array data flickers when one item is changed in React Native

In my React Native app, i am rendering an array and i have toggle buttons set in that page. If i change any one item, the array gets changed and the component rerenders which result in flickering of the component. How can i just change the item that's needed to be changed and stop the flickering.
Here's my current component:
const Notifications = () => {
const dispatch = useDispatch();
const { t } = useTranslation('setting');
const notificationStatus = useSelector(getNotificationModeSelector);
const setNotificationStatus = useCallback(
(value: any) => dispatch(notificationsActions.setNotifications(value)),
[dispatch],
);
const getNotificationStatus = useCallback(
() => dispatch(notificationsActions.getNotifications()),
[dispatch],
);
const onToggleNotification = useCallback(async (item) => {
var changeParam = notificationStatus.map(obj =>
obj.id === item.id ? { ...obj, enabled: obj.enabled ==='1' ? '0' : '1' } : obj
);
setNotificationStatus(changeParam);
}, [notificationStatus, setNotificationStatus]);
useEffect(() => {
getNotificationStatus();
}, []);
console.log('Notifications Page',notificationStatus );
return (
<DesktopContainer>
<Container>
{notificationStatus.length !== 0 && notificationStatus.map((item)=>{
return(
<Wrapper key={Math. floor(Math. random() * 100)}>
<Title>{item.name}</Title>
<Switch
onColor={styles.SWITCH_COLOR}
offColor={styles.MEDIUM_GRAY}
isOn={item.enabled === '1' ? true : false}
size={
Dimensions.get('screen').width > styles.MIN_TABLET_WIDTH
? 'large'
: 'medium'
}
onToggle={()=>onToggleNotification(item)}
/>
</Wrapper>
)
})
}
</Container>
</DesktopContainer>
);
};
export default memo(Notifications);
The issue is on this line:
<Wrapper key={Math. floor(Math. random() * 100)}>
Your key should always be a fixed value. If your notificationStatus item has a unique ID, that's ideal. React optimizes its render cycle by using these keys; that's what allows it to only change what it needs. Since all of your mapped components have new keys on each render, React re-renders them all.
See the "Picking a Key" section of this tutorial for more. https://reactjs.org/tutorial/tutorial.html

How to hide a specific element inside .map function in React?

I am looking for a way to hide a div once the button thats in it is clicked and continue to show all other div's.
I've tried using the setState method, however when setting it to false with onClick() all of my objects disappear.
class App extends React.PureComponent {
state: {
notHidden: false,
}
constructor(props: any) {
super(props);
this.state = {search: '', notHidden: true};
this.hideObject = this.hideObject.bind(this)
}
hideThisDiv() {
this.setState({notHidden: false})
}
async componentDidMount() {
this.setState({
objects: await api.getObjects()
});
}
render = (objects: Object[]) => {
return ({Object.map((object) =>
<div key={index} className='class'>
<button className='hide' type='button' onClick={() => hideThisDiv()}>Hide</button>
<p>object.text</p>
</div>}
render() {
const {objects} = this.state;
return (<main>
<h1>Objects List</h1>
<header>
<input type="search" onChange={(e) => this.onSearch(e.target.value)}/>
</header>
{objects ? this.render(objects) : null}
</main>)
}
);
The data is a data.json file filled with many of these objects in the array
{
"uuid": "dsfgkj24-sfg34-1r134ef"
"text": "Some Text"
}
Edit: Sorry for the badly asked question, I am new to react.
Not tested, just a blueprint... is it what you want to do?
And yes I didn't hide, I removed but again, just an idea on how you can hide button separately, by keeping in state which ones are concerned.
function MagicList() {
const [hidden, hiddenSet] = useState([]);
const items = [{ id:1, text:'hello'}, { id:2, text:'from'}, { id:3, text:'the other sided'}]
const hideMe = id => hiddenSet([...hidden, id]);
return {
items.filter( item => {
return hidden.indexOf(item.id) !== -1;
})
.map( item => (
<button key={item.id} onClick={hideMe.bind(this, item.id)}>{item.text}</button>
))
};
}
Edition
const hideMe = id => hiddenSet([...hidden, id]);
It is just a fancy way to write:
function hideMe(id) {
const newArray = hidden.concat(id);
hiddenSet(newArray);
}
I suggest using a Set, Map, or object, to track the element ids you want hidden upon click of button. This provides O(1) lookups for what needs to be hidden. Be sure to render your actual text and not a string literal, i.e. <p>{object.text}</p> versus <p>object.text</p>.
class MyComponent extends React.PureComponent {
state = {
hidden: {}, // <-- store ids to hide
objects: [],
search: "",
};
// Curried function to take id and return click handler function
hideThisDiv = id => () => {
this.setState(prevState => ({
hidden: {
...prevState.hidden, // <-- copy existing hidden state
[id]: id // <-- add new id
}
}));
}
...
render() {
const { hidden, objects } = this.state;
return (
<main>
...
{objects
.filter((el) => !hidden[el.uuid]) // <-- check id if not hidden
.map(({ uuid, text }) => (
<div key={uuid}>
<button
type="button"
onClick={this.hideThisDiv(uuid)} // <-- attach handler
>
Hide
</button>
<p>{text}</p>
</div>
))}
</main>
);
}
}

How to keep every two identical names red if both were clicked consecutively?

I have list items represent names, when clicking any name it turns red then take one second to return black again, but clicking two identical names consecutively make them keep red color, not turning black again
you can imagine it as a memory game, but i tried to make a simple example here of what i am trying to achieve in the original project
This is my code and my wrong trial:
const App = () => {
const { useState } = React;
const items = [
{
name: 'mark',
id: 1,
red: false
},
{
name: 'peter',
id: 2,
red: false
},
{
name: 'john',
id: 3,
red: false
},
{
name: 'mark',
id: 4,
red: false,
},
{
name: 'peter',
id: 5,
red: false
},
{
name: 'john',
id: 6,
red: false
}
];
const [names, setNames] = useState(items);
const [firstName, setFirstName] = useState(null);
const [secondName, setSecondName] = useState(null)
const handleItemClick = (item) => {
setNames(prev => prev.map(i => i.id === item.id ? { ...i, red: true } : i));
//the problem is here
setTimeout(() => {
setNames(prev => prev.map(n => {
if (secondName && (secondName.name === firstName.name) && n.name === firstName.name) {
return { ...n,red: true }
}
return { ...n, red: false };
}))
}, 1000)
if (!firstName) setFirstName(item);
else if (firstName && !secondName) setSecondName(item)
else if (firstName && secondName) {
setFirstName(item);
setSecondName(null)
}
}
return (
<div class="app">
<ul class="items">
{
names.map(i => {
return (
<Item
item={i}
handleItemClick={handleItemClick}
/>
)
})
}
</ul>
</div>
)
}
const Item = ({ item, ...props }) => {
const { id, name, red } = item;
const { handleItemClick } = props;
return (
<li
className={`${red ? 'red' : ''}`}
onClick={() => handleItemClick(item)}
>
{name}
</li>
)
}
ReactDOM.render(<App />, document.getElementById('root'))
But this code doesn't work correctly, when clicking two identical names consecutively they don't keep red color and turning black again
To me it seems the issue is overloading the event handler and violating the Single Responsibility Principle.
The handler should be responsible for handling the click event and nothing else. In this case, when the element is clicked you want to add the id to the state of selected/picked names, and toggle the red state value of item with matching id. Factor the timeout effect into (strangely enough) an useEffect hook, with the picks as dependencies. This inverts the logic of the timeout to clearing/resetting the state versus setting what is "red" or not. You can/should also move any logic of determining matches into this same effect (since it already has the dependencies anyway).
useEffect(() => {
... logic to determine matches
const timer = setTimeout(() => {
// time expired, reset only if two names selected
if (firstName && secondName) {
setFirstName(null);
setSecondName(null);
setNames(names => names.map(name => ({ ...name, red: false })));
}
}, 1000);
// clean up old timeout when state updates, i.e. new selected
return () => clearTimeout(timer);
}, [firstName, secondName]);
This will allow you to simplify your name setting logic to
if (!firstName) {
setFirstName(item);
} else {
setSecondName(item);
}
Note: I believe you need another data structure to hold/track/store existing matches made by the user.
How this works:
Starting from clean state, no names are chosen
When first name is picked, firstName is null and updated, red state updated
Timeout is set (but won't clear state yet)
When second name is picked, firstName is defined, so secondName is updated, red state updated
If match, add match to state (to keep red)
Timeout expire and reset state (go back to step 1)
The following is how I'd try to simplify state a bit more, using an array of selected ids that only update if the selected id isn't already chosen and 2 picks haven't been chosen yet.
const App = () => {
const [names, setNames] = useState(items);
const [picks, setPicks] = useState([]);
const [matched, setMatched] = useState({});
/**
* On click event, add id to `picks` array, allow only two picks
*/
const onClickHandler = id => () =>
picks.length !== 2 &&
!picks.includes(id) &&
setPicks(picks => [...picks, id]);
/**
* Effect to toggle red state if id is included in current picks
*/
useEffect(() => {
setNames(names =>
names.map(name => ({
...name,
red: picks.includes(name.id)
}))
);
}, [picks]);
/**
* Effect checks for name match, if a match is found it is added to the
* `matched` array.
*/
useEffect(() => {
// matches example: { mark: 1, peter: 0, john: 0 }
const matches = names.reduce((matches, { name, red }) => {
if (!matches[name]) matches[name] = 0;
red && matches[name]++;
return matches;
}, {});
const match = Object.entries(matches).find(([_, count]) => count === 2);
if (match) {
const [matchedName] = match;
setMatched(matched => ({ ...matched, [matchedName]: matchedName }));
}
const timer = setTimeout(() => {
if (picks.length === 2) {
setPicks([]);
setNames(names => names.map(name => ({ ...name, red: false })));
}
}, 1000);
return () => clearTimeout(timer);
}, [names, picks]);
return (
<div className="App">
<ul>
{names.map(item => (
<Item
key={item.id}
item={item}
matches={matched}
onClick={onClickHandler(item.id)}
/>
))}
</ul>
</div>
);
};
const Item = ({ item, matches, ...props }) => {
const { name, red } = item;
return (
<li
className={classnames({
red: red || matches[name], // for red text color
matched: matches[name] // any other style to make matches stand out
})}
{...props}
>
{name}
</li>
);
};

Applying multiple filters to a table reactjs

Here i have three filters on selection of which i need to filter data in a table.
I am using if else statement to check and filter the data , hence i want to modify the code in some modular way to achieve the same can any one suggest me , should i go with switch case ?
if (mapFilter === 'Mapped') {
if (listFilter) {
const result = fullData.filter(
data =>
data.partner_mapping_classification.length > 0 &&
data.account === listFilter,
);
setFinalData(result);
} else {
const result = fullData.filter(
data => data.partner_mapping_classification.length > 0,
);
setFinalData(result);
}
} else if (mapFilter === 'Not Mapped') {
if (listFilter) {
const result = fullData.filter(
data =>
data.partner_mapping_classification === '' &&
data.account === listFilter,
);
setFinalData(result);
} else {
const result = fullData.filter(
data => data.partner_mapping_classification === '',
);
setFinalData(result);
}
} else if (mapFilter === 'All') {
if (listFilter) {
const result = fullData.filter(
data => data.account === listFilter,
);
setFinalData(result);
} else {
const result = fullData.filter(
data => data.partner_mapping_classification.length > 0,
);
setFinalData(result);
}
} else if (mapFilter === '' && listFilter !== '') {
const result = fullData.filter(
data => data.account === listFilter,
);
setFinalData(result);
} else if (mapFilter === '' && listFilter === '') {
setFinalData([]);
} else {
setFinalData([]);
}
};
Easy to scale method (followed by live-demo)
Using switch statements or multiple chained if( statements (or, even, multiple conditions within same if( statement) doesn't seem to be a good idea, as scaling and maintaining such code will become way too difficult.
As the opposite to above mentioned hardcoding techniques, I would suggest to have an object within your table component's state that will bind object properties (you wish your table entries to get filtered by) to keywords (attached to your inputs).
Assuming (based on your screenshot) you use MaterialUI for styling your components, following example would demonstrate above approach:
const { useState } = React,
{ render } = ReactDOM,
{ Container, TextField, TableContainer, Table, TableHead, TableBody, TableRow, TableCell } = MaterialUI,
rootNode = document.getElementById('root')
const sampleData = [
{id: 0, name: 'apple', category: 'fruit', color: 'green'},
{id: 1, name: 'pear', category: 'fruit', color: 'green'},
{id: 2, name: 'banana', category: 'fruit', color: 'yellow'},
{id: 3, name: 'carrot', category: 'vegie', color: 'red'},
{id: 4, name: 'strawberry', category: 'berry', color: 'red'}
],
sampleColumns = [
{id: 0, property: 'name', columnLabel: 'Item Name'},
{id: 1, property: 'category', columnLabel: 'Category'},
{id: 2, property: 'color', columnLabel: 'Item Color'}
]
const MyFilter = ({filterProperties, onFilter}) => (
<Container>
{
filterProperties.map(({property,id}) => (
<TextField
key={id}
label={property}
name={property}
onKeyUp={onFilter}
/>
))
}
</Container>
)
const MyTable = ({tableData, tableColumns}) => (
<TableContainer>
<Table>
<TableHead>
<TableRow>
{
tableColumns.map(({id, columnLabel}) => (
<TableCell key={id}>
{columnLabel}
</TableCell>
))
}
</TableRow>
</TableHead>
<TableBody>
{
tableData.map(row => (
<TableRow key={row.id}>
{
tableColumns.map(({id, property}) => (
<TableCell key={id}>
{row[property]}
</TableCell>
))
}
</TableRow>
))
}
</TableBody>
</Table>
</TableContainer>
)
const App = () => {
const [state, setState] = useState({
data: sampleData,
columns: sampleColumns,
filterObj: sampleColumns.reduce((r,{property}) => (r[property]='', r), {})
}),
onFilterApply = ({target:{name,value}}) => {
const newFilterObj = {...state.filterObj, [name]: value}
setState({
...state,
filterObj: newFilterObj,
data: sampleData.filter(props =>
Object
.entries(newFilterObj)
.every(([key,val]) =>
!val.length ||
props[key].toLowerCase().includes(val.toLowerCase()))
)
})
}
return (
<Container>
<MyFilter
filterProperties={state.columns}
onFilter={onFilterApply}
/>
<MyTable
tableData={state.data}
tableColumns={state.columns}
/>
</Container>
)
}
render (
<App />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><script src="https://unpkg.com/#material-ui/core#latest/umd/material-ui.development.js"></script><div id="root"></div>
As Sudhanshu pointed out, you should create event listeners for all these select dropdowns and then update state based on that.
I created a small sample of how I would do it, but just be warned that this isn't tested and I just wrote it without actually running the code or anything. So it is buggy for sure in some regard.
const fullData = ['first', 'second', 'third'];
const BigFilter = () => {
const [activeFilters, setActiveFilters] = useState([]);
const [filteredValues, setFilteredValues] = useState([]);
const handleFilterChange = (event) => {
const { target } = event;
const isInFilter = activeFilters.some((element) => element.name === target.name);
if (!isInFilter) {
setActiveFilters((currentState) => {
return [...currentState, { name: target.name, value: target.value }];
});
} else {
setActiveFilters((currentState) => {
return [...currentState.filter((x) => x.name !== target.name), { name: target.name, value: target.value }];
});
}
};
useEffect(() => {
// Just set full data as filtered values if no filter is active
if (activeFilters.length === 0) {
setFilteredValues([...fullData]);
return;
};
let finalData = [...fullData];
// Returns undefined if it cannot find the element with .name === 'list' in array, otherwise it will return that element
const listData = activeFilters.find((element) => (element.name = 'list'));
if (listData) {
// Do some filtering for first select/dropdown
const { value } = listData;
// value is the value of your select dropdown that was selected
finalData = finalData.filter((x) => x.something > 0);
}
// Returns undefined if it cannot find the element with .name === 'list' in array, otherwise it will return that element
const statusData = activeFilters.find((element) => (element.name = 'status'));
if (statusData) {
// Do some filtering for second select/dropdown
const { value } = statusData;
// value is the value of your select dropdown that was selected
finalData = finalData.filter((x) => x.something > 0);
}
// Returns undefined if it cannot find the element with .name === 'list' in array, otherwise it will return that element
const amountData = activeFilters.find((element) => (element.name = 'amount'));
if (amountData) {
// Do some filtering for third select/dropdown
const { value } = amountData;
// value is the value of your select dropdown that was selected
finalData = finalData.filter((x) => x.something > 0);
}
setFilteredValues(finalData);
// You can go with multiple if statements to filter everything step by step
}, [activeFilters]);
return (
<>
<select name="list" onChange={handleFilterChange}>
<option>List Option 1</option>
</select>
<select name="status" onChange={handleFilterChange}>
<option>Status Option 1</option>
</select>
<select name="amount" onChange={handleFilterChange}>
<option>Amount Option 1</option>
</select>
<div>
{/* Render filtered values */}
{filteredValues.map((singleValue) => singleValue.name)}
</div>
</>
);
};
The basic idea here is that all your <select> elements react to the same event listener, making it easier to coordinate.
You got two basic arrays as state (activeFilters and filteredValues). When onChange handler is triggered, you check the name of the filter and check if that filter is already present in your activeFilters state. If it isn't, you add its name and value to that state. That's why I used name="some name" on each <select> in order to identify it somehow. In other case, if the filter is already present in that state, we remove it and just add its entry again with the new value. (This can probably be written way better, but it's just to give you an idea.)
Both of these cases set new state for active filters with setActiveFilter. Then we have the useEffect hook below which filters all the data based on active filters. As you can see it has that dependency array as a second argument and I added activeFilters variable to it so that every time activeFilters updates it will trigger all the logic in useEffect and it will change your filteredValues.
The logic in useEffect will go step by step and check if each filter is active and filter data for each of them if they are active step by step. If the first filter is active it will filter data that's needed and store it again in finalData and then it will go to the second if statement and if the filter for that is active it will perform another filter, but now on already filtered data. In the end, you should get data that passes through all active filters. I'm sure there's a better way of doing this, but it's a start.
Btw, usually I wouldn't do this
finalData = finalData.filter((x) => x.something > 0);
Re-assigning the same variable with filtered data from it, but I would say it's ok in this case since that finalData variable was created in that useEffect scope and it cannot be mutated from outside the scope. So it's easy to track what it is doing.
I'm sorry if this doesn't work, but it might guide you to your solution.
You can add a filter to the fullData array and provide the value of each of the dropdowns to the filter function
fullData.filter(element => {
return element.account == first && element.account == second && element.account == third;
});
You can also put in checks for the filters, like if the value is just '' then return false i.e return the whole array else return the filtered list

How to programmatically clear/reset React-Select?

ReactSelect V2 and V3 seems to have several props like clearValue, resetValue and setValue. Whatever I'm trying, I'm not able to clear the selections programmatically. resetValue seems not to be accessible from the outside.
selectRef.setValue([], 'clear')
// or
selectRef.clearValue()
This does not clear the current selection.
Do I miss something here or is it not fully implemented yet?
I came across this problem myself and managed to fix it by passing a key to the React-Select component, with the selected value appended to it. This will then force the ReactSelect to re-render itself when the selection is updated.
I hope this helps someone.
import ReactSelect from 'react-select';
...
<ReactSelect
key={`my_unique_select_key__${selected}`}
value={selected || ''}
...
/>
If you're using react-select you can try to pass null to value prop.
For example:
import React from "react";
import { render } from "react-dom";
import Select from "react-select";
class App extends React.Component {
constructor(props) {
super(props);
const options = [
{ value: "one", label: "One" },
{ value: "two", label: "Two" }
];
this.state = {
select: {
value: options[0], // "One" as initial value for react-select
options // all available options
}
};
}
setValue = value => {
this.setState(prevState => ({
select: {
...prevState.select,
value
}
}));
};
handleChange = value => {
this.setValue(value);
};
handleClick = () => {
this.setValue(null); // here we reset value
};
render() {
const { select } = this.state;
return (
<div>
<p>
<button type="button" onClick={this.handleClick}>
Reset value
</button>
</p>
<Select
name="form-field-name"
value={select.value}
onChange={this.handleChange}
options={select.options}
/>
</div>
);
}
}
render(<App />, document.getElementById("root"));
Here's a working example of this.
You can clear the value of react select using the ref.
import React, { useRef } from "react";
import Select from "react-select";
export default function App() {
const selectInputRef = useRef();
const onClear = () => {
selectInputRef.current.select.clearValue();
};
return (
<div className="App">
<h1>Select Gender</h1>
<Select
ref={selectInputRef}
options={[
{ value: "male", label: "Male" },
{ value: "female", label: "Female" }
]}
/>
<button onClick={onClear}>Clear Value</button>
</div>
);
}
Here is the CodeSandbox link
Just store the value in the state, and change the state programmatically using componentDidUpdate etc...
class Example extends Component {
constructor() {
super()
}
state = {
value: {label: 'Default value', key : '001'}
}
render() {
return(
<Select
...
value={this.state.value}
...
/>
)
)}
Note: 'value' should be an object.
A simple option would be to pass null to the value prop.
<Select value={null} />
This is my working implementation of a React-Select V3 cleared programmatically with Hooks.
You can play with it in the CodeSandbox DEMO. Any feedback is welcome.
const initialFormState = { mySelectKey: null };
const [myForm, setMyForm] = useState(initialFormState);
const updateForm = value => {
setMyForm({ ...myForm, mySelectKey: value });
};
const resetForm = () => {
setMyForm(initialFormState);
};
return (
<div className="App">
<form>
<Select name = "mySelect"
options = {options}
value = {options.filter(({ value }) => value === myForm.mySelectKey)}
getOptionLabel = {({ label }) => label}
getOptionValue = {({ value }) => value}
onChange = {({ value }) => updateForm(value)} />
<p>MyForm: {JSON.stringify(myForm)}</p>
<input type="button" value="Reset fields" onClick={resetForm} />
</form>
</div>
);
If someone looking for solution using Hooks. React-Select V3.05:
const initial_state = { my_field: "" }
const my_field_options = [
{ value: 1, label: "Daily" },
{ value: 2, label: "Weekly" },
{ value: 3, label: "Monthly" },
]
export default function Example(){
const [values, setValues] = useState(initial_state);
function handleSelectChange(newValue, actionMeta){
setValues({
...values,
[actionMeta.name]: newValue ? newValue.value : ""
})
}
return <Select
name={"my_field"}
inputId={"my_field"}
onChange={handleSelectChange}
options={my_field_options}
placeholder={values.my_field}
isClearable={true}
/>
}
Along the top answer, please note that the value needs to be "null" and not "undefined" to clear properly.
If you check Select component in React Developers panel you will see that it is wrapped by another – State Manager. So you ref is basically ref to State manager, but not to Select itself.
Luckily, StateManager has state) and a value object which you may set to whatever you want.
For example (this is from my project, resetGroup is onClick handler that I attach to some button in DOM):
<Select onChange={this.handleGroupSelect}
options={this.state.groupsName.map(group =>
({ label: group, value: group }) )}
instanceId="groupselect"
className='group-select-container'
classNamePrefix="select"
placeholder={this.context.t("Enter name")}
ref={c => (this.groupSelect = c)}
/>
resetGroup = (e) => {
e.preventDefault()
this.setState({
selectedGroupName: ""
})
this.groupSelect.state.value.value = ""
this.groupSelect.state.value.label = this.context.t("Enter name")
}
For those who are working with function component, here's a basic demo of how you can reset the react select Based on some change/trigger/reduxValue.
import React, { useState, useEffect } from 'react';
import Select from 'react-select';
const customReactSelect = ({ options }) => {
const [selectedValue, setSelectedValue] = useState([]);
/**
* Based on Some conditions you can reset your value
*/
useEffect(() => {
setSelectedValue([])
}, [someReduxStateVariable]);
const handleChange = (selectedVal) => {
setSelectedValue(selectedVal);
};
return (
<Select value={selectedValue} onChange={handleChange} options={options} />
);
};
export default customReactSelect;
in v5, you can actually pass the prop isClearable={true} to make it clearable, which easily resets the selected value
You can set the value to null
const [selectedValue, setSelectedValue] = useState();
const [valueList, setValueList] = useState([]);
const [loadingValueList, setLoadingValueList] = useState(true);
useEffect(() => {
//on page load update valueList and Loading as false
setValueList(list);
loadingValueList(false)
}, []);
const onClear = () => {
setSelectedValue(null); // this will reset the selected value
};
<Select
className="basic-single"
classNamePrefix="select"
value={selectedValue}
isLoading={loadingValueList}
isClearable={true}
isSearchable={true}
name="selectValue"
options={valueList}
onChange={(selectedValue) =>
setSelectedValue(selectedValue)}
/>
<button onClick={onClear}>Clear Value</button>
react-select/creatable.
The question explicitly seeks a solution to react-select/creatable. Please find the below code, a simple answer and solution to the question. You may modify the code for your specific task.
import CreatableSelect from "react-select/creatable";
const TestAction = (props) => {
const { buttonLabelView, className } = props;
const selectInputRef = useRef();
function clearSelected() {
selectInputRef.current.select.select.clearValue();
}
const createOption = (label, dataId) => ({
label,
value: dataId,
});
const Options = ["C1", "C2", "C3", "C4"]?.map((post, id) => {
return createOption(post, id);
});
return (
<div>
<CreatableSelect
ref={selectInputRef}
name="dataN"
id="dataN"
className="selctInputs"
placeholder=" Select..."
isMulti
options={Options}
/>
<button onClick={(e) => clearSelected()}> Clear </button>
</div>
);
};
export default TestAction;
In case it helps anyone, this is my solution: I created a button to clear the selected value by setting state back to it's initial value.
<button onClick={() => this.clearFilters()} >Clear</button>
clearFilters(){
this.setState({ startTime: null })
}
Full code sample below:
import React from "react"
import Select from 'react-select';
const timeSlots = [
{ value: '8:00', label: '8:00' },
{ value: '9:00', label: '9:00' },
{ value: '10:00', label: '10:00' },
]
class Filter extends React.Component {
constructor(){
super();
this.state = {
startTime: null,
}
}
startTime = (selectedTime) => {
this.setState({ startTime: selectedTime });
}
clearFilters(){
this.setState({
startTime: null,
})
}
render(){
const { startTime } = this.state;
return(
<div>
<button onClick={() => this.clearFilters()} >Clear</button>
<Select
value={startTime}
onChange={this.startTime}
options={timeSlots}
placeholder='Start time'
/>
</div>
)
}
}
export default Filter
passing null in value attribute of the react-select will reset it.
if you are using formik then use below code to reset react-select value.
useEffect(()=>{
formik.setFieldValue("stateName", [])
},[])
Where stateName is html field name.
if you want to change value according to another dropdown/select (countryName) then pass that field value in useEffect array like below
useEffect(()=>{
formik.setFieldValue("stateName", [])
},[formik.values.countryName])
Zeeshan's answer is indeed correct - you can use clearValue() but when you do so, the Select instance isn't reset to your defaultValue prop like you might be thinking it will be. clearValue() returns a general Select... label with no data in value.
You probably want to use selectOption() in your reset to explicitly tell react-select what value/label it should reset to. How I wired it up (using Next.js, styled-components and react-select):
import { useState, useRef } from 'react'
import styled from 'styled-components'
import Select from 'react-select'
// Basic button design for reset button
const UIButton = styled.button`
background-color: #fff;
border: none;
border-radius: 0;
color: inherit;
cursor: pointer;
font-weight: 700;
min-width: 250px;
padding: 17px 10px;
text-transform: uppercase;
transition: 0.2s ease-in-out;
&:hover {
background-color: lightgray;
}
`
// Using style object `react-select` library indicates as best practice
const selectStyles = {
control: (provided, state) => ({
...provided,
borderRadius: 0,
fontWeight: 700,
margin: '0 20px 10px 0',
padding: '10px',
textTransform: 'uppercase',
minWidth: '250px'
})
}
export default function Sample() {
// State for my data (assume `data` is valid)
const [ currentData, setCurrentData ] = useState(data.initial)
// Set refs for each select you have (one in this example)
const regionOption = useRef(null)
// Set region options, note how I have `data.initial` set here
// This is so that when my select resets, the data will reset as well
const regionSelectOptions = [
{ value: data.initial, label: 'Select a Region' },
{ value: data.regionOne, label: 'Region One' },
]
// Changes data by receiving event from select form
// We read the event's value and modify currentData accordingly
const handleSelectChange = (e) => {
setCurrentData(e.value)
}
// Reset, notice how you have to pass the selected Option you want to reset
// selectOption is smart enough to read the `value` key in regionSelectOptions
// All you have to do is pass in the array position that contains a value/label obj
// In my case this would return us to `Select a Region...` label with `data.initial` value
const resetData = () => {
regionOption.current.select.selectOption(regionSelectOptions[0])
setCurrentData(data.initial)
}
// notice how my `UIButton` for the reset is separate from my select menu
return(
<>
<h2>Select a region</h2>
<Select
aria-label="Region select menu"
defaultValue={ regionSelectOptions[0] }
onChange={ event => handleDataChange(event) }
options={ regionSelectOptions }
ref={ regionOption }
styles={ selectStyles }
/>
<UIButton
onClick={ resetData }
>
Reset
</UIButton>
</>
)
}
Nether of the solution help me.
This work for me:
import React, { Component, Fragment } from "react";
import Select from "react-select";
import { colourOptions } from "./docs/data";
export default class SingleSelect extends Component {
selectRef = null;
clearValue = () => {
this.selectRef.select.clearValue();
};
render() {
return (
<Fragment>
<Select
ref={ref => {
this.selectRef = ref;
}}
className="basic-single"
classNamePrefix="select"
defaultValue={colourOptions[0]}
name="color"
options={colourOptions}
/>
<button onClick={this.clearValue}>clear</button>
</Fragment>
);
}
}
None of the top suggestions worked for me and they all seemed a bit over the top. Here's the important part of what worked for me
<Select
value={this.state.selected && Object.keys(this.state.selected).length ? this.state.selected : null},
onChange={this.handleSelectChange}
/>
StateManager is abolished now, at least after version 5.5.0.
Now if you use ref, you can just do it like this:
selectRef = null
<Select
...
ref={c => (selectRef=c)}
/>
clearValue = () => {
selectRef.clearValue();
};
Here this c would be the Select2 React Component
This bugged me so here it is:
React select uses arrays so you have to pass an empty array not null.
Using React's useState:
import ReactSelect from 'react-select'
const Example = () => {
const [val, setVal] = useState()
const reset = () => {
setVal([])
}
return <ReactSelect
value={val}/>
}
export default Example
Create a function called onClear, and setSelected to empty string.
Inside the handle submit function, call the onClear function.
This will work perfectly.
Example code:
const onClear = () => {
setSelected("");
};
const handleSubmit = ()=>{
1 your data first ......(what you are posting or updating)
2 onClear();
}
if you are using formik then use below code to reset react-select value.
useEffect(()=>{
formik.setFieldValue("stateName", [])
},[])
Where stateName is html field name.
if you want to change value according to another dropdown/select (countryName) then pass that field value in useEffect array like below
useEffect(()=>{
formik.setFieldValue("stateName", [])
},[formik.values.countryName])
I use redux-observable.
Initial state:
firstSelectData: [],
secondSelectData:[],
secondSelectValue: null
I create an action for filling first select. on change of first select, I call an action to fill second one.
In success of fill first select I set (secondSelectData to [], secondSelectValue to null)
In success of fill second select I set (secondSelectValue to null)
on change of second select, I call an action to update secondSelectValue with the new value selected

Categories

Resources