Button click event not firing mobile in react - javascript

I am using reactjs for a login form and on the click event the client makes a request to the server with the details... For some reason when I click on mobile nothing happens.. I tried adding the cursor: pointer; property to the css file but nothing happens still. Any help is appreciated.
Code below:
import React, { Component } from "react";
import axios from "axios";
import "./login.styles.css";
import { setAccessToken, getAccessToken } from "../../authorization";
export class Login extends Component {
constructor() {
super();
this.state = {
email: "",
password: "",
accessToken: getAccessToken(),
};
}
componentDidMount() {
console.log(getAccessToken());
}
changeHandle = (e) => {
this.setState({ [e.target.name]: e.target.value });
console.log(this.state);
};
submitHandle = async () => {
try {
const response = await axios.post(
"https://linkhere.example/api/users/login",
this.state
);
setAccessToken(response.data.accessToken);
console.log(response.data.accessToken);
this.setState({ accessToken: response.data.accessToken });
this.props.history.push("/dashboard");
} catch (e) {
console.log(e);
}
};
render() {
return (
<body className="color">
<div className="container">
<div className="image">
<img alt="login" src={require("./Login.png")}></img>
</div>
<div className="top-text">
{this.state.accessToken ? <h1>{this.state.accessToken}</h1> : null}
<h1>Welcome Back!</h1>
<h3>Sign in to your account...</h3>
</div>
<div className="login-info">
<input
type="email"
name="email"
onChange={(e) => this.changeHandle(e)}
placeholder="Email"
/>
<br></br>
<input
name="password"
type="password"
placeholder="Password"
onChange={(e) => this.changeHandle(e)}
/>
<br></br>
<button onClick={this.submitHandle} type="submit">
Log In
</button>
</div>
</div>
</body>
);
}
}
export default Login;
button css
.login-info button {
color: #FFDEDE;
background-color: #3a1c4d;
border: none;
outline: none;
height: 40px;
border-radius: 10px;
font-size: 20px;
font-weight: 700;
margin-top: 20px;
cursor: pointer;
z-index: 999;
}

Related

In VueJS version 3, What is the correct way to Show and Hide the Login & Logout <router-link> links and viceversa which is placed on Nav Bar?

