React - save components visualization to JSON and read it back - reactjs

Imagine I have a hierarchy of react components, e.g. meals of the day
|------- breakfast --------|
| Meal1_image.png banana |
| Meal2_image.png porridge|
|------- lunch-------------|
| Meal3_image.png toast |
| Meal4_image.png apple |
I can add meals to a meal group (e.g. to the lunch group), I can add more lunch groups (e.g. midday snack) and so on.. it's a variable list of components and I give the user the ability to add meals and meal groups with '+' buttons, or to delete them and so on.
How would I go to save these in a text json and to read them back?
I read about "serializing react components" but maybe I don't need all of that stuff, I would just need to save a json like
{
"breakfast": {
{"food": "banana", "image": "Meal1_image.png"},
{"food": "porridge", "image": "Meal2_image.png"},
},
"lunch" : ... and so on ...
}
is there any simple way to achieve this or should I just go with components serialization with things like https://github.com/zasource-dev/React-Serialize ?

Question is basically too wide, but anyway, just split your task by subtasks.
Design the data models you are going to work with. Create a prototype of it, try to render it as is. Regarding your prototype - that is not valid js object, so i changed it a bit in my example. Note - for .map methods you need to use unique keys.
Figure out what will be the best place to keep your data (in state). You can store it in some component, you can store it in context and add all the needed methods to it and pass them down to your components. I did everything in a component for simplicity of example.
Add download/upload/parsing functions and wire everything up.
If I got your question right - you want each Client to be able to download and upload the Data to a local JSON file on Client, so no server interaction.
const { useState } = React;
function download(content, mimeType, filename) {
const a = document.createElement("a");
const blob = new Blob([content], { type: mimeType });
const url = URL.createObjectURL(blob);
a.setAttribute("href", url);
a.setAttribute("download", filename);
a.click();
}
function upload(event, callback) {
const input = event.target;
const reader = new FileReader();
reader.onload = function () {
const text = reader.result;
callback(text);
};
reader.readAsText(input.files[0]);
}
const DEFAULT_DATA = {
breakfast: [
{ food: "banana", image: "Meal1_image.png" },
{ food: "porridge", image: "Meal2_image.png" }
],
lunch: [{ food: "banana", image: "Meal1_image.png" }]
};
function App() {
const [data, setData] = useState(DEFAULT_DATA);
const onDownloadClick = (e) => {
download(JSON.stringify(data), "application/json", "file1.json");
};
const onUpload = (e) => {
upload(e, (text) => setData(JSON.parse(text)));
};
return (
<div className="App">
{Object.entries(data).map(([section, items]) => (
<div key={section}>
<p>{section}</p>
<ul>
{items.map((item) => (
<li key={item.food}>
<p>{item.food}</p>
</li>
))}
</ul>
</div>
))}
<button onClick={onDownloadClick} type="button">
Download JSON
</button>
<input type="file" accept="application/json" onChange={onUpload} />
</div>
);
}
// v18.x+
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
<div id="root"></div>
How to test - Download JSON file, open it and change something in it, like "banana" to "banana111", save, upload the file back and observe the updated values.
Not sure what is the plan about Images, but in worst case - you can store images in JSON as base64 strings.

If you mean physical saving in a file, then you need to have node js to access the file system of your phone or PC, your server:
// file system module to perform file operations
const fs = require('fs');
// json data
var jsonData = '{"persons":[{"name":"John","city":"New York"},{"name":"Phil","city":"Ohio"}]}';
// parse json
var jsonObj = JSON.parse(jsonData);
console.log(jsonObj);
// stringify JSON Object
var jsonContent = JSON.stringify(jsonObj);
console.log(jsonContent);
fs.writeFile("output.json", jsonContent, 'utf8', function (err) {
if (err) {
console.log("An error occured while writing JSON Object to File.");
return console.log(err);
}
console.log("JSON file has been saved.");
});
If we are talking about transformation for axios request, then you need to use the following javascript methods JSON.stringify() for json transformation and JSON.parse() for reading to transformation in javascript.
If you just want to transform the object in the hook state, this is also fine.

Related

array variable undefined when passing into React child component

