[react]: useMemo returns first element when passed an array - javascript

Here's the sample code
export default function App() {
const [links] = React.useMemo(
() => ['hello', 'world'],[]
)
return (
<div className="App">
<button onClick={() => console.log(links)}>console</button>
</div>
);
}
When the button is clicked, i get following in console
hello
My question: since it returns the first element, i'm unable to map over links array. is it even the way I should be using useMemo? note that the array i passed is for example and the elements could get bigger than just a simple string.

Issue was, as pointed by #Andrea Giammarchi, the destructuring of links in lvalue
Following code fixes the problem:
- const [links] = React.useMemo(
+ const links = React.useMemo(

Related

How to create dynamic states and update it in react?

I have list of data getting fetched from the API and each index in data is creating a <div> which consist of some information and button along with it.
Now when I click on button, a textarea and submit button should open for that <div> and closes when clicked again.
I tried to create this here.
To achieve this, I am creating n number of states and update the state when user click the button.
How can I achieve this and where am I going wrong ?
This is a working example of what you're looking for:
const { useState } = React;
const Counter = () => {
const [count, setCount] = useState(0);
const data = [
{"id":100},
{"id":200},
{"id":300},
{"id":400},
];
const [show,setShow] = useState([]);
const handleClick = id => e => {
const showCopy = [...show];
if(show[id]){
showCopy[id] = false;
} else {
showCopy[id] = true;
}
setShow(showCopy);
}
return (
<div>
{
data.map((k,v) => (
<div key={v}>
<div>
<p>Data{v+1}</p>
<p>Some More Information</p>
</div>
<button onClick={handleClick(v)}>Update</button>
<br/>
{
show[v] ? <div>
<textarea></textarea>
<button id={v}>Delete</button>
</div> : <></>
}
<hr/>
</div>
))
}
</div>
)
}
ReactDOM.render(<Counter />, document.getElementById('app'))
A couple things were wrong in your sample code:
Improper use of dot operators instead of brace operators
The JS dot (.) operator is used to access named attributes associated with the given JS object. In these particular cases, the values v and id are dynamic, and not the true names of the attributes desired. Using the brace [] operators allows you to access the dynamic value stored in the variable.
Improper array state update
In your example, the code you had written was creating a new array based on the contents of the previous array and adding a new object every time with a literal attribute named id. In the updated code, a copy of the original state array is created and the desired boolean value with index id is modified.

React State Hook initialised to [] still shows type as undefined

Code:
const [input, setInput] = useState('');
const [studentList, setStudentList] = useState([]);
useEffect(() => {
console.log(studentList)
console.log(studentList.type)
}, [studentList]);
return (
<div id="add-students-input-div">
<input
type="text"
id='add-students-input'
value={input}
placeholder='Enter a student to the database'
onChange={(event) => {
setInput(event.target.value)
}}
/>
<div id="add-students-button" onClick={() => {
setStudentList([document.getElementById('add-students-input').value, ...studentList])
setInput('')
}}>
<p>Add</p>
</div>
</div>
)
Problem:
The print statement for studentList is returning the array but the print statement for studentList.type is undefined at all times, even after elements are added to the array.
How can I ensure that studentList.type returns Array.
studentList in your code will ever be an array, empty when you initialize the state. If you want to check if there is something in the array you have to use studentList.length
Altough previous contributors solved your problem by eliminating it in other place, I would like to answer this:
How can I ensure that studentList.type returns Array
If you want to make sure your variable is an array, you may use isArray() static method of Array:
Array.isArray(studentList) // returns true if array or false if not
As mentioned in the comments, arrays do not have a type property.
Your studentList state value is always an array; there is no need to check its type.
One thing you do appear to be doing incorrectly is updating studentList when you click your button (<div>). In short, you really shouldn't need to use DOM methods in React.
To update your array with the value from your input state, use this...
const handleClick = () => {
setStudentList((prev) => [input, ...prev]);
setInput("");
};
and
<div id="add-students-button" onClick={handleClick}>
<p>Add</p>
</div>
See useState - Functional updates for information on how I'm using setStudentList()

Why is the first element getting removed too?

So I am new to javascript and I tried making a todo list. This works well with adding elements. The issue is when I am removing some item, the first one gets removed too, why is it so? I know I am missing a small thing and this may be really basic but I am not able to find out what that is.
const App1 = () => {
const [item, updatedItem]=useState('');
const [Items, setItems]=useState([]);
function inputEvent(event) {
updatedItem(event.target.value);
}
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return[
...prev,
item
]
});
updatedItem('');
}
let key=0;
return(<>
<div className='back'>
<div className='list'>
<header>ToDo List</header>
<form onSubmit={addItem}>
<input type='text' placeholder='Add an item' value={item} onChange={inputEvent}/>
<button type='submit'>+</button>
</form>
<div className='items'>
<ol>
{Items.map((val) => <li><button id={key++} onClick={(event) => {
setItems((Items) => {
return Items.filter((val, index) => {
if(index!==Number(event.target.id)){
return index;
}
}
);
});
key=0;
}}>x</button>{val}</li>)}
</ol>
</div>
</div>
</div>
</>);
You return the index in your filter, expecting this to always be true, yet 0 (the index of the first element) is a falsy value.
Try this instead:
return Items.filter((_val, index) => index !== Number(event.target.id));
Some unrelated code-quality notes:
In React, you should always set a key prop on each element when looping through them, rather than id.
map has a second argument, index, which it passes into the callback --- you don't have to keep track of this yourself with e.g. key++ etc.
If you use map's index parameter, then you can pass that directly into your filter rather than using Number(event.target.id), which is not very idiomatic in React.
If you don't use an argument of a callback, it's a good idea to prefix it with a _ (like I've done with _val here), to make it explicit that you're not using it.
Your filter callback should return a flag. index is a number. When treated as a flag, 0 is false (more on MDN). Instead:
return Items.filter((val, index) => index !== Number(event.target.id));
However, your code is returning an array of li elements without setting key on them (see: keys), which React needs in order to manage that list properly (you should be seeing a warning about it in devtools if you're using the development version of the libs, which is best in development). You can't use the mechanism you're using now for keys when doing that, it will not work reliably (see this article linked by the React documentation). Instead, assign each Todo item a unique ID when you create it that doesn't change, and use that as the key (and as the value to look for when removing the item):
// Outside the component:
let lastId = 0;
// Inside the component:
const addItem = (event) => {
event.preventDefault();
setItems((prev) => {
return [
...prev,
{text: item, id: ++lastId}
];
});
updatedItem("");
};
// Add a remove function:
const removeItem = ({currentTarget}) => {
const id = +currentTarget.getAttribute("data-id"); // Get the ID, convert string to number
setItems(items => items.filter(item => item.id !== id));
};
// When rendering:
{Items.map((item) => <li key={item.id}><button data-id={item.id} onClick={removeItem}>x</button>{item.text}</li>)}
In some cases it may be useful to use useCallback to memoize removeItem to avoid unnecessary rendering, but often that's overkill.

How can I give a key in JSX the value of a variable depending on conditions

I'm learning React by implementing a front-end interface for the note app API that I created. I have succeeded in having a list of all the note titles in my database appear. I want to be able to click on a title and have the note expand into the text of the note. The easiest way I've found for this is to give the "key" attribute of the 'li' as a variable and to also declare the same variable in the JSX { } object because they have the same name.
I've been looking for an answer for this for a few days and have been unable to find this exact problem. You can put a variable in a normal JSX expression but I need to do it on the 'li' which means technically in the HTML.
Here's some code to understand what I'm saying.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [open, setOpen] = useState(false);
useEffect(() => {
//AXIOS CALL
setTitles(response.data[0]);
});
}, []);
//^^^^^add the array there to stop the response.data from repeating WAY TOO MANY TIMES
let listTitles = titles.map(titles => (
<li className="noteTitles" key={titles.title}>
{titles.title}
</li>
));
let showText = titles.map(titles => (
<li className="openText" key= {titles.text_entry}>
{titles.text_entry}
</li>
))
let openNote = () => {
setOpen(open => !open);
if (open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{showText}
</ul>
</div>
);
}
if (!open) {
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listTitles}
</ul>
</div>
);
}
};
return { openNote };
};
export default NoteData;
That is the code I currently have. Here's showing a more simplified version of the openNote function that maybe makes more sense and shows what I'm trying to do:
VariableHere = "";
let openNote = () => {
setOpen(open => !open);
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
};
let listNotes = titles.map(titles => (
<li className="noteTitles" key={VariableHere}>
{VariableHere}
</li>
));
return (
<div>
<ul onClick={openNote}>
{listNotes}
</ul>
</div>
);
On click of each element there should be a switch of the key elements so if the element is 'open' the key variable and given variable in the JSX object should be mapped to titles.text_entry and on '(!open)' the key and JSX should be mapped to titles.title.
first of all, you're using a ternary in a weird way:
open ? (VariableHere = titles.text_entry) : (VariableHere = titles.title);
Ternaries are meant to be expressions whose value is conditional, but you're using it like a shorthand if/else. Try something like
VariableHere = open ? titles.text_entry : titles.title;
which is both shorter and more readable.
Second of all, keys in an array of elements are meant to help React determine which elements to update, if an item represents the same object, its key shouldn't change. In this case, regardless of what you're displaying, an item in the array represents the same note. Always using the title as the key should be fine provided items can't have the same title. If they can, use some sort of unique ID instead. If the order of the items doesn't change throughout the life of the component, using the array index as the key is fine.
Lastly, what you seem to want to do is called "conditional rendering". There are many ways to achieve this in react, one such way is to use the pre-cited ternary operator. Here is a minimal working example:
const listNotes = titles.map(note => (
<li className="noteTitles" key={note.title}>
{open ? note.title : note.text_entry}
</li>
));
const openNote = () => {
setOpen(!open);
}
return (
<div className="noteContainer">
<ul onClick={openNote} className="titlesList">
{listNotes}
</ul>
</div>
)
You could also use a ternary in the key expression, but as I talked about above, it's not a good idea to do so.
Given your data-structure, I think you can simplify your code a bit. There is no need to create separate arrays for titles and contents. It sounds like you just want to expand and collapse a note when it is selected.
Here is a really simplified version on how you an do this. I'll use a sample data-set since we don't have access to your API.
const NoteData = () => {
const [titles, setTitles] = useState([]);
const [currentNote, setCurrentNote] = useState({});
useEffect(() => {
//AXIOS CALL
// setTitles(response.data[0]);
let data = [
{ id: 1, title: "a", text_entry: "what" },
{ id: 2, title: "b", text_entry: "is" },
{ id: 3, title: "c", text_entry: "up?" }
];
setTitles(data);
}, []);
const handleClick = noteId => {
let selectedTitle = titles.find(title => title.id == noteId);
//"collapse" if already selected
if (noteId === currentNote.id) {
setCurrentNote({});
} else {
setCurrentNote(selectedTitle);
}
};
let listTitles = titles.map(title => (
<li
className="noteTitles"
key={title.title}
onClick={() => handleClick(title.id)}
>
{title.title}
{title.id === currentNote.id && <div>{title.text_entry}</div>}
</li>
));
return (
<div>
Click on link item
<ul>{listTitles}</ul>
</div>
);
};
See working sandbox: https://codesandbox.io/s/old-silence-366ne
The main updates:
You don't need to have an "open" state. To be more succinct and
accurate, you should have a currentNote state instead, which is
set when clicking on a list item.
Have your handleClick function accept a noteId as an argument.
Then use that noteId to find the corresponding note in your titles
state. Set that found note as the currentNote. If the selected
note was already the currentNote, simply set currentNote to an
empty object {}, thus creating our expanding/collapsing effect.
In the JSX, after the title, use a ternary operator to conditionally
display the currentNote. If the note being mapped matches the
currentNote, then you would display a div containing the
text_entry.

