Update state on react not getting right value - javascript

I have a dynamic navigation removable tabs using Fluent for react
I would like that when I close a tab ( for example test3) , the focus gets on the last tab in the nav bar like bellow
my actual problem is that when I close a tab , I loose the focus.
Here's my code
import React from "react";
import { Button, Menu, tabListBehavior } from "#fluentui/react-northstar";
import { CloseIcon } from "#fluentui/react-icons-northstar";
class MenuExampleTabShorthand extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0
};
}
items = [
{
key: "editorials",
content: (
<div>
"test"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={() => this.closeClick("editorials")}
/>
</div>
)
},
{
key: "review",
content: (
<div>
"test2"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={() => this.closeClick("review")}
/>
</div>
)
},
{
key: "events",
content: (
<div>
"test3"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={() => this.closeClick("events")}
/>
</div>
)
}
];
closeClick = task => {
this.setState(function(prev, props) { // Im setting the selectedIndex to 0
return { ...prev, selectedIndex:0 };
});
this.items = this.items.filter(elm => elm.key !== task);
};
render() {
return (
<Menu
activeIndex={this.state.selectedIndex}
onActiveIndexChange={(i, j) => {
this.setState(function(prev, props) {
return { ...prev, selectedIndex: j.activeIndex };
});
}}
items={this.items}
underlined
primary
accessibility={tabListBehavior}
aria-label="Today's events"
/>
);
}
}
export default MenuExampleTabShorthand;
Here's a reproduction of error demo

The issue you are facing is caused by event propagation, you can fix it by adding e.stopPropagation(); in close click event handler, and not having it will cause the active item click handler to fire and then set the current active item to the one removed (codesandbox), note that I'm passing the event object to closeClick:
import React from "react";
import { Button, Menu, tabListBehavior } from "#fluentui/react-northstar";
import { CloseIcon } from "#fluentui/react-icons-northstar";
class MenuExampleTabShorthand extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedIndex: 0
};
}
items = [
{
key: "editorials",
content: (
<div>
"test"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={e => this.closeClick("editorials", e)}
/>
</div>
)
},
{
key: "review",
content: (
<div>
"test2"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={e => this.closeClick("review", e)}
/>
</div>
)
},
{
key: "events",
content: (
<div>
"test3"
<Button
icon={<CloseIcon />}
text
iconOnly
title="Close"
onClick={e => this.closeClick("events", e)}
/>
</div>
)
}
];
closeClick = (task, e) => {
e.stopPropagation();
this.setState(function(prev, props) {
return { ...prev, selectedIndex: 0 };
});
console.log(this.items);
this.items = this.items.filter(elm => elm.key !== task);
console.log(this.items);
};
render() {
return (
<Menu
activeIndex={this.state.selectedIndex}
onActiveIndexChange={(i, j) => {
this.setState(function(prev, props) {
return { ...prev, selectedIndex: j.activeIndex };
});
}}
items={this.items}
underlined
primary
accessibility={tabListBehavior}
aria-label="Today's events"
/>
);
}
}
export default MenuExampleTabShorthand;

Related

Trying to render a Pop up outside of render: React Js

