React-Native Item Selection - javascript

I want to select an item from a list and to highlight it. I got a function on how to delete a particular item from the array, i want to reuse this function to highlight it.
Data is an array of objects. I want to use hooks to manage state. id is asigned to every item in the array. I want to presshandler function as property for onPress.
const [selected, setSelect] = useState(Data);
const presshandler = (id) => {
setSelect((prev) => {
return prev.filter((list) => list.id !== id);
});
};

If I get it right, you want to set highlighted to true after you press an image:
const toggleSelect = id => {
setSelect(prev => {
return prev.map(item => {
if(item.id !== id) return item
return {
...item,
highlighted: !item.highlighted
}
})
})
}
// Usage:
<Image onPress={() => toggleSelect(image.id)} {...rest} />
After reading your comment, I see you want a toggle. So I edited my example.

Related

I Need to fix a issue in antd tree component

what my issue is
Scenario 1: It is not opening sub-lists after searching or removing any list name from the search bar
Scenario 2: After searching any list name in the search bar that is already selected, then after searching that selected list it is showing that list but its checkbox is not selected.
so what do I need after searching list name in the search bar if that list has a sub-list for example if I New Watchlists then I want to show that sub-list also that present under this list after searching in the search bar but right it coming with empty list you can see image below..
const hasSearchTerm = (n, searchTerm) =>
n.toLowerCase().indexOf(searchTerm.toLowerCase()) !== -1;
const filterData = (arr, searchTerm) =>
arr?.filter(
(n) =>
hasSearchTerm(n.title, searchTerm) ||
filterData(n.children, searchTerm)?.length > 0
);
function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
let keysToExpand = [];
const filteredData = searchString
? filterData(data, searchString).map((n) => {
keysToExpand.push(n.key);
return {
...n,
children: filterData(n.children, searchString, checkedKeys)
};
})
: data;
setExpandedTree([...keysToExpand]);
return filteredData;
}
const Demo = () => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [checkedKeys, setCheckedKeys] = useState([]);
const [selectedKeys, setSelectedKeys] = useState([]);
const [autoExpandParent, setAutoExpandParent] = useState(true);
const [searchValue, setSearchValue] = useState("");
const [tree, setTree] = useState(treeData);
const onExpand = (expandedKeysValue) => {
console.log("onExpand", expandedKeysValue); // if not set autoExpandParent to false, if children expanded, parent can not collapse.
// or, you can remove all expanded children keys.
setExpandedKeys(expandedKeysValue);
setAutoExpandParent(false);
};
const onCheck = React.useCallback(
(checkedKeysValue, e) => {
if (e.checked) {
if (e.node?.children?.length) {
setCheckedKeys(
_.union(
checkedKeys,
_.cloneDeep([
...e.node.key,
...e.node.children.map((child) => child.key)
])
)
);
} else {
setCheckedKeys(_.union(checkedKeys, [e.node.key]));
}
} else {
if (e.node?.children?.length) {
setCheckedKeys(
_.union(
checkedKeys.filter((item) => {
return (
item !== e.node.key &&
!e.node.children.filter((child) => child.key === item).length
);
})
)
);
} else {
setCheckedKeys(
_.cloneDeep(checkedKeys.filter((item) => item !== e.node.key))
);
}
}
},
[checkedKeys, setCheckedKeys]
);
const onSelect = (selectedKeysValue, info) => {
console.log("onSelect", info);
setSelectedKeys(selectedKeysValue);
};
React.useEffect(() => {
const checked = [];
treeData.forEach((data) => {
data.children.forEach((item) => {
if (item.checked) {
checked.push(item.key);
}
});
});
setCheckedKeys(checked);
}, []);
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
React.useEffect(() => {
if (searchValue) {
const filteredData = filteredTreeData(
treeData,
searchValue,
checkedKeys,
setExpandedKeys
);
setTree([...filteredData]);
} else {
setTree(treeData);
// setExpandedKeys([]);
}
}, [searchValue, checkedKeys]);
return (
<div>
<Search
style={{ marginBottom: 8 }}
placeholder="Search"
onChange={(e) => {
setSearchValue(e.target.value);
}}
/>
<Tree
checkable
onExpand={onExpand}
expandedKeys={expandedKeys}
autoExpandParent={autoExpandParent}
onCheck={onCheck}
checkedKeys={checkedKeys}
onSelect={onSelect}
selectedKeys={selectedKeys}
treeData={tree}
/>
</div>
);
};
CodeSandBox Link
When you run the filterTreeData function, you assign the children to only the tree that contains the search term. So, you're searching n levels deep for the term. Notice that it works like I'd expect it to if you only search one level deep. That is, if the node has children, you return the node as such rather than filtering down all the children that don't also contain the term. when you filter with the string "new" in your example, you'll notice that none of the children in the collection "New Watchlists" contain the term and they are therefore filtered out leaving you with an empty array. My naive solution is to just return the children as in my following example:
function filteredTreeData(data, searchString, checkedKeys, setExpandedTree) {
let keysToExpand = [];
const filteredData = searchString
? filterData(data, searchString).map((n) => {
keysToExpand.push(n.key);
return {
...n,
children: n.children
};
})
: data;
setExpandedTree([...keysToExpand]);
return filteredData;
}
I call this solution naive, because I'm not exactly sure what your use case is. There might be more conditional logic you need to render in there for first searching n levels deep for the term and then and returning the whole node if the term is discovered anywhere inside the node or children.