I am newbie to VueJS. I have developed a simple Login Screen. After successful Login, Server will send userId in JSON format. I am storing this userId in localStorage. Using this, I thought of showing the Login (before Login). Post Login, 1. the Logout should be displayed and not Login & 2. Login Component should be displayed on logout click
In current code, Post Login, Logout link is not visible. I also tried v-else logic. That also did not worked. referce https://forum.vuejs.org/t/update-navbar-login-logout-button/103509
Let me know what mistake is there in the below code. Thanks in advance.
App.vue
<template>
<div id="app">
<Nav/>
<router-view />
</div>
</template>
<script>
import Nav from './components/Nav.vue'
export default {
name: 'App',
components: {
Nav
},
data() {
return {
form: {
username:'',
password:''
},
showError: false
};
},
};
</script>
<style>
#import url('htpp://fonts.googleapis.com/css?family=Fira+Sans:400,500,600,700,800');
* {
box-sizing:border-box;
}
body{
background: #fcfdfd !important;
}
body, html,#app, #root, .auth-wrapper{
width :100%;
height:100%;
padding-top:30px;
}
#app{
text-align:center;
}
.navbar-light{
background-color: #167bff;
box-shadow:0px 14px 80px rgba (34,35,58,0.2);
}
.custom-control-label{
font-weight:100;
}
.forgot-password, .forgot-password a{
text-align : right;
font-size : 13px;
padding-top:10px;
color:#7f7d7d;
margin:0;
}
.forgot-password a{
color:#167bff;
}
</style>
Nav.vue
<template>
<nav class="navbar navbar-expand navbar-light fixed-top">
<div class="container">
<router-link to="/" class="navbar-brand" > Test Data Generator </router-link>
<div class="collapse navbar-collapse">
<ul class="navbar-nav ml-auto">
<li class="nav-item" v-if="isLoggedIn==null">
<router-link to="/login" class="nav-link"> Login </router-link>
</li>
<li class="nav-item" v-else >
<router-link #click="handleLogout" class="nav-link"> Logout </router-link>
</li>
</ul>
</div>
</div>
</nav>
</template>
<script>
export default {
name:'Nav',
computed: {
isLoggedIn() {
return window.localStorage.getItem("userId");
}
},
methods:{
handleLogout(){
localStorage.removeItem('userId');
this.$router.push('/login');
}
}
}
</script>
<style>
nav .navbar-nav li a{
color: white !important;
}
</style>
Login.vue
<template>
<div class="auth-wrapper">
<div class="auth-inner">
<h4>Login</h4>
<p>
<label>Username</label>
<input type="text" v-model="state.username" placeholder="Username" class="form-control"/>
</p>
<p>
<label>Password</label>
<input type="password" class="form-control" v-model="state.password" placeholder="Password"/>
</p>
<button #click="handleLogin" class="btn btn-primary btn-block">Login</button>
</div>
</div>
</template>
<script>
import { required } from '#vuelidate/validators'
import useValidate from '#vuelidate/core'
import axios from 'axios'
import { apiHost } from '../config'
import {reactive, computed} from 'vue'
export default {
name:'Login',
setup() {
const state = reactive({
username:'',
password:'',
})
const rules=computed(() => {
return {
username: { required },
password: { required },
}
})
const v$ = useValidate(rules,state)
return{
state,v$,
}
},
methods:{
async handleLogin(){
this.v$.$validate()
if (!this.v$.$error) {
const loginURL=apiHost+'authenticate';
const response = await axios.post(loginURL,{
username:this.state.username,
password: this.state.password
});
console.log(response);
localStorage.setItem('userId',response.data.userId);
this.$router.push('/hello');
}else{
alert('Please enter username and password.')
}
}
}
}
</script>
<style>
.auth-wrapper{
display:flex;
justify-content:center;
flex-direction:column;
text-align:left;
}
.auth-inner{
width:300px;
margin :auto;
background: #167bff;
box-shadow:0px 14px 80px rgba (34,35,58,0.2);
padding:40px 55px 45px 55px;
border-radius:15px;
transition: all .3s;
}
.auth-wrapper .form-control:focus{
border-color:#167bff;
box-shadow:none;
}
.auth-wrapper h4{
text-align:center;
margin:0;
line-height:1;
padding-bottom:20px;
}
</style>
main.js
import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'
import App from './App.vue'
import Login from './components/Login.vue'
import Home from './components/Home.vue'
import States from './components/States.vue'
import HelloWorld from './components/HelloWorld.vue'
import Toaster from '#meforma/vue-toaster';
const router = createRouter({
history: createWebHistory(),
routes:[
{
path : '/login',
component:Login
},
{
path : '/getStates',
component:States
},
{
path : '/',
component:Home
},
{
path : '/hello',
component:HelloWorld
},
]
})
const app= createApp(App);
app.use(router).use(Toaster).mount('#app')
Image Link for the changes suggested by Keenal.
https://drive.google.com/file/d/1_EOJTM2HhJnAYjBJCaSobA2i2LS-cOww/view?usp=sharing
LocalStorage is not reactive, vuejs can detect changes in properties which were created in the instance. So it will be detected only after page referesh.
Following code will work
<div id="app">
<button #click="setLogin"> {{loggedIn !== 'null' ? 'Logout' : 'Login'}} </button>
</div>
JS
new Vue({
el: '#app',
data: function() {
return {
get loggedIn() {
return localStorage.getItem('userId');
},
set loggedIn(value) {
localStorage.setItem('userId', value);
}
};
},
methods:{
setLogin(){
if(localStorage.getItem('userId') !== 'null')
this.userId = null;
else
this.userId = Math.random();
}
}
});
You can update the value according to your requirement.

Is it possible to pass dynamic props, from one page to another with next.js?

