I am developing an application on React and I needed to add product checkboxes to the form. Everything works correctly except that the last action (marking or removing the checkbox) does not go to the server (the error is not in the server, I tested it separately). Here is the output of the checkboxes:
<Form.Group className="mb-3">
<Form.Label>Товары</Form.Label>
{prodsl.map((emp, index) =>
<div className="mb-3">
<Form.Check
type='checkbox'
value={emp.id}
name='pp'
checked={checkedState[index]}
onChange={() => handleOnChange(index)}
/>
{emp.name}
</div>
)}
</Form.Group>
And here is the handler (checkedState - an array of checkbox states):
const [prods, setProd] = useState([])
const [checkedState, setCheckedState] = useState(
new Array(555).fill(false)
);
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
let arr = []
const updatedProd = checkedState.map((item, index) => {
if (item) {
arr = [...arr, index]
}
}
);
setProd(arr);
};
Submit form handler:
const newOrder = async () => {
const response = await addOrder(prods, client, emp, d1, d2)
stat++;
}
I tried to do a separate check before sending, I tried to call the function before sending it to a random checkbox so that this change would be the last and would not be counted.
Set states in react are async. It means that your values are not updated immediately after your setstate.
In order to fix the issue, you have 2 options.
Use the updatedCheckedState variable to set your prod:
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
let arr = []
const updatedProd = updatedCheckedState.map((item, index) => {
if (item) {
arr = [...arr, index]
}
}
);
setProd(arr);
};
Move your logic into useEffect:
const handleOnChange = (position) => {
const updatedCheckedState = checkedState.map((item, index) =>
index === position ? !item : item
);
setCheckedState(updatedCheckedState);
};
useEffect(() => {
let arr = []
const updatedProd = checkedState.map((item, index) => {
if (item) {
arr = [...arr, index]
}
});
setProd(arr);
}, [checkedState]);
And as a suggestion, use arr.push(index) instead of arr = [...arr, index];
Related
I am trying to remove an item with an id from a state array. Currently, when I call the function it is removing the correct item and all other items following.
const [cardArr, setCardArr] = useState([])
const id = nanoid()
const addCard = () => {
setCardArr( [...cardArr,
<NoteCard
id = {id}
keyProp = {id + 1}
remove = {removeCard}
/>
])
console.log("add clicked")
}
const renderCards = cardArr.map(card => {return card})
const removeCard = () => {
setCardArr(
cardArr.filter(card => card.id !== id)
)
}
Like with my solution, I have to type "Back-End Developer" to get filter results. Can I just show results while typing "back" or "backend"? Even if I don't type the "-" filter doesn't work.
I guess I have to use some and toLowerCase but I don't know where to use it.
const Positions = ({ positions }: DataProps) => {
const [selectLocation, setSelectLocation] = useState<any>('');
const [selectJobType, setSelectJobType] = useState<any>('');
const [filtered, setFiltered] = useState<any[]>([]);
const [searchTerm, setSearchTerm] = useState<any>('');
useEffect(() => {
if (positions.length > 0) {
let newList = [...positions];
if (searchTerm) {
newList = newList.filter((i) => i.position === searchTerm);
}
if (selectJobType) {
newList = newList.filter((i) => i.position === selectJobType);
}
if (selectLocation) {
newList = newList.filter((i) => i.location === selectLocation);
}
setFiltered(newList);
}
}, [positions, searchTerm, selectJobType, selectLocation]);
return (
<>
<div>
<input
type='search'
placeholder='Search'
onChange={(e) => setSearchTerm(e.target.value)}
/>
<Select
defaultValue={selectLocation}
onChange={setSelectLocation}
options={locations}
instanceId={'1'}
placeholder='Location'
/>
</div>
<Select
defaultValue={selectJobType}
onChange={setSelectJobType}
options={jobs}
placeholder='Job Type'
instanceId={'2'}
/>
{positions?.map((position: any) => (
<SinglePosition
category={position.category}
type={position.type}
location={position.location}
position={position.position}
key={position._id}
/>
))}
</>
);
};
Do not need to use the === operator just use the includes method
useEffect(() => {
if(positions.length > 0) {
let newList = [...positions];
if(searchTerm) {
newList = newList.filter(i => i.position.toLowerCase().includes(searchTerm.toLowerCase()));
}
if(selectJobType) {
newList = newList.filter(i => i.position.toLowerCase().includes(selectJobType.toLowerCase()));
}
if(selectLocation) {
newList = newList.filter(i => i.location.toLowerCase().includes(selectLocation.toLowerCase()));
}
setFiltered(newList);
}
}, [positions, searchTerm, selectJobType, selectLocation]);
I have a list and this list has several elements and I iterate over the list. For each list I display two buttons and an input field.
Now I have the following problem: as soon as I write something in a text field, the same value is also entered in the other text fields. However, I only want to change a value in one text field, so the others should not receive this value.
How can I make it so that one text field is for one element and when I write something in this text field, it is not for all the other elements as well?
import React, { useState, useEffect } from 'react'
import axios from 'axios'
function Training({ teamid }) {
const [isTrainingExisting, setIsTrainingExisting] = useState(false);
const [trainingData, setTrainingData] = useState([]);
const [addTraining, setAddTraining] = useState(false);
const [day, setDay] = useState('');
const [from, setFrom] = useState('');
const [until, setUntil] = useState('');
const getTrainingData = () => {
axios
.get(`${process.env.REACT_APP_API_URL}/team/team_training-${teamid}`,
)
.then((res) => {
if (res.status === 200) {
if (typeof res.data !== 'undefined' && res.data.length > 0) {
// the array is defined and has at least one element
setIsTrainingExisting(true)
setTrainingData(res.data)
}
else {
setIsTrainingExisting(false)
}
}
})
.catch((error) => {
//console.log(error);
});
}
useEffect(() => {
getTrainingData();
}, []);
const deleteTraining = (id) => {
axios
.delete(`${process.env.REACT_APP_API_URL}/team/delete/team_training-${teamid}`,
{ data: { trainingsid: `${id}` } })
.then((res) => {
if (res.status === 200) {
var myArray = trainingData.filter(function (obj) {
return obj.trainingsid !== id;
});
//console.log(myArray)
setTrainingData(() => [...myArray]);
}
})
.catch((error) => {
console.log(error);
});
}
const addNewTraining = () => {
setAddTraining(true);
}
const addTrainingNew = () => {
axios
.post(`${process.env.REACT_APP_API_URL}/team/add/team_training-${teamid}`,
{ von: `${from}`, bis: `${until}`, tag: `${day}` })
.then((res) => {
if (res.status === 200) {
setAddTraining(false)
const newTraining = {
trainingsid: res.data,
mannschaftsid: teamid,
von: `${from}`,
bis: `${until}`,
tag: `${day}`
}
setTrainingData(() => [...trainingData, newTraining]);
//console.log(trainingData)
}
})
.catch((error) => {
console.log(error);
});
}
const [editing, setEditing] = useState(null);
const editingTraining = (id) => {
//console.log(id)
setEditing(id);
};
const updateTraining = (trainingsid) => {
}
return (
<div>
{trainingData.map((d, i) => (
<div key={i}>
Trainingszeiten
<input class="input is-normal" type="text" key={ d.trainingsid } value={day} placeholder="Wochentag" onChange={event => setDay(event.target.value)} readOnly={false}></input>
{d.tag} - {d.von} bis {d.bis} Uhr
<button className="button is-danger" onClick={() => deleteTraining(d.trainingsid)}>Löschen</button>
{editing === d.trainingsid ? (
<button className="button is-success" onClick={() => { editingTraining(null); updateTraining(d.trainingsid); }}>Save</button>
) : (
<button className="button is-info" onClick={() => editingTraining(d.trainingsid)}>Edit</button>
)}
<br />
</div>
))}
)
}
export default Training
The reason you see all fields changing is because when you build the input elements while using .map you are probably assigning the same onChange event and using the same state value to provide the value for the input element.
You should correctly manage this information and isolate the elements from their handlers. There are several ways to efficiently manage this with help of either useReducer or some other paradigm of your choice. I will provide a simple example showing the issue vs no issue with a controlled approach,
This is what I suspect you are doing, and this will show the issue. AS you can see, here I use the val to set the value of <input/> and that happens repeatedly for both the items for which we are building the elements,
const dataSource = [{id: '1', value: 'val1'}, {id: '2', value: 'val2'}]
export default function App() {
const [val, setVal]= useState('');
const onTextChange = (event) => {
setVal(event.target.value);
}
return (
<div className="App">
{dataSource.map(x => {
return (
<div key={x.id}>
<input type="text" value={val} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
This is how you would go about it.
export default function App() {
const [data, setData]= useState(dataSource);
const onTextChange = (event) => {
const id = String(event.target.dataset.id);
const val = String(event.target.value);
const match = data.find(x => x.id === id);
const updatedItem = {...match, value: val};
if(match && val){
const updatedArrayData = [...data.filter(x => x.id !== id), updatedItem];
const sortedData = updatedArrayData.sort((a, b) => Number(a.id) - Number(b.id));
console.log(sortedData);
setData(sortedData); // sorting to retain order of elements or else they will jump around
}
}
return (
<div className="App">
{data.map(x => {
return (
<div key={x.id}>
<input data-id={x.id} type="text" value={x.value} onChange={onTextChange}/>
</div>
)
})}
</div>
);
}
What im doing here is, finding a way to map an element to its own with the help of an identifier. I have used the data-id attribute for it. I use this value again in the callback to identify the match, update it correctly and update the state again so the re render shows correct values.
Not to sure why, but I am getting this error message:
TypeError: Invalid attempt to spread non-iterable instance
Is there a way to map over data to be able to store in the useEffect before the useEffect is triggered?
As it looks like this map does not seem to work.
const projectId = scheduleData && scheduleData.map(item => item.project_id).map(x => ({label: x, value: x}))
const schedules = () => {
const loading = useContext(LoadingContext)
const snackbar = useContext(SnackbarContext)
const user = useContext(UserContext)
const [scheduleData ,setScheduleData] = useState(null)
const [usageMode, setUsageMode] = useState(false)
const [scheduleFieldData, setScheduleFieldData] = useState({})
const [projectsAutoComplete, setProjectsAutoComplete] = useState([])
const [domainsAutoComplete, setDomainsAutoComplete] = useState([])
console.log(projectId, 'projectId');
console.log(projectsAutoComplete)
const projectId = scheduleData && scheduleData.map(item => item.project_id).map(x => ({label: x, value: x}))
useEffect(() => {
setProjectsAutoComplete([...projectId])
}, [])
console.log(projectsAutoComplete);
useEffect(() => {
async function onLoadScheduleData(){
loading.setLoading(true)
const results = await get('get_testing_schedules', user.user)
setScheduleData(results.data)
loading.setLoading(false)
}
onLoadScheduleData()
},[])
const onClick = () => {
setUsageMode(!usageMode)
}
console.log(usageMode);
const onScheduleFieldUpdate = (e, valueFromAutoComplete, nameFromAutoComplete) => {
const name = nameFromAutoComplete ? nameFromAutoComplete
: e.target.name || e.target.getAttribute('name')
const value = valueFromAutoComplete ? valueFromAutoComplete.map(val => val.value).join(',')
: e.target.innerText ? e.target.innerText
: e.target.value
setScheduleFieldData({...scheduleFieldData, ...{[name]: value}})
}
const onDomainAutoCompleteFieldUpdate = (e) => {
const value = e.target.innerText.toLowerCase()
const projectIdFiltered = projectId.filter(id => id.label.toLowerCase().startsWith(value))
setProjectsAutoComplete(projectIdFiltered)
}
console.log(scheduleFieldData);
return (
<div className=' CriticalObjectsGrid'>
<React.Fragment>
{configs.map((config, k) => {
const Field = config.field
return (
<div key={k} className='Main' style={{textAlign: 'center'}} >
<Field
uniqueIdentifier={k}
name={config.name}
onChange={onScheduleFieldUpdate}
onSelect={onScheduleFieldUpdate}
onAutoCompleteOnChange={onDomainAutoCompleteFieldUpdate}
value={scheduleFieldData[config.name]}
initialValues={
scheduleFieldData[config.name]}
options={projectsAutoComplete}
/>
</div>
)
})}
</React.Fragment>
}
</div>
)
}
export default schedules
Updated code
const schedules = () => {
const loading = useContext(LoadingContext)
const snackbar = useContext(SnackbarContext)
const user = useContext(UserContext)
const autoComplete = useContext(AutoCompleteContext)
const [scheduleData ,setScheduleData] = useState(null)
const [usageMode, setUsageMode] = useState(false)
const [scheduleFieldData, setScheduleFieldData] = useState({})
const [projectsAutoComplete, setProjectsAutoComplete] = useState([])
// const projectId = scheduleData && scheduleData.map(item => item.project_id).map(x => ({label: x, value: x}))
const [ScheduleAutoComplete, setScheduleAutoComplete] = useState([])
const intervals = ['"Daily"', '"Weekly"']
const projectId = scheduleData && scheduleData.map(item => item.project_id).map(x => ({label: x, value: x}))
useEffect(() => {
// setProjectsAutoComplete([...projectId])
setScheduleAutoComplete([...intervals])
}, [])
console.log(projectsAutoComplete);
useEffect(() => {
async function onLoadScheduleData(){
loading.setLoading(true)
const results = await get('get_testing_schedules', user.user)
setScheduleData(results.data)
if (projectId){setProjectsAutoComplete([...projectId])
}
loading.setLoading(false)
}
onLoadScheduleData()
},[])
const onClick = () => {
setUsageMode(!usageMode)
}
console.log(projectsAutoComplete);
const onScheduleFieldUpdate = (e, valueFromAutoComplete, nameFromAutoComplete) => {
const name = nameFromAutoComplete ? nameFromAutoComplete
: e.target.name || e.target.getAttribute('name')
const value = valueFromAutoComplete ? valueFromAutoComplete.map(val => val.value).join(',')
: e.target.innerText ? e.target.innerText
: e.target.value
setScheduleFieldData({...scheduleFieldData, ...{[name]: value}})
}
const onDomainAutoCompleteFieldUpdate = () => {
setScheduleAutoComplete(intervals)
setProjectsAutoComplete(projectId)
}
console.log(projectsAutoComplete);
return (
<div className=' CriticalObjectsGrid'>
{usageMode === false ?
<React.Fragment>
<Button
text='Creat schedule'
onClick={onClick}
/>
</React.Fragment>
:
<React.Fragment>
{configs.map((config, k) => {
const Field = config.field
return (
<div key={k} className='Main' style={{textAlign: 'center'}} >
<Field
uniqueIdentifier={k}
name={config.name}
onChange={onScheduleFieldUpdate}
onSelect={onScheduleFieldUpdate}
onAutoCompleteOnChange={onDomainAutoCompleteFieldUpdate}
value={scheduleFieldData[config.name]}
initialValues={scheduleFieldData[config.name]}
options={config.name === 'interval' ? ScheduleAutoComplete : config.name === 'project' ? ['projectsAutoComplete'] : [] }
/>
</div>
)
})}
</React.Fragment>
}
</div>
)
}
export default schedules
This error is referring to your attempt to spread projectId in your first useEffect hook. At the time of execution, projectId has a value of null, since it's getting it's value from the following line where scheduleData has an initial value of null.
const projectId = scheduleData && scheduleData.map(item => item.project_id).map(x => ({label: x, value: x}))
To avoid this error you could simply wrap setProjectsAutoComplete([...projectId]) in an if (projectId) check like:
useEffect(() => {
if (projectId){
setProjectsAutoComplete([...projectId])
}
}, [])
but a better solution would be to combine your two useEffect hooks, so that setProjectsAutoComplete([...projectId]) is run after onLoadScheduleData()
EDIT:
Try this:
const [projectId, setProjectId] = useState([])
const intervals = ['"Daily"', '"Weekly"']
async function onLoadScheduleData(){
loading.setLoading(true)
const results = await get('get_testing_schedules', user.user)
if (results.data) {
id = results.data.map(item => item.project_id).map(x => ({label: x, value: x}))
setProjectId(id)
setProjectsAutoComplete([...id])
}
loading.setLoading(false)
}
useEffect(() => {
setScheduleAutoComplete([...intervals])
onLoadScheduleData()
},[])
const onClick = () => {
setUsageMode(!usageMode)
}
I've removed the scheduleData variable entirely as it doesn't look like it needs to be kept in state. Instead I've made projectId a stateful variable, as it does seem to be used elsewhere. I moved the onScheduleLoadData function definition to outside of the useEffect hook and moved the projectId logic inside of it. I've also combined both useEffect hooks because they had the same dependencies. Let me know if this is more what you were looking for.
I want to get latest state after updating state.
So I need to use useEffect.
After I change content of todo, I call saveEditedTodo onBlur.
So my code is,
useEffect(() => {
console.log(todos)
// I need to setTodos(todos), but it causes infinite loop
}, [todos]);
const saveEditedTodo = (e, id) => {
const newContent = e.currentTarget.innerHTML;
const editedTodo = todos.map((todo) =>
todo.id === id ? { ...todo, todoItem: newContent } : todo,
);
setTodos(editedTodo); // Re-rendering
onBlur(todos); // Re-rendering
};
And onBlur from props is,
const handleOnBlurTodo = (value) => {
const newValue = convertTodoToNote(value);
setEditableNote({ ...editableNote, content: newValue });
};
How can I get latest state using useEffect?
(+) Here is my full code!
function TodoList({ todoContent, onBlur }) {
const [todos, setTodos] = useState(todoContent);
const [isHover, setIsHover] = useState({ hoverID: '', onHover: false });
const { hoverID, onHover } = isHover;
const isEditable = useSelector((state) => state.isSelected);
const doneTodo = todos ? todos.filter((todo) => todo.isDone).length : 0;
useEffect(() => {
console.log(todos);
}, [todos]);
const saveEditedTodo = (e, id) => {
const newContent = e.currentTarget.innerHTML;
const editedTodo = todos.map((todo) =>
todo.id === id ? { ...todo, todoItem: newContent } : todo,
);
setTodos(editedTodo); // Re-rendering
onBlur(todos); // Re-rendering
};
const handleDeleteTodo = (id) => {
let newTodos = todos.filter((el) => el.id !== id);
setTodos(newTodos);
onBlur(todos);
};
const handleOnMouseOver = (id) => {
setIsHover({ hoverID: id, onHover: true });
};
const handleOnMouseLeave = (id) => {
setIsHover({ hoverID: id, onHover: false });
};
const handleCheckbox = (id) => {
const newTodos = todos.map((todo) =>
todo.id === id ? { ...todo, isDone: !todo.isDone } : todo,
);
setTodos(newTodos);
console.log('[todos]' + todos);
};
const todoTask = todos.filter((todo) => !todo.isDone);
const doneTask = todos.filter((todo) => todo.isDone);
if (isEditable && todos) {
let todoList = todoTask.map((todo, i) => (
<TodoListContainer
key={i}
onMouseEnter={() => handleOnMouseOver(todo.id)}
onMouseLeave={() => handleOnMouseLeave(todo.id)}
>
<Checkbox
type="checkbox"
checked={todo.isDone}
onChange={() => handleCheckbox(todo.id)}
/>
<NoteTitle
isTodoItem
size="medium"
placeholder="Add Todo"
onBlur={(e) => saveEditedTodo(e, todo.id)}
contentEditable
suppressContentEditableWarning="true"
>
{todo.todoItem}
</NoteTitle>
{hoverID === todo.id && onHover && (
<Tool
title="Delete Todo"
bgImage={DeleteIcon}
deleteTodo={() => handleDeleteTodo(todo.id)}
/>
)}
</TodoListContainer>
));
let doneList = doneTask.map((todo, i) => (
<TodoListContainer
key={i}
onMouseEnter={() => handleOnMouseOver(todo.id)}
onMouseLeave={() => handleOnMouseLeave(todo.id)}
>
<Checkbox
type="checkbox"
onBlur={() => handleCheckbox(todo.id)}
checked={todo.isDone}
/>
<NoteTitle
isTodoItem
size="medium"
placeholder="Add Todo"
onInput={(e) => saveEditedTodo(e, todo.id)}
contentEditable
suppressContentEditableWarning="true"
>
{todo.todoItem}
</NoteTitle>
{hoverID === todo.id && onHover && (
<Tool
title="Delete Todo"
bgImage={DeleteIcon}
deleteTodo={() => handleDeleteTodo(todo.id)}
/>
)}
</TodoListContainer>
));
return (todoList = (
<div>
{todoList}
{doneTodo > 0 && <CompletedTodo doneTodo={doneTodo} />}
{doneList}
</div>
));
}
if (!isEditable && todos) {
const todoList = todos.map((todo, i) => (
<TodoListContainer key={i}>
<Checkbox
type="checkbox"
onChange={() => handleCheckbox(todo.id)}
checked={todo.isDone}
/>
<NoteTitle size="small">{todo.todoItem}</NoteTitle>
</TodoListContainer>
));
return todoList;
}
return null;
}
export default TodoList;
Generally React.useEffect() is used for performing side effects for a React component. What I believe is that you wish to get the new state rendered on screen after saving the TODO content, and that can be just achieved by an onChange handler wherever you are receiving the input for your todos.
<TextField onChange={(e) => saveEditedTodos(e, id)} />
This will trigger the saveEditedTodos callback every time the value of the TextField changes. If you want to trigger the callback on clicking a save button, you can add an onClick handler in the Button component.
Another scenario what I can imagine is that you're saving your TODOs somewhere, so you want to update the list on the screen after saving the TODO in some storage, in that case you can fetch the value of todoList on each save. This can be done inside a useEffect hook callback.
React.useEffect(() => {
fetchTodos().then((response) => setTodos(response.data))
})
Here fetchTodos() is a JS Promise or async function which fetches the updated state of TODOs and sets the received data using setTodos