How to save and load Markdown with React? - node.js

I'm new to React. I have an assignment building a Markdown Editor with Blockstack ID integrated.
I'm trying to save the content into a JSON file then load it again in the editor, but it seems like it can't load that JSON file back to the editor.
Here's some code:
import MDEditor from '#uiw/react-md-editor';
<MDEditor
onChange={e=>this.updateMarkdown(e)}
value={this.state.markdown}
/>
<button
onClick={e => this.saveNewText(e)}>
Submit Changes
</button>
updateMarkdown(editor, data, value) {
this.setState({ markdown: value})
}
saveNewText() {
const { userSession } = this.props
let currentDocument = this.state.currentDocument
let newDocument = {
md: this.state.markdown,
created_at: Date.now()
}
const options = { encrypt: true }
userSession.putFile('Document.json', JSON.stringify(newDocument), options)
.then(() => {
this.setState({
currentDocument:newDocument
})
})
}
loadNewText() {
const { userSession } = this.props
const options = { decrypt: true }
userSession.getFile('Document.json', options)
.then((file) => {
var docFile = JSON.parse(file || '[]');
this.setState({
currentDocument:docFile,
markdown:docFile.md
});
})
}
componentWillMount() {
const { userSession } = this.props
this.setState({
person: new Person(userSession.loadUserData().profile),
username: userSession.loadUserData().username
});
this.loadNewText();

The react-blockstack package provides a useFile hook for React to persist content in the Blockstack Gaia storage:
const [document, setDocument] = useFile('Document.json')
This replaces most of your code except transformation between text and JSON.

Related

Can't display data in localhost browser

I creating an app that stores data but when i finish the prompt input i get this error:
Here is my CptList.js
import React, { Component } from 'react';
import { Container, Button } from 'reactstrap';
import uuid from 'uuid';
export default class CpdList extends Component{
state = {}
handleClick = () => {
const date = prompt('Enter Date')
const activity = prompt('Enter Activity')
const hours = prompt('Enter Hours')
const learningStatement = prompt('Enter Learning Statement')
const evidence = prompt('YES! or NO!')
this.setState(state => ({
items: [
...state.items,
{
id: uuid(),
date,
activity,
hours,
learningStatement,
evidence
}
]
}));
}
render() {
const { items } = this.state;
return (
<Container>
<Button
color='dark'
style={{marginBottom: '2rem'}}
onClick={this.handleClick}
>Add Data</Button>
<Button
color='dark'
style={{marginBottom: '2rem'}}
onClick={() => { this.handleClick(items._id) }}
>Delete Data</Button>
</Container>
);
};
};
Can someone please tell me what im doing wrong? I am also having trouble with my delete function, this is my delete coding in my backend:
//Delete a Item
router.delete('/:id', (req, res) => {
Item.findById(req.params.id)
.then(item => item.remove().then(() => res.json({ success: true })))
.catch(err => res.status(404).json({ success: false }));
});
I think you have to initialize state with:
state = { items:[] }
The first time you add item to undefined empty list.
Moreover I think missing a state.items.map somewhere (at least for delete button)
state = [] // convert to array beacuse use map() or other javascipt method
this.setState(state => ({
items: [
// do not speard maybe
{
id: uuid(),
date,
activity,
hours,
learningStatement,
evidence
}
]
}));
plz write handleClick function
tell me working or not

submit button disabled when logging in a user

Hi I am making the logging in functionality and for some reason when I
fill in all the input fields my button stays disabled and I cant submit it.
this is the renderButton function...
renderButton(label) {
return (
<button
disabled={this.validate() ? true : false}
className="btn btn-primary"
>
{label}
</button>
);
}
this is the login form...
import React, { Component } from "react";
import Joi from "joi-browser";
import Form from "./form";
import { login } from "../services/authService";
class LoginForm extends Form {
state = {
data: { username: "", password: "" },
errors: {}
};
schema = {
email: Joi.string()
.required()
.label("Email"),
password: Joi.string()
.required()
.label("Password")
};
doSubmit = async () => {
//call server
const { data } = this.state;
await login(data.email, data.password);
};
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
{this.renderInput("email", "Email")}
{this.renderInput("password", "Password", "password")}
{this.renderButton("Login")}
</form>
</div>
);
}
}
export default LoginForm;
this is the validation functions where both validate and vaidate property is in it...
validate = () => {
const options = { abortEarly: false };
const { error } = Joi.validate(this.state.data, this.schema, options);
if (!error) return null;
const errors = {};
for (let item of error.details) errors[item.path[0]] = item.message;
return errors;
};
validateProperty = ({ name, value }) => {
const obj = { [name]: value };
const schema = { [name]: this.schema[name] };
const { error } = Joi.validate(obj, schema);
return error ? error.details[0].message : null;
};
Looks like the render function is not called each time your input changes, maybe it is because you are extending some Form and not React. It is anti pattern to make a deep nesting, just extend React, if you want to add some functionality you can wrap your component with HOC (higher order component function).

