nextjs errors with form handling - forms

I'm learning react and nextjs, the page works perfectly without the integration of a UI library, since i installed react suite my page with the login form doesn't work anymore, it returns me an error: TypeError: Cannot read property 'value 'of undefined
My code is:
import React, { useState } from "react"
import { setCookie, parseCookies } from "nookies"
import { useRouter } from "next/router"
import { Form, FormGroup, FormControl, ControlLabel, HelpBlock, Button, Input } from 'rsuite';
const Login = () => {
// set initial const
const router = useRouter()
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
// handle event submit
const handleSubmit = async (e) => {
e.preventDefault();
// fetch user
const response = await fetch( process.env.urlHeadless + '/api/cockpit/authUser', {
method: "post",
headers: {
"Content-Type": "application/json",
'Cockpit-Token': process.env.loginKey,
},
body: JSON.stringify({
user: username,
password: password
})
})
// if user exists
if (response.ok) {
const loggedUser = await response.json()
// set cookie with api_key
setCookie("", "tokenId", loggedUser._id, {
maxAge: 30 * 24 * 60 * 60,
path: "/"
})
// redirect to dashboard
return (router.push("/dashboard"))
} else if (response.status === 412) {
alert('compila il form')
} else if (response.status === 401) {
alert('credenziali di accesso errate')
}
}
return (
<>
<Form>
<FormGroup>
<ControlLabel>Username</ControlLabel>
<FormControl type="text" value={username} onChange={(e) => setUsername(e.target.value)} />
<HelpBlock>Required</HelpBlock>
</FormGroup>
<FormGroup>
<ControlLabel>Password</ControlLabel>
<FormControl type="password" value={password} onChange={(e) => setPassword(e.target.value)} />
</FormGroup>
<FormGroup>
<Button appearance="primary" onClick={handleSubmit}>Submit</Button>
</FormGroup>
</Form>
</>
)
}
// async function
export async function getServerSideProps(context) {
// get cookie value
const cookies = parseCookies(context).token;
// if cookie has value
if (cookies) {
return {
// redirect to dashboard
redirect: {
destination: '/dashboard',
permanent: false,
},
}
}
return { props: {} };
}
export default Login;
I also tried using useRef instead of useState, but it didn't work ... where am I wrong? thank you in advance

Looks like rsuite components might have a different onChange implementation, as described in their docs
onChange (formValue:Object, event:Object) => void Callback fired when data changing
If you want the event, try changing the onChange callback in your code to take the second parameter:
<FormControl
...
onChange={(value, e) => setUsername(e.target.value)}
/>

Related

When I do a post request to create a single user, it creates 6

