React hooks add mapped checkbox values to array or remove it - javascript

I tried some solutions on the net but cant fix it so i need to ask. I have checkboxes which is mapped in render. I want to add the values of the checkboxes to array state if checked or remove the value if unchecked. Thanks for helps Things i tried ;
My jsx ;
{props.edit && <input type="checkbox" value={props.id} onChange={(e) => setDeleteId(e)}/>}
Functions ;
const [deleteId, setdeleteId] = useState([]);
const setDeleteId = (e) => {
setdeleteId([...deleteId, e.target.value]);
}
useEffect(() => {
console.log(deleteId);
}, [deleteId])

Your event handler looks like it's always just adding the checkbox id to the state - you need to also remove it when the checkbox gets unchecked, e.g. -
const [selected, setSelected] = useState([]);
const handleChange = event => {
const { checked, value } = event.currentTarget;
setSelected(
prev => checked
? [...prev, value]
: prev.filter(val => val !== value)
);
};
Here is a demo on StackBlitz showing how it all fits together.

Related

how to use single save bar on multiple input fields - react js

image of save bar and input fields
I am new to react, I have a problem I want to display a Savebar on [onChange] on a number of input fields.
When input length is greater than 0 then it will show, but I have multiple input, my problem is when I type in 2 0r 3 fields it shows multiple time, but I want to show it by conditions that, when it is already open then it cannot open again. thanks in advance.
const [showBanner, setShowBanner] = useState(false);
const handelBanner = (e) => {
if (e.target.value.length > 0) {
setShowBanner(true)
}else{
setShowBanner(false)
}
}
return:
{showBanner ? (
<> save bar </>
) : null}
You can save all the input value to the state. Watch all input change and do your conditional logic in useEffect. Like this:
const [showBanner, setShowBanner] = useState(false);
const [input1, setInput1] = useState('');
const [input2, setInput2] = useState('');
useEffect(() => {
if () { // put your condition
setShowBanner(true);
} else {
setShowBanner(false)
}
}, [input1, input2])
const handleChangeInput1 = e => setInput1(e.target.value);
const handleChangeInput2 = e => setInput2(e.target.value);

React: Handling focus using array of useRef