fetch data with vue and sockets

I am using vuejs (CLI 3) with axios and sockets. My backend is NodeJs.
Html (view all users):
...
<ul v-if="users.length > 0">
<li v-for="user in users" :key="user.id">
<router-link :to="'/oneuser/' + user.permalink" tag="li" active-class="active" #click.native="setmyid(user._id)">
<a>{{ user.name }} - {{ user.last_name }}</a>
</router-link>
</li>
</ul>
...
<script>
import axios from 'axios'
import io from 'socket.io-client'
export default {
name: 'get-user',
data () {
return {
users: [],
socket: io('localhost:7000')
}
},
methods: {
mycall () {
axios.get('http://localhost:7000/api/users')
.then(res => {
// console.log(res)
const data = res.data
const users = []
for (let key in data) {
const user = data[key]
user.id = key
users.push(user)
}
// console.log(users)
this.users = users
})
.catch(error => console.log(error))
}
}
mounted () {
this.mycall()
this.socket.on('user-deleted', function (data) {
this.mycall()
})
}
}
</script>
Html (user view):
<script>
import axios from 'axios'
export default {
name: 'one-user',
data () {
return {
name: '',
surname: '',
id: localStorage.getItem('usId'),
socket: io('localhost:7000')
}
},
mounted () {
axios.get('http://localhost:7000/api/get-user/' + this.id)
.then(res => {
const data = res.data
this.name = data.name
this.surname = data.last_name
})
.catch(error => console.log(error))
},
methods: {
mySubmit () {
const formData = {
_id: this.id
}
axios.post('http://localhost:7000/api/delete-user', formData)
.then(this.$router.push({ name: 'get-user' }))
.catch(error => console.log(error))
}
}
}
</script>
backend NodeJs:
controller.postDeleteUser = function(req,res){
User.deleteOne({"_id" : req.body._id}, function(err){
io.emit('user-deleted', req.body._id);
res.send('ok');
});
};
When I go to user view and delete the user then it directs me to view all users. I have two major problems here.
1) After redirect, I saw again all the users even the deleted one. In my database the user has deleted correctly.
2) I don't know where exactly and how to use sockets in my code.
I am using in the front the socket.io-client npm plugin. Also I don't want to use (and I don't use it in my code) vue-socket.io because IE 11 and below version are not supported and it throws me some errors.
What I have tried so far:
1) Using watch like this:
watch: {
users: function (newValue) {
newValue = this.mycall()
}
}
This is very bad for browser performance, because always call request from the browser.
2) use beforeUpdate or Updated life-cycle:
updated () {
this.mycall()
}
That works but has bad performance. It makes requests many times to the server.
3) or that with sockets
updated () {
this.socket.on('user-deleted', function (data) {
this.mycall()
})
}
and that throws me an error:
this.mycall() is not a function
What am I doing wrong?
Where to put the code with sockets?
I have changed the view all users file to:
...
methods: {
mycall () {
axios.get('http://localhost:7000/api/users')
.then(res => {
const data = res.data
const users = []
for (let key in data) {
const user = data[key]
user.id = key
users.push(user)
}
this.users = users
})
.catch(error => console.log(error))
},
socketcall (thecall) {
this.socket.on(thecall, (data) => {
this.mycall()
})
}
}
....
created () {
this.mycall()
},
mounted () {
this.socketcall('user-deleted')
}
Life cycle-hooks cannot retrieved functions inside "this.socket.on" so I thought to do like above and it works!

TypeError: Cannot read property 'markdownRemark' of undefined (React + Node)

