How do I set Quill output to be JSON/delta instead of html? - quill

What do I need to do to set Quill's content or output to be JSON/delta instead of HTML?
I can't believe I'm asking such a simple question but I can't find the answer anywhere.
There's nothing about how to set the format in either the QuillJS doc or react-quill doc.
import React, { useState } from "react";
import ReactQuill, from 'react-quill';
import 'react-quill/dist/quill.snow.css';
export const Comment = () => {
const [value, setValue] = useState('');
function submit(e) {
e.preventDefault();
console.log(value) # This currently returns HTML instead of JSON
}
return (
<>
<ReactQuill theme="snow" value={value} onChange={setValue}/>
<p>{value}</p>
<button onClick={submit}>Submit</button>
</>
);
}

It looks like the onChange prop is a function that has the HTML contents as the first argument, which will be the value used by setState. You'll want to define a custom function that sets value to editor.getContents(), which returns a Delta representing the current document.
import React, { useState } from "react";
import ReactQuill, from 'react-quill';
import 'react-quill/dist/quill.snow.css';
export const Comment = () => {
const [value, setValue] = useState('');
function submit(e) {
e.preventDefault();
console.log(value);
}
// onChange expects a function with these 4 arguments
function handleChange(content, delta, source, editor) {
setValue(editor.getContents());
}
return (
<>
<ReactQuill theme="snow" value={value} onChange={handleChange}/>
<p>{value}</p>
<button onClick={submit}>Submit</button>
</>
);
}

As above denoted answer by person_v1.32.The handleChange method receives four params second param is the delta you can simply use that to get delta.
You can send it to the database or in your react state whatever suits you.
// onChange expects a function with these 4 arguments
function handleChange(content, delta, source, editor) {
setValue(delta);
}

Related

How to add searchinputs as array in localStorage with React TypeScript

I'm trying to make a search bar, where the search will occur once the button (or enter) is clicked. To this, I want to save the searched phrases in localStorage.
I have no problem with the first part. Things work fine when searching with a button or on enter-click. However, when I try to add the search as an array to localStorage I keep getting issues (like string doesn't work with an array, and many more. I've tried A LOT of different things). I've done this with JS and vanilla React, but never with TS.
In the provided code, the search works, and to put in at least something (as simple as I could) - the latest value is also stored in localStorage.
The code can be found here
Or here:
// index file
import { useState } from "react";
import { Searchbar } from "./searchbar";
import { SearchContainer } from "./style";
export const Search = () => {
const [search, setSearch] = useState<string>("");
return (
<SearchContainer>
<Searchbar setSearch={setSearch} />
<p>SEARCHED: {search}</p>
</SearchContainer>
);
};
// search bar file
import { useRef, KeyboardEvent, useEffect } from "react";
type SearchProps = {
setSearch: React.Dispatch<React.SetStateAction<string>>;
};
export const Searchbar = ({ setSearch }: SearchProps) => {
// with useRef we don't have to reload the page for every input
const inputRef = useRef<HTMLInputElement>(null);
const handleClick = () => {
setSearch(String(inputRef.current?.value));
localStorage.setItem("form", JSON.stringify(inputRef.current?.value)); // Issue here
};
// Enable search with the enter-key
const log = (e: KeyboardEvent): void => {
e.key === "Enter" ? handleClick() : null;
};
useEffect(() => {
const value = localStorage.getItem("form");
value ? JSON.parse(value) : "";
}, [setSearch]);
return (
<div>
<input
type="text"
autoComplete="off"
ref={inputRef}
placeholder="search game..."
onKeyDown={log}
/>
<button onClick={handleClick}>SEARCH</button>
</div>
);
};
I've been on this for a while now so any help is appreciated. (I am also trying to learn TS from scratch, but until I get to here...).

Invoke helper text method on typing