I have a deno fresh boiler plate app with an added view called create. You land on a simple (but ugly) form view where you make a new user by typing in an email and key then pressing submit. This is the screen:
When I hit submit, the screen doesn't change but I get this in my web console.
What's even weirder is that when I check my database I have 6 entries of the same email and key. I have no idea why this is happening. I only pressed the button once:
The three files where pretty much all of the create page logic is happening is create.tsx, createUser.tsx and Creator.tsx
create.tsx:
import Layout from '../components/layouts.tsx';
import Creator from "../islands/Creator.tsx"
export default function User(props: PageProps) {
return (
<Layout>
<Creator />
</Layout>
)
}
createUser.tsx:
import { Handlers, PageProps } from "$fresh/server.ts";
import UserDb from "../../database.ts";
export const handler = {
POST: async (request, ctx) => {
const reqJsn = (await request.json());
const body = reqJsn;
const email = body.email;
const key = body.key;
console.log(email);
console.log(key);
if (!email || !key) {
ctx.status = 422;
ctx.body = { msg: "Incorrect user data. Email and key are required" };
return;
}
const userId = await UserDb.create({
email: email,
key: key,
created_at: new Date()
});
ctx.body = { msg: "User created", userId };
}
}
Creator.tsx:
// import { useState } from "preact/hooks";
import { useState, useEffect } from "preact/hooks";
import { Handlers, PageProps } from "$fresh/server.ts";
import UserDb from "../database.ts";
interface CreatorProps {
email: string,
key: string
}
export default function Creator(props: CreatorProps) {
async function handleSubmit(event) {
event.preventDefault();
const emailInput = event.target.email;
const ageInput = event.target.key;
console.log(emailInput.value);
console.log(ageInput.value);
const resp = await createNewUser(emailInput.value, ageInput.value);
return resp
};
async function createNewUser(email, key) {
const rawPosts = await fetch('http://localhost:8000/api/createUser', {
"method": "POST",
"headers": {
"content-type": "text/plain"
},
"body": JSON.stringify({
email: email,
key: key,
})
});
console.log(rawPosts);
}
return (
<div>
<h1 class="text rounded-lg p-4 my-8"> Search </h1>
<form method="post" onSubmit={async (e) => handleSubmit(e)}>
<input class="center rounded-lg p-4 my-8" id="email" name="email" />
<input class="center rounded-lg p-4 my-8" id="key" name="key" />
<br />
<button
class="px-5 py-2.5 text-sm font-medium bg-blue-600 rounded-md shadow disabled:(bg-gray-800 border border-blue-600 opacity-50 cursor-not-allowed)"
type="submit">Submit
</button>
</form>
<br />
{/* <ul>
{results.map((name) => <li key={name}>{name}</li>)}
</ul> */}
</div>
);
};
Again, I provided a minimal reproducible example. to run it you need a postgres instance running, create a .env file in the root directory of the project and add your postgres variables like this:
.env:
POSTGRES_USER=postgresuserone
POSTGRES_PASSWORD=pass2woR3d
POSTGRES_DB=postgresdbone
go to the root directory of the repo and type deno task start in your terminal and it works. Remember to navigate to localhost:8000/create, fill in the 2 field and press submit. You will now have 6 entries in your db.
I fixed it, the key was that the POST request action was never actually returning. I added:
const res = new Response(null, {
status: 302,
headers: new Headers({
location: 'localhost' + '../../../create',
}),
});
return res;
to the end of createUser.tsx (also renamed createUser.tsx to createUser.ts) and it only posted 1 new user to the database.

Storing session in Browser for express.js (MERN Stack)

