How to create dynamic states and update it in react? - javascript

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.

Related

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()

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

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(

How to loop through new input boxes renderer in React and get the values in an array?

I need to dynamically generate multiple divs with a single input-box in it, so the user can add a number.
The user can add by clicking a button, any number of divs with input-box to put a number in it.
After the user end with the entry of the data, must click a button to process the data.
I've find out how to use React to iterate through an existing array, but not about how to iterate through a new DOM tree that was created dynamically by the user, and then generate an array with the values inside all the input-boxes.
After processing, different values will be displayed (max value, min value, average, return results from equations, etc)
Without seeing your code it's hard to help, but you probably want to use controlled inputs rather than uncontrolled ones, and so you'd have an array of the current values for the inputs as state information.
For instance, in a functional component using hooks:
const { useState } = React;
function Example() {
// The values we use on the inputs
const [values, setValues] = useState([]);
// Update the value at the given index
const updateValue = (value, index) => {
setValues(values =>
Object.assign([], values, {[index]: value})
);
};
// Add an input
const addInput = () => {
setValues(values => [...values, ""]);
};
// Get the sum (just an example; and one of the very few places I'll use `reduce`)
const sum = values.reduce((sum, value) => sum + Number(value), 0);
// Render as many inputs as we have values, along with the
// button to add an input and the sum
return (
<div>
<div>
{values.map((value, index) =>
<div key={index}>
<input type="text" value={value} onChange={evt => updateValue(evt.target.value, index)} />
</div>
)}
</div>
<div>Sum: {sum}</div>
<input type="button" value="Add Input" onClick={addInput} />
</div>
);
}
ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>
I think you could just create one div, and an array to store the values, then create a function that everytime the user choose to add a new value, it saves the current on that array and clean the input. So, when the user select to process the data it takes that array and do what you need.

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.

How to add dynamic input values to the local state for retrieval

I have a React Native form that allows me to add an Input UI in the form, by clicking a button with this function. This allow me to generate it on the fly. The code for that is this.
addClick() {
this.setState(prevState => ({ values: [...prevState.values, ""] }));
console.log(this.values[0].name);
}
That part works well, but I'm having a problem extracting the data from the dynamic inputs, and add it to an array. So far I have tried this
setVal = value => {
const values = this.state.values[0];
if (values[0].name === "" || values[0].description === "") return;
[...this.state.values, value];
this.setState(values);
console.log(values);
};
How do I organize my states properly so that I can add as many inputs I need, and when I'm finished, I can update the state, and access the new data in my list component?
How do I update my state to the new Array? at the moment, this.state only shows the initial state set at the top.
I'm missing a few things
Please take a look at the full code sandbox HERE so you can see:
See...your created isssue is not so obvious we need to see where you call setVal() function but....
i think you will be more comfortable if you render your <input/> s directly from your state array, not from const x = [] variant. because it seems like you want a dynamic view and in such a cases you will need to bind your loop quantity from state. so:
this.state = {
x: [0,1,2,3,4]
}
and inside your render :
{this.state.x.map(x => {
return (
<TextInput
placeholder={`name${x}`}
value={values[x.toString()]}
handleBlur={() => handleBlur(x.toString())}
onChangeText={handleChange(x.toString())}
style={styles.input}
/>
);
})}

Categories