I'm new to Next and have been trying to make a page(index.js) that fetches data(countries) and then displays that data, where each returned element(country) has a button to go to a page(info.js) where that specific countries data will be displayed, was wondering if its possible to pass the props(all country data) to the info.js page? I've tried reading the documentation and watching YT videos but can't seem understand what i'm reading/watching.
index.js:
import Link from 'next/link'
Welcome.getInitialProps = async function (props) {
const res = await fetch('https://restcountries.eu/rest/v2/all')
const data = await res.json()
return {
data: data
}
}
const MyLink = props => {
return (
<p>
<Link href={`/info?name=${props.name}`} >
<a>Learn More</a>
</Link>
</p>
)
}
function Welcome(props) {
return (
<div>
<div className="main-content">
<style jsx>{`
.main-content {
width: 80%;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(5, 1fr);
grid-gap: 5px;
}
.item {
border: 1px solid black;
text-align: center;
}
.item ul{
padding: 0;
}
.item ul li {
list-style-type: none;
}
`}</style>
{props.data.map(country => (
<div key={country.numericCode} className="item">
<h4>{country.name}</h4>
<p>Region: {country.region}</p>
<p>Population: {country.population}</p>
<MyLink name={country.name} borders={country.borders} currencies={country.currencies}/>
</div>
))}
</div>
</div>
)
}
export default Welcome
info.js:
import { withRouter } from 'next/router'
import Link from 'next/link'
const Info = (props) => {
return (
<div>
<h1>{props.router.query.name}</h1>
<Link href="/">
<a>Home</a>
</Link>
</div>
)
}
export default withRouter(Info)
In MyLink component instead of using Link you can create a normal div (style it like a link) and onClick of that div push it to different page using nextjs router:
//import useRouter
import { useRouter } from 'next/router'
//then call it
const router = useRouter()
const MyLink = props => {
return (
<p onClick={() => {
router.push({
pathname: `/info?name=${props.name}`,
query: { data: //data to pass },
})
}}>
<a>Learn More</a>
</p>
)
}
You can access that data in the location object in the query key
import {useLocation} from ""
const location = useLocation()
const data = location.query

Form is listening to enter key Vue

I have made a form component (CreateDocument) in Nuxt. Inside this component i made also an autocomplete (AutoCompleteFilters).
When I hit enter inside the autocomplete component, also the CreateDocument is listening to the enter key. But I only want that a specific input field is listing to the enter key event.
This is the CreateDocument component:
<template>
<div>
<Notification :message="notification" v-if="notification"/>
<form method="post" #submit.prevent="createDocument">
<div class="create__document-new-document">
<div class="create__document-new-document-title">
<label>Titel</label>
<input
type="text"
class="input"
name="title"
v-model="title"
required
>
</div>
<div class="create__document-new-document-textarea">
<editor
apiKey="nothing"
v-model="text"
initialValue=""
:init="{
height: 750,
width: 1400
}"
>
</editor>
</div>
<div class="create__document-new-document-extra-info">
<div class="create__document-new-document-tags">
<label>Tags</label>
<AutoCompleteFilters/>
</div>
<div class="create__document-new-document-clients">
<label>Klant</label>
<input
type="text"
class="input"
name="client"
v-model="client"
required
>
</div>
</div>
<Button buttonText="save" />
</div>
</form>
</div>
</template>
<script>
import Notification from '~/components/Notification'
import Editor from '#tinymce/tinymce-vue'
import Button from "../Button";
import { mapGetters, mapActions } from 'vuex'
import AutoCompleteFilters from "./filters/AutoCompleteFilters";
export default {
computed: {
...mapGetters({
loggedInUser: 'loggedInUser',
})
},
middleware: 'auth',
components: {
Notification,
Button,
editor: Editor,
AutoCompleteFilters
},
data() {
return {
title: '',
text: '',
tags: '',
client: '',
notification: null,
}
},
methods: {
...mapActions({
create: 'document/create'
}),
createDocument () {
const documentData = {
title: this.title,
text: this.text,
tags: this.tags,
client: this.client,
userId: this.loggedInUser.userId
};
this.create(documentData).then((response) => {
this.notification = response;
this.title = '';
this.text = '';
this.tags = '';
this.client= '';
})
}
}
}
</script>
And this is the AutoCompleteFilters component:
<template>
<div class="autocomplete">
<input
type="text"
id="my-input"
#input="onChange"
v-model="search"
#keydown.down="onArrowDown"
#keydown.up="onArrowUp"
#keydown.enter="onEnter"
/>
<ul
v-show="isOpen"
class="autocomplete-results"
>
<li
v-for="result in results"
:key="results.id"
class="autocomplete-result"
#click="setResult(result.name)"
:class="{ 'is-active': results.indexOf(result) === arrowCounter }"
>
{{ result.name }}
</li>
</ul>
</div>
</template>
<script>
import {mapActions} from 'vuex'
export default {
data() {
return {
isOpen: false,
results: false,
search: '',
arrowCounter: 0,
filter: null,
position: 0
};
},
methods: {
...mapActions({
getFilterByCharacter: 'tags/getTagsFromDb'
}),
onChange(e) {
this.isOpen = true;
this.position = e.target.selectionStart;
},
setResult(result) {
this.search = result;
this.isOpen = false;
},
getResults(){
this.getTagsByValue(this.search).then((response) => {
this.results = response;
});
},
async getTagsByValue(value){
const filters = {autocompleteCharacter : value};
return await this.getFilterByCharacter(filters);
},
onArrowDown() {
if (this.arrowCounter < this.results.length) {
this.arrowCounter = this.arrowCounter + 1;
}
},
onArrowUp() {
if (this.arrowCounter > 0) {
this.arrowCounter = this.arrowCounter - 1;
}
},
onEnter(evt) {
this.search = this.results[this.arrowCounter].name;
this.isOpen = false;
this.arrowCounter = -1;
}
},
watch: {
search: function() {
this.getResults();
}
},
};
</script>
<style>
.autocomplete {
position: relative;
}
.autocomplete-results {
padding: 0;
margin: 0;
border: 1px solid #eeeeee;
height: 120px;
overflow: auto;
width: 100%;
}
.autocomplete-result {
list-style: none;
text-align: left;
padding: 4px 2px;
cursor: pointer;
}
.autocomplete-result.is-active,
.autocomplete-result:hover {
background-color: #4AAE9B;
color: white;
}
</style>
Just as you did in your form to avoid "natural" form submit and replace it with a custom action:
#submit.prevent="createDocument"
... you have to preventDefault the "natural" event that submits the form when you press Enter while focusing the form.
To do so, just add .prevent to your events in the template:
#keydown.down.prevent="onArrowDown"
#keydown.up.prevent="onArrowUp"
#keydown.enter.prevent="onEnter"

