Displaying array data onclick in React - javascript

I would like to iterate through an array and display each element in the array every time I click a button.
so far I have this:
{this.state.users.map(function(user, index){
return <p key={ index }>{user.name} </p>;
}, this)}
This displays each users name on screen, one after each other.
How can I do it so it just shows users[0] and then moves to users[1] or even better, click removes the user at position users[0] and then a new person is at position users[0] and then if the array is empty, it displays the text 'no more users'
I know how to remove elements from arrays etc, it's just the displaying one at a time in React land which I cant do

Based on my understanding to your question may be you are trying to achieve this -
let users = [{
name: "abcd"
}, {
name: "xyz"
}, {
name: "temp"
}];
class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
activeIndex: 0
};
}
tick =() => {
let activeIndex = this.state.activeIndex;
if (activeIndex == this.props.users.length -1){
activeIndex = 0;
} else {
activeIndex++;
}
this.setState({
activeIndex
});
}
render() {
return (
< ul className = "list-group" >
< li className = "list-group-item" >
{this.props.users[this.state.activeIndex].name}
< button className = "btn btn-default" onClick={this.tick} >
show Next
< /button>
</li >
< /ul>
);
}
}
ReactDOM.render(
< Example users = {users}/ > ,
document.getElementById('test')
);
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="test">
</div>

Keep the index of activeUser in state, then render just this one user: { this.state.users[ this.state.activeUser ].name}. Increment/decrement the activeUser on click.
demo on jsfiddle
var Users = React.createClass({
getInitialState(){
return {
users : [{ name : 'John'}, { name : 'Jane'}, { name : 'Robert' }],
activeUser : 0
};
},
next(){
/* here you should add checking if there are more users before setting the state */
this.setState({ activeUser : this.state.activeUser + 1 });
},
render: function() {
return (<div>
<p>{ this.state.users[ this.state.activeUser ].name} </p>
<span onClick={ this.next }>next</span>
</div>);
}
});

Seems like you have the pseudo code there in your question which like Rajesh asked seems to describe some kind of paginated list of users/carousel.
You'll need to define the maximum number of users that are visible in the list and then store the current topmost element index. I'd suggest storing these in state.
Then store the list of names as an array of object each containing the name details as well as a parameter that will represent whether it is visible or not.
Your initial state could look like this:
{
currentTopElement: 0,
maxNamesToShow: 1,
names: [
{name: 'John Smith', isVisible: false},
{name: 'Jane Smith', isVisible: false},
...etc.
]
}
Add a click handler on your button would be required that would increment the currentTopElement by one.
iterate through the names array setting isVisible to false unless the index matches that of currentTopElement, if it does set isVisible to true
To finish off, in the component that needs to render your names list can do so if you conditionally render a name only if its corresponding isVisible value is true:
const elementsToShow = this.state.filter( t => t.isVisible);
You can the iterate over elementsToShow like so:
elementsToShow.map((user, index) => <p key={ index }>{user.name} </p>);

Related

I'm trying to add to an array of objects that is broken into two inputs in React

