Why is onClick function call only passing one character? (ReactJs) - javascript

I'm trying to invoke a function call when the user clicks on a button and pass a variable, however when I console.log the param it outputs only one letter as response (so if gallery is passed it only returns g). I'm not sure whether the way the function is being invoked needs to change, or the function receiving it.
const Button = ({selectedIcon}) => {
const [selected] = selectedIcon;
return(
<div className="buttonBlock">
<span className="btn">
<input className="btn-outline" type="button" value="Press Me" onClick={ () => getResponse(selected) }/>
</span>
</div>
);
}
export default function getResponse(selected){
console.log (selected);
if(selected === "gallery"){
console.log (selected);
getGallery();
...
}

With this instruction
const [selected] = selectedIcon;
you are de-structuring the string which is treated as an array and therefore only returns the first character to the variable selected.
More on Destructuring assignments.

I think your problem lies here:
const [selected] = selectedIcon;
if selectedIcon = 'gallery',and [selected] = selectedIcon, then selected = 'g'
> selectedIcon = 'gallery'
'gallery'
> [selected] = selectedIcon
'gallery'
> selected
'g'
>

Related

Delete last item from array with useState

I'm new to JS, React and TypeScript. I did a tutorial to add a todo-list. To get some practice, I decided to add the delete button and the "remove last item" button.
Deleting the complete list worked out fine (I'm proud of myself, hah!) but the "delete last item" is not working, I tried different things (e.g. just todos.pop()).
function App() {
const [todos, setTodos] = useState([])
const [input, setInput] = useState("")
// prevents default, adds input to "todos" array and removes the input from form
const addTodo = (e) => {
e.preventDefault()
setTodos([...todos, input])
setInput("")
}
// deletes the todo-list
const clearTodo = (e) => {
e.preventDefault()
setTodos([])
}
// deletes the last entry from the todo-list
const clearLastTodo = (e) => {
e.preventDefault()
setTodos(todos.pop())
}
return (
<div className="App">
<h1>ToDo Liste</h1>
<form>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
type="text"
/>
<button type="submit" onClick={addTodo}>
Hinzufügen
</button>
</form>
<div>
<h2>Bisherige ToDo Liste:</h2>
<ul>
{todos.map((todo) => (
<li>{todo}</li>
))}
</ul>
</div>
<div>
<form action="submit">
<button type="submit" onClick={clearLastTodo}>
Letzten Eintrag löschen
</button>
<button type="submit" onClick={clearTodo}>
Liste löschen
</button>
</form>
</div>
</div>
);
}
export default App;
Am I missing something (clearly I am, otherwise it would work)? But what? :D
Thank you in advance!
There are 3 possible solutions to your problem.
Solution 1:
Slice the array from the first element to the -1 (1 before the last) element.
setTodos(todos.slice(0, -1)));
Or
setTodos((previousArr) => (previousArr.slice(0, -1)));
Solution 2:
Create a copy array and splice it with the value of -1. Then set the array from the first element to the -1 element.
const copyArr = [...todos];
copyArr.splice(-1);
setTodos(copyArr);
Solution 3:
Create a copy list with the ..., pop the copy and set the new value of the array to the copy.
const copyArr = [...todos];
copyArr.pop();
setTodos(copyArr);
You could do it as below. The spread operator makes sure you don't give the same reference to setTodos:
const clearLastTodo = (e) => {
e.preventDefault();
let copy = [...todos]; // makes sure you don't give the same reference to setTodos
copy.pop()
setTodos(copy);
}
A note about state in React:
Always treat state variables as if they were immutable. It's obvious for for primitive values like Number, Boolean... But for Objects and Arrays changing their content doesn't make them different, it should a new memory reference.
There are couple way to remove the last item from an array in javascript
const arr = [1, 2, 3, 4, 5]
arr.splice(-1) // -> arr = [1,2,3,4]
// or
arr.pop() // -> arr = [1,2,3,4]
// or
const [last, ...rest] = arr.reverse()
const removedLast = rest.reverse()
Following the guide updating objects and arrays in state
const onRemoveLastTodo = () => {
// Try with each way above
setTodos(todos.splice(-1))
}

React state update produces wrong result

My Fixtures.js code:
const [selectedButton, setSelectedButton] = useState(1);
console.log("selected button = ",selectedButton);
const dateClick = (e) => {
const clcikedDivId = e.currentTarget.id;
// some code here!
setSelectedButton(clcikedDivId);
}
};
<div className={classes.roundDatesContainer}>
{roundDates.map((item, index) => (
<Button
id={index + 1}
key={index}
lowOpacity={index + 1 !== selectedButton ? true : false}
className="primary"
customCssStyle={customCssStyle}
click={dateClick}
>
{item}
</Button>
))}
</div>
In my above code snippet, I have a round dates array which I'm iterating over with the map function and for each item I'm outputting a Button component that will have an id property equal to index + 1 and a lowOpacity property of true in case the index + 1 is different from the selectedButton value and false otherwise. In addition it has a click property that tells the button to execute the dateClick function when the button is clicked, and this function will execute some code (that doesn't matter for this question) and at the end of the fucntion the selectedButton value will be set to the clicked button id
My Button component:
function Button(props) {
const { id, type, click, className, lowOpacity, customCssStyle } = props;
console.log("id = ", id, " & low opacity from btn componenet = ", lowOpacity);
return (
<button
id={id}
type={type || "button"}
onClick={
click ||
(() => {
return;
})
}
className={`${classes[className || ""]} ${lowOpacity ? classes.lowOpacity : undefined}`}
>
<span style={{ ...customCssStyle }}>{props.children}</span>
</button>
);
}
As you can see in this component I'm setting a class of lowOpacity to the button in case that prop is set to true (which will be true when index + 1 is different from the selectedButton value).
Now when the page first loads I get the following correct result:
(Note that I have react strict mode on which is why the fixtures.js is loading twice)
Since at first the selectedButton value defaults to 1 and index+1 is equal to 1 that means all buttons except the first 1 will have their lowOpacity property set to true (though there is also an undefined value for a Button with an id of undefined which I have no idea where did that come from).
The problem is when I click on another button all buttons will now have the lowOpacity propert set to true:
For some reason when I click the second (or third) Button the selectedButton changes and become equal to the id of that clicked Button however as you can see from the above image all of the Button components now have their lowOpacity property set to true (plus one of undefined for a button of id undefined which I have no idea where that came from)
The issue here is that IDs are treated as strings in the DOM, so here: index + 1 !== selectedButton (Where selectedButton is set from e.currentTarget.id)
You are comparing a number to a string, and they will never be equal, so !== will always return true
E.g.
function example(e) {
console.log(e.currentTarget.id)
console.log(1 === e.currentTarget.id)
}
<input id="1" onClick="example(event)" placeholder="Click me" />
You will need to either:
Convert the index + 1 to a string before comparing the values
Parse the ID as a number before the comparison
Parse the ID as a number before storing it to selectedButton
I would recommend replacing:
const clcikedDivId = e.currentTarget.id
with
const clcikedDivId = parseInt(e.currentTarget.id)

How to toggle two different buttons (function/text) based on external condition (e.g. num of items purchase)

I am revisiting an earlier idea to toggle between two buttons conditionally in a CRA.
import ...
const ...
export const Index = () => {
// Boolean to toggle buttons conditionally
const [reachMax] = React.useState( id <= 8 );
return (
<div>{(reachMax &&
<div{(reachMax &&
<button
id="btn1"
type="button"
className="..."
onClick={event}
>
Event A: "Sales!"
</button>
</div>)
||
<div>
<button
id="btn2"
type="button"
className=" "
disabled='true'
>
Event B: "Out of Stock"
</button>
</div>)
}
)
}
Using a state hook. The condition for the Boolean ( contract.tokenUri.id <= 8 ) is taken from an external smart contract dynamically. How can the condition be set so that it would not return an undefined error?
You're trying to access this in an arrow function. Besides not knowing what this will point to, in React that's not how you access props in functional components.
I assume you use Index like this in your Toggle component:
<Index data={...} />
Then you access it like:
export const Index = (props) => {
const data = props.data;
// ....
};
Managed to resolve by setting this condition in order to read the ID off the smart contract:
const reachMax = mintedState.state === "SUCCESS" && mintedState.data.length <= 8;
There's no need for the constructor as the state is directly passed into the app from the external contract. Once the nmintedState.state condition is satisfied (success), the data.length (a proxy for the ID) can be used as the final condition for setting the right button.
This problem is specific to the combination of Solidity contract and ReactJS used.

cannot reach mapped li element inside of html collection

I have a small react search app where I search data and I put 5 data per page
I want to reach the li element's length (I mapped user names in an li tag in my Posts component)
However when I consoled log below code it is an empty array:
const el = document.getElementsByClassName("list-group-item")
console.log(el)
I tried to reach without mapping method, it works properly however since there is a mapping inside of the tag, it gives me an empty array.
How can I reach li element's length here?
Here is the console result:
HTMLCollection {}
__proto__: HTMLCollection
Below is my code of my components
import React, {useState} from 'react';
const Posts = ({ posts }) => {
const [searchTerm,setSearchTerm]=useState([]);
const el = document.getElementsByClassName("list-group-item")
console.log(el)
return (
<div>
<div>
<input
type="text"
placeholder="Type here..."
onChange={e => setSearchTerm(e.target.value)}
/>
<button
type="submit"
>
Search
</button>
</div>
<ul className='list-group mb-4'>
{posts.filter(value => {
if(searchTerm === ""){
return value
} else if(value.name.toString().toLowerCase().includes(searchTerm.toString().toLowerCase())){
return value
}
}).map(post => (
<li key={post.id} className='list-group-item'>
{post.name}
</li>
))}
</ul>
</div>
);
};
export default Posts;
Lots of problems here.
You're comparing an array searchTerm to an empty string '', which will never work.
.filter returns a boolean, or a truthy/falsy value, implicitly symbolizing a boolean. So you should recall the 6 falsy values in JS, and verify you're not returning one of them. null, undefined, 0, '', false, NaN.
Because of these 2 problems, you're always returning undefined from your filter function which is a falsy value, which means, your map never iterates and you're getting your unexpected results.
Fix 1
const [searchTerm,setSearchTerm]=useState(''); // change 1
Fix 2
posts.filter(value => {
if(searchTerm === ""){
return true // change 2
} else if(value.name.toString().toLowerCase().includes(searchTerm.toString().toLowerCase())){
return true // change 3
}
return false; // change 4
}
You're accessing DOM element before rendering.
First, render the DOM Element then we can access so we use useEffect.
Like this :
useEffect(()=>{
const el = document.getElementsByClassName("list-group-item")
console.log(el)
},[])

When creating buttons for each item in an array in React, how do I pass in unique onClick parameters based on the array value?

I am creating a button for each string an an array (ie. majorDivisions = ["upper", "lower"]). I need each button to have an onClick function with parameters equal to the unique value (ie. "upper" or "lower") depending on which value was used to create the button. It is creating the correct buttons but for when calling the function, the parameter is not the string but instead some class object.
return(
<div>
{this.state.majorDivisions.map((division) => {
return <button onClick = {this.renderDivisionRequirements.bind(null, {division})}>{division}</button>
})}
</div>)
My code is successfully creating 2 buttons [lower] and [upper], however the onClick parameter {division} is appearing as a class object.
When you wrap your argument in curly braces like {division} , you are passing in an object. ie: { upper: "upper" } and { lower: "lower" }
This is recognized as a short-hand method to create an object inside a function declaration.
Just remove the curly-braces and the individual division parameter will be passed as expected to each button.
return(
<div>
{this.state.majorDivisions.map((division) => {
return <button onClick = {this.renderDivisionRequirements.bind(null, division)}>{division}</button>
})}
</div>)
Also, to make things a bit cleaner, I would recommend turning renderDivisionRequirements() into an arrow-function so you don't have to bind this. Or using anonymous function to call it on button-click.
renderDivisionRequirements = () => {
...renderDivisionRequirements logic
}
So your return logic can just be
return(
<div>
{this.state.majorDivisions.map((division) => {
return <button onClick = {() => this.renderDivisionRequirements(division)}>{division}</button>
})}
</div>)

Categories

Resources