React close all child modal from parent

I've three components with the following tree:
<Update>
<ExpenseItem>
<ExpenseItemModal>
Update takes an array of expenses and render a ExpenseItem component for each expense.
I'm using an hook to handle modal visibility. As you can expect, i'm using this modal to edit the expense attributes.
A toggle method is imported from useModal hook on ExpenseItem to open and close the modal. What I expect is to click outside of the modal and close it. But if I've another ExpenseItem with the modal set to true, it will close the current, but it will still show the other one. I want to click outside of the modal (maybe on Update component) and close all modals at once, to avoid multiple modals opened. Actually I want only on modal open at once.
These are the following components:
Upload
import { useState, useEffect } from 'react';
import useModal from '../hooks/useModal';
import ExpenseItem from './expenseItem';
import axios from 'axios';
function Update({ data }) {
useEffect(() => console.log('update component', expenses));
const saveToDatabase = () => {
axios.post('http://localhost:3001/expenses', expenses).then((res) => {
console.log('data is saved to database');
});
};
const { setIsShowing } = useModal();
const closeModals = () => setIsShowing(false);
const [ expenses, setExpenses ] = useState(data);
return (
<div>
{expenses.map((expense, index) => {
return <ExpenseItem key={index} index={index} expenses={expenses} setExpenses={setExpenses} />;
})}
<button onClick={() => saveToDatabase()}>Save</button>
</div>
);
}
export default Update;
ExpenseItem
import useModal from '../hooks/useModal';
import EditExpenseModal from './editExpenseModal';
function ExpenseItem(props) {
const { isShowing, toggle, setIsShowing } = useModal();
let { description, date, credit, debit } = props.expenses[props.index];
const updateValue = (expense, setExpenses, success) => {
const expenses = [ ...props.expenses ];
expenses.splice(props.index, 1, {
...expense
});
setExpenses(expenses);
success();
};
return (
<div>
<div className="expense-box" onClick={toggle}>
<p>{date}</p>
<div className="expense-info">
<p className="expense-info--description">{description}</p>
<p className="expense-info--debit">{debit}</p>
<p className="expense-info--credit">{credit}</p>
</div>
</div>
<EditExpenseModal
isShowing={isShowing}
hide={toggle}
expense={props.expenses[props.index]}
updateExpense={updateValue}
setExpenses={props.setExpenses}
/>
<style jsx>{`
.expense-box {
width: 800px;
border: 1px solid black;
border-radius: 2px;
margin: 25px auto;
padding: 0 10px;
}
.expense-info {
display: flex;
}
.expense-info--description {
margin: 0 auto 0 0;
}
.expense-info--debit {
color: red;
}
.expense-info--credit {
color: green;
}
`}</style>
</div>
);
}
export default ExpenseItem;
EditExpenseModal
import { useState, useEffect, Fragment } from 'react';
import { createPortal } from 'react-dom';
const EditExpenseModal = ({ expense, isShowing, hide, updateExpense, setExpenses }) => {
const { description, date, credit, debit } = expense;
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => (document.body.style.overflow = 'unset');
}, []);
const [ expenseItem, setExpenseItem ] = useState({
date,
description,
category: null,
subcategory: null,
credit,
debit
});
const handleInputChange = (e) => {
const { name, value } = e.target;
setExpenseItem({ ...expenseItem, [name]: value });
};
return isShowing
? createPortal(
<Fragment>
<div>
<div className="form">
<form>
<ul>
<li className="form-inputs">
<label>Date</label>
<input type="text" name="date" defaultValue={date} onChange={handleInputChange} />
</li>
<li className="form-inputs">
<label>Description</label>
<input
type="text"
name="description"
defaultValue={description}
onChange={handleInputChange}
/>
</li>
<li className="form-inputs">
<label>Category</label>
<input type="text" name="category" onChange={handleInputChange} />
</li>
<li className="form-inputs">
<label>Subcategory</label>
<input type="text" name="subcategory" onChange={handleInputChange} />
</li>
<li className="form-inputs">
<label>Credit</label>
<input
type="text"
name="credit"
defaultValue={credit}
onChange={handleInputChange}
/>
</li>
<li className="form-inputs">
<label>Debit</label>
<input
type="text"
name="debit"
defaultValue={debit}
onChange={handleInputChange}
/>
</li>
</ul>
</form>
<button onClick={() => updateExpense(expenseItem, setExpenses, hide)}>save</button>
<button onClick={hide}>close</button>
</div>
<style jsx>{`
.form {
background: grey;
display: flex;
flex-direction: column;
position: absolute;
height: 100vh;
top: 0;
right: 0;
width: 40%;
}
.form-inputs {
display: flex;
flex-direction: column;
list-style-type: none;
padding: 1rem 2rem;
}
`}</style>
</div>
</Fragment>,
document.body
)
: null;
};
export default EditExpenseModal;
useModal Hook
import { useState } from 'react';
const useModal = () => {
const [ isShowing, setIsShowing ] = useState(false);
function toggle() {
setIsShowing(!isShowing);
}
return {
isShowing,
setIsShowing,
toggle
};
};
export default useModal;
I don't mind to change these modal structure to make it work.
In this case, to avoid these scenarios you can write a separate method to close modal,
inside ExpenseItem.js
<EditExpenseModal
isShowing={isShowing}
hide={hideModal} //instead of toggle
...
>
and write hideModal method to close modal by passing directly 'false' value instead of using! operator.
like this in useModal Hook :
function hideModal() {
setIsShowing(false);
}

