How to clear all text in Slate.JS editor - reactjs

For the life of me, I can't figure out how to clear all of the text in an Editor component from slate.js.
I have tried:
Transforms.delete(editor, {}); -> doesn't do anything
editor.deleteBackward("line"); -> only deletes one line, not all
I have also tried manually rerendering the editor component and that unfortunately doesn't update it to its initial state :(
I have been tearing through the slate js docs and can't find anything anywhere! If anyone has any ideas, would be very happy.
This is how editor is implemented:
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
<Editable
key={stateKey}
onKeyDown={(event: any) => handleKeyDown(event)}
style={{ overflowWrap: "anywhere", width: "100%" }}
onPaste={(e) => {
if (e.clipboardData) {
if (e.clipboardData.files.length > 0) {
setFiles([...files, ...Array.from(e.clipboardData.files)]);
e.preventDefault();
}
}
}}
decorate={decorate}
renderLeaf={renderLeaf}
placeholder="What's happening?"
/>

You can mutate editor.children and set it to an "empty" value.
const editor = useMemo(() => withHistory(withReact(createEditor())), []);
editor.children = [
{
type: "paragraph",
children: [{ text: "" }]
}
];
Note: Slate needs a minimum of an array with one empty text node to render correctly.

You can use
import { Transforms } from "slate";
// useref
const editorRef = useRef()
if (!editorRef.current) editorRef.current = withHistory(withReact(createEditor()))
const editor = editorRef.current
const reset = () =>{
// loop delete all
editor.children.map(item => {
Transforms.delete(editor, { at: [0] })
})
// reset init
editor.children = [
{
type: "p",
children: [{ text: "" }]
}];
}

Related

AG Grid React does not render cell values when testing using Enzyme and RTL

so recently we updated ag-grid-react and ag-grid-community from 27.0.1 to 28.0.0 and previous working tests now fail.
Test tries to get a value from a row cell and compares to the given one.
Test (v. 27.3.0)
describe("Simple list rendering", () => {
const handleRowDoubleClick = () => { }
const handleRowSelect = () => { }
const formDataWithId = formData.map((item, index) => {
const newItem = checkNumberLength(item, items);
return { ...newItem, id: index };
});
const colDefs = [{
headerName: "test",
valueGetter: "7"
}]
const listRender = (
<AgGridReact
columnDefs={colDefs}
rowData={formDataWithId}
rowSelection="multiple"
suppressClickEdit={true}
onRowClicked={handleRowSelect}
onRowDoubleClicked={handleRowDoubleClick}
immutableData={true}
rowHeight={28}
/>
)
let component
let agGridReact
beforeEach((done) => {
component = mount(listRender);
agGridReact = component.find(AgGridReact).instance();
// don't start our tests until the grid is ready
ensureGridApiHasBeenSet(component).then(() => done(), () => fail("Grid API not set within expected time limits"));
});
it('stateful component returns a valid component instance', () => {
expect(agGridReact.api).toBeTruthy();
});
it("agGrid shows password field as * instead of string", () => {
console.log(component.find(".ag-cell-value").first().html())
expect(component.render().find(".ag-cell-value").first()).toEqual("7");
});
it("List contains derivative field", () => {
render(listRender);
expect(screen.getAllByText("5")).toHaveLength(1);
})})
RowData Used
[
{ CPS: 2, TST: 2, DERIVATIVE: "" },
{ CPS: 5, TST: 2, DERIVATIVE: "" },
]
Console log in test
When running the App the Grid renders the cell values using custom valueGetters.
Test is running in v27.3.0 and renders the div using ag-cell-value class but not the value (in v28.0.0 does not even render the div when trying to find node using ag-cell-value class)
Is there something wrong we are doing? Any help is appreciated!

REACT- Displaying and filtering specific data