Dynamically Delete Elements

I'm having trouble deleting elements. Instead of deleting a specific element, it only deletes the last newly created element. I'm not sure where I'm going wrong here. I referenced this tutorial that shows what I kinda want to do. (I'm new to React)
import React,{useState, useRef} from "react";
const Body = () => {
const [list, setList] = useState([]);
const AddInput = () => {
setList([...list, {placeholder:"Class Name"}]);
};
const DeleteInput = (index) => {
const l = [...list];
l.splice(index,1);
setList(l);
};
const InputChangeHandler = (event, index) => {
const l = [...list];
(l[index]).value = event.target.value;
setList(l);
};
return (
<div>
<button onClick={AddInput}>Add</button>
{list.map((item, key)=>
<div key={key}>
<input type={"text"} id={key} placeholder={item.placeholder} onChange={e=>InputChangeHandler(e, key)}/>
<button id={key} onClick={() => DeleteInput(key)}>Delete</button>
</div>
)}
</div>
);
}
export default Body;
Element (input fields + button):
Deletes Last Created:
I think the main problem is the key of items that you set as react doc says:
When you don’t have stable IDs for rendered items, you may use the item index as a key as a last resort:
const todoItems = todos.map((todo, index) =>
// Only do this if items have no stable IDs
<li key={index}>
{todo.text}
</li>
);
We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state. Check out Robin Pokorny’s article for an
in-depth explanation on the negative impacts of using an index as a
key. If you choose not to assign an explicit key to list items then
React will default to using indexes as keys.
As in this Article says:
Reordering a list, or adding and removing items from a list can cause issues with the component state, when indexes are used as keys. If the key is an index, reordering an item changes it. Hence, the component state can get mixed up and may use the old key for a different component instance.
What are some exceptions where it is safe to use index as key?
-If your list is static and will not change.
-The list will never be re-ordered.
-The list will not be filtered (adding/removing items from the list).
-There are no ids for the items in the list.
If you set an reliable key in your items with some counter or id generator your problem would solve.
something like this:
export default function App() {
const [list, setList] = useState([]);
const id = useRef({ counter: 0 });
const AddInput = () => {
console.log(id);
setList([...list, { placeholder: "Class Name", id: id.current.counter++ }]);
};
const DeleteInput = (id) => {
setList(list.filter((item, i) => item.id !== id));
};
const InputChangeHandler = (event, index) => {
const l = [...list];
l[index].value = event.target.value;
setList(l);
};
return (
<div>
<button onClick={AddInput}>Add</button>
{list.map((item, key) => (
<div key={item.id}>
<input
type={"text"}
id={key}
placeholder={item.placeholder}
onChange={(e) => InputChangeHandler(e, key)}
/>
<button id={item.id} onClick={() => DeleteInput(item.id)}>
Delete
</button>
</div>
))}
</div>
);
}
Use filter.
const DeleteInput = (index) => {
const l = list.filter((_, i) => i !== index);
setList(l);
};
Pass id to your DeleteInput function and for remove just filter the item list with id
const DeleteInput = (id) => {const filterItemList = list.filter((item) => item.id!== id);setList(filterItemList ); };

How do I call a function within another function when the data I need is in the global scope?