I am trying to bring popup based on clicking of the respective links. Here I could see onClick is being triggered along with the state change But no modal is appearing.
Can some one tell me what is it that I am doing wrong. I am using semantic-ui-react modal for the same puropse
Sandbox: https://codesandbox.io/s/semantic-ui-example-seg91?file=/Modal.js
import React from "react";
import Modal from "./Modal";
class LoaderExampleText extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false
};
}
setModal = (e, value) => {
console.log(value);
e.stopPropagation();
this.setState({ isModalOpen: true });
return (
<Modal
modalOpen={this.state.isModalOpen}
handleClose={() => {
this.setState({ isModalOpen: false });
}}
items={value}
/>
);
};
render() {
return (
<>
<a onClick={e => this.setModal(e, "first item")}>Modal A</a>
<a onClick={e => this.setModal(e, "second Item")}>Modal B</a>
</>
);
}
}
export default LoaderExampleText;
import * as React from "react";
import { Modal } from "semantic-ui-react";
class NestedTableViewer extends React.Component {
render() {
return (
<>
<Modal closeIcon={true} open={this.props.modalOpen}>
<Modal.Header>Modal</Modal.Header>
<Modal.Content>
<h1> {this.props.items}</h1>
</Modal.Content>
</Modal>
</>
);
}
}
export default NestedTableViewer;
Save the value into state and move the modal into the render return. All renderable JSX needs to be returned as a single node tree.
import React from "react";
import Modal from "./Modal";
class LoaderExampleText extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false,
value: null
};
}
setModal = (e, value) => {
console.log(value);
e.stopPropagation();
this.setState({ isModalOpen: true, value });
};
render() {
return (
<>
<a onClick={e => this.setModal(e, "first item")}>Modal A</a>
<a onClick={e => this.setModal(e, "second Item")}>Modal B</a>
<Modal
modalOpen={this.state.isModalOpen}
handleClose={() => {
this.setState({ isModalOpen: false, value: null });
}}
items={this.state.value}
/>
</>
);
}
}
export default LoaderExampleText;
In setModal is a function , so you cannot do some think like that, but if you want return you must add in render {setModal}.
So in this case :
import React from "react";
import Modal from "./Modal";
class LoaderExampleText extends React.Component {
constructor(props) {
super(props);
this.state = {
isModalOpen: false,
value: ""
};
}
setModal = (e, value) => {
console.log(value);
e.stopPropagation();
this.setState({ isModalOpen: true });
this.setState({ value: value });
};
render() {
return (
<>
<a onClick={e => this.setModal(e, "first item")}>Modal A</a>
<a onClick={e => this.setModal(e, "second Item")}>Modal B</a>
<Modal
modalOpen={this.state.isModalOpen}
handleClose={() => {
this.setState({ isModalOpen: false });
}}
items={this.state.value}
/>
</>
);
}
}
export default LoaderExampleText;
it will work;

How would I hover only one specific element instead hovering two at the same time

