I have a list from backend. This list is displaying the user's invoices. I want the button to be disabled if the user doesn't select at least one invoice. If the user selects at least 1 invoice, I want the button to be active. But the invoices and the button are in different components.
payment-table.js
Here the IDE warns me like this; 'setEnabledButton' is assigned a value but never used.
const [selectedItems, setSelectedItems] = useState([]);
const [enabledButton, setEnabledButton] = useState();
useEffect(() => {
if (selectedItems.length > 0) {
setEnabledButton = true;
} else {
setEnabledButton = false;
}
}, [selectedItems]);
policy-payment-info.js
<div className={hiddenControlButtonClass}>
<AS.Button variant="outlined" onClick={handlePayment} enabledButton>
{t('policy-payment-info.continue')}
</AS.Button>
</div>
First set default state for your useState function :
const [enabledButton, setEnabledButton] = useState(false);
After this you need to execute the seState by add ()
Like this :
useEffect(() => {
if (selectedItems.length > 0) {
setEnabledButton(true);
} else {
setEnabledButton(false);
}
}, [selectedItems]);
And then, you need to use this state to enable or disable the button :
<div className={hiddenControlButtonClass}>
<AS.Button variant="outlined" onClick={handlePayment} disabled={enabledButton}>
{t('policy-payment-info.continue')}
</AS.Button>
</div>
Your syntax is a little bit off. setEnabledButton is a function that has to be invoked and given the a certain value. Try this:
const [selectedItems, setSelectedItems] = useState([]);
const [enabledButton, setEnabledButton] = useState(false);
useEffect(() => {
if (selectedItems.length > 0) {
setEnabledButton(true);
} else {
setEnabledButton(false);
}
}, [selectedItems]);
Related
How can we handle disabling of dropdown selection in react hooks web app without refreshing the page ? In my case, I have a multiselect dropdown box. When I selected an item from the dropdown, display a text field, once after typing some text and submit it, details get saved into database. Once saved into DB, the respective dropdown item should be disabled for selection.
But in my case, its not immediately disabled after submit. Its is disabled only after i manually refresh the page. How can I fix this issue, can someone please advise ?
const [option, setOption] = useState([]);
const [selectedOption, setSelectedOption] = useState([]);
const {
register,
handleSubmit,
watch,
formState: { errors },
reset,
} = useForm();
const refSelect = useRef(null);
const [submittedNominees, setSubmittedNominees] = useState([{}]);
const [maxOptions, setMaxOptions] = useState(0);
const [showOptions, setShowOptions] = useState(false);
const focusOnInput = () => {
setTimeout(() => {
document.querySelector("input").focus();
// Adding some delay to allow the component to re-mount
}, 10);
};
const handleTypeSelect = (e, i) => {
const copy = [...selectedOption];
copy.push(e[3 - maxOptions]); //A.H-fix error: select one more record it still console log the pre selected one
setSelectedOption(copy);
setMaxOptions((prevState) => prevState - 1); //A.H-making maxOption dynamic
focusOnInput();
};
const handleTypeRemove = (e) => {
const copy = [...selectedOption];
let index = copy.indexOf(e);
copy.splice(index, 1);
setSelectedOption(copy);
setMaxOptions((prevState) => prevState + 1);
// immutating state (best practice)
const updateList = nomRegister.map((item) => {
return { ...item };
});
//delete the specific array case depends on the id
updateList.splice(index, 1);
setNomRegister(updateList);
focusOnInput();
};
useEffect(() => {
const fetchData = async () => {
const userEmail = localStorage.getItem("loginEmail");
try {
let res = [];
res = await Axios.get(
`${appURL}/service/submittednominations`,
{params:{userEmail}}
);
const data1 = res.data;
console.log(data1, "data1");
setSubmittedNominees(data1);
setMaxOptions(3 - data1.length); //maxOption dynamic because we don't the length of data from submittednominations
console.log("Submitted nominations :" + JSON.stringify(data1));
} catch (e) {
console.log(e);
}
};
fetchData();
}, []);
Droddown box:
<section className="col1">
<div className='nomineeSelectBox'>
<div id='dialog2' className='triangle_down1' />
<div className='arrowdown'>
<Multiselect
ref={refSelect}
onSelect={(e) => handleTypeSelect(e, selectedOption.length)}
onRemove={handleTypeRemove}
options={!showOptions ? [] : option}
displayValue='displayValue'
disablePreSelectedValues={true}
selectedValues={submittedNominees}
showCheckbox={true}
emptyRecordMsg={"Maximum nominees selected !"}
/>
</div>
</div>
</section>
If you add the changed state to the dependency list of the useEffect(), it will re-run that section of code inside.
useEffect(() => {
// this code will re-run every time "selectedOptions" is changed
// therefore every time "handleTypeSelect()" or "handleRemove()" is run
const fetchData = async () => {
...
}, [selectedOption])
This will trigger a fresh list of submittedNominees, and cause a rerender (just the component, not the whole page) because submittedNominees is one of the render properties
<Multiselect
...
selectedValues={submittedNominees}
...
/>
Alternatively (and probably quicker UI), make a call to setSubmittedNominees() inside handleTypeSelect() and handleRemove(). That will also trigger a component rerender.
Using React Native Async Storage. I have a single storage item "favorites" which holds an array of post IDs. Works great, adding and removing articles successfully, but there is a problem:
In "Favorites" TabScreen - showing all currently favorited posts - it works but is rendering an outdated list of items.
E.g. Load the app (Expo), the Favorites screen shows current list, but if I go ahead and a remove an item from the array, and go back to the Favorites screen, it still shows the removed item. Same if I add a new item and navigate back to Favorite screen, new item missing.
It only updates if I reload the app.
If you don't mind taking a look, here's the relevant code:
const POSTS_QUERY = gql`
query posts {
posts {
data {
id
attributes {
title
}
}
}
}
`
export default ({ navigation }) => {
const [isLoading, setIsLoading] = useState(true)
const [isAsyncLoading, setIsAsyncLoading] = useState(true)
const [nodes, setNodes] = useState({})
const favCurrent = useRef();
const getFavorites = async () => {
try {
const value = await AsyncStorage.getItem('favorites')
if(value !== null) {
const val = JSON.parse(value)
favCurrent.current = val
setIsAsyncLoading(false)
console.log(favCurrent.current,'favCurrent')
}
} catch(e) {
// error reading value
}
}
getFavorites()
// note the console.log above always shows correct, updated list
const { data, loading, error } = useQuery(POSTS_QUERY)
useEffect(() => {
if (loading) {
return <Loading/>
}
if (error) {
return <Text>Error :( </Text>;
}
const filterNodes = data?.posts?.data.filter(item => favCurrent.current.includes(item.id));
setNodes(filterNodes)
setIsLoading(false)
}, [data]);
if( isLoading || isAsyncLoading ) {
return (
// 'Loading...'
)
} else {
return (
// 'List of items...'
)
}
}
I've also tried a solution from this answer, to no effect:
const [favData, setFavData] = useState(null)
useEffect(() => {
async function getFavorites() {
setFavData(await AsyncStorage.getItem('favorites'));
}
getFavorites()
setIsAsyncLoading(false)
}, []);
i am trying to make a filter feature for a website i am working on, i am using an html range slider. The problem is that the values update just if they are going down, for example if i set the slider to $500, only the products that cost $500 or less will appear, if i set the value lower, it's going to work how is supposed to work, but if i try to set the value bigger, the items will not filter, for example, the value is set to $500, if set the value to $600 only the items that are $500 or less will render, but not the $600 ones.
here is my code:
const Shop = () => {
const [sliderValue, setValue] = useState(0);
const [filterItems, setApplyFilter] = useState(false);
const [newData, setData] = useState(data);
const checkChange = () => {
if (sliderValue > 3) {
setApplyFilter(true);
} else {
setApplyFilter(false);
}
console.log(applyFilter);
};
const applyFilter = () => {
if (filterItems === true) {
const filteredData = newData.filter((item) => item.price <= sliderValue);
console.log(filteredData);
setData(filteredData);
} else {
setData(data);
}
};
useEffect(() => {
checkChange();
applyFilter();
}, [sliderValue]);
const handleChange = (value) => {
setValue(value);
};
return (
<div className="slider-container">
<input
type="range"
min={0}
max={1000}
value={sliderValue}
onChange={(e) => handleChange(e.target.value)}
className="slider"
/>
</div>
);
}
The problem: you are changing the data with setData(), so every time you move your scrollbar this deletes some data. If you want to keep a constant information that is available to all your application, consider using useRef(). This creates a persistent object for the full lifetime of the component.
import { useRef } from 'react'
const Shop = () => {
const dataArr = useRef(data)
...
const applyFilter = () => {
if (filterItems === true) {
// Access with "current" attribute
const filteredData = dataArr.current
.filter((item) => item.price <= sliderValue);
setData(filteredData);
}
}
}
Working example
I think it's something to do with this two lines:
const filteredData = newData.filter((item) => item.price <= sliderValue);
setData(filteredData);
Once you have filtered your data once, the value of newData in your state will be only the already filtered data.
Let's say we start with prices: newData=[100, 200, 300, 400]
We filter it for the first time down to 200, so now newData=[100, 200]
Next we filter up to 300, but newData only has [100, 200]
So just change those two lines for:
const filteredData = data.filter((item) => item.price <= sliderValue);
setData(filteredData);
This is asuming you have a variable data declared or imported somewhere with the comple data set.
You don't need state for data array since it can be determined on every render based on some other state.
const Shop = ({ inputData }) => {
const [sliderValue, setValue] = useState(0);
// This flag is deterministic based on sliderValue, so determine it here
const filterItems = sliderValue > 3;
// The items that will make it past the filter are deterministic, based on your filterItems flag
// so no state is necessary
const renderItems = filterItems ? inputData.filter(i => i.price <= sliderValue) : inputData;
const handleChange = (value) => {
setValue(value);
};
return ...
};
I have a <Day/> component which renders a table full of times through the day.
Right now I have a handleChange function in the <TableLayout/> which is listening for changes in the text-area.
function TableLayout({ sendEventTargetToParent }) {
var [amountOfRows, setAmountOfRows] = useState(24);
var [textValue, setTextValue] = useState('');
var [eventName, setEventName] = useState('');
useEffect(() => {
sendDataToParent();
}, [eventName, textValue]);
function sendDataToParent() {
sendEventTargetToParent(eventName, textValue);
}
function handleChange(event) {
var { name, value } = event.target;
setEventName(name);
setTextValue(value);
}
The markup:
<TextArea
rows={2}
name="textarea"
value={textValue}
onChange={(e) => handleChange(e)}
placeholder="Tell us more"
/>
I'd like to send that info from the text-area to the component, you probably noticed already noticed the prop TableLayout is consuming:
function TableLayout({ sendEventTargetToParent }) {
Which I thought belongs in useEffect because it's creating a side-effect:
useEffect(() => {
sendDataToParent();
}, [eventName, textValue]);
function sendDataToParent() {
sendEventTargetToParent(eventName, textValue);
}
Anyway the idea is when those local variables/state change in TableLayout it gets kicked up to Day...
export default function Day({ dayInfo }) {
var [dayInfoInChild, setDayInfoInChild] = useState({});
var [currentDate, setCurrentDate] = useState('');
var [timeOfDay, setTimeOfDay] = useState('');
var [eventNameFromChild, setEventNameFromChild] = useState('');
var [textValueFromChild, setTextValueFromChild] = useState('');
function getEventTargetFromChild(eventName, textValue) {
setEventNameFromChild(eventName);
setTextValueFromChild(textValue);
}
useEffect(() => {
if (dayInfo !== null) {
var modifiedDayInfo = dayInfo
.split(' ')
.map((item) => {
if (item.indexOf(',')) return item.replace(/,/g, '');
})
.join('-');
setCurrentDate(modifiedDayInfo);
if (localStorage.getItem(modifiedDayInfo)) {
var stringVersionOfModifiedDayInfo = modifiedDayInfo;
modifiedDayInfo = JSON.parse(localStorage.getItem(modifiedDayInfo));
if (!dayInfoInChild.hasOwnProperty(stringVersionOfModifiedDayInfo)) {
setDayInfoInChild({
...dayInfoInChild,
[stringVersionOfModifiedDayInfo]: modifiedDayInfo,
});
}
} else {
localStorage.setItem(modifiedDayInfo, JSON.stringify({}));
}
if (dayInfoInChild.hasOwnProperty(currentDate)) {
setDayInfoInChild({
...dayInfoInChild,
[currentDate]: {
[eventNameFromChild]: textValueFromChild,
},
});
}
}
}, []);
And the code in the Day'suseEffectessentially creates an JSON object in localstorage with its key based on the date if needed if not it pulls that key and object converts it to a JS object and puts it into the state usinguseState`.
var [eventNameFromChild, setEventNameFromChild] = useState('');
var [textValueFromChild, setTextValueFromChild] = useState('');
function getEventTargetFromChild(eventName, textValue) {
setEventNameFromChild(eventName);
setTextValueFromChild(textValue);
}
this is the part where the state from the Child gets set in Day.
if (dayInfoInChild.hasOwnProperty(currentDate)) {
setDayInfoInChild({
...dayInfoInChild,
[currentDate]: {
[eventNameFromChild]: textValueFromChild,
},
});
}
But right now I able to add one key stroke into the object and then the UI locks:
So how can I create a cohesive flow from my Child to the Parent using hooks?
By understanding your problem i suggest you to use useContext
Here is a link which can help you to solve this problem.
https://vimalselvam.com/post/react-hooks-lift-up-pass-down-state-using-usecontext-and-usereducer/
I'm creating a search input with a submit button and a clear button. When submit button is clicked, it will trigger a search request with available input value. When clear button is clicked, it will also trigger a search request with empty value. I try to use comparison in useEffect dependency array to trigger effect when the search value is empty to accomodate the clear button.
const Test = ({ doGetData }) => {
const [status, setStatus] = useState(null);
const [activePage, setActivePage] = useState(1);
const [term, setTerm] = useState("");
const handleFilter = () => {
const query = {
page: activePage,
q: term,
active: status
};
doGetData(query);
};
useEffect(() => {
handleFilter();
}, [status, activePage, term === ""]);
const setEmptySearch = () => setTerm("");
const handleInputChange = e => {
const { value } = e.currentTarget;
setTerm(value);
};
return (
<SearchInput
handleFilter={handleFilter}
handleDismissSearch={setEmptySearch}
handleInputChange={handleInputChange}
status={status}
term={term}
/>
);
};
It worked well when I click search button, click the clear button, and also when I manually erase the input.But, the only problem I have is when I type the first letter, it will trigger the search. Can you help me guys?
Just check with an if-statement inside of your useEffect-function:
useEffect(() => {
if(term === "") {
handleFilter();
}
}, [status, activePage, term]);