I have a set of card objects that I map over.
When I click on a card it adds the selected class which in turn gives it a border to show the user it is selected, it also adds the id of the card to the selectedCards useState array.
WHAT I WANT TO HAPPEN:
Each card object has a creditAvailable key state which is equal to a figure.
On selection (click) of the card, in addition to selecting the card I would also like to add up the creditAvailable and display it on the screen. and when I unselect the card I would like the figure to go down.
WHAT I HAVE TRIED:
I thought it would be as simple as calling the function to add up the credit inside the first function which selects the card, however when console logging inside the first function I see that the state has not yet updated. (scope).
I then tried to call the function outside of the first function but it gave me an infinite loop. Here is my code.
Any ideas? Thanks
const [cards, setCards] = useState([]);
const [selectedCards, setSelectedCards] = useState([]);
const [total, setTotal] = useState();
const handleSelectCard = (id) => {
if (selectedCards.includes(id)) {
const filteredIds = selectedCards.filter((c) => c !== id);
setSelectedCards([...filteredIds]);
} else {
setSelectedCards([...selectedCards, id]);
}
// addUpCreditAvailable(); // nothing happens
console.log(selectedCards); // []
};
console.log(selectedCards) // [1] for example. This is in the global scope
const addUpCreditAvailable = () => {
console.log("inside add up credit");
const chosenCards = selectedCards.map((id) => {
const foundCard = allCards.find((card) => {
return card.id === id;
});
return foundCard;
});
const result = chosenCards.reduce((acc, card) => {
return acc + card.creditAvailable;
}, 0);
setTotal(result);
return result;
};
return (
<div className="Container">
<UserInputForm submitData={handleSubmitData} />
<h1> Cards available to you displayed are below!</h1>
{cards.map(
({
id,
name,
number,
apr,
balanceTransfer,
purchaseDuration,
creditAvailable,
expiry,
}) => (
<CreditCard
key={id}
name={name}
number={number}
apr={apr}
balanceTransferDuration={balanceTransfer}
purchaseOfferDuration={purchaseDuration}
creditAvailable={creditAvailable}
expiry={expiry}
onClickCard={() => handleSelectCard(id)}
selected={selectedCards.includes(id)}
/>
)
)}
<span> £{total}</span>
)}
I figured it out with the help from above. As Wilms said i had to return the result of the handleSelectCard function and return the result of the addUpCredit function. Then I called the addUpCreditAvailable with the selectedCards state and stored the result in a variable which i then displayed in my render method.
const [cards, setCards] = useState([]);
const [selectedCards, setSelectedCards] = useState([]);
const handleSelectCard = (id) => {
if (selectedCards.includes(id)) {
const filteredIds = selectedCards.filter((c) => c !== id);
setSelectedCards([...filteredIds]);
} else {
setSelectedCards([...selectedCards, id]);
}
return selectedCards;
};
const addUpCreditAvailable = (selectedCards) => {
const chosenCards = selectedCards.map((id) => {
const foundCard = allCards.find((card) => {
return card.id === id;
});
return foundCard;
});
const result = chosenCards.reduce((acc, card) => {
return acc + card.creditAvailable;
}, 0);
return result;
};
const totalCredit = addUpCreditAvailable(selectedCards);
render method:
render (
[...]
{selectedCards.length && (
<div className={bem(baseClass, "total-credit")}>
Total Credit available: £{totalCredit}
</div>
)}
[...]
)

React useState setter wont update the value inside a context

