NextJS: How to keep button active? - javascript

I have 3 buttons in a group and a form. When I select a button it stays active which is correct behaviour. However if I click "submit" on the form, the previously selected button is not active anymore. How do I keep the button active even after submitting the form? I'm using NextJS and css styles.
const [position, setPosition] = useState("");
const categoryOptions = ["Left", "Middle", "Right"];
return (
<div className={styles.card}>
{categoryOptions.map((category) => (
<button
type="button"
className={styles.buttonGroup}
onClick={() => setPosition(category)}
>
{category}
</button>
))}
<form className={styles.form} onSubmit={someFunction}>
<label htmlFor="amount">Amount</label>
<input
id={props.id}
name="amount"
type="number"
required
/>
<button className={styles.button} type="submit">
Submit amount
</button>
</form>
</div>
)
here is the css for buttons
.buttonGroup {
color: #000000;
border-radius: 0px;
border: 0px;
min-width: 80px;
min-height: 30px;
}
.buttonGroup:hover {
color: red;
}
.buttonGroup:focus,
.buttonGroup:active {
color: red;
font-weight: bold;
}

You can call useRef to keep your active element, and then call focus to regain your previous button focus state. For example
const [position, setPosition] = useState("");
const categoryOptions = ["Left", "Middle", "Right"];
const currentCategoryRef = useRef()
return (
<div className={styles.card}>
{categoryOptions.map((category) => (
<button
type="button"
className={styles.buttonGroup}
onClick={(event) => {
setPosition(category)
currentCategoryRef.current = event.target //set current active button
}}
>
{category}
</button>
))}
<form className={styles.form} onSubmit={someFunction}>
<label htmlFor="amount">Amount</label>
<input
id={props.id}
name="amount"
type="number"
required
/>
<button className={styles.button} type="submit" onClick={() => {
setTimeout(() => currentCategoryRef.current && currentCategoryRef.current.focus()) //focus back to the previous button in categories
}}>
Submit amount
</button>
</form>
</div>
)

Related

react check box not rendering correctly

I am trying to create a react component that represents a tile.
This component is just a div that's composed of a label and a checkbox.
The problem that I have is that I can click wherever on the component and the states changes like it would normally do (eg: by clicking on the component i can check or uncheck the checkbox). but when I click on the checkbox nothing happens.
Here is my newly created component code:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(()=>{
console.log(selected)
},[selected])
return (
<>
<div className="tile" onClick={ev=>setSelected(curr=>!curr)}>
<label>{title}</label>
<input
type="checkbox"
checked={!!selected}
onChange={ev=>{setSelected(curr=>!curr)}}
></input>
</div>
</>
);
};
and here I use it in my App.js :
return (
<Container>
<Row>
<Col md={4}>
<Tile title="USA"></Tile>
<Tile title="MOROCCO"></Tile>
<Tile title="FRANCE"></Tile>
</Col>
<Col md={8}>
<h1>Hello</h1>
</Col>
</Row>
</Container>
and finally here is my css :
body {
padding-top: 20px;
font-family: "Poppins", sans-serif;
background-color: cornsilk;
}
.tile {
position: relative;
display: block;
width: 100%;
min-height: fit-content;
background: bisque;
padding: 8px;
margin: 1px;
}
.tile input[type="checkbox"] {
position: absolute;
top: 50%;
right: 0%;
transform: translate(-50%, -50%);
}
EDIT: the problem with using the htmlFor fix on the label is that the label is clickable and the checkbox is clickable but the space between them is not. I want the the whole component to be clickable
You don't need the onClick on your div.
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(() => {
console.log(selected);
}, [selected]);
return (
<>
<div className="tile" onClick={() => setSelected((curr) => !curr)}>
<label htmlFor={title}>{title}</label>
<input
id={title}
type="checkbox"
checked={!!selected}
onChange={(ev) => {}}
/>
</div>
</>
);
};
I made a code sandbox to test: https://codesandbox.io/s/optimistic-tharp-czlgp?file=/src/App.js:124-601
When you click on the checkbox, your click event is propagated and handled by both the div and the checkbox inside the div, which results in state being toggled twice and ultimately having the same value as before.
You need to remove one of the onClicks, depending on what you want to be clickable (either the whole div or just the checkbox with the label).
Clickable div:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(() => {
console.log(selected)
}, [selected])
return (
<>
<div className="tile" onClick={() => setSelected(curr => !curr)}>
<label>{title}</label>
<input
type="checkbox"
checked={!!selected}
/>
</div>
</>
);
};
Clickable checkbox and label:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(() => {
console.log(selected)
}, [selected])
return (
<>
<div className="tile">
<label htmlFor="title">{title}</label>
<input
id="title"
type="checkbox"
checked={!!selected}
onChange={() => setSelected(curr => !curr)}
/>
</div>
</>
);
};
Add htmlFor prop to the label and add id to the input matching that htmlFor value.
In your case Tile component would be:
const Tile = ({ title }) => {
const [selected, setSelected] = useState(false);
useEffect(()=>{
console.log(selected)
},[selected])
return (
<>
<div className="tile" onClick={ev=>setSelected(curr=>!curr)}>
<label htmlFor={title}>{title}</label>
<input
id={title}
type="checkbox"
checked={!!selected}
onChange={ev=>{setSelected(curr=>!curr)}}
></input>
</div>
</>
);
};