I have created a basic Todo app but having a hard time in implementing proper focus into it. Following are the requirements for focus:
Focus should be on the newly created input field when "Add" is clicked so the user can begin typing right away.
On deleting an item "Button X", focus will move to the input field in the row which replaced the deleted row, nowhere if there are no fields left, or on the new last row if the last row was deleted.
On moving an item up, focus should be placed on the newly moved field, and all associated buttons should move alongside the element. If a field is already at the top of a list, no reordering should occur, but focus should be transferred to the topmost field nonetheless.
On moving an item down, same principle should apply here (focus should be placed on the field that is moved down). If its the last field, focus it nonetheless.
Here is my implementation.
App.js:
import React, { useState, useRef } from "https://cdn.skypack.dev/react#17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom#17.0.1";
const App = () => {
const [myRows, setMyRows] = useState([]);
const focusInput = useRef([]);
const onAddRow = () => {
setMyRows((prevRows) => {
return [
...prevRows,
{ id: prevRows.length, text: "", up: "↑", down: "↓", delete: "X" },
];
});
focusInput.current[myRows.length - 1].focus();
};
const onMoveUp = (index) => (event) => {
const currentState = [...myRows];
if (index !== 0) {
const prevObject = currentState[index - 1];
const nextObject = currentState[index];
currentState[index - 1] = nextObject;
currentState[index] = prevObject;
setMyRows(currentState);
}
};
const onMoveDown = (index) => (event) => {
const currentState = [...myRows];
if (index !== myRows.length - 1) {
const currObject = currentState[index];
const nextObject = currentState[index + 1];
currentState[index] = nextObject;
currentState[index + 1] = currObject;
setMyRows(currentState);
}
};
const onDelete = (index) => (event) => {
const currentState = [...myRows];
currentState.splice(index, 1);
setMyRows(currentState);
};
const onTextUpdate = (id) => (event) => {
setMyRows((prevState) => {
const data = [...prevState];
data[id] = {
...data[id],
text: event.target.value,
};
return data;
});
};
return (
<div className="container">
<button onClick={onAddRow}>Add</button>
<br />
{myRows?.map((row, index) => {
return (
<div key={row.id}>
<input
ref={(el) => (focusInput.current[index] = el)}
onChange={onTextUpdate(index)}
value={row.text}
type="text"></input>
<button onClick={onMoveUp(index)}>{row.up}</button>
<button onClick={onMoveDown(index)}>{row.down}</button>
<button onClick={onDelete(index)}>{row.delete}</button>
</div>
);
})}
</div>
);
}
ReactDOM.render(<App />,
document.getElementById("root"))
In my implementation when I click Add, focus changes but in unexpected way (First click doesn't do anything and subsequent click moves the focus but with the lag, i.e. focus should be on the next item, but it is at the prior item.
Would really appreciate if someone can assist me in this. Thanks!
The strange behaviour that you observe is caused by the fact that state updates are asynchronous. When you, in onAddRow, do this:
focusInput.current[myRows.length - 1].focus()
then myRows.length - 1 is still the last index of the previous set of rows, corresponding to the penultimate row of what you're actually seeing. This explains exactly the behaviour you're describing - the new focus is always "one behind" where it should be, if all you're doing is adding rows.
Given that description, you might think you could fix this by just replacing myRows.length - 1 with myRows.length in the above statement. But it isn't so simple. Doing this will work even less well, because at the point this code runs, right when the Add button is clicked, focusInput hasn't yet been adjusted to the new length, and nor in fact has the new row even been rendered in the DOM yet. That all happens a little bit later (although appears instantaneous to the human eye), after React has realised there has been a state change and done its thing.
Given that you are manipulating the focus in a number of different ways as described in your requirements, I believe the easiest way to fix this is to make the index you want to focus its own piece of state. That makes it quite easy to manage focus in any way you want, just by calling the appropriate state-updating function.
This is implemented in the code below, which I got working by testing it out on your Codepen link. I've tried to make it a snippet here on Stack Overflow, but for some reason couldn't get it to run without errors, despite including React and enabling Babel to transform the JSX - but if you paste the below into the JS of your Codepen, I think you'll find it working to your satisfaction. (Or, if I've misinterpreted some requirements, hopefully it gets you at least a lot closer than you were.)
Rather than just leaving you to study the code yourself though, I'll explain the key parts, which are:
the introduction of that new state variable I just mentioned, which I've called focusIndex
as mentioned, the calling of setFocusIndex with an appropriate value whenever rows are added, removed or moved. (I've been trying to follow your requirements here and it seems to work well to me, but as I said, I may have misunderstood.)
the key is the useEffect which runs whenever focusIndex updates, and does the actual focusing in the DOM. Without this, of course, the focus will never be updated on calling setFocusIndex, but with it, calling that function will "always" have the desired effect.
one last subtlety is that the "always" I put above is not strictly true. The useEffect only runs when focusIndex actually changes, but when moving rows there are some situations where it is set to the same value it had before, but where you still want to move focus. I found this happening when clicking outside the inputs, then moving the first field up or the last one down - nothing happened, when we want the first/last input to be focused. This was happening because focusIndex was being set to the value it already had, so the useEffect didn't run, but we still wanted it to in order to set the focus. The solution I came up with was to add an onBlur handler to each input to ensure that the focus index is set to some "impossible" value (I chose -1, but something like null or undefined would have worked fine as well) when focus is lost - this may seem artificial but actually better represents the fact that when the focus is on no inputs, you don't want to have a "sensible" focusIndex, otherwise the React state is saying one of the inputs is focused, when none are. Note that I also used -1 for the initial state, for much the same reason - if it starts at 0 then adding the first row doesn't cause focus to change.
I hope this helps and my explanations are clear enough - if you're confused by anything, or notice anything going wrong with this implementation (I confess I have not exactly tested it to destruction), please let me know!
import React, { useState, useRef, useEffect } from "https://cdn.skypack.dev/react#17.0.1";
import ReactDOM from "https://cdn.skypack.dev/react-dom#17.0.1";
const App = () => {
const [myRows, setMyRows] = useState([]);
const focusInput = useRef([]);
const [focusIndex, setFocusIndex] = useState(-1);
const onAddRow = () => {
setMyRows((prevRows) => {
return [
...prevRows,
{ id: prevRows.length, text: "", up: "↑", down: "↓", delete: "X" },
];
});
setFocusIndex(myRows.length);
};
useEffect(() => {
console.log(`focusing index ${focusIndex} in`, focusInput.current);
focusInput.current[focusIndex]?.focus();
}, [focusIndex]);
const onMoveUp = (index) => (event) => {
const currentState = [...myRows];
if (index !== 0) {
const prevObject = currentState[index - 1];
const nextObject = currentState[index];
currentState[index - 1] = nextObject;
currentState[index] = prevObject;
setMyRows(currentState);
setFocusIndex(index - 1);
} else {
setFocusIndex(0);
}
};
const onMoveDown = (index) => (event) => {
const currentState = [...myRows];
if (index !== myRows.length - 1) {
const currObject = currentState[index];
const nextObject = currentState[index + 1];
currentState[index] = nextObject;
currentState[index + 1] = currObject;
setMyRows(currentState);
setFocusIndex(index + 1);
} else {
setFocusIndex(myRows.length - 1);
}
};
const onDelete = (index) => (event) => {
const currentState = [...myRows];
currentState.splice(index, 1);
setMyRows(currentState);
const newFocusIndex = index < currentState.length
? index
: currentState.length - 1;
setFocusIndex(newFocusIndex);
};
const onTextUpdate = (id) => (event) => {
setMyRows((prevState) => {
const data = [...prevState];
data[id] = {
...data[id],
text: event.target.value,
};
return data;
});
};
return (
<div className="container">
<button onClick={onAddRow}>Add</button>
<br />
{myRows?.map((row, index) => {
return (
<div key={row.id}>
<input
ref={(el) => (focusInput.current[index] = el)}
onChange={onTextUpdate(index)}
onBlur={() => setFocusIndex(-1)}
value={row.text}
type="text"></input>
<button onClick={onMoveUp(index)}>{row.up}</button>
<button onClick={onMoveDown(index)}>{row.down}</button>
<button onClick={onDelete(index)}>{row.delete}</button>
</div>
);
})}
</div>
);
}
ReactDOM.render(<App />,
document.getElementById("root"))

Handling mutiple radio-button-groups with useState

I have this renderUpgrades-function in which the options of an item get included into radio-button-groups. So an item has multiple options where each option has a a radio-button-group. I know that a radio-button-group can be handled with useState where each useState gets a group assigned. But in my case I don't know how many options an item has so I can't initialize the exact amount of useStates at the beginning. Is there a way how I can initalize the useStates depending on how many options there are or is there another way how the radio-button-goups can be handled?
const renderUpgrades=(item)=>{
return item.optionModules.map((optionModule,index)=> {
console.log(optionModule.module)
if (optionModule.module && optionModule.module.selectionRequired) {
return(
<div key={index}>
<h4>{optionModule.module.name}</h4>
{optionModule.module.options.map((moduleOptions) => {
if(optionModule){
return (
<div onChange={()=>{}}>
<label><input type="radio" value={moduleOptions.option.name} name={index} checked={moduleOptions.isDefault}/> {moduleOptions.option.name}</label>
</div>
)
}else{
return console.log("No shifts applied");
}
})
}
</div>
)
}})
}
You can use an object as state.
const [radioGroups, setRadioGroups] = useState({});
The initialization can be done separately, for example in a useEffect with empty dependency array:
useEffect(() => {
const groups = {};
// Loop through your radio groups here, I don't think I got the right array
item.optionModules.forEach(module => {
groups[module.option.name] = "default selected value";
});
setRadioGroups(groups);
}, []);
Then everytime you have to edit a group you get the current state and edit the group
setRadioGroups({ ...radioGroups, [groupToBeChanged]: groupValue });

REACT Dynamic checkboxes

Purpose: I want to create any # of rows containing any # of checkboxes that will be handled by a useState hook.
Problem: Page becomes frozen / constant loading state with nothing showing. No console logs, and debugger doesn't even start. React usually will prevent endless loops of updates. But in this case it didn't get caught.
What I've tried:
console.logs (nothing gets outputted)
debugger statements (nothing
gets paused)
Cant do much bc of frozen page.
CODE:
const CreateCheckboxes = ({ rows, cols }) => {
const classes = useStyles()
const [checked, setChecked] = useState({})
const [isLoaded, setIsLoaded] = useState(false)
//temp initializer
let state = {};
//configures state based on unique checkbox name.
const handleChange = (e) => {
const value = {
...checked,
[e.target.name]: e.target.checked,
}
setChecked(value)
};
//Helper function
const createBoxes = (row, col) => {
let rowArr = [];
for (let i = 0; i < row; i++) {
let checkboxArr = []
for (let j = 0; i < col; j++) {
checkboxArr.push(
<Checkbox
name={`row-${i}-checkbox-${j}`} //unique identifier in the state.
checked={checked[`row-${i}-checkbox-${j}`]}
onChange={(e) => handleChange(e)}
/>
)
//store temp state so that react useState is given a list of initialized 'controlled' states.
//react deosnt like undefined states.
state[`row-${i}-checkbox-${j}`] = false;
}
rowArr.push(
<div className={classes.row}>
<Typography>{`Sound ${i}`}</Typography>
{/* JSX array */}
{checkboxArr}
</div>
)
}
// JSX array
return rowArr
}
//output as a jsx array of 'x row divs' contiaining 'y checkboxes'
const sequenceData = createBoxes(rows, cols)
useEffect(() => {
setChecked(state)
setIsLoaded(true)
}, [])
return isLoaded && (
<>
{sequenceData}
</>
);
}
Solution: Check your loop conditions. Inner loop set to i instead of j.
yes, but I think that component doesn't need to have state various.
I tried to create one. You can check it out following when you have time to see :)
https://codesandbox.io/s/stackoverflow-dynamic-checkboxes-5dh08
Solution: Check your loop conditions. Inner loop set to i instead of j.