So, I'm using the React Context API to manage some global state for my app
const blueprintList = [
{
id: new Date().toISOString(),
name: 'Getting started',
color: '#c04af6',
code: '<h1>Hello World!</h1>,
},
];
export const BlueprintsContext = createContext();
export const BlueprintsProvider = ({ children }) => {
const [blueprints, setBlueprints] = useState(blueprintList);
let [selected, setSelected] = useState(0);
const [count, setCount] = useState(blueprints.length);
useEffect(() => {
setCount(blueprints.length);
}, [blueprints]);
// just for debugging
useEffect(() => {
console.log(`DEBUG ==> ${selected}`);
}, [selected]);
const create = blueprint => {
blueprint.id = new Date().toISOString();
setBlueprints(blueprints => [...blueprints, blueprint]);
setSelected(count);
};
const remove = blueprint => {
if (count === 1) return;
setBlueprints(blueprints => blueprints.filter(b => b.id !== blueprint.id));
setSelected(-1);
};
const select = blueprint => {
const index = blueprints.indexOf(blueprint);
if (index !== -1) {
setSelected(index);
}
};
const value = {
blueprints,
count,
selected,
select,
create,
remove,
};
return (
<BlueprintsContext.Provider value={value}>
{children}
</BlueprintsContext.Provider>
);
};
Then I'm using the remove function inside the component like this.
const Blueprint = ({ blueprint, isSelected }) => {
const { select, remove } = useContext(BlueprintsContext);
return (
<div
className={classnames(`${CSS.blueprint}`, {
[`${CSS.blueprintActive}`]: isSelected,
})}
key={blueprint.id}
onClick={() => select(blueprint)}
>
<div
className={CSS.blueprintDot}
style={{ backgroundColor: blueprint.color }}
/>
<span
className={classnames(`${CSS.blueprintText}`, {
['text-gray-300']: isSelected,
['text-gray-600']: !isSelected,
})}
>
{blueprint.name}
</span>
{isSelected && (
<IoMdClose className={CSS.close} onClick={() => remove(blueprint)} />
)}
</div>
);
};
The parent component receives a list of items and renders a Blueprint component for each item. The problem is that when the IoMdClose icon is pressed the item will be removed from the list but the selected value won't update to -1. 🤷‍♂️
Ciao, I found your problem: in function select you already set selected:
const select = blueprint => {
const index = blueprints.indexOf(blueprint);
if (index !== -1) {
setSelected(index); // here you set selected
}
};
When you click on IoMdClose icon, also select will be fired. So the result is that this useEffect:
useEffect(() => {
console.log(`DEBUG ==> ${selected}`);
}, [selected]);
doesn't log anything.
I tried to remove setSelected(index); from select function and when I click on IoMdClose icon, selected will be setted to -1 and useEffect logs DEBUG ==> -1.
But now you have another problem: if you remove setSelected(index); from select and you try to select one blueprint from left treeview, blueprint will be not selected. So I re-added setSelected(index); in select function. Removed setSelected(-1); from remove function and now useEffect doesn't log anything!
I think this happens because you are trying to set selected to an index that doesn't exists (because you removed blueprint on icon click). To verify this, I modified setSelected(index); in select function to setSelected(0); and infact now if I remove one blueprint, useEffect will be triggered and logs DEBUG ==> 0.
If the idea behind setSelected(-1); is to deselect all the blueprints in treeview, you could do something like:
export const BlueprintsProvider = ({ children }) => {
....
const removeSelection = useRef(0);
useEffect(() => {
if (blueprints.length < removeSelection.current) setSelected(-1); // if I removed a blueprint, then fire setSelected(-1);
setCount(blueprints.length);
removeSelection.current = blueprints.length; //removeSelection.current setted as blueprints.length
}, [blueprints]);
And of course remove setSelected(-1); from remove function.

Update a single property of object from array using useState() hook

I have an array of objects in todos state hook. And I want to change a single property if it is completed. Seems I can update it but without using setTodos. I am a React beginner.
const [todos, setTodos] = useState([]);
const [input, setInput] = useState("");
const addTodoHandler = (e) => {
e.preventDefault();
if (input.length < 2) return;
setTodos([...todos, { id: Date.now(), text: input, isComplete: false }]);
setInput("");
};
const removeHandler = (id) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
const completeHandler = (id) => {
// working without setTodo()
// errors when added setTodo()
todos.map((todo) =>
todo.id === id ? console.log((todo.isComplete = !todo.isComplete)) : ""
);
};
<div className="todolist">
{todos.map((todo) => (
<Todo
key={todo.id}
id={todo.id}
text={todo.text}
removeHandler={removeHandler}
completeHandler={completeHandler}
isComplete={todo.isComplete}
/>
))}
</div>
To fix this inside completeHandler() first create a new array using map() method and inside map() method update the isComplete for the current todo and simply return the updated value like:
var updatedTodos = todos.map((todo) => todo.id === id ? {
...todo,
isComplete: !todo.isComplete
} : todo);
Then inside setTodos() just return this new updatedTodos array like:
setTodos(updatedTodos);
You can also do this in one-line like:
setTodos(todos.map((todo) => todo.id === id ? { ...todo, isComplete: !todo.isComplete } : todo));
But the previous code provides more readability and also helps in better debugging if you want to check each variable line by line.

Categories

Resources