return html for every key in object

I do apologize in advance I'm a JS/ReactJS newbie and I have had trouble phrasing the question, so if this is already answered somewhere I am sorry.
I have an object like this:
Object {Joy: 0.719115, Extraversion: 0.59527, Agreeableness: 0.650457}
I'd like to be able to return html for all of the keys in the object. As of right now it only returns the html for the first key (obviously, as return breaks the loop, if I'm not mistaken). How do I achieve rendering of html of all the keys from the object?
import React from 'react'
export const MessageSentiment = (props) => {
var sentiment = JSON.parse(props.sentiment)
console.log(sentiment)
for(var key in sentiment ) {
console.log(key, sentiment[key])
return (<h1>{key}: {sentiment[key]*100}</h1>)
}
}
These are the output and required output
Output:
<h1>Joy: 71.9115</h1>
Expected output:
<h1>Joy: 71.9115</h1>
<h1>Extraversion: 59.527</h1>
<h1>Agreeableness: 65.0456</h1>
Not sure if this has anything to do with it, but I get a warning in the console:
../src/components/messages/MessageSentiment.js
6:5 warning The body of a for-in should be wrapped in an if statement to filter unwanted properties from the prototype guard-for-in
✖ 1 problem (0 errors, 1 warning)
printWarnings # webpackHotDevClient.js:196
handleWarnings # webpackHotDevClient.js:209
connection.onmessage # webpackHotDevClient.js:255
EventTarget.dispatchEvent # eventtarget.js:49
(anonymous) # main.js:274
SockJS._transportMessage # main.js:272
EventEmitter.emit # emitter.js:44
WebSocketTransport.ws.onmessage # websocket.js:35
Two things here.
You need to always return one single element, in this case a div. Inside of this element you can have whatever you want, but it has to be a single parent component.
You will use map to iterate an array and get a new array. In this case the new array will contain the <h1/> elements.
```
export const MessageSentiment = (props) => {
const sentiments = JSON.parse(props.sentiment);
const keys = Object.keys(sentiments);
return (
<div>
{ keys.map(key => (<h1 key={key}>{key}: {sentiments[key]*100}</h1>)) }
</div>
);
}
```
Regards
A React component can't return multiple React elements. You should wrap them inside <div> or any other container element.
export const MessageSentiment = (props) => {
var sentiment = JSON.parse(props.sentiment)
return (
<div>
{
Object.keys(sentiment).map(key => (
<h1 key={key}>{key}: {sentiment[key]*100}</h1>
))
}
</div>
)
}
And remember: keys should be given to the elements inside the array to give the elements a stable identity.
you need to collect all the HTML in the array and return it. you can do 2 way
using map - map return new array without modifying existing array
.
export const MessageSentiment = (props) => {
var sentiment = JSON.parse(props.sentiment)
return (
<div>
{
Object.keys(sentiment).map((key, index) => <h1 key={index}> {key}:{sentiment[key]*100}</h1>)
}
</div>
)
}
Using array push method
.
export const MessageSentiment = (props) => {
var sentiment = JSON.parse(props.sentiment)
let itemList = [];
for(var key in sentiment ) {
itemList.push(<h1>{key}: {sentiment[key]*100}</h1>)
}
return (
<div>{itemList}</div>
)
}

Categories