I've been trying to store a user's email in the session for my express.js file. But everytime I try something, and call another function, the session remains undefined. I wanted to store it in the browser only without storing each session in the database. Been working on this for weeks now, and I can't seem to find the answer.
server.js file:
import express from 'express';
import cookieParser from 'cookie-parser';
import session from 'express-session';
const app = express();
app.use(cookieParser());
app.use(session({
secret: 'keyboard cat',
resave: false,
saveUninitialized: true,
cookie: {
maxAge : 1000 * 60 * 60 * 3, // if 1 day * 24 but since *3 its for 3 hours only
},
}))
app.post('/user/login', (req, res) => {
const loginUser = req.body;
const email = loginUser['email'];
const password = loginUser['password'];
if (!email || !password){
res.status(400).json({success: false, error: "Please provide email and password"});
}
try {
Users.findOne({ email: email }, (err, user) => {
if (password == user['password']){
req.session.user(user['email']);
res.status(200).send(user_email);
} else {
res.status(400).json({success: false, error: "incorrect password"});
}
});
} catch {
}
})
Calling the Login file from the frontend (react js):
import React, { useState } from 'react';
import { Grid, TextField, Button } from '#mui/material';
import "./SignUpLogin.css";
import axios from '../../axios';
import useForm from './useForm';
import { Form } from './useForm';
const initialValues = {
email: '',
password: ''
}
function Login({ modalFunc }) {
const LoginUser = e => {
console.log("INSIDE LOGIN USER");
modalFunc();
e.preventDefault();
axios.post('/user/login', values, {withCredentials: true})
.then(response => {
console.log("in login user");
console.log(response.data);
})
}
const {
values,
setValues,
handleInputChange
} = useForm(initialValues);
return (
<div className="Login">
<Form>
<Grid item>
<TextField
required
variant="outlined"
label="Email"
name="email"
color="secondary"
fullWidth
value={ values.email }
onChange={ handleInputChange }
/>
</Grid>
<Grid item>
<TextField
required
variant="outlined"
label="Password"
name="password"
type="password"
color="secondary"
fullWidth
value={ values.password }
onChange={ handleInputChange }
/>
</Grid>
<Grid item>
<Button
variant="contained"
fullWidth
onClick = { LoginUser }>
Login
</Button>
</Grid>
</Form>
</div>
)
}
export default Login
But when I call server.js again in another get function, session is undefined.
app.get('/user/loggedIn', (req, res) => {
console.log(req.session.user);
user_email = req.session.user;
if (req.session.user) {
Users.findOne({ email: user_email }, (err, user) => {
// console.log("in logged in, in server!!!");
// console.log(user);
res.status(200).send(user);
})
} else {
console.log("no session");
res.status(400);
}
})
Calling app.get('/user/loggedIn') in react.js file:
function Header() {
const [modalOpen, setModalOpen] = useState(false);
const changeModal = () => {
setModalOpen(!modalOpen)
}
const [user, setUser] = useState(null);
useEffect(() => {
// axios.get('/user/loggedIn', {}, {withCredentials: true})
axios.get('/user/loggedIn', {}, {withCredentials: true})
.then(response => {
// console.log("RESPONSE FROM LOGGED IN");
// console.log(response.data);
setUser(response.data);
})
})
server.js
app.use(
cors({
origin: "http://localhost:3000",
credentials: true,
})
);
withCredentials should be defined this way
axios.defaults.withCredentials = true;

Can't get data from api with axios in Nuxt components

<template>
<div id="post">
<p>{{ data }}</p>
</div>
</template>
<script>
export default {
data () {
return {
data: ''
}
},
async asyncData({$axios}) {
const res = await $axios.get('/v1/posts')
.catch( error => {
console.log("response error", error)
return false
})
return {
data: res
}
},
}
</script>
At first, I tried to get the data with the above code, it worked in pages/post.vue but not in components/post.vue.
Then, I realized that I can't use asyncData in the nuxt components and changed the code as follows.
<template>
<div id="post">
<p>{{ data }}</p>
</div>
</template>
<script>
export default {
data () {
return {
data: ''
}
},
mounted () {
this.asyncData()
},
asyncData() {
await axios.get('/v1/posts')
.then(res => {
this.data = res.data
})
},
}
</script>
Then, I got a syntax error "Unexpected reserved word 'await'".
How can I get data via api in Nuxt components?
===================================
I read https://nuxtjs.org/docs/features/data-fetching#accessing-the-fetch-state and changed the code as below.
<script>
export default {
data () {
return {
data: ''
}
},
async fetch() {
this.data = await fetch('/v1/posts')
.then(res => res.json())
},
}
</script>
And now, I'm stacking with another error 'Error in fetch(): SyntaxError: Unexpected token < in JSON at position 0'.
This code worked.
<script>
export default {
data () {
return {
data: '',
}
},
async fetch() {
const res = await this.$axios.get('/v1/posts')
this.data = res.data
},
}
</script>
Glad that found a solution to your issue.
You can even use this.$axios.$get directly if you don't want to have to write .data afterwards.

Uploading an image to mongodb using Multer with Express and Axios

I am basically trying to make a small application which allows an admin user to enter a name, price and image of a product which can then be viewed on another page. The details will be sent to a mongo database which will be performed via an axios post from the front end. I can send the name and the price no problem which can be seen on the front end dynamically, however, I am unable to send image to the mongo database which i've been trying to achieve now for quite some time.
I am using multer and axios to try and sent the file over as the application is a react app. I think the problem is to do with the "req.file" within the back end of the application. The code below is my endpoint:
api.js
var express = require('express');
var bodyParser = require('body-parser');
var cors = require('cors')
var app = express();
var mongodb = require('mongodb');
var path = require('path');
var fsextra = require('fs-extra');
var fs = require('fs')
var util = require('util')
var multer = require('multer')
var upload = multer( {dest: __dirname + '/uploads'} )
var ejs = require('ejs')
const MongoClient = require('mongodb').MongoClient;
app.use(express.static(path.resolve(__dirname, '../react', 'build')));
app.get('*',(req,res)=>{
res.sendFile(path.resolve(__dirname, '../react', 'build', 'index.html'));
});
console.log(__dirname)
app.use(cors());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());
app.use(express.static(path.join(__dirname, 'public')));
app.set('views', __dirname);
app.engine('html', require('ejs').renderFile);
app.set('view engine', 'html');
var db;
mongodb.MongoClient.connect('mongodb://<mydbdetails>', (err, database) => {
if (err) {
console.log(err)
process.exit(1);
}
db = database;
console.log('Database connection is ready')
});
var server= app.listen(process.env.PORT || 8082, function () {
var port = server.address().port;
console.log("App now running on port", port);
});
app.post('/api/submitImage', upload.single('inputForm'), function(req,res){
var file = req.body.file
if (file == null) {
// If Submit was accidentally clicked with no file selected...
//res.render('admin', { title:'Please select a picture file to submit!'});
res.send({success: false, message: "dsfdsg"})
console.log('There is no file present')
console.log(req.file,'file')
}
else{
// read the img file from tmp in-memory location
var newImg = fs.readFileSync(req.files.path);
console.log(newImg,'details of the new image')
// encode the file as a base64 string.
var encImg = newImg.toString('base64');
console.log(encImg,'kdfjndodj')
// define your new document
var newItem = {
description: req.body.description,
contentType: req.file.mimetype,
size: req.files.size,
img: Buffer(encImg, 'base64')
};
db.collection('products').insert(newItem, function(err, result){
if(err) {
console.log(err)
}
var newoid = new ObjectId(result.ops[0]._id);
fs.remove(req.file.path, function(err) {
if (err) { console.log(err) };
res.render('./src/components/adminContainer.js', {title:'Thanks for the Picture!'});
});
})
}
})
The next code is the how I am trying to send it over using Axios:
import axios from 'axios';
class ProductsApi {
static submitProduct(name,prices,callback){
axios.post('http://localhost:8082/api/submitProduct', {name: name, prices: prices})
.then( response => {
callback(response)
})
}
static viewName(callback){
axios.post('http://localhost:8082/api/retrieveName')
.then( response => {
return callback(response)
})
}
static viewPrice(callback){
axios.post('http://localhost:8082/api/retrievePrice')
.then( response => {
return callback(response)
})
}
static viewProducts(callback){
axios.post('http://localhost:8082/api/retrieveProducts')
.then( response => {
return callback(response)
})
}
static submitImages(image,callback){
axios.post('http://localhost:8082/api/submitImage',{image: image})
.then( response => {
return callback(response)
console.log('response has been made,', image,'has been recieved by axios')
})
}
}
export default ProductsApi;
The last file is how I am trying to send the file to the database using react with event handlers:
import React, { Component } from 'react'
import '../App.css'
import AppHeader from './appHeader.js'
import ProductsApi from '../api/axios.js'
const AdminContainer = () => {
return(
<div>
<AppHeader />
<FormContainer />
</div>
)
}
class FormContainer extends Component{
constructor(props){
super(props);
this.state={
file: '',
inputName: '',
inputPrice: '',
image: ''
};
this.handleNameChange = this.handleNameChange.bind(this);
this.handlePriceChange = this.handlePriceChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.sendName = this.handleSubmit.bind(this);
}
handleNameChange(e){
console.log(e.target.value)
this.setState({
name : e.target.value,
})
}
handlePriceChange(e){
console.log(e.target.value)
this.setState({
prices : e.target.value
})
}
sendName(e){
this.setState({
inputName: e.target.value,
inputName:e.target.value
})
}
handleSubmit(e){
e.preventDefault();
console.log('attempting to access axios...')
ProductsApi.submitProduct(this.state.name, this.state.prices, resp => {
console.log('response has been made', resp)
//if error message, add to state and show error message on front end
this.setState({
inputName:this.state.name,
inputPrice:this.state.prices
},function(){
console.log(resp,'this is resp')
console.log('Axios has send ',this.state.name,' to the database')
});
})
console.log(this.state.prices,'This is the new price')
console.log(this.state.name,'This is the new name')
ProductsApi.submitImages(this.state.image, response => {
console.log('axios has been notified to submit an image...')
this.setState({
image: this.state.image
},function(){
console.log('Image submission axios response details are as follows: ', response)
console.log(this.state.image, ': has been sent to the db')
})
})
}
render(){
return(
<div>
<h2>Add a new product to the Shop</h2>
<div className='formWrapper'>
<div className='center'>
<form name='inputForm' encType='multipart/form-data' method='post'>
<label>
Name:
<input value = {this.state.name} onChange={this.handleNameChange} type="text" placeholder='Name' /><br />
Price:
<input value = {this.state.prices} onChange={this.handlePriceChange} type='text' /><br />
</label>
<label>
Choose an Image:
<input className='imgInsert' name ='inputForm' type='file'/>
</label>
<div>
<img className = 'previewImage' value={this.state.image}/>
</div>
<button className='btn updateBtn' onClick={(e) => this.handleSubmit(e)}>Submit</button>
</form>
</div>
</div>
</div>
)
}
}
export default AdminContainer
Common errors I am getting when trying debug it is
TypeError: Cannot read property 'path' of undefined."
and "file" being undefined.
When using multer to save images you need to make sure that the image comes to the server as form data. this is because multer requires the multipart/form-data encoding which you do not get when submitting a form with an ajax request unless if you specifically do something to make it happen.
You can do this by using the FormData object. Here is an example of this being used. I hope this helps.