Trying to populate array with string values based on filter selections, and filter records by comparing the array to string value of record attribute

I have an API response that returns a list of records each with a 'status' attribute. The status can be 'current', 'former', 'never'.
I have a set of 3 checkboxes that each carry a value for the same that a user would click to filter the list of records accordingly.
The way I'm trying to achieve the filtering functionality is by using a hook for const [statuses, setStatuses] = useState<string[]>([]);
And then populating that array with the value of each checkbox from:
<div>FILTER BY STATUS</div>
<FilterSection>
<span><input type="checkbox" value="Current" onClick={handleStatusChange}/> Current</span>
<span><input type="checkbox" value="Former" onClick={handleStatusChange}/> Former</span>
<span><input type="checkbox" value="Never" onClick={handleStatusChange}/> Never </span>
</FilterSection>
</div>
I then have the onClick method that calls handleStatusChange:
const handleStatusChange = e => {
setStatuses([...statuses, e.target.value]);
props.onFilterChange(statuses, state)
console.log('status value: ', e.target.value)
};
Which passes it's values up to the container component and feeds into the filter function which looks like:
const handleFilterChange = (status: string[], state: string) => {
store.set('currentPage')(0);
const allLocations = store.get('locations');
let filteredLocations = allLocations.slice();
const pageSize = store.get('pageSize');
if (status && status.length > 0) {
filteredLocations = filteredLocations
.filter(l => {
l.financialDetails && l.financialDetails.locationStatus === status;
})
.slice();
}
store.set('filteredLocations')(filteredLocations);
const pagedLocations = filteredLocations.slice(0, pageSize);
store.set('pagedLocations')(pagedLocations);
store.set('locationsLoading')(false);
};
The problem I'm seeing is that I get a TypeScript error inside handleFilterChange saying This condition will always return 'false' since the types 'string' and 'string[]' have no overlap.
EDIT
I've updated the handleStatusChange function and it's populating the array but only on the second click of a checkbox. So on first click it produces an empty array, on second click it grabs whatever the first clicked value was and pushes it into the array so that it's always one click behind what a user actually selected.
I think something like this would work.
const handleStatusChange = e => {
const status = e.target.value;
if (!statuses.includes(status)) {
setStatuses(statuses.concat(status));
}
else {
const statusIndex = statuses.findIndex(status);
statuses.splice(statusIndex, 1);
setStatuses(statuses);
}
props.onFilterChange(statuses)
};
And for the handleFilterChange...
const handleFilterChange = (statuses: string[]) => {
const allLocations = store.get('locations');
let filteredLocations = allLocations.slice();
if (statuses && statuses.length) {
statuses.forEach((status) => {
filteredLocations = filteredLocations
.filter(l => (l.financialDetails && l.financialDetails.locationStatus !== status));
}
const pagedLocations = filteredLocations.slice(0, pageSize);
};
I removed some of the code I thought was irrelevant to the problem.
const handleStatusChange = e => {
const updatedStatus = [...statuses, e.target.value];
props.onFilterChange(updatedStatus, state)
setStatuses(updatedStatus);
console.log('status value: ', e.target.value)
};
you always should call parent (props function) to pass state to parent because when you call setStatuses at that time statuses array is empty so call that
props.onFilterChange
first before setting state.
or in functional component you can use useEffect like this
useEffect(()=>{
props.onFilterChange(statuses, state);
},[statuses]);

Categories

Resources