So I have an array of objects where the keys are 'cost' and 'service' called estimate. You can add to the array by clicking 'Add' which adds a new index (i) to the array. The issue is on the first cycle I get a good array of {'cost': 2500, 'service': "commercial cleaning"} (imgSet-1) but when I add another item it completely erases the array and sets only one of the nested objects key and value. (imgSet-2). This is the outcome I'm looking for once the state has been saved (imgSet-3) I have tried going with #RubenSmn approach but then I receive this error. (imgSet-4)
imgSet-1 *********
Adding an initial service
Outcome of the initial service addition
imgSet-2 *********
Adding the second service
Outcome of the second service addition
imgSet-3 *********
imgSet-4 *********
Below is the code for the part of the page where you can add services and the output of the text inputs.
const [estimate, setEstimate] = useState([]);
{[...Array(numServices)].map((e, i) => {
return (
<div key={i} className="flex justify-between">
<div>
<NumericTextBoxComponent
format="c2"
name={`cost-${i}`}
value={estimate?.items?.["cost"]?.[i]}
change={(e) =>
setEstimate({ ...estimate, items: [{...estimate?.items?.[i],cost: e?.value}]})
}
placeholder='Price'
floatLabelType="Auto"
data-msg-containerid="errorForCost"
/>
</div>
<div>
<DropDownListComponent
showClearButton
fields={{ value: "id", text: "service" }}
name={`service-${i}`}
value={estimate?.items?.["service"]?.[i]}
change={(e) =>
setEstimate({ ...estimate, items: [{...estimate?.items?.[i],service: e?.value}]})
}
id={`service-${i}`}
floatLabelType="Auto"
data-name={`service-${i}`}
dataSource={estimateData?.services}
placeholder="Service"
data-msg-containerid="errorForLead"
></DropDownListComponent>
<div id="errorForLead" />
</div>
</div>
);
})}
</form>
<button onClick={() => setNumServices(numServices + 1)}>Add</button>
I have tried multiple variations of spread operators but I can't seem to get it to work. My expected result would be:
estimate:{
items: [
{'cost': 2500, 'service': 'Commercial Clean'},
{'cost': 500, 'service': 'Bathroom Clean'},
{'cost': 180, 'service': 'Apartment Clean'},
{etc.}
]
}
The initial state is an array which is not the object you're setting in the change handlers. You can have an initial state like this.
const [estimate, setEstimate] = useState({ items: [] });
You're not adding back the old items of the state when you're setting the new state.
setEstimate({
...estimate,
items: [{ ...estimate?.items?.[i], cost: e?.value }],
// should be something like
// items: [...estimate.items, { ...estimate.items?.[i], cost: e?.value }],
});
But you can't do that since it will create a new object in your items array every time you change a value.
I made this dynamic handleChange function which you can use for you state changes. The first if statement is to check if the itemIndex is already in the items array. If not, create a new item with the propertyName and the value
const handleChange = (e, itemIndex, propertyName) => {
const newValue = e?.value;
setEstimate((prevEstimate) => {
if (prevEstimate.items.length <= itemIndex) {
const newItem = { [propertyName]: newValue };
return {
...prevEstimate,
items: [...prevEstimate.items, newItem]
};
}
// loop over old items
const newItems = [...prevEstimate.items].map((item, idx) => {
// if index' are not the same just return the old item
if (idx !== itemIndex) return item;
// else return the item with the new service
return { ...item, [propertyName]: newValue };
});
return {
...prevEstimate,
items: newItems,
};
});
};
For the Service dropdown, you can do the same for the Cost just change the property name
<DropDownListComponent
...
value={estimate.items[i]?.service}
change={(e) => handleChange(e, i, "service")}
...
></DropDownListComponent>
See here a simplified live version

Updating single item in react state hook