Right now I have it so when I hover one card it renders data for both cards like so:
How could I refactor my code to make it so when I hover over one card it only displays that cards data? I know I can pass down the specific id of the card that I hover over but I'm not sure how to go about doing that exactly.
class Landing extends React.Component {
constructor(props) {
super(props)
this.state = {
hover: false,
stats: [
{
image: 'https://cdn.bulbagarden.net/upload/thumb/6/62/Sapphire_EN_boxart.png/250px-Sapphire_EN_boxart.png',
votes: 10745,
rating: 1601
},
{
image: 'https://www.zeldadungeon.net/wiki/images/2/25/Minish-Cap-Cover.jpg',
votes: 19345,
rating: 5670
}
]
}
}
handleMouseHover = (id) => {
this.setState({
hover: !this.state.hover
})
}
renderCards = () => {
return (
<div className='card-carousel'>
{this.state.stats.map(stat => {
return (
<Card
key={stat.id}
image={stat.image}
onMouseEnter={() => this.handleMouseHover(stat.id)}
onMouseLeave={() => this.handleMouseHover()}
renderVotesRatings={this.renderVotesRatings()}>
</Card>
)
})}
</div>
)
}
renderVotesRatings = () => {
if (this.state.hover)
return (
<>
{this.state.stats.map(stat => {
return (
<div className='stats-card' key={stat.id}>
<img src='https://i.imgur.com/rXfPua4.png'></img>
<h3>{stat.votes}</h3>
<img src='https://i.imgur.com/1aiORiI.png'></img>
<h3>{stat.rating}</h3>
</div>
)
})}
</>
)
}
The code doesn't seem to be complete. However, I will tell you the general idea.
You should have each item as a separate component that has its own state like the following:
class Landing extends React.Component {
constructor(props) {
super(props);
this.state = {
stats: [
{
image:
"https://cdn.bulbagarden.net/upload/thumb/6/62/Sapphire_EN_boxart.png/250px-Sapphire_EN_boxart.png",
votes: 10745,
rating: 1601
},
{
image:
"https://www.zeldadungeon.net/wiki/images/2/25/Minish-Cap-Cover.jpg",
votes: 19345,
rating: 5670
}
]
};
}
render() {
return (
<div className="card-carousel">
{this.state.stats.map(stat => {
return (
<CardItem
key={stat.id}
image={stat.image}
votes={stat.votes}
rating={stat.rating}
/>
);
})}
</div>
);
}
}
and create another file "card-item.js" for CardItem component
class CardItem extends React.Component {
constructor(props) {
super(props);
this.state = {
hover: false
};
}
renderVotesRatings = () => {
if (this.state.hover) {
return (
<div className="stats-card">
<img src="https://i.imgur.com/rXfPua4.png" />
<h3>{this.props.votes}</h3>
<img src="https://i.imgur.com/1aiORiI.png" />
<h3>{this.props.rating}</h3>
</div>
);
}
};
render() {
return (
<Card
image={this.props.image}
onMouseEnter={() => this.setState({ hover: true })}
onMouseLeave={() => this.setState({ hover: false })}
renderVotesRatings={this.renderVotesRatings()}
/>
);
}
}

ReactJS: Adding multiple input fields of different types on click

I've created a React app for a school project that can add multiple types of input fields to a view by clicking a button (sort of like Wordpress Gutenberg).
Currently, I can add one of each type of item onto the view. However, if I click the button again, it erases the current text that was added. I'd like the ability to click the button to add as many fields as I'd like on click.
Also, the items are only added into the view in the order they were created meaning, even if I choose photo first and I click headline after, it (headline) will appear at the top of the list above the initial item.
I've had a look at these solutions (which were pretty good) but they didn't provide what I need.
Dynamically adding Input form field issue reactjs
and "update delete list elements using unique key": https://www.youtube.com/watch?v=tJYBMSuOX3s
which was closer to what I needed to do.
Apologies in advance for the length of the code,(there are two other related components for text input and an editform). I'm sure there is a much more simple way to do this. I haven't been able to find an npm package or solution to this specific problem online and am open to a simpler solution.
Edit.jsx
export default class Edit extends React.Component {
state = {
texts: {
hl: '',
shl: '',
txt: '',
photo: []
},
coms: {
hl: false,
shl: false,
txt: false,
photo: null
},
labels: {
// Replace with icons
hl: 'Headline',
shl: 'Sub',
txt: 'Text Area',
photo: 'Photo'
},
selectedItem: '',
}
componentDidMount() {
const saveData = localStorage.getItem('saveData') === 'true';
const user = saveData ? localStorage.getItem('user') : '';
this.setState({ user, saveData });
}
createPage = async () => {
await this.props.postPage(this.state.texts)
}
// add options
addOptions = (item) => {
const { coms } = this.state
coms[item] = !coms[item]
this.setState({ coms: coms })
}
// ADD TEXT
addTxt = () => {
this.setState({ texts: [...this.state.texts, ""] })
}
enableAllButtons = () => {
this.setState({ selectedItem: '' })
}
handleChange = (e, index) => {
this.state.texts[index] = e.target.value
//set the changed state.
this.setState({ texts: this.state.texts })
}
setDisable = (selectedItem) => {
this.setState({ selectedItem })
}
handleRemove = () => {
// this.state.texts.splice(index, 1)
this.setState({ texts: this.state.texts })
}
handleSubmit = (e) => {
console.log(this.state, 'all text')
}
handleChange = (e, item) => {
let { texts } = this.state
texts[item] = e.target.value
//set the changed state.
this.setState({ texts })
console.log(texts)
}
render() {
const { coms, labels, selectedItem, texts } = this.state
let buttons = Object.keys(coms)
let showItems = Object.keys(coms).filter(key => coms[key] === true)
return (
<div>
<InnerHeader />
{/* Make a route for edit here */}
<Route path='/edit/form' render={() => (
<EditForm
texts={texts}
coms={coms}
labels={labels}
addOptions={this.addOptions}
setDisable={this.setDisable}
selectedItem={selectedItem}
showItems={showItems}
handleChange={this.handleChange}
enableAllButtons={this.enableAllButtons}
/>
)} />
{/* Make route for preview */}
<Route path='/edit/preview' render={(props) => (
<Preview
{...props}
createPage={this.createPage}
/>
)}
/>
</div>
)
}
}
AddText.jsx:
export default class AddText extends Component {
state = {
}
// ADD TEXT
addTxt(item) {
const {
addOptions } = this.props
addOptions(item)
}
render() {
const { coms, labels } = this.props
const { selectedItem } = this.props
let buttons = Object.keys(coms)
console.log('here', selectedItem)
return (
<div>
<Card>
<Card.Body>
{
buttons.map((item, index) => <button
value={(selectedItem === "") ? false : (selectedItem === item) ? false : true} key={index} onClick={() => this.addTxt(item)}>
{labels[item]}
</button>
)
}
</Card.Body>
</Card>
</div>
)
}
}
EditForm.jsx
export default function EditForm(props) {
return (
<div>
<div className='some-page-wrapper-sm'>
<div className="dash-card-sm">
<button><Link to={{
pathname: '/edit/preview',
item: props.texts
}}>preview</Link></button>
<br />
<br />
<AddText
coms={props.coms}
labels={props.labels}
addOptions={props.addOptions}
setDisable={props.setDisable}
selectedItem={props.selectedItem}
/>
<div>
{
props.showItems.map((item, index) => {
return (
<InputFieldComponent
// setDisable={props.setDisable}
onChangeText={(e) => props.handleChange(e, item)}
enableAllButtons={props.enableAllButtons}
key={index}
item={item}
labels={props.labels}
texts={props.texts}
/>
)
})
}
</div>
</div>
</div>
</div>
)
}
InputFieldComponent.jsx
export default class InputFieldComponent extends React.Component {
setWrapperRef = (node) => {
this.wrapperRef = node;
}
render() {
const { labels, item, onChangeText, texts } = this.props
return (
<div>
<textarea
className="txt-box"
ref={this.setWrapperRef}
onChange={onChangeText}
placeholder={labels[item]}
value={texts[item]} />
</div>
)
}
}

when changing a single todo into a textfield instead of all of todos

I was trying to create a react component. If a user clicks on the edit button on a to-do item, it should replace the specific clicked todo area with a text field.
What do I expect to see
When a user clicks on one of the to-do items, that item becomes into a text field, which can then be edited.
What do I actually see
When a user clicks on one of the to-do items, all items become into text fields.
Here is my code:
PARENT
handleEditClick = (e,id,text) => {
this.setState({val: !this.state.val})
}
render() {
return (
<div style={{display: "flex"}}>
<div>
{this.props.todos.map(todo => (
<div key={todo.id}>
<EditButton todo={todo} val={this.state.val} text={this.state.text}
handleEditClick={(e, id, text) => this.handleEditClick(e, id, text)}/>
</div>
))}
</div>
</div>
)
}
}
CHILD(EDITBUTTON):
const EditButton = (props) => {
if(!props.val) {
return(
<div>
<List>
<ListItem
role={undefined}
dense
button
onClick={() => this.updateTodo(props.todo)}
><Checkbox
checked={props.todo.complete}
/>
<ListItemText primary={props.todo.text} />
<ListItemSecondaryAction>
<Button mini color="secondary" variant="fab"
disabled={props.todo.complete}
onClick={(e) => props.handleEditClick(e, props.todo.id, props.todo.text)}
>
<Icon>edit_icon</Icon>
</Button>
<IconButton onClick={() => this.removeTodo(props.todo)}>
<CloseIcon />
</IconButton>
</ListItemSecondaryAction>
</ListItem>
</List>
</div>
)
}else {
return(
<TextField
onChange={this.handleChange}
onKeyDown={this.handleKeyDown}
label=""
margin="normal"
fullWidth
value={props.text}
color="secondary"
/>
)
}
}
This example is not complete, but it does show how to implement what you descript. The program with your code was when you update the status of edit, it is updating every single item's status.
https://codesandbox.io/s/x7rvqm6xz4
import React from "react";
import { Button, TextField } from "#material-ui/core";
class Todos extends React.Component {
constructor(props) {
super(props);
this.state = {
todos: null
};
}
componentDidMount() {
this.setState({
todos: this.props.todos.map(todo => ({ name: todo, isEdit: false }))
});
}
handleEdit(name) {
const { todos } = this.state;
const updateTodos = todos.map(todo => {
if (todo.name === name) {
return { name: todo.name, isEdit: !todo.isEdit };
}
return todo;
});
this.setState({ todos: updateTodos });
}
_renderTodos() {
if (!this.state.todos) {
return null;
}
return this.state.todos.map(todo => {
return (
<div key={todo.name}>
{todo.isEdit ? <TextField /> : <span>{todo.name}</span>}
<Button onClick={() => this.handleEdit(todo.name)}>Click</Button>
</div>
);
});
}
render() {
if (!this.props.todos.length > 0) {
return null;
}
return <div>{this._renderTodos()}</div>;
}
}
export default Todos;
Basically, this component takes a list of todos. Put them into a state, and this state tracks whether to display them as todos or TextField.
I've done this before. See in CODESANDBOX.
There is TodoList class that can accept todos and saveEditedTodo as props. It has editedTodo state which dinamically change when "Edit" button is clicked. Of course, it contains id and text.
import React from "react";
export default class extends React.Component {
state = {
editedTodo: null
};
toggleEditTodo = (todo = null) => {
this.setState({ editedTodo: todo });
};
onChangeTodoText = text => {
this.setState(prevState => ({
editedTodo: Object.assign(prevState.editedTodo, { text })
}));
};
submitTodoForm = e => {
e.preventDefault();
this.props.saveEditedTodo(this.state.editedTodo);
this.setState({
editedTodo: null
});
};
render() {
const { editedTodo } = this.state;
const { todos } = this.props;
return (
<ul>
{todos.map(todo => (
<li key={todo.id}>
{todo === editedTodo ? (
// Show text field
) : (
// Show todo
)}
</li>
))}
</ul>
);
}
}

