React Hooks - Select Option value and name in the same method - javascript

I have the following situation where i want to get due different values to set the State ( item.id and item.name ) when i select an item in my dropdown. At the moment i can only do for 1 value ( item.id )
How can i do in the same method, is this possible?
This is the code
const [selectedValue, setSelectedValue] = useState("");
const [selectedName, setSelectedName] = useState("");
const selectValue = evt => {
const { value } = evt.target;
setSelectedValue(value);
};
<select value={selectedValue} onChange={selectValue}>
{teamsDetail && teamsDetail.length > 0
? teamsDetail.map(item => (
<option key={`${item.team_id}`} value={item.team_id}>
{item.name}
</option>
))
: null}
</select>
{selectedValue}
{selectedName} ??
The question is how can i now add the same logic also for te name value in in order to display for example the selectedName?
I provide the demo here => https://codesandbox.io/s/select-demo-u1o1k

You can get the name from teamDetail based on id
const selectValue = evt => {
const { value } = evt.target;
const item = teamsDetail.find(item => item.id == value);
setSelectedValue(value);
setSelectedName(item.name);
};
or you could get value using nativeEvent to get option text like
const selectValue = evt => {
const { value } = evt.target;
const index = evt.nativeEvent.target.selectedIndex;
const text = evt.nativeEvent.target[index].text;
setSelectedValue(value);
setSelectedName(text);
};

Related

React component not rerendering based on props change. JSX in async function