I have array useState hook which consists of items with className. I want to update className of a single item from that hook. How do I update single value of a useState hook.Of course I am going to put condition before updating .
here is the code -
display.map( word =>
( Words[leftPointer] === word.props.children ?
{...display, word:(<span key={uuid()} className="singleWord greenword">
{Words[leftPointer]}</span>)} :
display )
)
setDisplay(display)
here display contains array of span.
what I want is go change the className based on the condition example- after updating the item
className = "singleWord greenword" should be className="singleWord" of that item.
Map function will return a new array so you just have to store the array in a new variable and set the state with that new variable
const newDisplay = display.map( (word) =>{
if(Words[leftPointer] === word.props.children){
return {word:(<span key={uuid()} className="singleWord greenword">
{Words[leftPointer]}</span>)}
}else{
return word
}
})
setDisplay(newDisplay)
I would recommend against setting className in the state's domain as that is part of the UI's domain. Instead data changes can be used to signal responses in the UI rendering. This is what it means for a program to be data-driven.
Here is a minimal verifiable example. Run the program below and change some quantities to see the display state update. The className is automatically changed based on conditions applied to the application state.
function App() {
const [display, setDisplay] = React.useState([
{item: "walnut", quantity: 0 },
{item: "peanut", quantity: 3 },
{item: "donut", quantity: 6 }
])
function update(index) { return event =>
setDisplay([
...display.slice(0, index),
{ ...display[index], quantity: Number(event.target.value) },
...display.slice(index + 1)
])
}
return <div>
{display.map((d, index) =>
<div key={index}>
{d.item}:
<input
className={d.quantity > 0 ? "instock" : "outofstock" }
value={d.quantity}
onChange={update(index)}
/>
</div>
)}
<div>{display.every(d => d.quantity > 0) ? "✅" : "❌"}</div>
<pre>{JSON.stringify(display, null, 2)}</pre>
</div>
}
ReactDOM.render(<App/>, document.querySelector("#app"))
input { outline: 0 }
.instock { border-color: limegreen; }
.outofstock { border-color: red; }
pre { padding: 0.5rem; background-color: #ffc; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.14.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.14.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>
could you just use a state variable that holds the text for className and update that state variable on the useState hook to whatever you want?

React JS Custom Sidebar Navigation

I have Created a toggled navigation in React APP. Everything working fine except the toggle. Below is the code of toggle. I am using the check of parentId if parentId exist then include the children id's and it will open the toggle. If not exist then add the main Id which one is parentId in children.
Problem is I parentId already exist and if I click the toggle it is not closing the old one and displaying the new one along with old.
const handleArrowClick = ( data: CoursesNav ) => {
const { id, parentId } = data;
let newtoggledMenus = [...toggledMenus];
if( parentId && newtoggledMenus.includes(parentId)){
if (newtoggledMenus.includes(id)) {
var index = newtoggledMenus.indexOf(id);
if (index > -1) {
newtoggledMenus.splice(index, 1);
}
} else {
newtoggledMenus.push(id);
}
settoggledMenus(newtoggledMenus);
}else{
settoggledMenus([id]);
}
};
I have created the nav structure given below in which under the main item children are added.
export const CourseMenu = ( book: Book ): CoursesNav[] => {
if( course ){
// Chapters from book
return book.chapters.map( c => {
return {
id: c.id,
name: c.name,
link: `/my-book/${course.id}/chapter/${c.id}/edit`,
parentId: c.id,
// get topics and put under children
children: c.topics.map( m => {
return {
id: m.id,
name: m.name,
link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/edit`,
parentId: c.id,
//get blocks and put under blocks
children: m.blocks.map( b => {
return {
id: b.id,
name: b.title,
link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/block/${b.id}/edit`,
parentId: m.id
}
})
}
})
}
});
}
}
Nav logic is added just to show you that what I am trying to create.
Every parent has button for toggle to show the sub items. If any sub item has children again the toggle will displayed.
Only I have the problem in opening and closing the correct toggle. Right now under a main children if I open its all child previous one are not closing because they are getting the parentId in toggledMenu state which is a numeric array.
yow broh, why don't you use aria. don't search for the parent of no body.
The user clicks your button/link whatever the user clicks, but you need to set an aria attribute call aria-controls in which you need to put the value of which eva you want to hide/show.
start by closing any open menu or so, then wait 150 milliseconds or so. just to prevent that your loop closes the one that needs to be opened
function App () {
/**
* togging menus
*/
function onClickHandler (e) {
e.preventDefault();
const btnPressed = e.target;
// first you need to check if there is a item toggled and stuff.
const openMenus = document.querySelectorAll("[aria-expanded]");
if (openMenus.length) {
Object.keys(openMenus).forEach(button => {
const toggler = openMenus[button];
const menuId = toggler.getAttribute("aria-controls");
const menuToClose = document.getElementById(menuId);
if (menuToClose && menuToClose !== null) {
menuToClose.classList.remove("show");
menuToClose.setAttribute("aria-hidden", "true");
menuToClose.classList.add("hidden");
// now the button
toggler.setAttribute("aria-expanded", "false");
}
})
}
//wait a minute to prevent conflicts
setTimeout(()=>{
const toOpen = document.getElementById(btnPressed.getAttribute("aria-controls"));
if (!toOpen) {return;}
btnPressed.setAttribute("aria-expanded", "true");
toOpen.classList.add("show");
toOpen.setAttribute("aria-hidden", "false");
toOpen.classList.remove("hidden");
},[150])
}
return (
<div className="accordion-example">
<ul aria-label="Accordion Control Group Buttons" className="accordion-controls">
<li>
<button onClick={onClickHandler} aria-controls="content-1" aria-expanded="false" id="accordion-control-1">Apples</button>
<div className="menu hidden" aria-hidden="true" id="content-1">
<p>Apples are a fine fruit often associated with good health, and fewer doctor's appointments.</p>
<p>Example. An apple a day keeps the doctor away.</p>
</div>
</li>
<li>
<button onClick={onClickHandler} aria-controls="content-2" aria-expanded="false" id="accordion-control-2">Lemons</button>
<div className="menu hidden" aria-hidden="true" id="content-2">
<p>Lemons are good with almost anything, yet are often have a negative connotation when used in conversation.</p>
<p>Example. The bread from the french bakery is normally very good, but the one we bought today was a lemon.</p>
</div>
</li>
<li>
<button onClick={onClickHandler} aria-controls="content-3" aria-expanded="false" id="accordion-control-3">Kiwis</button>
<div className="menu hidden" aria-hidden="true" id="content-3">
<p>Kiwis are a fun, under-appreciated fruit.</p>
</div>
</li>
</ul>
</div>)
}
ReactDOM.render( < App / > , document.getElementById("page"));
.hidden {
display: none;
}
.show {
display: blocK
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="page"></div>
I am guessing you have a 3 level hierarchy inside a Book -> chapter, topic & block. On click of (let's say) right button, you want that specific item's children to be toggled. This is evidently at chapter and topic level.
One solution is maintain the entire hierarchy which you shared (book.chapters) as a state, and have a showChildren flag at each level.
return book.chapters.map( c => {
return {
id: c.id,
name: c.name,
link: `/my-book/${course.id}/chapter/${c.id}/edit`,
parentId: c.id,
showChildren: false,
// get topics and put under children
children: c.topics.map( m => {
return {
id: m.id,
name: m.name,
link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/edit`,
parentId: c.id,
showChildren: false,
//get blocks and put under blocks
children: m.blocks.map( b => {
return {
id: b.id,
name: b.title,
link: `/my-book/${course.id}/chapter/${c.id}/topics/${m.id}/block/${b.id}/edit`,
parentId: m.id
}
})
}
})
}
});
}
Based on the click on the right arrow, toggle that particular state.
With the above approach, you can keep multiple levels in the nav hierarchy open to toggle.

Push dynamically added html list item into last array

How can i push html into the last array. I was trying to add an item and supposed be add instantly into list array. The cod is working except I'm struggling to add new list into last array.
function addItem(id,name){
const array = JSON.parse(localStorage.getItem('categories'));
array.push({
name: name,
id:id,
});
//<li>{name}</li> push this into last array
localStorage.setItem('categories',JSON.stringify(array));
}
{categories.map(function(item, key){
return <div>
<ul>
<li>item.name</li>
</ul>
<button onClick={() => addItem(item.id,'value name')}>Add</button>
</div>
})}
Something looks wrong in your example. I have added a complete exampl. You can maintain localStorage and State both. I hope this example helps you.
You mistake is that while adding new item you are pushing it to localStoage due to which react dom does not get rerendered. You have to update the value of state for that.
class App extends React.Component {
constructor() {
super();
this.state = {
categories: [
{
name: "Hello",
id: 1
},
{
name: "World",
id: 2
}
]
};
this.addItem = this.addItem.bind(this);
this.SaveToLocalStorage = this.SaveToLocalStorage.bind(this);
}
SaveToLocalStorage() {
const categories = this.state.categories;
localStorage.setItem("categories", JSON.stringify(categories));
}
addItem(id, name) {
const categories = this.state.categories;
categories.push({
name: name,
id: id
});
this.setState({ categories });
//localStorage.setItem("categories", JSON.stringify(categories));
}
render() {
let categories = this.state.categories;
const test = categories.map(item => (
<div key={item.id}>
<li>{item.name}</li>
</div>
));
return (
<div>
{test}
<button onClick={() => this.addItem(Date.now(), "Item")}>
Click to Add More
</button>
<button onClick={() => this.SaveToLocalStorage()}>
Save To LocalStorage{" "}
</button>
</div>
);
}
}
ReactDOM.render( < App / > , document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
I guess this is what you are asking for. You just need to set it to state and re-render it when ever you are trying to add an element to list/array. I don't know why you are setting it to local storage but you can do it from state directly if your intention is to just store the previous array for future additions.
import React, { Component } from "react";
class App extends Component {
state = {};
constructor(props){
super(props);
this.state={
arr = []
}
}
addItem(id, name) {
const array = JSON.parse(localStorage.getItem("categories"));
array.push({
name: name,
id: id
});
//<li>{name}</li> push this into last array
localStorage.setItem("categories", JSON.stringify(array));
this.setState({arr:array});
}
renderList = () => {
return this.state.array.map(function(item, key) {
return (
<div>
<ul>
<li>item.name</li>
</ul>
<button onClick={() => addItem(item.id, "value name")}>Add</button>
</div>
);
});
};
render() {
return <div>{this.renderList()}</div>;
}
}
export default App;

Why mapping filtered array of objects is not rendered using react?

I am new to programming. I want to display the list of filtered users in a dropdown.
Below is what I am trying to do,
when user types '#' in the input field I get the string after '#' and use that string to filter the one that matches from user_list.
But this doesn't show up in the dropdown.
Say, user types '#', but it doesn't show the dropdown with user list.
Could someone help me fix this? thanks.
class UserMention extends react.PureComponent {
constructor(props) {
super(props);
this.state = {
text='',
user_mention=false,
};
this.user='';
}
get_user = s => s.includes('#') && s.substr(s.lastIndexOf('#')
+ 1).split(' ')[0];
user_list = [
{name: 'user1'},
{name: 'first_user'},
{name: 'second_user'},
];
handle_input_change = (event) => {
let is_user_mention;
if (event.target.value.endsWith('#')) {
is_user_mention = true;
} else {
is_user_mention = false;
}
this.setState({
is_user_mention: is_user_mention,
[event.target.name]: event.target.value,
});
this.user = this.get_user(event.targe.value);
}
render = () => {
const user_mention_name = get_user(this.state.text);
console.log("filtered_users", filtered_users);
return {
<input
required
name="text"
value={this.state.text}
onChange={this.handle_input_change}
type="text"/>
{this.user &&
<div>
{this.user_list.filter(user =>
user.name.indexOf(this.user)
!== -1).map((user,index) => (
<div key={index}>{user.name}
</div>
))
}
</div>
}
);};}
It seems like this.state.user_mention is always false. In handle_input_change you set is_user_mention, but in render you check user_mention. Could this possibly be the issue?
Since user_mention is false, the expression will short-circuit and not render the list.

Categories

Resources