I use react-quill made a rich text editor. I want to use this.state to store data, so that the content of input text box can be automatically saved, and the text input will not disappear after refreshing the page.
The code is for react-quill,How to modify the code to achieve my goal
import React from 'react'
export default class Editor extends React.Component {
constructor (props) {
super(props)
this.state = {
editorHtml: '',
}
this.handleChange = this.handleChange.bind(this)
if (typeof window !== 'undefined') {
this.ReactQuill = require('react-quill')
}
}
handleChange (html) {
this.setState({ editorHtml: html });
}
render() {
const ReactQuill = this.ReactQuill
if (typeof window !== 'undefined' && ReactQuill) {
return (
<div className='app'>
<ReactQuill
onChange={this.handleChange}
value={this.state.editorHtml}
modules={Editor.modules}
formats={Editor.formats}
/>
)
} else {
return null;
}
}
}
Editor.modules = {
toolbar: [
[{ 'header': '1'}, {'header': '2'}, { 'font': [] }],
[{size: []}],
['bold', 'italic', 'underline', 'strike', 'blockquote'],
[{'list': 'ordered'}, {'list': 'bullet'},
{'indent': '-1'}, {'indent': '+1'}],
['link', 'image', 'video'],
['clean']
],
clipboard: {
matchVisual: false,
}
}
Editor.formats = [
'header', 'font', 'size',
'bold', 'italic', 'underline', 'strike', 'blockquote',
'list', 'bullet', 'indent',
'link', 'image', 'video'
]
Where should your data be saved?
You don't mention a backend service so I'm not sure where you want to save this data. Since a react runs fresh on every page refresh you cannot store data only retrieve it from either an api or local storage.
local storage
If you would like to store data in local storage you will need to hydrate your app when it loads. This is the process of looking into the local storage and retrieving data for use. Note that this data will only be accessible from the machine & browser its written on and no good if you want to post this to other platforms.
API
If you wish to store your data in "the cloud" for later retrieval you should make an api to handle post and get requests. these will let you save data to a connected cloud database. In this case when called componentDidMount you can retrieve the previous sessions data with a get request.
Lambda functions
Similar to an api you can write "serverless" functions to handle data. firebase functions is a good place to start there, they are easy to understand and write in a few minutes.
Conclusion
All of these solutions will take quite a bit of time to put together, there's no "easy" way to just store and retrieve data outside of using some 3rd party service or setting up your own server & database.
Where you want to store data is the main question. If you are saving it to database and have an endpoint for getting the data. Let`s say "http://localhost:3001/api/form/data".
Make a request to the endpoint and set state after getting data in componentDidMount method of React, like this:
componentDidMount(){
fetch("http://localhost:3001/api/form/data")
.then(res=>res.json())
.then(data=>{
if(data){
this.setState.({editorHtml : data});
}
}).catch(ex){
//handle error here
}
Related
Here is the issue I am having. I am trying to have the Ag-grid render it's html output using NextJS getServerSideProps. However, when I view the source code, it doesn't appear to have any of the HTML rendered for SEO purposes. If I go ahead and output the "staff" array to a div then the HTML output is viewable in the source code so at least I know the function is working. Is there something I need to do to have AGGridReact render its contents?
export default function Home({ staff }) {
const gridRef = useRef();
const defaultColDef = {
resizable: true,
sortable: true,
};
const [columnDefs] = useState([
{ headerName: 'First Name', field: 'first_name' },
{ headerName: 'Last Name', field: 'last_name' },
{ headerName: 'Job Title', field: 'job_title' },
{ field: 'office' },
{ field: 'email' },
{ field: 'phone' },
]);
return (
<>
<main>
<div style={{ height: '600px' }}>
<AgGridReact
id='staff_grid'
ref={gridRef}
rowData={staff}
defaultColDef={defaultColDef}
columnDefs={columnDefs}
rowSelection={'single'}
style={{ height: '100%', width: '100%' }}
></AgGridReact>
</div>
</main>
</>
);
}
// This gets called on every request
export async function getServerSideProps() {
const staff = [];
for (let id = 1; id <= 3; id++) {
staff.push({
id: id,
first_name: 'first' + id,
last_name: 'last' + id,
email: 'member' + id + '#company.com',
phone: '12345' + id,
office: 'place' + id,
job_title: 'Worker ' + id,
});
}
// Pass data to the page via props
return { props: { staff } };
}
TLDR: Diving deep into the ag-grid's node_modules abyss and into their documentation, I found that their grid component is being injected into the DOM (client-side) once an "AG Grid" wrapper component has been mounted. Therefore, this is a client-side only component.
Debugging
When I request the Next page from Postman, I see an empty div where the grid should be:
But when I request the page from the browser, I see the grid:
An even easier way to determine that this is a client-side only component would be to assign the grid a debug prop:
<AgGridReact
debug
rowData={staff}
columnDefs={columnDefs}
rowSelection="single"
/>
We see AG Grid debug logs in the browser (Notice the Rendered on Client message):
But, we don't see any AG Grid debug logs on the server (Notice the Rendered on Server message):
More investigation
I thought I found a server-side rendering solution via their Row Models, but unfortunately it's not referring to the table being SSR'd, but the data being lazy loaded via dynamically fetching data from a server. My guess as to why this table is client-side only is that AG Grid doesn't use a native table, but instead a bunch of div elements with custom styles to represent a table. Since the server doesn't have a DOM (eg, can't access document nor window), calculating these dynamic styles wouldn't be possible.
Alternatives
If you're creating this table for an enterprise and it's absolutely vital to have this page SSR'd for SEO, then I'd recommend having some sort of bot detection in Next's middleware and within gSSP. Then pass an isBot prop to the component and conditionally render a native table (styling won't matter since it's mainly used for SEO). We do something similar for our web application where search results need to be baked into the page on the server, but can be lazy-loaded client-side for a snappier UX.
Here's a working demo. You can change the User-Agent using your browser's tools or by changing it within the request headers.
A more comprehensive bot list can be found here.
What a user sees:
What a bot sees:
I was told at a previous job that I should never access HTML elements directly through means like getElementById in React TypeScript. I'm currently implementing Chart.js. For setting up the chart, I was initially using a useRef hook instead of accessing context, but now it seems like I need to grab the canvas by ID in order to instantiate it properly. I want to know if this is kosher.
I suspect something is wrong with me not using a context, because my chart data doesn't load and throws a console error: "Failed to create chart: can't acquire context from the given item"
useEffect(() => {
chart = new Chart(chartRef.current, {
type: "bar",
data: {
labels: labelsArray.map((label) => {
const date = new Date(label);
// add one because month is 0-indexed
return date.getUTCMonth() + 1 + "/" + date.getUTCDate();
}),
datasets: [
{
data: valuesArray,
backgroundColor: "#1565C0",
borderRadius: 6,
},
],
},
options: {
interaction: { mode: "index" },
onHover: (event, chartElement) => {
const target = event.native.target;
(target as HTMLInputElement).style.cursor = chartElement[0]
? "pointer"
: "default";
},
plugins: {
tooltip: {
mode: "index",
enabled: true,
},
title: {
display: true,
text: "Daily Usage Past 30 Days",
align: "start",
fullSize: true,
font: {
size: 24,
},
padding: {
bottom: 36,
},
},
},
scales: {
x: {
display: false,
},
},
elements: {
line: {
borderJoinStyle: "round",
},
},
},
});
return () => {
chart.destroy();
};
}, [labelsArray, valuesArray]);
and HTML:
<div className="mt-80 ml-12 p-8 shadow-lg border-2 border-slate-100 rounded-xl items-center">
<canvas id="chart" ref={chartRef}></canvas>
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</div>
Also, per the Chart.js documentation: "To create a chart, we need to instantiate the Chart class. To do this, we need to pass in the node, jQuery instance, or 2d context of the canvas of where we want to draw the chart." Not sure how we would do this with a useRef
Yes,
It is not good practice to access dom elements directly through document API.
Because in react
virtual dom is responsible for painting/ re-rendering the UI.
State updation is the proper way to tell react to trigger re-render.
The flow is state updation -> calculate differences -> find who over is using that state -> grab those components -> re-render only those components.
virtual dom is the source of truth for react to render and update actual DOM.
Now, If you directly access some dom elements and do some operation on it, like updating, react will never know that some change has happened and it will basically break the flow of the react, in which case there will be no reason to use react.js
The flow would be accessing some dom element -> updating -> displaying.
The problem with this approach if react encounters that later what i have in virtual dom is not actual presentation in the actual dom, which will create mess.
That is the reason there is useRef hook to manipulate dom.
I'm using Redux state to update an array of coordinates in a Mapbox source. I initially check if there is a source with the id, if yes, I set the data of the source, if not I add the source to the map. When the redux state is changed, it triggers an effect which updates the coordinates of the features in the geojson object and uses setData to change the source. I've tried removing the layer, changing source and adding the layer, which just gave me the old layer (even though the source had indeed been updated). I also tried just updating the source alone and seeing if the layer would update dynamically, it did not.
Here is the code for the effect, which is triggered when the redux state is changed.
useEffect(() => {
const geoJsonObj = {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
};
for (let i = 0; i < (props.mapRoutes.length); i++) {
geoJsonObj.data.features.push({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: props.mapRoutes[i].geometry.coordinates
}
});
};
const routeLayer = {
id: 'route',
type: 'line',
source: 'route',
layout: {
'line-join': 'round',
'line-cap': 'round'
},
paint: {
'line-color': '#ff3814',
'line-width': 5,
'line-opacity': 0.75
}
};
const jsonString = JSON.stringify(geoJsonObj);
const jsonObj = JSON.parse(jsonString);
if (props.mapRoutes.length) {
if (map.current.getSource('route')) {
map.current.getSource('route').setData(jsonObj);
} else {
map.current.addSource('route', jsonObj);
map.current.addLayer(routeLayer);
};
};
}, [props.mapRoutes]);
Neither of these worked and I am having trouble finding how to update a layer based on an updated source. Everything seems right when I inspect the source in the console, I just can't manage to update the layer on the map.
Any help would be appreciated.
I found the problem, I was using the original geoJson object for the setData method instead of the data entry, which was one level too high in the object. Just a simple error which was overlooked.
I have a react-redux application this has the following code structure, which is running correctly.
class Customers extends Component{
state = {
showAddCustomerForm : false
}
toggleAddCustomerForm = ()=>{
this.setState({showAddCustomerForm: !this.state.showAddCustomerForm})
}
render(){
return (
<Fragment>
<AddCustomerForm />
<Datatable
options={{
data: this.props.customers,
buttons: [
{
extend: 'csv',
text: '<i class="fa fa-file-excel-o"></i>Excel'
},
{
text: 'Add Customer',
action: this.toggleAddCustomerForm
}
],
columns: [
{ data: "name" },
{ data: "email" },
{ data: "mobile", "defaultContent": "<i>Not set</i>" },
{ data: "landline", "defaultContent": "<i>Not set</i>"}
]
}}
filter="true"
className="table table-striped table-bordered"
width="100%"
/>
</Fragment>
)
}
}
The Datatable Component uses the redux store for rendering the table. When I add a new Customer using the AddCustomerForm I update Redux store. This actually changes the application state, So I expect the Datatable Component to RE-Render and show me the new customer in the table. however, this does not happen. If I hit it shows me correctly.
I understand that both React and jQuery Datatables manage DOM independently, but I am sure there must be a way to use datatables in a rerenderable component...
Please help..
For datatables fed by HTML or JavaScript source, I'm afraid, there's no way to re-render datatable upon source data modification.
However, it is rather good news from the standpoint of data consistency across multiple clients who might use your application simultaneously. To maintain that, you may use AJAX requests to update back-end data which all the users refer to and, upon successful update, throw ajax.reload() to sync your client with backend.
If, for some reason, you might wish to update your datatable locally, you may consider cleaning table contents and re-populating it with your data, like that:
//datatable initialization
var datatable = $('#mytable').DataTable({
data: myDataArray
});
//source data modification
myDataArray.push({
attr1: "value",
attr2: "value"...
});
//purge datatable contents
datatable.clear();
//re-populate datatable
$.each(myDataArray, function () {
datatable.row().add(this);
});
//re-render up to date datatable
datatable.draw();
You can update datatable by implementing the "componentDidUpdate" function of your component. Example:
componentDidUpdate=()=>{
//console.log("Component did update");
if (this.props.customers.length>0){
this.createTable()
this.updateTable();
}
};
Then implement the function for updating data. E.g
updateTable=()=>{
let table=$('#mytable').DataTable();
table.clear();
table.rows.add(this.props.consumers).draw();
}
I would also create the table using javascript rather than in the JSX. That way you can destroy the table and create it afresh every time you want to update
createTable=()=>{
if ( $.fn.DataTable.isDataTable( '#mytable' ) ) {
$('#mytable').DataTable().destroy();
}
let table=$('#mytable').DataTable({
"columns":columns,
});
And the JSX would only contain the id of the table
<table id="mytable">
My firebase users tree has this structure:
users:
{
{
'userName': 'abc',
'userEmail' : 'abc#abc.com',
'userPreferences':
[
0:'Cinema',
1:'It'
]
},
{
'userName': 'abc',
'userEmail' : 'abc#abc.com',
'userPreferences':
[
0:'Cinema',
1:'Music'
]
}
}
Then, I try to find all users that their preference list contain 'Cinema'.
I try this code:
var ref1 = new Firebase("https://event-application.firebaseio.com/users");
$scope.user = $firebaseArray(ref1.orderByChild("userpreferences").equalTo('Cinema'));
console.log($scope.user);
But I don't get the best result. I get this record:
Your JSON structure shows preferences as userPreferences, so wouldn't the following work?
var ref1 = new Firebase("https://event-application.firebaseio.com/users");
$scope.user = $firebaseArray(ref1.orderByChild("userPreferences").equalTo('Cinema'));
console.log($scope.user);
However I think there is also another problem with your code, you're called an .equalTo('Cinema') however you're comparing it to an array, correct me if i'm wrong but I don't think the behaviour of .equalTo('Cinema') is to loop through each of the values and compare them, I think it's just a straight up comparison
If this is the case, you may need to build a custom query by reading the data from firebase and manipulating it via function available to a snapshot
In NoSQL you'll often end up with a data model that reflects the way your application uses the data. If you want to read all the users that have a preference for Cinema, you should model that in your tree:
users: {
'uid-of-abc': {
'userName': 'abc',
'userEmail' : 'abc#abc.com',
'userPreferences': [
0:'Cinema',
1:'It'
]
},
'uid-of-def': {
'userName': 'def',
'userEmail' : 'abc#abc.com',
'userPreferences': [
0:'Cinema',
1:'Music'
]
}
},
"preferences-lookup": {
"Cinema": {
"uid-of-abc": true,
"uid-of-def": true
},
"It": {
"uid-of-abc": true
},
"Music": {
"uid-of-def": true
}
}
Now you can find out what users prefer cinema with:
ref.child('preferences-lookup/Cinema').on('value', function(snapshot) {
snapshot.forEach(function(userKey) {
console.log(userKey.key()+' prefers Cinema');
});
});
This is covered in this blog post on denormalizing data with Firebase, in the Firebase documentation on structuring data and in dozens of answers here on Stack Overflow. A few:
Storing Relational "Type" or "Category" Data in Firebase Without the Need to Update Multiple Locations
Get Firebase items belonging to category
Retrieve data based on categories in Firebase
How to query firebase for property with specific value inside all children