I am trying to retrieve props from an internal api for a component, PresetFetcher, and pass them to a child component, PromptForm. My console logs show that the data is pulled from the API correctly and saved into an object {preset} which has the correct data. But no matter what I do, when I try to pass {preset} to the child component, it reports null value and fails to compile. What is wrong here? It must be some very basic misunderstanding on my part.
To keep it simple, I am avoiding state or effect, since the data does not need to be refreshed once the PromptForm receives it.
import axios from 'axios'
import { useParams } from 'react-router-dom'
import React, { useEffect,useState } from 'react'
import PromptForm from './PromptForm';
const API_SEARCH_BASE = 'http://0.0.0.0:5001/api2/preset_loader';
function PresetFetcher(props) {
const { preset_name } = useParams();
const API_SEARCH_URL = API_SEARCH_BASE + '/' + preset_name;
console.log('API_SEARCH_URL: ' + API_SEARCH_URL);
console.log('props entering PresetFetcher page', props);
const test = [{ name: 'item 1' }, { name: 'item2' }];
axios.get(API_SEARCH_URL)
.then
(response => {
console.log('PresetFetcher: response: ', response.data);
const preset = response.data;
console.log('preset after const', preset);
var preset_description = preset[0].preset_description;
console.log('preset_description: ', preset_description);
})
return (
<div> <PromptForm preset_out = {preset_description} /></div>
)
{console.log('props leaving Presetfetcher: ', props)};
}
export default PresetFetcher;
[HMR] Waiting for update signal from WDS...
index.js:8 Array(1)
launcher.js:11 on LauncherPage, preset name is baby-name-generator
launcher.js:12 props on launcherpage are Object
PresetFetcher.js:13 API_SEARCH_URL: http://0.0.0.0:5001/api2/preset_loader/baby-name-generator
PresetFetcher.js:14 props entering PresetFetcher page Object
PresetFetcher.js:13 API_SEARCH_URL: http://0.0.0.0:5001/api2/preset_loader/baby-name-generator
PresetFetcher.js:14 props entering PresetFetcher page Object
PresetFetcher.js:21 PresetFetcher: response: Array(1)
PresetFetcher.js:23 preset after const Array(1)
PresetFetcher.js:25 preset_description: Simple baby name generator powered by OpenAI's GPT-3 model gives you creative alternatives from a fresh perspective.
PresetFetcher.js:21 PresetFetcher: response: Array(1)
PresetFetcher.js:23 preset after const Array(1)0: {id: "2", preset_name: "Baby Name Generator (by Attributes)", preset_author: "WebBabyShower.com", preset_active: "True", preset_launched: "20210610", …}length: 1[[Prototype]]: Array(0)
PresetFetcher.js:25 preset_description: Simple baby name generator powered by OpenAI's GPT-3 model gives you creative alternatives from a fresh perspective.
ReferenceError: preset_description is not defined
PresetFetcher
src/components/PresetFetcher.js:28
25 | console.log('preset_description: ', preset_description);
26 | })
27 | return (
> 28 | <div> <PromptForm preset_out = {preset_description} /></div>
| ^ 29 |
30 | )
31 | {console.log('props leaving Presetfetcher: ', props)};
the API data looks like this. there is only one item in the list.
[
{
"id": "2",
"preset_name": "Baby Name Generator (by Attributes)",
"preset_author": "WebBabyShower.com",
"preset_active": "True",
"preset_launched": "20210610",
"preset_description": "Simple baby name generator powered by OpenAI's GPT-3 model gives you creative alternatives from a fresh perspective.",
"preset_instructions":
That variable is not available in the scope you're calling it. Try moving it outside the then block. I suggest storing it in local state so the component updates when the fetch completes.
const [presetDescription, setPresetDescription] = useState('')
axios.get(API_SEARCH_URL)
.then
(response => {
const preset = response.data;
setPresetDescription(preset[0].preset_description);
})
<PromptForm preset_out={presetDescription} />
You can use the "useState" hook as mentioned in the above answer or you can also use the 'useRef' hook as it can be used to hold values similar to an instance property on a class. You can read more about it here.
To use it in this case, you can do something like this:-
const presetDescription = useRef('');
axios.get(API_SEARCH_URL)
.then
(response => {
const preset = response.data;
presetDescription.current = preset[0].preset_description;
})
<PromptForm preset_out={presetDescription.current} />

How can I create a parent html element by appending sub element from an object?

In my react app I need to return a line which will be created based on a list.
Here is the object,
searchCriteria: {
op_company: "039",
doc_type: "ALL"
}
and in my UI, i need to show it as a paragraph with bold values. So the hard coded code would be like below
<p>Download request for op_company: <b>{searchCriteria.op_company}</b>, doc_type: <b>{searchCriteria.doc_type}</b></p>
But the object(searchCriteria) will be changed based on the user request. So I tried like below.
const getSearchCriteria = (criteria) => {
let searchCriteria = []
searchCriteria.push('Download request for')
Object.keys(criteria).forEach((key) => {
if(criteria[key] !== '') {
searchCriteria.push(` ${key}: ${criteria[key]},`)
}
});
return searchCriteria;
}
return (
<p>
{getSearchCriteria(searchCriteria).map((item) => <span key = {item}>{item}</span>)}
</p>
);
here i'm getting the expected output. But I can't get the value as bold (highlighted). Is there another way to directly deal with html elements?

How to upload image and pass in to tensorflowjs model to get prediction using reactjs?

EDIT
Using the graph model format and the updated code example, I've managed to get it to return a prediction. Issue is now it always returns 1, no matter which image I feed it, so wondering if I am not passing in the right image data?
Second EDIT: Changed the way I was passing in the img object, but still getting 1 for every image I feed it.
I have only just started looking into tensorflowjs and am using a prebuilt keras model I have been given access to. This model is a binary classifier. The model has been saved as an .h5 file and I have been asked to run it in the browser using tensorflowjs and react. Essentially I want to select an image from my local storage or an sd card and feed it to the model to get a yes or no classification.
I’ve followed the tensorflowjs docs in converting the keras model to a TF.js Layers format, but then can’t load the model. I’m getting an error about an unknown layer: RandomFlip. So I then tried converting the model to a graph model as I couldn’t find a solution to the error and thought I’d give it a try. This loaded the model but then there were more issues when feeding it the image. The shape of dict['image_tensor'] provided in model.execute(dict) must be [-1,380,380,1], but was [380, 380] . Then I searched for that and got it to resize to [-1,380,380,1] , but then it was complaining about size not being the expected, so I thought maybe I've messed up in some of the previous steps.
To convert to a graph model I used the following command: tensorflowjs_converter --input_format keras --output_format tfjs_layers_model /Users/myUser/Documents/save_at_45.h5 /Users/myUser/Documents/convert-keras-model and in my code loading it with the loadGraphModel method. Following this path has at least allowed me to load the model.
I also tried converting it to a Layers format with: tensorflowjs_converter --input_format keras --output_format tfjs_layers_model /Users/myUser/Documents/save_at_45.h5 /myUser/mariomendes/Documents/convert-keras-model and in my code loading it with the loadLayersModel. This returns the error Unknown layer: RandomFlip. I've tried searching for a solution to this, but haven't been able to find one.
Does knowing it is a .h5 file mean I should know if it needs to be converted to a tf Graph format or Layers format or is there something else that determines which format it should be converted to?
I've stored the converted model in both formats and it's weights in S3 and am getting it from there.
For my react code I have done the following:
import React, { useState, useEffect } from "react";
import "./index.css";
import * as tf from "#tensorflow/tfjs";
function ImgImporter() {
const [file, setFile] = useState(null);
const [model, setModel] = useState(null);
const [processing, setProcessing] = useState(false);
const [prediction, setPrediction] = useState(null);
const [imageLoaded, setImageLoaded] = useState(false);
function readImage(file) {
return new Promise((rs, rj) => {
const fileReader = new FileReader();
fileReader.onload = () => rs(fileReader.result);
fileReader.onerror = () => rj(fileReader.error);
fileReader.readAsDataURL(file);
});
}
async function handleImgUpload(event) {
const {
target: { files },
} = event;
const _file = files[0];
const fileData = await readImage(_file);
setFile(fileData);
setProcessing(true);
}
useEffect(() => {
async function loadModel() {
if (!model) {
const _model = await tf.loadGraphModel("/model.json");
setModel(_model);
}
}
loadModel();
});
useEffect(() => {
async function predict() {
if (imageLoaded && file) {
const imageElement = document.createElement("img");
imageElement.src = file;
imageElement.onload = async () => {
const tensor = tf.browser
.fromPixels(imageElement, 1)
.resizeNearestNeighbor([380, 380])
.expandDims()
.toFloat();
const prediction = await model.predict(tensor).data();
setPrediction(parseInt(prediction, 10));
setProcessing(false);
setImageLoaded(false);
};
}
}
predict();
}, [imageLoaded, model, file]);
return (
<div className="File-input-container">
<form className="Form">
<label htmlFor="upload-image">Upload image</label>
<input
id="image-selector"
type="file"
name="upload-image"
accept="image/*"
className="File-selector"
onChange={handleImgUpload}
disabled={!model || processing}
/>
</form>
<div className="Img-display-container">
<img
onLoad={() => {
setImageLoaded(true);
}}
alt=""
src={file}
/>
</div>
<div className="Img-processing-container">
{processing ? (
<p>Loading ...</p>
) : prediction !== null ? (
<div>
<p>{prediction === 1 ? "Yes" : "No"}</p>
</div>
) : null}
</div>
</div>
);
}
export default ImgImporter;
When I upload an image this is returning the following result in the console as the value of prediction:
dataId: {id: 195}
dtype: "float32"
id: 94
isDisposedInternal: false
kept: false
rankType: "2"
scopeId: 6
shape: (2) [1, 1]
size: 1
strides: [1]
Would be great if someone could shed some light on this or help me finding the right direction.
If you want to get the value, you can use prediction.dataSync() or its promise counterpart await prediction.data()
Regarding your second edit. You're converting the image to a float, but does the model expect a normalized float? You might need to append .div(255) or whatever normalization is needed. Please post the specs for your model.
Also, as stated by edkeveked, you can used dataSync() to get your data, but it's worth noting you could have also used arraySync too, which would maintain the returned tensor depth.
Also, I noticed you didn't do any cleanup. So your tensors will build up in GPU memory. Don't forget to dispose.
I hope these things help.

How to load HTML template instead of json using react-email-editor?

I am using react-email-editor to edit email templates. Now I want to save template data in html format and also want to load html data in editor.
As react-email-editor is loading data in json format(I have used onLoad function and passes json data in it), but now the question is how can I load editor data using html.
Is there any method to convert html to json format using this package?
Or if there is any other method to do this please suggest.
Details:
https://github.com/unlayer/react-email-editor/blob/master/demo/src/example/index.js
Even though its been above 1 year, someone might find it useful later:
const emailEditorRef = useRef(null);
const saveDesign = () => {
emailEditorRef.current.editor.saveDesign((design) => {
console.log('saveDesign', design);
alert('Design JSON has been logged in your developer console.');
});
};
const exportHtml = () => {
emailEditorRef.current.editor.exportHtml((data) => {
const { design, html } = data;
console.log('exportHtml', html);
alert('Output HTML has been logged in your developer console.');
});
};
<button onClick={saveDesign}>Save Design</button>
<button onClick={exportHtml}>Export HTML</button>

Displaying data from Firebase in React without arrays

I am new to both React and Firebase. I struggled a bit to get data from the database, even though the instructions on the Firebase website were pretty straightforward.
I managed to print data in the view by using this code:
Get data from DB and save it in state:
INSTRUMENTS_DB.once('value').then(function(snapshot) {
this.state.instruments.push(snapshot.val());
this.setState({
instruments: this.state.instruments
});
From Firebase, I receive and Object containing several objects, which correspond to the differen instruments, like shown in the following snippet:
Object {
Object {
name: "Electric guitar",
image: "img/guitar.svg"
}
Object {
name: "Bass guitar",
image: "img/bass.svg"
}
// and so on..
}
Currently, I print data by populating an array like this:
var rows = [];
for (var obj in this.state.instruments[0]) {
rows.push(<Instrument name={this.state.instruments[0][obj].name}
image={this.state.instruments[0][obj].image}/>);
}
I feel like there's a better way to do it, can somedody give a hint? Thanks
I user firebase a lot and mu solution is little ES6 helper function
const toArray = function (firebaseObj) {
return Object.keys(firebaseObj).map((key)=> {
return Object.assign(firebaseObj[key], {key});
})
};
I also assign the firebase key to object key property, so later I can work with the keys.
The native map function only works for arrays, so using directly it on this object won't work.
What you can do instead is:
Call the map function on the keys of your object using Object.keys():
getInstrumentRows() {
const instruments = this.state.instruments;
Object.keys(instruments).map((key, index) => {
let instrument = instruments[key];
// You can now use instrument.name and instrument.image
return <Instrument name={instrument.name} image={instrument.image}/>
});
}
Alternatively, you can also import the lodash library and use its map method which would allow you to refactor the above code into:
getInstrumentRowsUsingLodash() {
const instruments = this.state.instruments;
_.map(instruments, (key, index) => {
let instrument = instruments[key];
// You can now use instrument.name and instrument.image
return <Instrument name={instrument.name} image={instrument.image}/>
});
}
Side note:
When you retrieve you data from Firebase you attempt to update the state directly with a call on this.state.instruments. The state in React should be treated as Immutable and should not be mutated with direct calls to it like push.
I would use map function:
_getInstrumentRows() {
const instruments = this.state.instruments[0];
if (instruments) {
return instruments.map((instrument) =>
<Instrument name={instrument.name}
image={instrument.image}/>);
}
}
In your render() method you just use {_getInstrumentRows()} wherever you need it.

Resources