I use Gatsby v1.0 to write my app.
Fot now I have I error in my console:
First of all it is strange, because 'children' has its' PropTypes, you can see component below:
import React from "react"
import Link from "gatsby-link"
import { Container } from "react-responsive-grid"
import { rhythm, scale } from "../utils/typography"
class Template extends React.Component {
render() {
const { location, children } = this.props
let header
if (location.pathname === "/") {
header = (
<h1 >
<Link to={"/"}>
Gatsby Starter Blog
</Link>
</h1>
)
} else {
header = (
<h3>
<Link to={"/"} >
Gatsby Starter Blog
</Link>
</h3>
)
}
return (
<Container>
{header}
{children()}
</Container>
)
}
}
Template.propTypes = {
children: React.PropTypes.function,
location: React.PropTypes.object,
route: React.PropTypes.object,
}
export default Template
Also, my project is not launch, bacause localhost shows error:
TypeError: Cannot read property 'markdownRemark' of undefined
I think something wrong in Node.js part, but all modules are installed and I am really sure in all connection with components, because it is starter kit and it is strange to see these mistakes here. Or maybe I should to fix previous component (they are in connect).
const _ = require("lodash")
const Promise = require("bluebird")
const path = require("path")
const select = require(`unist-util-select`)
const fs = require(`fs-extra`)
exports.createPages = ({ graphql, boundActionCreators }) => {
const { upsertPage } = boundActionCreators
return new Promise((resolve, reject) => {
const pages = []
const blogPost = path.resolve("./src/templates/blog-post.js")
resolve(
graphql(
`
{
allMarkdownRemark(limit: 1000) {
edges {
node {
fields {
slug
}
}
}
}
}
`
).then(result => {
if (result.errors) {
console.log(result.errors)
reject(result.errors)
}
// Create blog posts pages.
_.each(result.data.allMarkdownRemark.edges, edge => {
upsertPage({
path: edge.node.fields.slug, // required
component: blogPost,
context: {
slug: edge.node.fields.slug,
},
})
})
})
)
})
}
// Add custom slug for blog posts to both File and MarkdownRemark nodes.
exports.onNodeCreate = ({ node, boundActionCreators, getNode }) => {
const { addFieldToNode } = boundActionCreators
if (node.internal.type === `File`) {
const parsedFilePath = path.parse(node.relativePath)
const slug = `/${parsedFilePath.dir}/`
addFieldToNode({ node, fieldName: `slug`, fieldValue: slug })
} else if (node.internal.type === `MarkdownRemark`) {
const fileNode = getNode(node.parent)
addFieldToNode({
node,
fieldName: `slug`,
fieldValue: fileNode.fields.slug,
})
}
}

How to save image in local folder using Angular2?