OnChange method does not work when trying to get text from input - reactjs

I am developing a chat and the problem I am having has to do with creating a new chat. When clicking on the button to create a new chat, a popup appears with the chat name field to fill in. However, when I click on "Create Now" I can't get the input text.
I've tried adding value = {nameChat} but this way I can't write to the input. Nothing appears. I also tested defaultValue = {nameChat} but it doesn't work either.
If anyone could help me, I would appreciate it.
Popup to create new chat
import { confirmAlert } from 'react-confirm-alert'; // Import
import 'react-confirm-alert/src/react-confirm-alert.css' // Import css
import { useState } from 'react'
const HeaderChats = ({ onAddNewChat }) => {
var [chatName, setChatName] = useState('');
const onSubmit = (e) => {
e.preventDefault()
if (!chatName) {
alert('Please write the name of the chat!')
console.log("Nome -> " + chatName)
return
}
console.log(chatName)
onAddNewChat(chatName)
setChatName('')
}
function createChat() {
confirmAlert({
customUI: ({ onClose }) => {
return (
<div className='custom-ui'>
<h1>Create new chat</h1>
<form style={{resize: "vertical"}} onSubmit={onSubmit}>
<label className="cname-label">Chat Name</label>
<input className="cname-input" type='text' placeholder="Type the name of the chat..." onChange={(e) => setChatName(e.target.value)} value={chatName}/>
<button className="cancel-button" onClick={onClose}>Cancel</button>
<button className="submit-new-chat-button" type='submit'>Create now</button>
</form>
</div>
)
}
})
}
return (
<div className="header-left">
<div className="header-left-column-left">
<HiMenu className="icon" size={25} style={{ color: "#b3c5d3" }} />
</div>
<div className="header-left-column-right">
<input className="input-search" type="text" placeholder="Pesquisar conversa..." />
<MdAddCircleOutline className="button-new-message" size={25} style={{ color: "#dca297" }} onClick={() => createChat()} /> </div>
</div>
);
}
Because once the confirmAlert is created, the submit function will not be changed. So to avoid this, you need to render the chat as a component.
function Chat(props){
var [chatName, setChatName] = useState("");
const onSubmit = useCallback((e) => {
e.preventDefault();
if (!chatName) {
alert("Please write the name of the chat!");
return;
}
alert(chatName);
setChatName("");
}, [chatName]);
return <div className="custom-ui">
<h1>Create new chat</h1>
<form style={{ resize: "vertical" }} onSubmit={onSubmit}>
<label className="cname-label">Chat Name</label>
<input
className="cname-input"
type="text"
placeholder="Type the name of the chat..."
onChange={(e) => setChatName(e.target.value)}
/>
<button className="cancel-button" onClick={props.onClose}>
Cancel
</button>
<button className="submit-new-chat-button" type="submit">
Create now
</button>
</form>
</div>
}
function App() {
function createChat() {
confirmAlert({
customUI: ({ onClose }) => {
return (
<Chat onClose={onClose}/>
);
}
});
}
return (
<div className="header-left">
<MdAddCircleOutline
className="button-new-message"
size={25}
style={{ color: "#dca297" }}
onClick={() => createChat()}
/>{" "}
</div>
);
}
You should remove onSubmit in form element and also remove type="submit" on button change it to onClick={onSubmit}
<div className="custom-ui">
<h1>Create new chat</h1>
<form style={{ resize: "vertical" }} >
<label className="cname-label">Chat Name</label>
<input
className="cname-input"
type="text"
placeholder="Type the name of the chat..."
onChange={(e) =>
{
setChatName(e.currentTarget.value)}
}
/>
<button className="cancel-button" onClick={onClose}>
Cancel
</button>
<button className="submit-new-chat-button" onClick={onSubmit}>
Create now
</button>
</form>
</div>