React/Redux clear form elements values after submit

I have problems with clearing values from input and select form elements in react form after successful submit through axios library. Just want to mention that i do not use redux-form.
I don't know if I am on the right track here, this is my workflow by far: I wrote a form with react-bootstrap, give every input and select value through props and I access and update the state through these props. I have wrote actions and reducers for updating input values, and one action is dispatched in my component, but the second action and the reducer that is supposed to clear values after submit doesn't work as expected. This is the main problem, I'm not sure if I dispatch FORM_RESET action form in the right place, because I call it in the action that is responsible for posting data to server, and on success callback I dispatch FORM_RESET.
Below is the code relevant for this problem.
/* actionRegister.js */
let _registerUserFailure = (payload) => {
return {
type: types.SAVE_USER_FAILURE,
payload
};
};
let _registerUserSuccess = (payload) => {
return {
type: types.SAVE_USER_SUCCESS,
payload,
is_Active: 0,
isLoading:true
};
};
let _hideNotification = (payload) => {
return {
type: types.HIDE_NOTIFICATION,
payload: ''
};
};
//asynchronous helpers
export function registerUser({ //use redux-thunk for asynchronous dispatch
timezone,
password,
passwordConfirmation,
email,
name
}) {
return dispatch => {
axios.all([axios.post('/auth/signup', {
timezone,
password,
passwordConfirmation,
email,
name,
is_Active: 0
})
// axios.post('/send', {email})
])
.then(axios.spread(res => {
dispatch(_registerUserSuccess(res.data.message));
dispatch(formReset()); //here I dispatch clearing form data
setTimeout(() => {
dispatch(_hideNotification(res.data.message));
}, 10000);
}))
.catch(res => {
dispatch(_registerUserFailure(res.data.message)); //BE validation and passport error message
setTimeout(() => {
dispatch(_hideNotification(res.data.message));
}, 10000);
});
};
}
/* actionForm.js */
//synchronous action creators
export function formUpdate(name, value) {
return {
type: types.FORM_UPDATE_VALUE,
name, //shorthand from name:name introduced in ES2016
value
};
}
export function formReset() {
return {
type: types.FORM_RESET
};
}
/* reducerRegister.js */
const INITIAL_STATE = {
error:{},
is_Active:false,
isLoading:false
};
const reducerSignup = (state = INITIAL_STATE , action) => {
switch(action.type) {
case types.SAVE_USER_SUCCESS:
return { ...state, is_Active:false, isLoading: true, error: { register: action.payload }};
case types.SAVE_USER_FAILURE:
return { ...state, error: { register: action.payload }};
case types.HIDE_NOTIFICATION:
return { ...state , error:{} };
}
return state;
};
export default reducerSignup;
/* reducerForm.js */
const INITIAL_STATE = {
values: {}
};
const reducerUpdate = (state = INITIAL_STATE, action) => {
switch (action.type) {
case types.FORM_UPDATE_VALUE:
return Object.assign({}, state, {
values: Object.assign({}, state.values, {
[action.name]: action.value,
})
});
case types.FORM_RESET:
return INITIAL_STATE;
//here I need isLoading value from reducerRegister.js
}
return state;
};
export default reducerUpdate;
/* SignupForm.js */
import React, {Component} from 'react';
import {reduxForm} from 'redux-form';
import {connect} from 'react-redux';
import map from 'lodash/map';
import timezones from '../../data/timezones';
import styles from '../formElements/formElements.scss';
import {registerUser} from '../../actions/actionRegister';
import {formUpdate} from '../../actions/actionForm';
import FieldGroup from '../formElements/FieldGroup';
import { Form, FormControl, Col, Checkbox, Button, FormGroup } from 'react-bootstrap';
// {... props} passing large number of props wrap in object with spread notation
class SignupForm extends Component { //if component have state it needs to be class
constructor(props) {
super(props);
this.state = {
errors: { //this errors are irrelevant for now
name:'',
email: '',
password: '',
passwordConfirmation:'',
timezone:''
},
};
}
onChange = (event, index, value) => {
this.props.onChange(event.target.name, event.target.value);
};
onSave = (event) => {
event.preventDefault();
this.props.onSave(this.props.values);
}
render() {
let isLoading = this.props.isLoading;
return (
// this.props.handleSubmit is created by reduxForm()
// if the form is valid, it will call this.props.onSubmit
<Form onSubmit={this.onSave} horizontal>
<FieldGroup
id="formControlsName"
type="text"
label="Name"
name="name"
placeholder="Enter Name"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.name}
/>
<FieldGroup
id="formControlsEmail"
type="text"
label="Email"
name="email"
placeholder="Enter Email"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.email}
/>
<FieldGroup
id="formControlsPassword"
type="password"
label="Password"
name="password"
placeholder="Enter Password"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.password}
/>
<FieldGroup
id="formControlsPasswordConfirmation"
type="password"
label="Password Confirmation"
name="passwordConfirmation"
placeholder="Enter Password"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.passwordConfirmation}
/>
<FieldGroup
id="formControlsTimezone"
label="Time Zone"
name="timezone"
placeholder="Select Time Zone"
componentClass="select"
defaultValue="Select Your Timezone"
value={this.props.values[name]}
onChange={this.onChange}
help={this.state.errors.timezone}
>
<option value="Select Your Timezone">Select Your Timezone</option>
{
map(timezones, (key, value) =>
<option key={key} value={key}>{value}</option>)
}
</FieldGroup>
<FormGroup>
<Col smOffset={4} sm={8}>
<Checkbox>Remember me</Checkbox>
</Col>
</FormGroup>
<FormGroup>
<Col smOffset={4} sm={8}>
<Button type="submit" disabled={isLoading}
onClick={!isLoading ? isLoading : null}
>
{ isLoading ? 'Creating...' : 'Create New Account'}
</Button>
</Col>
</FormGroup>
{this.props.errorMessage && this.props.errorMessage.register &&
<div className="error-container">{this.props.errorMessage.register}</div>}
</Form>
//this.setState({ disabled: true });
//this.props.errorMessage.register == this.props = {errorMessage :{ register: ''}}
);
}
}
function mapStateToProps(state) {
return {
errorMessage: state.signup.error,
isLoading: state.signup.isLoading,
values: state.form.values
};
}
function mapDispatchToProps(dispatch) {
return {
onSave: (values) => dispatch(registerUser(values)),
onChange: (name, value) => dispatch(formUpdate(name, value))
};
}
export default connect(mapStateToProps, mapDispatchToProps)(SignupForm)
;
There is no need to use redux-form :-) You're on the right path and you're calling FORM_RESET action in the right place.
Couple of things:
are you sure you are importing formReset in actionRegister.js?
in reducerForm I would suggest to still return new state here:
case types.FORM_RESET:
return { ...INITIAL_STATE }; // or Object.assign({}, INITIAL_STATE)
And btw. why are you setting isLoading: true on success? I would suggest to create 3 actions instead of 2:
SAVE_USER_START (which you dispatch before sending a request),
set isLoading to true,
SAVE_USER_SUCCESS - set isLoading to false
SAVE_USER_FAILURE - set isLoading to false
I would suggest to look into redux-form library. It provides configuration option to clear fields after submit out of the box.