Error in v-on handler: "Error: Reference.set failed: First argument contains undefined"

Where could The blow error be occurring? I have been staring at this for awhile and cannot figure it out. I have tried to console log, all of the data that is being imputed into the database and they all have values...
I am trying to push the current message to the database in order to create a real time chat app. Provided below is the code.
This project is using firebase and VueJs.
CreateMessage.vue (where I believe the error is)
<template>
<div class="container" style="margin-bottom: 30px">
<form>
<div class="form-group">
<input type="text" class="form-control" placeholder="Enter message ..." v-model="newMessage">
<p class="text-danger" v-if="errorText">{{ errorText }}</p>
</div>
<button class="btn btn-primary" type="submit" #click.stop.prevent="createMessage"> Submit</button>
</form>
</div>
</template>
<script>
import firebase from '../components/firebaseconfig';
import AuthMonitor from '../AuthMonitor';
import Login from '../components/Login';
export default {
name: 'CreateMessage',
mixins:[AuthMonitor],
data(){
return {
newMessage: "",
errorText: "",
user: firebase.auth().currentUser
}
},
methods: {
createMessage () {
console.log(this.newMessage);
if (this.newMessage != '') {
console.log(Date.now());
firebase.database().ref("messages").push().set({
message: this.newMessage,
name: this.user,
timestamp: Date.now()
}).catch(err => {
console.log(err);
});
this.newMessage = "";
this.errorText = "";
} else {
this.errorText = "A message must be entered!"
}
}
}
}
</script>
ChatRoom.vue . (the view)
<template>
<div class="chat container" v-if="isAuth">
<h2 class="text-primary text-center">Real-Time Chat</h2>
<h5 class="text-secondary text-center">Powered by Vue.js & Firebase</h5>
<div class="card">
<div class="card-body">
<p class="nomessages text-secondary" v-if="messages.length == 0">
[No messages yet!]
</p>
<div class="messages" v-chat-scroll="{always: false, smooth: true}">
<div v-for="message in messages" :key="message.id">
<span class="text-info">[{{ message.name }}]: </span>
<span>{{message.message}}</span>
<span class="text-secondary time">{{message.timestamp}}</span>
</div>
</div>
</div>
<div class="card-action">
<CreateMessage/>
</div>
</div>
</div>
</template>
<script>
import CreateMessage from '#/components/CreateMessage';
import firebase from '../components/firebaseconfig';
import AuthMonitor from '../AuthMonitor';
import moment from 'moment';
export default {
name: 'Chat',
mixins:[AuthMonitor],
components: {
CreateMessage
},
data() {
return{
messages: []
}
},
// created() {
// let ref = firebase.database().ref('messages');
// ref.onSnapshot(snapshot => {
// snapshot.docChanges().forEach(change => {
// if (change.type == 'added') {
// let doc = change.doc;
// this.messages.push({
// id: doc.id,
// name: doc.data().name,
// message: doc.data().message,
// timestamp: moment(doc.data().timestamp).format('LTS')
// });
// }
// });
// });
// }
}
</script>
<style>
.chat h2{
font-size: 2.6em;
margin-bottom: 0px;
}
.chat h5{
margin-top: 0px;
margin-bottom: 40px;
}
.chat span{
font-size: 1.2em;
}
.chat .time{
display: block;
font-size: 0.7em;
}
.messages{
max-height: 300px;
overflow: auto;
}
</style>
The error message is telling you that this.user contains a property ja which contains an undefined value. You can't put undefined values into Realtime Database.

Categories