I am new to stack overflow as well as in angular2. i am currently learning how to upload image in local folder inside application using angular2. i can't understand how to save images in local folder. i have read many answers from stack overflow but all of them (almost) using angularjs not angular2. can any one please help me how to upload and save images in local folder using angular2 and nodejs. i have just html code.
<div class="form-group">
<label for="single">single</label>
<input type="file" class="form-control" name="single"
/></div>
i have spent my whole night but all in vain. can someone please give me simple tutorial by just showing how to save in local folder using angular 2
I created this very detailed example working with an Item model, component, service, route and controller to select, upload and store a picture using latest versions of Angular and NodeJS (currently Angular 6 and NodeJS 8.11) but should work in previous versions too.
I'm not going to explain much taking in consideration you can learn and understand most of it just by examining the code. But do not hesitate to ask if you don't understand something on your own. Here we go...
item.component.ts:
import { Component, OnInit } from '#angular/core';
import { FormGroup, FormControl, FormArray, Validators } from '#angular/forms';
import { ActivatedRoute, ParamMap } from '#angular/router';
import { Subscription } from 'rxjs';
import { mimeType } from './mime-type.validator';
import { ItemService} from '../item.service';
import { Item } from '../item.model';
#Component({
selector: 'app-item',
templateUrl: './item.component.html',
styleUrls: ['./item.component.css']
})
export class ItemComponent implements OnInit {
item: Item;
form: FormGroup;
imagePreview: string;
private id: string;
loading = false;
constructor(public itemService: ItemService, public route: ActivatedRoute) { }
initForm() {
this.imagePreview = item.imagePath;
const item = this.item ? this.item : new Item();
return new FormGroup({
id: new FormControl({value: item.id, disabled: true}),
name: new FormControl(item.name, { validators: [Validators.required, Validators.minLength(3)] }),
image: new FormControl(item.imagePath, { validators: [Validators.required], asyncValidators: [mimeType] })
});
}
ngOnInit() {
this.form = this.initForm();
this.route.paramMap.subscribe((paramMap: ParamMap) => {
if (paramMap.has('id')) {
this.id = paramMap.get('id');
this.loading = true;
this.itemService.getItem(this.id).subscribe(data => {
this.item = new Item(
data._id,
data.name ? data.name : '',
data.imagePath ? data.imagePath : '',
);
this.form = this.initForm();
this.loading = false;
});
} else {
this.id = null;
this.item = this.form.value;
}
});
}
onImagePicked(event: Event) {
const file = (event.target as HTMLInputElement).files[0];
this.form.patchValue({ image: file });
this.form.get('image').updateValueAndValidity();
const reader = new FileReader();
reader.onload = () => {
this.imagePreview = reader.result;
};
reader.readAsDataURL(file);
}
onSave() {
if (this.form.invalid) {
return;
}
this.loading = true;
if (!this.id) { // creating item...
const item: Item = {
id: null,
name: this.form.value.name,
imagePath: null
};
this.itemService.createItem(item, this.form.value.image);
} else { // updating item...
const item: Item = {
id: this.id,
name: this.form.value.name,
imagePath: null
};
this.itemService.updateItem(item, this.form.value.image);
}
this.form.reset();
}
}
The mimeType is a validator to limit the user selecting / loading only an image file from a group of image types...
mimetype.validator:
import { AbstractControl } from '#angular/forms';
import { Observable, Observer, of } from 'rxjs';
export const mimeType = (control: AbstractControl): Promise<{[ key: string ]: any}> | Observable<{[ key: string ]: any}> => {
if (typeof(control.value) === 'string') {
return of(null);
}
const file = control.value as File;
const fileReader = new FileReader();
const frObs = Observable.create((observer: Observer<{[ key: string ]: any}>) => {
fileReader.addEventListener('loadend', () => {
const arr = new Uint8Array(fileReader.result).subarray(0, 4);
let header = '';
let isValid = false;
for (let i = 0; i < arr.length; i++) {
header += arr[i].toString(16);
}
switch (header) {
case '89504e47':
isValid = true;
break;
case '89504e47': // png
case '47494638': // gif
case 'ffd8ffe0': // JPEG IMAGE (Extensions: JFIF, JPE, JPEG, JPG)
case 'ffd8ffe1': // jpg: Digital camera JPG using Exchangeable Image File Format (EXIF)
case 'ffd8ffe2': // jpg: CANNON EOS JPEG FILE
case 'ffd8ffe3': // jpg: SAMSUNG D500 JPEG FILE
case 'ffd8ffe8': // jpg: Still Picture Interchange File Format (SPIFF)
isValid = true;
break;
default:
isValid = false;
break;
}
if (isValid) {
observer.next(null);
} else {
observer.next({ invalidMimeType: true });
}
observer.complete();
});
fileReader.readAsArrayBuffer(file);
});
return frObs;
};
item.component.html:
<form [formGroup]="form" (submit)="onSave()" *ngIf="!loading">
<input type="text" formControlName="name" placeholder="Name" autofocus>
<span class="error" *ngIf="form.get('name').invalid">Name is required.</span>
<!-- IMAGE BLOCK -->
<div class="image">
<button class="pick-image" type="button" (click)="filePicker.click()">
Pick Image
</button>
<input type="file" #filePicker (change)="onImagePicked($event)">
<div class="image-preview" *ngIf="imagePreview !== '' && imagePreview && form.get('image').valid">
<img [src]="imagePreview" [alt]="form.value.title">
</div>
</div>
<div id="buttons-bar">
<button class="submit" type="submit">SAVE</button>
</div>
</form>
Using some CSS to hide the input type "file" HTML Element because it looks ugly but still necessary to trigger the user browser to open a dialog window to select a file for upload (a nice button is shown instead for a better user experience)...
item.component.css
.pick-image {
padding: 10px;
background-color: rgba(150, 220, 255, 0.7);
width: 100px;
}
.pick-image:hover {
cursor: pointer;
background-color: rgba(150, 220, 255, 0.9);
}
input[type="file"] {
visibility: hidden;
display: none;
}
.image-preview {
height: 200px;
margin: 0;
padding: 0;
}
.image-preview img {
height: 100%;
width: 100%;
object-fit: contain;
}
item.service.ts:
import { Injectable } from '#angular/core';
import { HttpClient } from '#angular/common/http';
import { Router } from '#angular/router';
import { Subject } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import { Item } from './item.model';
const SERVER_URL = environment.API_URL + '/items/';
#Injectable({ providedIn: 'root' })
export class ItemService {
private items: Item[] = [];
private itemsUpdated = new Subject<{items: Item[], count: number}>();
constructor(private http: HttpClient, private router: Router) {}
getItems(perPage: number, currentPage: number) {
const queryParams = `?ps=${perPage}&pg=${currentPage}`;
this.http.get<{message: string, items: any, total: number}>(SERVER_URL + queryParams)
.pipe(map((itemData) => {
return {
items: Item.extractAll(itemData.items),
count: itemData.total
};
}))
.subscribe((mappedData) => {
if (mappedData !== undefined) {
this.items = mappedData.items;
this.itemsUpdated.next({
items: [...this.items],
count: mappedData.count
});
}
}, error => {
this.itemsUpdated.next({items: [], count: 0});
});
}
getItemUpdatedListener() {
return this.ItemsUpdated.asObservable();
}
getItem(id: string) {
return this.http.get<{
_id: string,
name: string,
imagePath: string
}>(SERVER_URL + id);
}
createItem(itemToCreate: Item, image: File) {
const itemData = new FormData();
itemData.append('name', itemToCreate.name);
itemData.append('image', image, itemToCreate.name);
this.http.post<{ message: string, item: Item}>(SERVER_URL, itemData ).subscribe((response) => {
this.router.navigate(['/']);
});
}
updateItem(itemToUpdate: Item, image: File | string) {
let itemData: Item | FormData;
if (typeof(image) === 'object') {
itemData = new FormData();
itemData.append('id', itemToUpdate.id);
itemData.append('name', itemToUpdate.name);
itemData.append('image', image, itemToUpdate.name);
} else {
itemData = {
id: itemToUpdate.id,
name: itemToUpdate.name,
imagePath: image
};
}
this.http.put(SERVER_URL + itemToUpdate.id, itemData).subscribe(
(response) => {
this.router.navigate(['/']);
}
);
}
deleteItem(itemId) {
return this.http.delete<{ message: string }>(SERVER_URL + itemId);
}
}
Now, in the backend (NodeJS using ExpressJS), picture you have your app.js where, among other things, you reference your connection with the database (here using MongoDB) and middlewares in the following order...
app.js:
const path = require('path');
const express = require('express');
const bodyParser = require('body-parser');
const mongoose = require('mongoose');
const itemRoutes = require('./routes/items');
const app = express();
mongoose.connect(
'mongodb+srv://username:' +
process.env.MONGO_ATLAS_PW +
'#cluster0-tmykc.mongodb.net/database-name', { useNewUrlParser: true })
.then(() => {
console.log('Mongoose is connected.');
})
.catch(() => {
console.log('Connection failed!');
});
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use('/images', express.static(path.join(__dirname, 'images')));
app.use('/', express.static(path.join(__dirname, 'angular')));
app.use((req, res, next) => {
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, Authorization');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PATCH, PUT, DELETE, OPTIONS');
next();
});
app.use('/api/items', itemRoutes);
app.use((req, res, next) => {
res.sendFile(path.join(__dirname, 'angular', 'index.html'));
});
module.exports = app;
Your itemRoutes items.js file is in a routes folder...
routes/items.js:
const express = require('express');
const extractFile = require('./../middleware/file');
const router = express.Router();
const ItemController = require('./../controllers/items');
router.get('', ItemController.get_all);
router.get('/:id', ItemController.get_one);
router.post('', extractFile, ItemController.create);
router.put('/:id', extractFile, ItemController.update);
router.delete('/:id', ItemController.delete);
module.exports = router;
Your ItemController items.js file in a controllers folder...
controllers/items.js
const Item = require('./../models/item');
// ...
exports.create = (req, res, next) => {
const url = req.protocol + '://' + req.get('host');
const item = req.body; // item = item to create
const itemToCreate = new Item({
name: item.name,
imagePath: url + "/images/" + req.file.filename,
});
itemToCreate.save().then((newItem) => {
res.status(201).json({
message: 'Item created.',
item: {
...newItem,
id: newItem._id
}
});
})
.catch(error => {
console.log('Error while creating item: ', error);
res.status(500).json({
message: 'Creating item failed!',
error: error
});
});
};
// ...
And finally, the file.js middleware:
const multer = require('multer');
const MIME_TYPE_MAP = {
'image/png': 'png',
'image/jpeg': 'jpg',
'image/jpg': 'jpg'
};
const storage = multer.diskStorage({
destination: (req, file, cb) => {
const isValid = MIME_TYPE_MAP[file.mimetype];
let error = isValid ? null : new Error('Invalid mime type');
cb(error, 'backend/images'); // USE THIS WHEN APP IS ON LOCALHOST
// cb(error, 'images'); // USE THIS WHEN APP IS ON A SERVER
},
filename: (req, file, cb) => {
const name = file.originalname.toLowerCase().split(' ').join('-');
const ext = MIME_TYPE_MAP[file.mimetype];
cb(null, name + '-' + Date.now() + '.' + ext);
}
});
module.exports = multer({storage: storage}).single('image');
I simplified parts of an app of mine adapting to this example to make it easier to understand (and at same time leaving a lot in there to help completing the "circle"). I hope this helps.

Resources