In the following, there is a MUI textfield method, which has a helperText attribute.
I want to invoke that method, when somebody starts typing in the text field.
import { useState } from "react";
import TextField from "#mui/material/TextField";
export default function App() {
const [inputText, setInputText] = useState(null);
const [txtLength, setTxtLength] = useState(0);
const handleChange = (event) => {
const textValue = event.target.value;
setInputText(textValue);
setTxtLength(textValue.length);
};
const displayHelperText = () => {
return `${12 - txtLength} characters left`;
};
return (
<TextField
onChange={handleChange}
inputProps={{ maxLength: 12 }}
helperText={displayHelperText} // Invoke on typing in text field
/>
);
}
CodeSandbox demo.
Just do
helperText={ txtLength > 0? displayHelperText() : ""}
You could potentially use undefined instead of "" for the last portion, I don't know offhand if MUI allows undefined for that prop.
(also I'd just use the string inline, no need for a stand alone displayHelperText function here)

useState not updating[NOT AN ASYNC ISSUE]

I'm new to react hooks so I'm practicing with showing and hiding a div when checking and unckecking a checkbox input. The problem is that the state updates on the main file where I have the function that handles it but in the file where I actually have the div it does not update so it does not hide or display.
File that handles the change of the state:
import {react, useState} from "react";
export const Checker = () => {
const [checked, setChecked] = useState(true)
const clickHandler = () => {
setChecked(!checked)
console.log(checked)
}
return {checked, clickHandler, setChecked}
}
File where the checkbox is located:
import React from "react";
import { Extras, Wrapper } from "./extras.styles";
import { Checker } from "../hooks/useCheckboxes";
const Extra = () => {
const {checked, setChecked, clickHandler} = Checker()
return <>
<Extras>
<Wrapper>
<input type= 'checkbox' onClick={clickHandler} checked = {checked} onChange={e => setChecked(e.target.checked)}></input>
</Wrapper>
</Extras>
</>
}
export default Extra;
File that contains the div i want to display and hide dynamically:
import house from '../../icons/house.png'
import { Wrapper } from "./foto.styles";
import { Checker } from "../hooks/useCheckboxes";
import { Inside, Middle} from "./foto.styles";
const Home = () => {
const {checked} = Checker()
return <>
<Wrapper>
<Inside>
<Middle>
{checked && <House src={house}/>}
</Middle>
</Inside>
</Wrapper>
</>
}
export default Home;
Some issues are:
Checker looks like you want it to be a custom hook, not a React component, so it should be called useChecker or something like that, not Checker
You have both a change handler and a click handler. You should only have one. If you want the new state to come from the checkbox, you should use e.target.checked. If you want the new state to flip the old state, use the clickHandler you defined in Checker.
You only need a fragment when enclosing multiple elements. If you only have one, you don't need a fragment.
Because state setters don't update the variable immediately, your console.log(checked) won't display the new value, but the old value - if you want to log the new value when it changes, use useEffect with a dependency array of [checked] instead.
const Extra = () => {
const { checked, clickHandler } = useChecker()
return (
<Extras>
<Wrapper>
<input type='checkbox'checked={checked} onChange={clickHandler} />
</Wrapper>
</Extras>
)
}
use it like that
const {checked, clickHandler, setChecked} = Checker()
Or if you want to be able to make custom names then you need to use an array instead of an object.
the function return value.
return [checked, clickHandler, setChecked]
the function call
const [checked, setChecked, clickHandler] = Checker()
and for convention follow react hooks naming rules by renaming the function to useChecker() instead of Checker()

state not changing on onChange event in react?

This is my code:
function AddPost() {
const [file, setFile] = useState({})
const handleChange = (event) => {
setFile(event.target.files[0]);
console.log(file);
}
return (
<div>
<TextField type='file' onChange={handleChange} label='image' variant='outlined' />
</div>
)
}
I am not getting file info. on console, while i am selecting a file . Instead of that I am getting empty object why ?
You need to use a useEffect to besure you are doing your action after a state is updated. Here is an example :
import React, { Component } from "react";
import { render } from "react-dom";
const App = () => {
const [num, setNum] = React.useState(0);
const handleClick = () => {
setNum(1);
console.log('num =', num);
}
// Note the dependency array below
React.useEffect(() => console.log('num (useEffect) = ', num), [num]);
return (
<div>
<button onClick={handleClick}>Click</button>
</div>
);
};
render(<App />, document.getElementById("root"));
and here is repro on Stackblitz.
Here, on click, num will be 0 in the function, but it will be set to 1 in the useEffect.
Just the file is not updated yet (whitin onChange) , so you see the initial state
The state update is performed asynchronously and only visible in the next render call.
In the next render call, file will be updated to the new value and then your (also new) function handleChange will use that value.
This means in your current code the log will always be off by one (empty object, file 1, file 2 etc.) when uploading one file after each other.
The behavior is described in the documentation of the useState hook.
To fix the log, simply write
const newFile = event.target.files[0];
setFile(newFile);
console.log(newFile);

input value not updating when mutating state

While creating a little project for learning purposes I have come across an issue with the updating of the input value. This is the component (I have tried to reduce it to a minimum).
function TipSelector({selections, onTipChanged}: {selections: TipSelectorItem[], onTipChanged?:(tipPercent:number)=>void}) {
const [controls, setControls] = useState<any>([]);
const [tip, setTip] = useState<string>("0");
function customTipChanged(percent: string) {
setTip(percent);
}
//Build controls
function buildControls()
{
let controlList: any[] = [];
controlList.push(<input className={styles.input} value={tip.toString()} onChange={(event)=> {customTipChanged(event.target.value)}}></input>);
setControls(controlList);
}
useEffect(()=>{
console.log("TipSelector: useEffect");
buildControls();
return ()=> {
console.log("unmounts");
}
},[])
console.log("TipSelector: Render -> "+tip);
return (
<div className={styles.tipSelector}>
<span className={globalStyles.label}>Select Tip %</span>
<div className={styles.btnContainer}>
{
controls
}
</div>
</div>
);
}
If I move the creation of the input directly into the return() statement the value is updated properly.
I'd move your inputs out of that component, and let them manage their own state out of the TipSelector.
See:
https://codesandbox.io/s/naughty-http-d38w9
e.g.:
import { useState, useEffect } from "react";
import CustomInput from "./Input";
function TipSelector({ selections, onTipChanged }) {
const [controls, setControls] = useState([]);
//Build controls
function buildControls() {
let controlList = [];
controlList.push(<CustomInput />);
controlList.push(<CustomInput />);
setControls(controlList);
}
useEffect(() => {
buildControls();
return () => {
console.log("unmounts");
};
}, []);
return (
<div>
<span>Select Tip %</span>
<div>{controls}</div>
</div>
);
}
export default TipSelector;
import { useState, useEffect } from "react";
function CustomInput() {
const [tip, setTip] = useState("0");
function customTipChanged(percent) {
setTip(percent);
}
return (
<input
value={tip.toString()}
onChange={(event) => {
customTipChanged(event.target.value);
}}
></input>
);
}
export default CustomInput;
You are only calling buildControls once, where the <input ... gets its value only that single time.
Whenever React re-renders your component (because e.g. some state changes), your {controls} will tell React to render that original <input ... with the old value.
I'm not sure why you are storing your controls in a state variable? There's no need for that, and as you noticed, it complicates things a lot. You would basically require a renderControls() function too that you would replace {controls} with.

Resources