I have a filterTime state which is set to "Daily" as default. It is located at the parent of Friends.js and passed as prop.
Friends.js
const Friends = (props) =>{
const [friendsDisplay, setFriendsDisplay] = useState([]);
let friends = [];
useEffect(()=>{
fetchData();
}, [])
const fetchData = async() => {
try{
let doc = await docRef.get();
let data = doc.data();
for(let i of data.Friends){ //Getting the array of friends' uids
friends.push(i);
}
friends = await Promise.all(friends.map(async UserFriend => {
if(UserFriend !== ""){
let friend = await fetchUserFriendData(UserFriend);
return (<Friend key = {UserFriend}
UserFriend = {friend}
isUser = {false}
src = {``}
filterTime = {props.filterTime}/>
)
}
return null;
}));
let userInfo = <Friend key = {uid}
UserFriend = {data}
isUser = {true}
src = {``}
filterTime = {props.filterTime}/>
friends = friends.concat(userInfo);
sortFriends();
}catch(error){
console.log(error);
}
}
const sortFriends = () =>{
if(friends.length !== 0){ //If User Has Friends Sorts Them in Descending Order
friends.sort((a, b) => {
if(props.filterTime === "Daily"){
return (a.props.UserFriend.XpEarnedToday > b.props.UserFriend.XpEarnedToday ? -1 : 1);
}
if(props.filterTime === "Weekly"){
return (a.props.UserFriend.XpEarnedWeek > b.props.UserFriend.XpEarnedWeek ? -1 : 1);
}
if(props.filterTime === "Monthly"){
return (a.props.UserFriend.XpEarnedMonth > b.props.UserFriend.XpEarnedMonth ? -1 : 1);
}
return -1;
});
setFriendsDisplay(friends);
}
return (
<>
<div className = "friendsContainer">
{friendsDisplay.length !== 0 && //If User Has Friends (ScoreBoard)
<div className = "friends">{friendsDisplay}</div>
}{friendsDisplay.length === 1 && //If User Has No Friends (Add Friend Button)
<>{friendsDisplay}</>
}
</div>
</>
);
}
Friend.js
const Friend = (props) =>{ //filterTime---> Daily Weekly etc. UserFriend--->all properties of friend isUser--->boolean src--->source for picture
const[friendName, setFriendName] = useState(props.UserFriend.Name);
let WindowWidth = windowResizeListener();
return (
<div className = {`friendBackground ${props.isUser ? "friendBackground-User" : ""}`}>
<div className = "friendPorperties">
<p className = {`friendRank ${props.isUser ? "friendRank-User" : ""}`}>1</p>
<p className = {`friendName ${props.isUser ? "friendName-User" : ""}`}>{friendName}</p>
</div>
{props.filterTime === "Daily" && //checks if filter = daily
<p className = {`friendXp ${props.isUser ? "friendXp-User" : ""}`}>{`+${props.UserFriend.XpEarnedToday}Xp`}</p>
}
{props.filterTime === "Weekly" && //checks if filter = weekly
<p className = {`friendXp ${props.isUser ? "friendXp-User" : ""}`}>{`+${props.UserFriend.XpEarnedWeek}Xp`}</p>
}
{props.filterTime === "Monthly" && //checks if filter = monthly
<p className = {`friendXp ${props.isUser ? "friendXp-User" : ""}`}>{`+${props.UserFriend.XpEarnedMonth}Xp`}</p>
}
</div>
);
}
When I use;
useEffect(()=>{
console.log(props.filterTime);
}, [props.filterTime])
In Friends.js it logs the correct filterTime value as I click different filters. Whereas in Friend.js it is stuck in the first value ("Daily") and won't update if I click different filters.
So the problem isn't about filterTime it is about child not rerendering as the filterTime value changes.
React Dev tools also shows prop.filterTime isn't changing for Friend.js.
How can I fix this.

How to auto grow text on input value?

I have an input form field that outputs text on submit to another created input, essentially an editable todo list. I have tried to make the input text value auto grow, but cannot figure out how to do it. Right now the user has to scroll over to see the rest of the text on each list item. This should not be.
What I tried:
I have tried creating a span and attaching editableContent but that makes my input text disappear.
I have tried setting an attribute on max-length on the created input but cannot get it to work. What is the best way to accomplish auto growing the text input value?
Here is the full codepen
const createTodoText = (todo) => {
const itemText = document.createElement("INPUT");
// const itemText = document.createElement("span");
// itemText.contentEditable
// itemText.contentEditable = 'true'
itemText.classList.add("todoText");
itemText.value = todo.name;
itemText.addEventListener("click", (e) => {
e.currentTarget.classList.add("active");
});
// update todo item when user clicks away
itemText.addEventListener("blur", (e) => {
todo.name = e.currentTarget.value;
renderTodos();
});
return itemText;
};
There you go: -
// select DOM elements
const todoForm = document.querySelector(".todo-form");
const addButton = document.querySelector(".add-button");
const input = document.querySelector(".todo-input");
const ul = document.getElementById("todoList");
let todos = [];
todoForm.addEventListener("submit", function (e) {
e.preventDefault();
addTodo(input.value);
});
const addTodo = (input) => {
if (input !== "") {
const todo = {
id: Date.now(),
name: input,
completed: false
};
todos.push(todo);
renderTodos();
todoForm.reset();
}
};
const renderTodos = (todo) => {
ul.innerHTML = "";
todos.forEach((item) => {
let li = document.createElement("LI");
// li.classList.add('item');
li.setAttribute("class", "item");
li.setAttribute("data-key", item.id);
const itemText = createTodoText(item);
const cb = buildCheckbox(item);
const db = buildDeleteButton(item);
// if (item.completed === true) {
// li.classList.add('checked');
// }
li.append(cb);
li.append(db);
li.append(itemText);
ul.append(li);
});
};
const createTodoText = (todo) => {
const itemText = document.createElement("span");
itemText.setAttribute('role','textbox');
itemText.setAttribute('contenteditable',"true");
itemText.classList.add("todoText");
itemText.innerHTML = todo.name;
itemText.addEventListener("click", (e) => {
e.currentTarget.classList.add("active");
});
// update todo item when user clicks away
itemText.addEventListener("blur", (e) => {
todo.name = e.target.textContent;
renderTodos();
});
return itemText;
};
const buildCheckbox = (todo) => {
const cb = document.createElement('input');
cb.type = 'checkbox';
cb.name = 'checkbox';
cb.classList.add('checkbox');
cb.checked = todo.completed;
// checkbox not staying on current state ??
cb.addEventListener('click', function (e) {
if (e.target.type === 'checkbox') {
// todo.completed = e.target.value;
todo.completed = e.currentTarget.checked
e.target.parentElement.classList.toggle('checked');
}
});
return cb;
};
const buildDeleteButton = (todo) => {
const deleteButton = document.createElement("button");
deleteButton.className = "delete-button";
deleteButton.innerText = "x";
deleteButton.addEventListener("click", function (e) {
// duplicates children sometimes ??
const div = this.parentElement;
div.style.display = "none";
todos = todos.filter((item) => item.id !== todo.id);
});
return deleteButton;
};
// //------ Local Storage ------
function addToLocalStorage(todos) {}
function getFromLocalStorage() {}
// getFromLocalStorage();
This is the Javscript code part. In createTodoText, you can see the changes i've made. It's working according to what you want. What i've done is simple used 'span' instead of 'input'.
How about trying something like
if (todo.name.length) {itemText.size = todo.name.length;}

Use a prop function inside useEffect return: prop.function is not a function

I am currently trying to build an next.js app.
So what I want to do is passing a function to a component and call it with useEffect
I have the following component in order to change the props of the parents element. Therefore I pass the function from the parent like this:
<NumberInput name="height" update={manuelUpdate}></NumberInput>
The manuelUpdate function is a another props function from the actual parent:
const manuelUpdate = (name, value) => {
props.update(name, value);
};
It works fine if I run the props.function with the onclick functions. However as soon as I try to use it in useEffect, it returns that the function is not a function.
Maybe I am just thinking to complicated..
this is the component;
const NumberInput = ({ name, min = undefined, max = undefined, ...props }) => {
const minInput = min !== undefined
? min
: null;
const maxInput = max !== undefined
? max
: null;
const [numb, setNumb] = useState(0);
useEffect(() => {
console.log(props.update)
}, [numb]);
const increaseNumb = () => {
if (numb < maxInput || maxInput == null) {
setNumb(numb + 1)
}
props.update(name, numb)
};
const decreaseNumb = () => {
if (numb < minInput || minInput == null) {
setNumb(numb - 1)
}
};
const changeHandler = ({ e }) => {
let n = parseInt(e.target.value)
if (Number.isNaN(n)) {
setNumb(numb)
} else {
setNumb(n)
}
}
return (
<div className={styles.def_number_input, styles.number_input}>
<button onClick={decreaseNumb} className={styles.minus}></button>
<input className={styles.quantity} name="quantity" value={numb} onChange={(e) => changeHandler({ e })}
type="number" />
<button onClick={increaseNumb} className={styles.plus}></button>
</div>
);
};
sorry in advance if my question is stupid or my code messy, I am still in the process of learning :D
Issue was related to another problem. I was accidentally calling the component 2 times and forgot to adjust the parameters. CASE CLOSED

Handling changes in single and multiple select/input in Vanilla JavaScript

I have a webpage with two select menus, one input, three buttons, and a checkbox. Everything is added and manipulated by JavaScript. On page page load, there is an object initiated with no values ( either value correspond either one of select or input ). You can select values and update it ( target is array of objects ) in array. Then, you can choose one of the buttons to copy selected options. Each object has three value (copying means that you copy the segment containing those values) times as many as you typed in input. After that, all objects are shown on screen. You can click a button to check all checkboxes and all objects change value. Value is updated in all selects of changed type and in the corresponding array.
Now my problems begin. When you unselect all checkboxes and want to change only 1 object all objects are updated and I have no idea why. There are conditions to check for each object if the checkbox is unchecked. If a single object is updated (either select or input) all of the other objects are updated ( none of checkbox is checked).
Here is github link to this project to check everything out without problems.
Also one more thing... whenever I update all values handleDOMChange function should fire automatically, but as you can see I have to fire it up manually so the changes are "visible" on my target array
const collectedData = [ {
cableType: "",
calbleLen_m: 0,
deviceType: ""
} ];
const completeData = {};
window.addEventListener('load', () => {
handleButtonEvents();
const segments = document.querySelectorAll('.installationSegment');
segments.forEach((segment, i) => {
handleInputAndSelectChange(segment, i);
});
const targetNode = document.getElementById("installationContainer");
targetNode.append(selectAllCheckboxesButton);
const config = {
childList: true,
subtree: true,
attributes: true,
characterData: true
};
const observer = new MutationObserver(handleDOMChange);
observer.observe(targetNode, config);
}
//Handling DOMChanges
const handleDOMChange = function() {
const powerSupplyElement = document.getElementById('powerSupply');
powerSupplyElement.addEventListener('change', e => completeData.supplyType = e.target.value);
const segments = document.querySelectorAll('.installationSegment');
if( segments.length >= 2 ) {
segments.forEach((segment, i) => {
const checkbox = segment.querySelector('input[type="checkbox"]');
if( !checkbox.checked ) {
//updating value for single change, i is index of element in array and none of checkboxes is checked.
handleInputAndSelectChange(segment, i);
} else if( checkbox.checked ) {
//updating value for every segment exisitng in DOM whose checkbox has been checked.
handleManySegmentsChange(segment);
}
});
}
completeData.bus = [ ...collectedData ];
console.table(completeData.bus);
}
//handling ButtonEvents
handleButtonEvents = function() {
const installationSegment = document.getElementById('installationContainer');
installationSegment.addEventListener('click', e => {
if( e.target.id.includes("Skopiuj") ) {
handleCopyNthTimes(e);
} else if( e.target.id.includes("Usun") ) {
handleDeleteDevice(e);
} else if( e.target.id === 'selectAllCheckboxes' ) {
checkAllCheckboxes(e);
}
});
}
//handling all checkboxes select and unselect
const checkAllCheckboxes = function() {
const segments = document.querySelectorAll('.installationSegment');
segments.forEach((segment, i) => {
const checkbox = segment.querySelector('input[type="checkbox"]');
checkbox.checked = !checkbox.checked;
handleDOMChange();
});
};
//handling single and multiple changes in select/input
handleManySegmentsChange = function(segment) {
segment.addEventListener('change', (e) => {
switch( e.target.name ) {
case 'cableSelect': {
const cableSelect = document.querySelectorAll('.cableSelect');
cableSelect.forEach(cable => cable.value = e.target.value);
collectedData.forEach(cable => cable.cableType = e.target.value);
handleDOMChange();
break;
}
case 'deviceSelect': {
const deviceSelect = document.querySelectorAll('.deviceSelect');
deviceSelect.forEach(device => device.value = e.target.value);
collectedData.forEach(device => device.deviceType = e.target.value);
handleDOMChange();
break;
}
case 'cableInput': {
const cableInput = document.querySelectorAll('input[name="cableInput"]');
cableInput.forEach(input => input.value = e.target.value);
collectedData.forEach(input => input.calbleLen_m = parseInt(e.target.value));
handleDOMChange();
break;
}
}
})
}
handleInputAndSelectChange = function(segment, index) {
segment.addEventListener('change', (event) => {
switch( event.target.name ) {
case 'cableSelect': {
collectedData[index].cableType = event.target.value;
break;
}
case 'deviceSelect': {
collectedData[index].deviceType = event.target.value;
break;
}
case 'cableInput': {
collectedData[index].calbleLen_m = parseInt(event.target.value);
break;
}
}
});
}
All right, I've found the answer. The reason it didn't work is that handleManySegmentsChange function is going to be fired in one circumstance - if user changes many select values at once. But this cannot be the case. My solution to the problem is to detect one change in segment, check for checkbox value, pass it to the function and depending on the checkbox value either update one value or all of them. Like so:
const segments = document.querySelectorAll('.installationSegment');
segments.forEach((segment, i) => {
segment.addEventListener('change', e => {
const checkbox = segment.querySelector('input[type="checkbox"]');
handleInputAndSelectChange(segment, e, i, checkbox.checked);
})
});
and then in function
handleInputAndSelectChange = function(segment, event, index, checked) {
switch( event.target.name ) {
case 'cableSelect': {
if( checked ) {
const cableSelect = document.querySelectorAll('.cableSelect');
cableSelect.forEach(cable => cable.value = event.target.value);
collectedData.forEach(data => data.cableType = event.target.value);
} else if( !checked ) {
collectedData[index].cableType = event.target.value;
}
break;
}
case 'deviceSelect': {
if( checked ) {
const deviceSelect = document.querySelectorAll('.deviceSelect');
deviceSelect.forEach(device => device.value = event.target.value);
collectedData.forEach(device => device.deviceType = event.target.value);
} else if( !checked ) {
collectedData[index].deviceType = event.target.value;
}
break;
}
case 'cableInput': {
if( checked ) {
const cableInput = document.querySelectorAll('input[name="cableInput"]');
cableInput.forEach(input => input.value = event.target.value);
collectedData.forEach(input => input.calbleLen_m = parseInt(event.target.value));
} else if( !checked ) {
collectedData[index].calbleLen_m = parseInt(event.target.value);
}
break;
}
}
}

Shared state in React updates the rest of the components and the current one as well

I have a use case where I have 4 select components all sharing the same state. The problem is when the user selects an option from one select, the other selects should not show that option and so on. That problem is simple and solved. On doing so I am updating the shared state and hence all 4 selects are re-rendered by react and hence I am losing the selected value from the select. See attached GIF.
Can someone point out a solution where I can solve this using minimum states and less re-rendering?
Attaching the whole code below:
const SendTroops = () => {
const [planets, setPlanets] = useState([]);
const [vehicles, setVehicles] = useState([]);
const constructState = (data) => {
const arr = [];
let obj = {};
for (let i = 0; i < 4; i++) {
obj[i] = data;
}
arr.push(obj);
return arr;
};
useEffect(() => {
const getPlanets = async () => {
try {
const response = await callAPI(PLANETS.url, PLANETS.method);
const data = await response.json();
setPlanets(constructState(data));
} catch (err) {
console.log(err);
}
}
const getVehicles = async () => {
try {
const response = await callAPI(VEHICLES.url, VEHICLES.method);
const data = await response.json();
setVehicles(constructState(data));
} catch (err) {
console.log(err);
}
};
getPlanets();
getVehicles();
}, []);
const ShowInputs = (n) => {
const options = [];
for (let i = 0; i < n; i++) {
options.push((
<div className="column" key={i}>
<Select
styles="select"
name={`destination${i}`}
options={(planets && planets[0]) ? planets[0][i] : []}
onSelect={(e) => onDestinationChange(e, i)} />
<Radio
styles="radio"
name={`vehicle${i}`}
options={(vehicles && vehicles[0]) ? vehicles[0][i] : []}
onSelect={(e) => onVehicleChange(e, i)} />
</div>
))
}
return options;
}
const onDestinationChange = (e, index) => {
const selectedName = e.target.value;
const values = Object.values(planets[0]);
let obj = {};
for (let i = 0; i < 4; i++) {
if (i === index) obj[i] = values[i];
else {
obj[i] = values[i].filter((value) => value.name !== selectedName);
}
}
const updatedPlanets = [].concat([obj]);
setPlanets(updatedPlanets);
};
const onVehicleChange = (e) => {
console.log(e.target.value);
};
const onReset = (e) => {
e.preventDefault();
};
return (
< main >
<p className="search-title">Select planets for your Troops to search</p>
<form>
<div className="row">
{ShowInputs(4)}
</div>
<div className="row button-group">
<Link to='/missionreport'>
<Button styles="button" type="submit" text="Find Falcone" />
</Link>
<Button styles="button" type="reset" text="Reset" onPress={onReset} />
</div>
</form>
</main >
);
};
Just remember the index of selected planet in planets list and the index of select element where that planet has been chosen. Then show the selected option only on the select where it's been previously chosen.
That way you must add 2 more fields to your state:
Index of selected planet in planets list (let default be -1 or something negative)
Index of <select> element where it's been chosen (in your case it's the iterator i of the for loop in ShowInputs()).
Then update that fields every time when the user selects something (using onDestinationChange()).

Categories

Resources