How do I make a button trigger a file input onChange in React?

I have an input of type file. For some reason it doesn't let me change the value attribute to it, and it looks ugly. I swapped it with a button, but now I need the button to somehow trigger the input file on click. How can I do this in React?
Edit:
It should trigger the input onClick, not the onChange like the title says. Unless it's the same thing in this case.
const fireInput = () => {
let input = document.getElementById('inputFile');
}
<div>
<input
id="inputFile"
type='file'
className='mt-2 mb-3 text-primary'
onChange={uploadProfilePic}
/>
<button
type='button'
className='btn btn-primary py-2 px-5 mb-3'
onClick={fireInput}
>
Upload Picture
</button>
</div>
You shouldn't put display:none then the element will not be in dom, you need to use opacity:0 or visibility css.
Code for doing above can be done like this:
import "./styles.css";
import { useRef } from "react";
export default function App() {
const fileUpload = useRef(null);
const uploadProfilePic = (e) => {
console.log(e);
};
const handleUpload = () => {
console.log(fileUpload.current.click(), "fileUpload");
};
return (
<div className="App">
<input
type="file"
ref={fileUpload}
onChange={uploadProfilePic}
style={{ opacity: "0" }}
/>
<button onClick={() => handleUpload()}>Upload Picture</button>
</div>
);
}
You can simply use a label:
.d-none {
display: none;
}
.button {
background-color: #123456;
color: white;
padding: 15px 32px;
text-align: center;
}
<label for="inputFile" class="button">Upload</label>
<input type="file" id="inputFile" name="inputFile" class="d-none">
You could use the html label element without using any JS:
const Component = () => {
// You should also use a ref instead of document.getElementById
const inputRef = useRef()
return (
<label>
<div> {/*Style this however you want*/}
Upload Photo
</div>
<input type="file" style={{display: "none"}} ref={inputRef} />
</label>
)
}
I think you can do like this.
<div>
<input type="file" onChange={uploadProfilePic} ref={(ref) => this.upload = ref} style={{ display: 'none' }}/>
<button
type='button'
className='btn btn-primary py-2 px-5 mb-3'
onClick={(e) => this.upload.click()}
>
Upload Picture
</button>
</div>
You can confirm here.
https://codesandbox.io/s/youthful-cdn-lpntx

How to add a fixed button in the footer in material ui dialog?

I'm trying to create a dialog pop up for my react js app, when user clicks on button, dialog opens up. I have form with input fields in that dialog, after user fills out all inputs he can submit info by clicking "submit" button at the bottom of the dialog pop up. The problem is that I don't know how to stick submit button to the footer so even if there are 15+ inputs, user doesn't have to scroll all the way down to see "submit" button. I know that material ui has DialogActions for this purpose, but because dialog is in parent component, I don't have access to DialogActions from child. My code:
App.js (parent)
import React, { useState } from "react";
import Info from "./Info";
import Dialog from "#material-ui/core/Dialog";
import { DialogTitle } from "#material-ui/core";
import DialogContent from "#material-ui/core/DialogContent";
import DialogContentText from "#material-ui/core/DialogContentText";
import { DialogActions } from "#material-ui/core";
export default function App() {
const [open, setOpen] = useState(false);
const handleClickOpen = () => {
setOpen(true);
};
const handleClose = () => {
setOpen(false);
};
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<button onClick={handleClickOpen}>Click me to open dialog</button>
<Dialog
open={open}
aria-labelledby="responsive-dialog-title"
maxWidth="md"
setMaxWidth="md"
fullWidth={true}
>
<dialogContent>
<dialogTitle>
{" "}
<div>
<h4>Fill out the form</h4>
</div>
</dialogTitle>
<DialogContentText>
<Info />
</DialogContentText>
</dialogContent>
{/* <DialogActions>
<button id="responsive-dialog-title" onClick={handleClose}>
{" "}
Submit{" "}
</button>
</DialogActions> */}
</Dialog>{" "}
</div>
);
}
and Info.js (child) :
import React, { useState } from "react";
export default function Info() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const handleClickOpen = () => {
setOpen(true);
};
const handleSubmit = () => {
console.log(username);
console.log(password);
console.log(address);
};
return (
<form onSubmit={handleSubmit}>
<div
style={{
display: "flex",
flexDirection: "column",
width: "350px",
padding: "20px"
}}
>
<label> Username</label>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
type="text"
/>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
width: "350px",
padding: "20px"
}}
>
<label> Password</label>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
type="password"
/>
</div>
<button> Submit</button>
</form>
);
}
codesandbox
Is there any way to make that "submit" button in Info.js displayed as DialogActions/ fixed to bottom? Any help and suggestion are greatly aprreciated.
Use the position: fixed property to achieve that. Your code would look like this:
<form onSubmit={handleSubmit}>
<div>
<div
style={{
display: "flex",
flexDirection: "column",
width: "350px",
padding: "20px"
}}
>
<label> Username</label>
<input
value={username}
onChange={(e) => setUsername(e.target.value)}
type="text"
/>
</div>
<div
style={{
display: "flex",
flexDirection: "column",
width: "350px",
padding: "20px"
}}
>
<label> Password</label>
<input
value={password}
onChange={(e) => setPassword(e.target.value)}
type="password"
/>
</div>
</div>
<div style={{ position: "fixed" }}>
<button> Submit</button>
<button> Cancel</button>
</div>
</form>
The scroll that the dialog has by default also had to be removed
<Dialog
open={open}
aria-labelledby="responsive-dialog-title"
maxWidth="md"
setMaxWidth="md"
fullWidth={true}
>
<div style={{ overflow: "hidden", height: "100%", width: "100%" }}>
<dialogTitle>
{" "}
<div>
<h4>Fill out the form</h4>
</div>
</dialogTitle>
<Info />
</div>
</Dialog>
This works, but the best thing to do is call the submit function from the parent, or the submit function exists in the parent and the inputs can be filled with context or a simple state