How to handle state of multiple buttons with react?

I have a bootstrap grid where each grid item is populated from an array of objects but after each grid item I would like to have a vote button. How could I achieve this with maintaining state on each button separately, ie when button 1 is clicked the text should change from 'vote' to 'voted' whilst the others remain as 'vote'.
At the moment when a button is clicked, all of them change to 'Voted'
class Items extends Component {
constructor(props) {
super(props);
this.state = { hasVoted: false };
this.OnClick = this.OnClick.bind(this);
}
OnClick() {
this.setState(prevState => ({
hasVoted: !prevState.hasVoted
}));
}
render() {
const Item = teasers.items.map(item =>
<Col key={item.nid}>
<span>
{itemType}
</span>
<a href={item.path}>
<Image src={item.image.src} title={item.productType} />
<span>
{item.Title}
</span>
<div className={teasersStyle.copy}>
{" "}{item.Copy}>
</div>
</a>
<div
className={this.state.hasVoted ? "active" : "notactive"}
onClick={this.OnClick}
>
{this.state.hasVoted ? "Voted" : "Vote"}
</div>
</Col>
);
return (
<div>
<Grid>
<Row>
{Item}
</Row>
</Grid>
</div>
);
}
}
export default Items;
I have created a simple example for you:
class App extends React.Component {
constructor() {
super();
this.onClick = this.onClick.bind(this);
this.state = {
arr: [
{ name: "first", isActive: true },
{ name: "second", isActive: true },
{ name: "third", isActive: true },
{ name: "fourth", isActive: true }
]
};
}
onClick(index) {
let tmp = this.state.arr;
tmp[index].isActive = !tmp[index].isActive;
this.setState({ arr: tmp });
}
render() {
return (
<div>
{this.state.arr.map((el, index) =>
<div key={index} onClick={() => this.onClick(index)}>
name: {el.name} / isActive: {el.isActive ? "true" : "false"}
</div>
)}
</div>
);
}
}
Check the fiddle and implement it in your case.
One more way to handle this is keeping the index of an active button in the state:
class App extends React.Component {
state = {
users: [
{ name: "John" },
{ name: "Sarah" },
{ name: "Siri" },
{ name: "Jim" },
{ name: "Simon" },
],
activeIndex: 0,
}
render() {
const { users, activeIndex } = this.state;
return (
<div>
{users.map((u, i) => (
<div
className={i === activeIndex ? 'active' : ''}
onClick={() => this.setState({ activeIndex: i })}
key={u.name}
>
{u.name}
</div>
))}
</div>
)
}
}
https://jsfiddle.net/846tfe3u/

Categories