I want to display by default only data where the status are Pending and Not started. For now, all data are displayed in my Table with
these status: Good,Pending, Not started (see the picture).
But I also want to have the possibility to see the Good status either by creating next to the Apply button a toggle switch : Show good menus, ( I've made a function Toggle.jsx), which will offer the possibility to see all status included Good.
I really don't know how to do that, here what I have now :
export default function MenuDisplay() {
const { menuId } = useParams();
const [selected, setSelected] = useState({});
const [hidden, setHidden] = useState({});
const [menus, setMenus] = useState([]);
useEffect(() => {
axios.post(url,{menuId:parseInt(menuId)})
.then(res => {
console.log(res)
setMenus(res.data.menus)
})
.catch(err => {
console.log(err)
})
}, [menuId]);
// If any row is selected, the button should be in the Apply state
// else it should be in the Cancel state
const buttonMode = Object.values(selected).some((isSelected) => isSelected)
? "apply"
: "cancel";
const rowSelectHandler = (id) => (checked) => {
setSelected((selected) => ({
...selected,
[id]: checked
}));
};
const handleClick = () => {
if (buttonMode === "apply") {
// Hide currently selected items
const currentlySelected = {};
Object.entries(selected).forEach(([id, isSelected]) => {
if (isSelected) {
currentlySelected[id] = isSelected;
}
});
setHidden({ ...hidden, ...currentlySelected });
// Clear all selection
const newSelected = {};
Object.keys(selected).forEach((id) => {
newSelected[id] = false;
});
setSelected(newSelected);
} else {
// Select all currently hidden items
const currentlyHidden = {};
Object.entries(hidden).forEach(([id, isHidden]) => {
if (isHidden) {
currentlyHidden[id] = isHidden;
}
});
setSelected({ ...selected, ...currentlyHidden });
// Clear all hidden items
const newHidden = {};
Object.keys(hidden).forEach((id) => {
newHidden[id] = false;
});
setHidden(newHidden);
}
};
const matchData = (
menus.filter(({ _id }) => {
return !hidden[_id];
});
const getRowProps = (row) => {
return {
style: {
backgroundColor: selected[row.values.id] ? "lightgrey" : "white"
}
};
};
const data = [
{
Header: "id",
accessor: (row) => row._id
},
{
Header: "Name",
accessor: (row) => (
<Link to={{ pathname: `/menu/${menuId}/${row._id}` }}>{row.name}</Link>
)
},
{
Header: "Description",
//check current row is in hidden rows or not
accessor: (row) => row.description
},
{
Header: "Status",
accessor: (row) => row.status
},
{
Header: "Dishes",
//check current row is in hidden rows or not
accessor: (row) => row.dishes,
id: "dishes",
Cell: ({ value }) => value && Object.values(value[0]).join(", ")
},
{
Header: "Show",
accessor: (row) => (
<Toggle
value={selected[row._id]}
onChange={rowSelectHandler(row._id)}
/>
)
}
];
const initialState = {
sortBy: [
{ desc: false, id: "id" },
{ desc: false, id: "description" }
],
hiddenColumns: ["dishes", "id"]
};
return (
<div>
<button type="button" onClick={handleClick}>
{buttonMode === "cancel" ? "Cancel" : "Apply"}
</button>
<Table
data={matchData}
columns={data}
initialState={initialState}
withCellBorder
withRowBorder
withSorting
withPagination
rowProps={getRowProps}
/>
</div>
);
}
Here my json from my api for menuId:1:
[
{
"menuId": 1,
"_id": "123ml66",
"name": "Pea Soup",
"description": "Creamy pea soup topped with melted cheese and sourdough croutons.",
"dishes": [
{
"meat": "N/A",
"vegetables": "pea"
}
],
"taste": "Good",
"comments": "3/4",
"price": "Low",
"availability": 0,
"trust": 1,
"status": "Pending",
"apply": 1
},
//...other data
]
Here my CodeSandbox
Here a picture to get the idea:
Here's the second solution I proposed in the comment:
// Setting up toggle button state
const [showGood, setShowGood] = useState(false);
const [menus, setMenus] = useState([]);
// Simulate fetch data from API
useEffect(() => {
async function fetchData() {
// After fetching data with axios or fetch api
// We process the data
const goodMenus = dataFromAPI.filter((i) => i.taste === "Good");
const restOfMenus = dataFromAPI.filter((i) => i.taste !== "Good");
// Combine two arrays into one using spread operator
// Put the good ones to the front of the array
setMenus([...goodMenus, ...restOfMenus]);
}
fetchData();
}, []);
return (
<div>
// Create a checkbox (you can change it to a toggle button)
<input type="checkbox" onChange={() => setShowGood(!showGood)} />
// Conditionally pass in menu data based on the value of toggle button "showGood"
<Table
data={showGood ? menus : menus.filter((i) => i.taste !== "Good")}
/>
</div>
);
On ternary operator and filter function:
showGood ? menus : menus.filter((i) => i.taste !== "Good")
If button is checked, then showGood's value is true, and all data is passed down to the table, but the good ones will be displayed first, since we have processed it right after the data is fetched, otherwise, the menus that doesn't have good status is shown to the UI.
See sandbox for the simple demo.

tinyMCE React loosing state value

I'm using the tinyMCE editor in my React project. I need a custom button based on number of additional users. If it has 3 additional users, I add 3 additional buttons in my dropdown.
import { Editor } from '#tinymce/tinymce-react';
...
const [ totalAdditionalUsers, setTotalAdditionalUsers] = useState(0);
// I get this data from NodeJS backend and set the value inside my useEffect
// I'll simplify the code here
useEffect(() => {
setTotalAdditionalUsers(myVariable); // The value here is 3, for example
});
console.log(totalAdditionalUsers); // it shows 3
return (
<>
<Editor
apiKey={TINYMCEKEY}
value={editorContent}
init={{
height: 600,
menubar: false,
branding: false,
plugins: [
"print"
],
setup: function (editor) {
editor.ui.registry.addMenuButton('addAllSignatures', {
text: "Users Signature",
fetch: function (callback) {
var items = [
{
type: 'menuitem',
text: 'Primary User Signature',
onAction: function () {
editor.insertContent(' <strong>#userSignature#</strong> ');
}
}, {
type: 'menuitem',
text: 'Primary User Signature Date',
onAction: function () {
editor.insertContent(' <strong>#userSignatureDate#</strong> ');
}
}
];
console.log(totalAdditionalUsers); // It is showing 0. Why??
for(let i=1; i<=totalAdditionalUsers; i++) {
let s = 'th';
if(i === 1) s = 'nd';
else if(i === 2) s = 'th';
const objSign = {
type: 'menuitem',
text: `${(i+1)}${s}User Signature`,
onAction: function () {
editor.insertContent(` <strong>#addUser${i}#</strong> `);
}
};
const objDate = {
type: 'menuitem',
text: `${(i+1)}${s}User Signature Date`,
onAction: function () {
editor.insertContent(` <strong>#addUser${i}SignatureDate#</strong> `);
}
};
items.push(objSign);
items.push(objDate);
}
callback(items);
}
})
},
toolbar1: "print | addAllSignatures"
}}
onEditorChange={handleEditorChange}
/>
</>
);
My issue, it that inside the TinyMCE editor, the totalAdditionalUsers is always 0. Looks like it is not updating.
Am I setting in wrong?
Thanks

Navigation in DetailsList not possible with Monaco editor

Hi I'm using the DetailsList and I want to be able to move my selection from column to column using tab.
But I came across this issue on Github:
https://github.com/microsoft/fluentui/issues/4690
Arrow keys needs to be used to navigate across the list but unfortunately I'm using a Monaco Editor in the list and the arrow key is blocked inside the Editor...
I would like to know if there is way to disable the List to set the TabIndex to -1
or
if Monaco can release the arrow key when the cursor is at the end of the text (Like a textbox).
I got something working following this rationale:
listen to the onKeydown event on monaco editor
identify the position of the caret
know the total of lines
get the string of a specific line
move the focus out from monaco editor
Knowing these then you can check if the caret is at the end of the last line and move the focus when the user press the right arrow key. I also added the code to check when the caret is at the very beginning and move the focus to the cell to the left.
This is the code I ended up with
import * as React from "react";
import "./styles.css";
import { DetailsList, IColumn } from "#fluentui/react";
import MonacoEditor from "react-monaco-editor";
export default function App() {
const columns: IColumn[] = [
{
key: "name",
minWidth: 50,
maxWidth: 50,
name: "Name",
onRender: (item, index) => (
<input id={`name-row-${index}`} value={item.name} />
)
},
{
key: "type",
minWidth: 200,
name: "Type",
onRender: (item, index) => {
return (
<MonacoEditor
editorDidMount={(editor, monaco) => {
editor.onKeyDown((event) => {
if (event.code === "ArrowRight") {
const { column, lineNumber } = editor.getPosition();
const model = editor.getModel();
if (lineNumber === model?.getLineCount()) {
const lastString = model?.getLineContent(lineNumber);
if (column > lastString?.length) {
const nextInput = document.getElementById(
`default-value-row-${index}`
);
(nextInput as HTMLInputElement).focus();
}
}
}
if (event.code === "ArrowLeft") {
const { column, lineNumber } = editor.getPosition();
if (lineNumber === 1 && column === 1) {
const previousInput = document.getElementById(
`name-row-${index}`
);
(previousInput as HTMLInputElement).focus();
}
}
});
}}
value={item.type}
/>
);
}
},
{
key: "defaultValue",
minWidth: 100,
name: "Default Value",
onRender: (item, index) => (
<input id={`default-value-row-${index}`} value={item.defaultValue} />
)
}
];
const items = [{ name: "name", type: "type", defaultValue: "name" }];
return <DetailsList columns={columns} items={items} />;
}
You can see it working in this codesandbox https://codesandbox.io/s/wild-smoke-vy61m?file=/src/App.tsx
monaco-editor seems to be something quite complex, probably you'll have to improve this code in order to support other interactions (ex: I don't know if this works when code is folded)

Handling multiple textfield with single onChange react hooks

I have some issues with managing controls over some textfields.
I have an object
let obj = {
[
{
name:"a",
adress:[{"foo":a1,"bar":a1}],
comment:"aaaaa"
},
{
name:"b",
adress:[{"foo":b1,"bar":b1}],
comment:"bbbbb"
}
]
}
And I generate dynamic textfields
{obj.map((item, i) => {
return <TextField name = {item.name} value={item.comment} onChange={(e)=>{handleComment(e,item.name)}}/>
})}
I want to modify comment for every specific textfield based by its name
const handleComment = (e,name) =>{
e.preventDefault();
setObject({...obj,comment:e.target.value})
}
I can't managed the right way to do it. I can't figure it out how to do it. If you guys can help me, it would be awesome. Thanks!
Ciao, you could use an array in handleComment function in which you can copy current state. Then update that array and set the state.
Lets say you have:
const obj = [
{
name: "a",
adress: [{ foo: "a1", bar: "a1" }],
comment: ""
},
{
name: "b",
adress: [{ foo: "b1", bar: "b1" }],
comment: ""
}
];
const [object, setObject] = useState(obj);
and return like:
return object.map((item, i) => {
return (
<TextField
name={item.name}
value={item.comment}
onChange={(e) => {
handleComment(e, item);
}}
/>
);
});
Then your handleComment becomes:
const handleComment = (e, item) => {
e.preventDefault();
let result = object; // copy state
result = result.map((el) => { // map array to replace the old comment with the new one
if (el.name === item.name) el.comment = e.target.value;
return el;
});
setObject(result); // set state with new comment
};
Here a codesandbox example.

Resources