Identify Clicked Button In Unordered List: React

I a week new in learning react coming from an angular background. I have the following unordered list in React.
const QueueManage: React.FC = () => {
const { queue, setQueue, loading, error } = useGetQueue();
const [btnState, setBtnState] = useState(state);
const enterIconLoading = (event: React.MouseEvent<HTMLElement, MouseEvent>) => {
const item = '';
const btn = '';
console.log(item, btn);
setBtnState({ loading: true, iconLoading: true, item: item, btnType: btn });
};
<ul className="listCont">
{queue.map(queueItem => (
<li className="col-12" key={queueItem.id}>
<div className="row">
<div className="listName col-3">
<p>{queueItem.user.firstName} {queueItem.user.lastName}</p>
</div>
<div className="listName col-5">
<div className="row">
<div className="col-3">
<Button type="primary" loading={btnState.loading} onClick={enterIconLoading}>
Assign
</Button>
</div>
<div className="col-3">
<Button type="primary" loading={btnState.loading} onClick={enterIconLoading}>
Absent
</Button>
</div>
<div className="col-3">
<Button type="primary" loading={btnState.loading} onClick={enterIconLoading}>
Done
</Button>
</div>
<div className="col-3">
<Button type="primary" loading={btnState.loading} onClick={enterIconLoading}>
Cancel
</Button>
</div>
</div>
</div>
</div>
</li>
)
)}
</ul>
}
For each list item, the list item will have for buttons, namely Assign, Absent, Done, Cancel. My goal is to identify which button was clicked and for which list item so that I can apply a loader for that specific button. Can any one please assist me with an explanation of how I can achieve this in my code
Here is a visual representation of the list that i get
https://i.imgur.com/kxcpxOo.png
At the moment went i click one button, all buttons are applied a spinner like below:
Your assistance and explanation is highly appreciated.
The Reactful approach involved splitting the li into a separate component. This will help keep each item's state separate. Let's call that QueueItem.
const QueueItem = ({ user }) => {
const [loading, setLoading] = useState(false)
function onClickAssign() {
setLoading(true)
// do something
setLoading(false)
}
function onClickAbsent() {
setLoading(true)
// do something
setLoading(false)
}
function onClickDone() {
setLoading(true)
// do something
setLoading(false)
}
function onClickCancel() {
setLoading(true)
// do something
setLoading(false)
}
return (
<li className='col-12'>
<div className='row'>
<div className='listName col-3'>
<p>
{user.firstName} {user.lastName}
</p>
</div>
<div className='listName col-5'>
<div className='row'>
<div className='col-3'>
<Button type='primary' loading={loading} onClick={onClickAssign}>
Assign
</Button>
</div>
<div className='col-3'>
<Button type='primary' loading={loading} onClick={onClickAbsent}>
Absent
</Button>
</div>
<div className='col-3'>
<Button type='primary' loading={loading} onClick={onClickDone}>
Done
</Button>
</div>
<div className='col-3'>
<Button type='primary' loading={loading} onClick={onClickCancel}>
Cancel
</Button>
</div>
</div>
</div>
</div>
</li>
)
}
Here I've also split out each button's onClick into a separate callback since they are well defined and probably have unique behaviours. Another approach mentioned above in a comment is
function onClickButton(action) {
...
}
<Button type='primary' loading={loading} onClick={() => onClickButton('cancel')}>
Cancel
</Button>
This follows the action / reducer pattern which might be applicable here instead of state (useState)
Move the buttons or the whole li to a component and let each list manage it's state.
// Get a hook function
const {useState} = React;
//pass the index of li as prop
const Buttons = ({ listId }) => {
const [clicked, setClickedButton] = useState(0);
return (
<div>
<button
className={clicked === 1 && "Button"}
onClick={() => setClickedButton(1)}
>
Assign
</button>
<button className={clicked === 2 && "Button"} onClick={() => setClickedButton(2)}>Absent</button>
<button className={clicked === 3 && "Button"} onClick={() => setClickedButton(3)}>Done</button>
<button className={clicked === 4 && "Button"} onClick={() => setClickedButton(4)}>Cancel</button>
</div>
);
};
// Render it
ReactDOM.render(
<Buttons />,
document.getElementById("react")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<style>
.Button {
background-color: #4CAF50; /* Green */
border: none;
color: white;
padding: 15px 32px;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
}
</style>
<div id="react"></div>
In addition to the previous answer it's worth adding that making simple components (in our case buttons) stateful is often considered a bad practice as it gets harder to track all the state changes, and to use state from different buttons together (e.g. if you want to disable all 4 buttons in a row after any of them is pressed)
Take a look at the following implementation, where entire buttons state is contained within parent component
enum ButtonType {
ASSIGN, ABSENT, DONE, CANCEL
}
// this component is stateless and will render a button
const ActionButton = ({ label, loading, onClick }) =>
<Button type="primary" loading={loading} onClick={onClick}>
{label}
</Button>
/* inside the QueueManage component */
const [buttonsState, setButtonsState] = useState({})
const updateButton = (itemId: string, buttonType: ButtonType) => {
setButtonsState({
...buttonsState,
[itemId]: {
...(buttonsState[itemId] || {}),
[buttonType]: {
...(buttonsState[itemId]?.[buttonType] || {}),
loading: true,
}
}
})
}
const isButtonLoading = (itemId: string, buttonType: ButtonType) => {
return buttonsState[itemId]?.[buttonType]?.loading
}
return (
<ul className="listCont">
{queue.map(queueItem => (
<li className="col-12" key={queueItem.id}>
<div className="row">
<div className="listName col-3">
<p>{queueItem.user.firstName} {queueItem.user.lastName}</p>
</div>
<div className="listName col-5">
<div className="row">
<div className="col-3">
<ActionButton
label={'Assign'}
onClick={() => updateButton(queueItem.id, ButtonType.ASSIGN)}
loading={isButtonLoading(queueItem.id, ButtonType.ASSIGN)}
/>
</div>
<div className="col-3">
<ActionButton
label={'Absent'}
onClick={() => updateButton(queueItem.id, ButtonType.ABSENT)}
loading={isButtonLoading(queueItem.id, ButtonType.ABSENT)}
/>
</div>
<div className="col-3">
<ActionButton
label={'Done'}
onClick={() => updateButton(queueItem.id, ButtonType.DONE)}
loading={isButtonLoading(queueItem.id, ButtonType.DONE)}
/>
</div>
<div className="col-3">
<ActionButton
label={'Cancel'}
onClick={() => updateButton(queueItem.id, ButtonType.CANCEL)}
loading={isButtonLoading(queueItem.id, ButtonType.CANCEL)}
/>
</div>
</div>
</div>
</div>
</li>
)
)}
</ul>
)
The goal here is to keep buttons loading state in parent component and manage it from here. buttonsState is a multilevel object like
{
'23': {
[ButtonType.ASSIGN]: { loading: false },
[ButtonType.ABSENT]: { loading: false },
[ButtonType.DONE]: { loading: false },
[ButtonType.CANCEL]: { loading: false },
},
...
}
where keys are ids of queueItems and values describe the state of the 4 buttons for that item. It is usually preferred to use useReducer instead of nested spreading in updateButton but it is good to start with

Categories

Resources