I have this code, adding works perfectly but removing is not triggering, it seems if is only firing else:
// variables for dynamic columns
const productName = <Table.Column dataIndex="title" title="Product Name"/>
const productPrice = <Table.Column
dataIndex = "status"
title = "Price"
sorter={(a: any, b: any) => a.status - b.status}
/>
const productOffers = <Table.Column
dataIndex = "createdAt"
title = "Offers"
sorter={(a: any, b: any) => a.createdAt - b.createdAt}
render={(value) => <DateField format="LLL" value={value} />}
/>
const productPriceVariation = <Table.Column
dataIndex = "createdAt"
title = "Price Variation"
sorter={(a: any, b: any) => a.createdAt - b.createdAt}
render={(value) => <DateField format="LLL" value={value} />}
/>
// on click event for dropdown menu
const [dynamicColumns, setDynamic] = useState([productName, productPrice, productOffers, productPriceVariation])
const removeElement = (element: any) => {
setDynamic(prevState => {
const updatedColumns = prevState.filter(column => column.props.dataIndex !== element.props.dataIndex);
return updatedColumns;
});
};
const addElement = (element: any) => {
const updatedColumns = dynamicColumns.concat([element]);
setDynamic(updatedColumns);
};
const onClick: MenuProps['onClick'] = ({key}) => {
if (key == '1') {
if (dynamicColumns.includes(productPrice)) {
removeElement(productPrice);
} else {
addElement(productPrice);
}
}
};
I would like to remove column if it exists, so when key value is 1 if it s already in array I want to remove it.
If anyone has any suggestions I would be very happy
Code works nice and adding is working, but for some reason removing part of if statement is not working at all
You should not put a ReactNode in state. Keep state as pure data. E.g. if you do this instead, you can easily add/remove to the array.
const [dynamicColumns, setDynamic] = useState(['productName', 'productPrice', 'productOffers', 'productPriceVariation'])
Then, you check it in your return statetement
return dynamicColumns.map(column => {
if (column === 'productName') return productName; // or better yet, inline the JSX here.
else if (column === 'productPrice') return productPrice;
// etc..
})
Related
The input loses its focus when I start typing a character. I saw many StackOverflow answers but none of them is working. I have added unique keys also. What is the reason the code is not working? Without the state, it is working fine. But after adding the state, the input loses the focus.
import React, { useState } from "react";
const Footer = ({ formData }) => {
const [colorsArray, setColors] = useState(["Red", "Green", "Blue", "Yellow"]);
const [sizeArray, setSizes] = useState(["S", "M", "L", "XL"]);
const [sizeInput, setsizeInput] = useState("");
const colorElementRemoveHandler = (indexToRemove) => {
const filteredValue = colorsArray.filter((data, index) => {
return indexToRemove !== index;
});
setColors(filteredValue);
};
const sizeElementRemoveHandler = (indexToRemove) => {
const filteredValue = sizeArray.filter((data, index) => {
return indexToRemove !== index;
});
setSizes(filteredValue);
};
const addColorHandler = (e) => {
let input = e.target.value.toLowerCase();
if (input.length > 2) {
let temp = colorsArray;
temp.push(input);
setColors(temp);
}
};
const addSizeHandler = (e) => {
let input = e.target.value.toUpperCase();
if (input.length > 0) {
let temp = sizeArray;
temp.push(input);
setSizes(temp);
console.log(sizeArray);
}
};
const Test = () => {
return (
<input
type="text"
onChange={(e) => {
setsizeInput(e.target.value);
}}
value={sizeInput}
/>
);
};
const VariantUI = () => {
return (
<div>
<label>Size</label>
<input
id="optionName"
type="text"
placeholder="e.g S, M, L, XL"
onChange={(e) => {
setsizeInput(e.target.value);
}}
value={sizeInput}
/>
</div>
<ul>
{sizeArray.map((data, index) => {
return (
<li key={index}>
{data}
<i onClick={() => {sizeElementRemoveHandler(index);}}></i>
</li>
);
})}
</ul
);
};
return (
<VariantUI formData={formData} />
);
};
export default Footer;
Thanks in advance.
const Footer = ({ formData }) => {
// ..
const VariantUI = () => {
// ...
return (<VariantUI formData={formData} />)
}
You are creating a brand new type of component (VariantUI), in the middle of rendering Footer. This will happen on ever render. Each VariantUi function might have the same text as the previous one, but it's a different function, and thus to react it's a different type of component. Since it's a different type of component, the old one unmounts, and the new one mounts. A newly-mounted <input> does not have focus.
Component types must be defined only once, not on ever render. So VariantUI needs to be moved outside of footer. Since you're currently relying on closure variables, you will need to changes those to props:
const VariantUI = ({
sizeArray, setSizes, sizeInput, setSizeInput, // I might have missed a couple props
}) => {
// ...
}
const Footer = ({ formData }) => {
// ...
return (
<VariantUI
sizeArray={sizeArray}
setSizes={setSizes}
sizeInput={sizeInput}
setSizeInput={setSizeInput}
/>
);
}
I have an input with label "Ilosc osob". When I change it, I want to change the Select's options, depends of number in input. It happens, but with one step gap. What should I do?
matchedTables depends on props.tables, and it is filtered array from parent component.
const ReservationForm = (props) => {
const [enteredSize, setEnteredSize] = useState("");
const [enteredTable, setEnteredTable] = useState("");
const [enteredDate, setEnteredDate] = useState("");
const [enteredTime, setEnteredTime] = useState("");
const [sizeMatchedTables, setSizeMatchedTables] = useState([
{ id: 55, table_size: 1, isBusy: false },
{ id: 56, table_size: 2, isBusy: true },
]);
//some code
const matchingSizeTablesHandler = () => {
const newArray = props.tables.filter((tables) => {
if (tables.table_size >= enteredSize) {
return tables;
}
});
setSizeMatchedTables(newArray);
};
const sizeChangeHandler = (event) => {
setEnteredSize(event.target.value);
matchingSizeTablesHandler();
};
//some code
return(
<div>
<div className="new-reservation__control">
<label>Ilość osób</label>
<input
type="number"
min={1}
max={10}
value={enteredSize}
onChange={sizeChangeHandler}
/>
</div>
<select
className="new-reservation__control"
value={enteredTable}
onChange={tableChangeHandler}
>
<TablesInSelect passedOptions={sizeMatchedTables} />
</select>
</div>
)};
const TablesInSelect = (props) => {
return (
<>
{props.passedOptions.map((option, index) => {
return (
<option key={index} value={option.id}>
{option.id}
</option>
);
})}
</>
);
};
I found workaround this problem, but dont think its best way to do this. I changed matchingSizeTableHandler so it work with argument (and also change filter to reduce but it is not the case):
const matchingSizeTablesHandler = (size) => {
const newArray = props.tables.reduce((newTables, tables) => {
if (tables.table_size >= size) {
var newValue = tables;
newTables.push(newValue);
}
if (size === "") newTables = [];
return newTables;
}, []);
setSizeMatchedTables(newArray);
};
and then, in sizeChangeHandler I changed call out matchingSizeTableHandler with event.target.value parameter
const sizeChangeHandler = (event) => {
setEnteredSize(event.target.value);
matchingSizeTablesHandler(event.target.value);
};
. If someone can explain to me the other way to implement this, using the sizeMatchedTable state as a parameter in matchingSizeTablesHandler function, I would be thankful.
Im fetching data from an API and changing the value from the frontend to display it in a table. Im fetching a list of objects and storing it in a state and displaying the objects in the state in a html table. The table has a checkbox to display a boolean value. If the value is true, then defaultChecked is true in the checkbox. There's a checkbox in the table header to check or uncheck all items.
The following is the json object which i fetch.
{
completed: false
id: 1
title: "delectus aut autem"
userId: 1
}
If I checked the checkbox in the table header, I want to set completed to true in all 200 items.
The following is the code to set all items to either true or false.
const checkAllHandler = (e) => {
let val = e.target.checked;
console.log(val);
let allTodoList = [];
if (val === true) {
if (todo.length > 0) {
for (let index = 0; index < todo.length; index++) {
const newObject = {
userId: todo[index].userId,
id: todo[index].id,
title: todo[index].title,
completed: true,
};
allTodoList.push(newObject);
}
setTodo(allTodoList);
}
} else if (val === false) {
if (todo.length > 0) {
for (let index = 0; index < todo.length; index++) {
const newObject = {
userId: todo[index].userId,
id: todo[index].id,
title: todo[index].title,
completed: false,
};
allTodoList.push(newObject);
}
setTodo(allTodoList);
}
}
};
When I console the todo state, all the values are updated to either true or false but it doesnt show on the table.
The table has a filter function. If I filter a word and go back, then the values of entire table is changing. I want to display the change when the checkbox is clicked and not when search and go back. How to I do that?
The complete code of the component is below.
const DataTable = () => {
const { loading, items } = useSelector((state) => state.allData);
// console.log(items)
const dispatch = useDispatch();
// const history = useNavigate();
const [searchText, setsearchText] = useState("");
const [todo, setTodo] = useState(items);
console.log("todo");
console.log(todo);
const [isFetch, setisFetch] = useState(false);
const [checkedloading, setcheckedLoading] = useState(false);
const [isChecked, setisChecked] = useState(false);
console.log(isChecked);
useEffect(() => {
const setDataToState = () => {
if (loading === false) {
setTodo(items);
}
};
setDataToState();
}, [loading]);
useEffect(() => {
dispatch(getData());
// setTodo(items)
// console.log(items)
}, [dispatch]);
//etTodo(items)
const handleSearch = (event) => {
//let value = event.target.value.toLowerCase();
setsearchText(event.target.value);
};
const onChangeHandler = (e, item) => {
console.log(e.target.checked);
console.log(item);
if (e.target.checked === true) {
// const item = todo.filter(x=> x.id === id)
// console.log("added")
// console.log(item)
for (let index = 0; index < todo.length; index++) {
if (todo[index].id === item.id) {
console.log(todo[index]);
console.log("deleting");
todo.splice(index, 1);
console.log("deleted");
const newObject = {
userId: item.userId,
id: item.id,
title: item.title,
completed: true,
};
console.log("adding updated object");
todo.splice(index, 0, newObject);
console.log("added");
console.log(todo);
}
}
} else {
// const item = todo.filter(x=> x.id === id)
// console.log("removed")
// console.log(item)
for (let index = 0; index < todo.length; index++) {
if (todo[index].id === item.id) {
console.log(todo[index]);
console.log("deleting");
todo.splice(index, 1);
console.log("deleted");
const newObject = {
userId: item.userId,
id: item.id,
title: item.title,
completed: false,
};
console.log("adding updated object");
todo.splice(index, 0, newObject);
console.log("added");
console.log(todo);
}
}
}
};
const onSubmitHandler = () => {
localStorage.setItem("items", JSON.stringify(todo));
};
const getItem = () => {
const items = localStorage.getItem("items");
console.log("local storage items");
console.log(JSON.parse(items));
};
const checkAllHandler = async (e) => {
const { checked } = e.target;
console.log(checked);
setTodo((todos) =>
todos.map((todo) => ({
...todo,
completed: checked,
}))
);
};
return (
<>
{console.log("todo in render")}
{console.log(todo)}
<div className={styles.container}>
<div className={styles.top}>
<div className={styles.search_bar}>
<input
type="text"
onChange={(e) => handleSearch(e)}
placeholder="search by name"
/>
</div>
</div>
<div className={styles.btn_container}>
<button onClick={onSubmitHandler}>Submit</button>
</div>
<div className={styles.data_table_container}>
{checkedloading === false ? (
<>
<div className={styles.data_table}>
{loading || todo === null || todo === undefined ? (
<>
<p>Loading!!</p>
</>
) : (
<>
<table>
<tr>
<th>ID</th>
<th>userId</th>
<th>Title</th>
<th>
<>
Completed
<input type="checkbox" onChange={checkAllHandler} />
</>
</th>
</tr>
<tbody>
{todo
.filter((val) => {
if (searchText === "") {
return val;
} else if (
val.title.toLowerCase().includes(searchText)
) {
return val;
}
})
.map((item) => (
<>
<tr key={item.id}>
<td>{item.id}</td>
<td>{item.userId}</td>
<td>{item.title}</td>
<td>
<input
type="checkbox"
defaultChecked={item.completed}
onClick={(e) => onChangeHandler(e, item)}
/>
</td>
</tr>
</>
))}
</tbody>
</table>
</>
)}
</div>
</>
) : (
<>Loading</>
)}
</div>
</div>
</>
);
};
CodeSandbox link: https://codesandbox.io/s/flamboyant-proskuriakova-60t19
I don't see any overt issues in this code, but it is quite verbose. Both logic branches are identical save for the completed boolean value assigned. When updating arrays in React it is common to use functional state updates to make the shallow copy of the previous state, not whatever state may be closed over in the callback scope.
Example:
const checkAllHandler = (e) => {
const { checked } = e.target;
console.log(checked);
setTodo(todos => todos.map(todo => ({
...todo,
completed: checked,
})));
};
If there's still further issue from here with updating the table then please add the rest of the component code and any other code you think is relevant.
Update
The reason none of the checkbox inputs are changing in the table is because you've used the defaultChecked prop, which makes these inputs fully uncontrolled inputs. They take the initial item.completed value when mounted and don't change from there other than if you interact with the checkbox.
If you want them to respond to changes/updates to the todo state then they should be converted to fully controlled inputs and use the checked prop.
<input
type="checkbox"
checked={item.completed}
onClick={(e) => onChangeHandler(e, item)}
/>
Update #2
The individual checkbox inputs were being mutated with Array.prototype.splice in onChangeHandler. .splice does an in-place mutation. Again a functional state update should be used to shallow copy the previous state and check for the matched todo object by id.
const onChangeHandler = (e, item) => {
const { checked } = e.target;
setTodo((todos) =>
todos.map((todo) =>
todo.id === item.id
? {
...todo,
completed: checked
}
: todo
)
);
};
I am trying to apply a change to only one item in an array depending on a prop.
I have this list:
interface FieldProps {
fileLimitWarning?: // I STILL NEED TO FIGURE THIS OUT
}
const DataField = (props: FieldProps) => {
const { fileLimitWarning, handleFileSelection } = props;
return (
<>
{fileLimitWarning && <p>This file exceeds the max size of 250mb</p>}
<input onChange={e => handleFileSelection(e.target.files)} />
</>
}
const ContainerOfDataField = () => {
const [fileLimitWarning, setFileLimitWarning] = useState(false);
const handleFileSelection = (files) => {
setFileLimitWarning(false);
const [currentFile] = files;
if(currentFile?.size <= 250000000) {
setFileLimitWarning(true);
}
}
return (
<ul>
{
fields?.map((promoField: Field, index: number) => {
return (
<DataField
key={index}
fileLimitWarning={fileLimitWarning}
handleFileSelection={handleFileSelection}
/>
);
})
}
</ul>
)
}
In this case what I want is to only return null in the DataField component when it corresponds. Right now whenever that fileLimitWarning === true on the ContainerOfDataField component, it hides/removes/deletes all of the Typography nodes from the DataField component. So what I need is to hide only the index that matches where the problem is coming from.
Is it clear?
I think ideally you would define fileLimitWarning in each iteration of your map, since (I assume) it is a property of the current item, rather than a global property:
return (
<ul>
{
fields?.map((promoField: Field, index: number) => {
// { currentFile } = promoField???
return (
<DataField
key={index}
fileLimitWarning={currentFile?.size <= 250000000}
handleFileSelection={handleFileSelection}
/>
);
})
}
</ul>
)
}
I have a list of buttons and they are multi selectable. when I select the buttons I want to add , it will be added to the array perfectly and turned to blue and when I click on a already selected button it should be get removed from the array and turned to white but it doesn't. Below shows what I tried so far.
The first array (products) is to save the API data. Second one is to save the selected products.
const [products, setProducts] = useState([]);
const [selectedProducts, setselectedProducts] = useState<any>([]);
{products.length !== 0 ? (
products?.map(
(item: any, index) => (
<SButton
key={item.key}
label={item.value}
onClick={() => {
selectedProducts(item);
}}
isSelected={item.selected === "YES"}
/>
)
)
) : (
<p>No products</p>
)}
function selectedProducts(item:any){
if(selectedProducts.length !== 0){
selectedProducts.map((selecteditem:any)=>{
if(selecteditem.key == item.key ){
item.selected = "NO";
setselectedProducts(selectedProducts.filter((item: any )=> item.key !== selecteditem.key))
}else{
item.selected = "YES";
setselectedProducts([...selectedProducts, item]);
}
})
}else{
setselectedProducts([...selectedProducts, item]);
item.selected = "YES";
}
}
How about something like this?
const [products, setProducts] = useState([]);
const [selectedProduct, setSelectedProduct] = useState();
{(products.length > 0) ? (
<Fragment>
{products.map((item)=>{
const {key, value, selected } = item;
return (
<SButton
key={key}
label={value}
onClick={() => {
setSelectedProduct(item);
const newState = !selected;
products.forEach((p)=>{
if (p.key === item.key) p.selected = newState;
});
setProducts([...products]);
}}
isSelected={selected}
/>
);
})}
</Fragment>
): (
<p>No products</p>
)}
First, you are using selectedProducts both as function name and name of selected products state.
Second, you should not assign values to item. Use spread operator instead.
Also, you can access the previous state from setState instead of using state directly.
function removeFromSelectedProducts(item: any) {
// Set selected to 'NO' in products array
setProducts((prevProducts) =>
prevProducts.filter((product) =>
product.key === item.key ? { ...product, selected: 'NO' } : product
)
)
// Remove product from selectedProducts
setSelectedProducts((prevSelectedProducts) =>
prevSelectedProducts.filter((product) => product.key !== item.key)
)
}
function addToSelectedProducts(item: any) {
// Set selected to 'YES' in products array
setProducts((prevProducts) =>
prevProducts.filter((product) =>
product.key === item.key ? { ...product, selected: 'YES' } : product
)
)
// Add item to selectedProducts
setSelectedProducts((prevSelectedProducts) => [...prevSelectedProducts, { ...item, selected: 'YES'}])
}
function selectProduct(item: any) => {
if (selectedProducts.some((product) => product.key === item.key)) {
removeFromSelectedProducts(item)
} else {
addToSelectedProducts(item)
}
}
You can simplify this using useReducer hook instead of using separate functions for addition & removal of selected products.