Fill WinForms Control to Parent - winforms

I am attempting to build a simple text editor in F# with WinForms. I would like to know how to get items to fill their parent containers most easily, and I am finding that a text box is not filling its parent despite its Dock property being set to DockStyle.Fill. I would like both the top MenuStrip and the RichTextBox to fill the parent containers and take up the whole form (as you'd expect to see in a text editor). Here is a screenshot demonstrating the issue:
As you can see, it would be nice to have both the MenuStrip and RichTextBox to fill the entire form.
Here is the code (along with the .fsproj file):
Program.fs:
module FsEdit.Program
open System
open System.Windows.Forms
[<EntryPoint; STAThread>]
let main argv =
Application.Run FsEdit.MainForm.MainForm
0
MainForm.fs:
[<RequireQualifiedAccess>]
module FsEdit.MainForm
open System.Windows.Forms
// MenuBar
let private FileMenuTab =
new ToolStripMenuItem(
Text = "File"
)
let private EditMenuTab =
new ToolStripMenuItem(
Text = "Edit"
)
let private AboutMenuTab =
new ToolStripMenuItem(
Text = "About"
)
let private MainMenuStrip =
new MenuStrip(
Text = "MainMenuStrip"
)
let private allMenuStripItems =
[|
FileMenuTab
EditMenuTab
AboutMenuTab
|]
|> Array.map (fun tab -> tab :> ToolStripItem)
MainMenuStrip.Items.AddRange(allMenuStripItems)
// Text Editor
let private MainTextBox =
let richTextBox = new RichTextBox(
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut a eleifend nunc. Suspendisse non purus varius, ullamcorper arcu et, vehicula lacus. Integer pellentesque facilisis interdum. Aliquam id leo arcu. Nam mauris nisl, semper eget massa sed, aliquam convallis lacus. Etiam a neque blandit, sollicitudin nisl quis, ornare dui. Aliquam nec lorem sit amet arcu iaculis elementum rutrum eu velit. Curabitur dignissim blandit ligula at efficitur. Curabitur id justo quis tortor egestas ultrices. Nam arcu quam, ullamcorper id velit quis, aliquam finibus libero. Pellentesque semper fermentum sem a scelerisque. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae;"
)
richTextBox.Dock <- DockStyle.Fill
richTextBox.Anchor <- AnchorStyles.Left ||| AnchorStyles.Right ||| AnchorStyles.Top
richTextBox.AllowDrop <- true
richTextBox.AutoSize <- true
richTextBox
let private MainTextBoxPanel =
let p = new FlowLayoutPanel()
p.Dock <- DockStyle.Fill
p.WrapContents <- false
p.FlowDirection <- FlowDirection.TopDown
p.Anchor <- AnchorStyles.Left ||| AnchorStyles.Right ||| AnchorStyles.Top
p
MainTextBoxPanel.Controls.Add(MainMenuStrip)
MainTextBoxPanel.Controls.Add(MainTextBox)
// MainForm
let MainForm =
let form = new Form(
Text = "FsEdit"
)
form.Width <- 800
form.Height <- 600
form
MainForm.Controls.Add(MainTextBoxPanel)
FsEdit.fsproj:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net7.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<UseWpf>true</UseWpf>
</PropertyGroup>
<ItemGroup>
<Compile Include="MainForm.fs" />
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
How do I get these Control objects to fill their respective parents successfully?

The behavior is expected for a FlowLayoutPanel having TopDown flow direction. According to the documentation:
This is the general rule for anchoring and docking in the FlowLayoutPanel control: for vertical flow directions, the FlowLayoutPanel control calculates the width of an implied column from the widest child control in the column. All other controls in this column with Anchor or Dock properties are aligned or stretched to fit this implied column.
If you'd like to dock the controls to top and fill, you don't need a FlowLayoutPanel. Use a Panel instead.
You can use either of the following layouts:
- Form
- Menu (Dock = Top)
- RichTextBox (Dock = Fill)
Or if for ant reason you want to host both the menu and the text editor in the same panel as container:
- Form
- Panel (Dock = Fill)
- Menu (Dock = Top)
- RichTextBox (Dock = Fill)
Important Note: Pay attention to the order that you add controls to the form. Z-order of the controls is important and has impact on the result. Controls are docked in reverse z-order. You may want to learn more about how to dock and anchor controls in Windows Forms.
To learn more:
How to: Anchor and Dock Child Controls in a FlowLayoutPanel Control
FlowLayoutPanel control overview
Position and layout of controls
How to dock and anchor controls
Example 1 - Docking Menu to Top, RichTextBox to Fill, and StatusStrip to Bottom
Use the following code for the form, it will dock Menu to Top, RichTextBox to Fill, and StatusStrip to Bottom, without using any other container:
[<RequireQualifiedAccess>]
module FsEdit.MainForm
open System.Windows.Forms
open System.Drawing
// Main menu strip
let private FileMenuTab =
new ToolStripMenuItem(
Text = "File"
)
let private EditMenuTab =
new ToolStripMenuItem(
Text = "Edit"
)
let private AboutMenuTab =
new ToolStripMenuItem(
Text = "About"
)
let private MainMenuStrip =
let m = new MenuStrip(
Text = "MainMenuStrip"
)
m
let private allMenuStripItems =
[|
FileMenuTab
EditMenuTab
AboutMenuTab
|]
|> Array.map (fun tab -> tab :> ToolStripItem)
MainMenuStrip.Items.AddRange(allMenuStripItems)
// Text Editor
let private MainTextBox =
let richTextBox = new RichTextBox(
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut a eleifend nunc. Suspendisse non purus varius, ullamcorper arcu et, vehicula lacus. Integer pellentesque facilisis interdum. Aliquam id leo arcu. Nam mauris nisl, semper eget massa sed, aliquam convallis lacus. Etiam a neque blandit, sollicitudin nisl quis, ornare dui. Aliquam nec lorem sit amet arcu iaculis elementum rutrum eu velit. Curabitur dignissim blandit ligula at efficitur. Curabitur id justo quis tortor egestas ultrices. Nam arcu quam, ullamcorper id velit quis, aliquam finibus libero. Pellentesque semper fermentum sem a scelerisque. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae;"
)
richTextBox.Dock <- DockStyle.Fill
richTextBox.AllowDrop <- true
richTextBox.AutoSize <- true
richTextBox
let private MainStatusBar =
let s = new StatusStrip()
s.Dock <- DockStyle.Bottom
s
// MainForm
let MainForm =
let form = new Form(
Text = "FsEdit"
)
form.Width <- 800
form.Height <- 600
form.MinimumSize <- Size(320, 240)
form
MainForm.Controls.Add(MainTextBox)
MainForm.Controls.Add(MainMenuStrip)
MainForm.Controls.Add(MainStatusBar)

An option to make life easier for one who wants WinForms UI with F# is creating the UI with a Windows Forms Designer and using design UI with drag and drop and all the design-time features, then adding the reference the WinForms project to the F# project and add F# code. Follow the steps to see how it works in action:
Example - WinForms UI Designer + F# Code
Create UI Project: Create a WinForms Class Library (.NET 6) and set its name to WinFormsFS.UI
You can create a WinForms Project or a WinForms Control Library as well and modify the project and files. The only thing that matters here, is the project output type which we'd like it to be class library, and we don't need main entry point in this project.
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Library</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
</Project>
If you created a WinForms project, remove the Program.cs, or Form1. For WinForms Class Library, remove Class1, and for WinForms Control Library remove UserControl1. We don't need them for this examample.
Add Form: Add a new Form, and set its name MainFormUI
Add controls: Drop a MenuStrip, StatusStrip and a RichTextBox, and let them use their default names. The MenuStrip will be docked to top automatically, and the StatuStrip will be docked to bottom. You can setup all properties, or add menu items to the menu strip.
Configure properties: Dock the RichTextBox to Fill (using property editor, and setting Dock property, or using the smart tags panel 🞂 and choosing Dock in parent container.)
Change access modifiers: For the controls which you want to use in F# code, you need to change access modifier to public. To do so, Choose RichTextBox and in property browser, set its Modifiers to Public.
Create the F# project: Create a F# console app with name WinFormsFS.Code. And set is as startup project. Change the output type to win exe and use winfows forms. The project file should be like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net6.0-windows</TargetFramework>
<UseWindowsForms>true</UseWindowsForms>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<Compile Include="Program.fs" />
</ItemGroup>
</Project>
Add WinForms project reference: Right click on F# project and add reference to WinForms project.
Add code for the UI: Add a file MainForm.fs with the following code:
namespace WinFormsFS.Code
open System.Windows.Forms
open WinFormsFS.UI
type MainForm() as this =
inherit MainFormUI()
do
this.richTextBox1.Text <- "Lorem ipsum!"
Add code for Startup: Modify the Program.fs to the following:
module WinFormsFS.Code.Program
open System
open System.Windows.Forms
[<EntryPoint; STAThread>]
let main args =
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(false)
Application.SetHighDpiMode(HighDpiMode.SystemAware) |>ignore
Application.Run (new WinFormsFS.Code.MainForm())
0
Run the program by pressing f5 and see the output. (Make sure the startup project is the F# project)

I tried both the regular Panel approach per #Reza Aghaei and the TableLayoutPanel approach per #Jimi and decided to go with the latter as I was still getting overlap with the plain Panel objects. I also added a StatusStrip to the bottom of the Form, but found that if I added it to the TableLayoutPanel it would not display correctly even with the Dock set to DocStyle.Bottom (see here). (I will of course update if I find better programmatic solutions in F# for WinForms development.)
Here's a screenshot followed by the updated code:
MainWindow.fs:
[<RequireQualifiedAccess>]
module FsEdit.MainForm
open System.Windows.Forms
open System.Drawing
// Main panel
let private MainTableLayoutPanel =
let t = new TableLayoutPanel()
t.Dock <- DockStyle.Fill
t
// Main menu strip
let private FileMenuTab =
new ToolStripMenuItem(
Text = "File"
)
let private EditMenuTab =
new ToolStripMenuItem(
Text = "Edit"
)
let private AboutMenuTab =
new ToolStripMenuItem(
Text = "About"
)
let private MainMenuStrip =
let m = new MenuStrip(
Text = "MainMenuStrip"
)
m
let private allMenuStripItems =
[|
FileMenuTab
EditMenuTab
AboutMenuTab
|]
|> Array.map (fun tab -> tab :> ToolStripItem)
MainMenuStrip.Items.AddRange(allMenuStripItems)
// Text Editor
let private MainTextBox =
let richTextBox = new RichTextBox(
Text = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut a eleifend nunc. Suspendisse non purus varius, ullamcorper arcu et, vehicula lacus. Integer pellentesque facilisis interdum. Aliquam id leo arcu. Nam mauris nisl, semper eget massa sed, aliquam convallis lacus. Etiam a neque blandit, sollicitudin nisl quis, ornare dui. Aliquam nec lorem sit amet arcu iaculis elementum rutrum eu velit. Curabitur dignissim blandit ligula at efficitur. Curabitur id justo quis tortor egestas ultrices. Nam arcu quam, ullamcorper id velit quis, aliquam finibus libero. Pellentesque semper fermentum sem a scelerisque. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae;"
)
richTextBox.Dock <- DockStyle.Fill
// richTextBox.Anchor <- AnchorStyles.Left ||| AnchorStyles.Right ||| AnchorStyles.Top ||| AnchorStyles.Bottom
richTextBox.AllowDrop <- true
richTextBox.AutoSize <- true
richTextBox
let private MainStatusBar =
let s = new StatusStrip()
s.Dock <- DockStyle.Bottom // if anything other than "bottom" does not show up
s
// Add controls to main panel
MainTableLayoutPanel.Controls.Add(MainMenuStrip)
MainTableLayoutPanel.Controls.Add(MainTextBox)
// MainForm
let MainForm =
let form = new Form(
Text = "FsEdit"
)
form.Width <- 800
form.Height <- 600
form.MinimumSize <- Size(320, 240)
form
MainForm.Controls.Add(MainTableLayoutPanel)
MainForm.Controls.Add(MainStatusBar) // Adding to MainTableLayoutPanel makes it size incorrectly even with DockStyle.Bottom

Related

How to convert JSON object to an Typescript array in an api

I'm having an API request which returns the following:
{
"FarmerDetails": [
{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
},
{
"userId": 1,
"id": 2,
"title": "quis ut nam facilis et officia qui",
"completed": false
}
]
}
I need to show the title when I click the button and when the button clicks this "fetchPeople()" function gets call.
fetchPeople() {
this.peopleService.fetchPeople().subscribe((res:any)=>{
this.people$ = res;
});
}
How can I convert my Object to Array in typescript and show particular or all values.
Handling an API response object is no different to handling any other JSON object. Just query it in the same way.
Given a response like this:
{
"FarmerDetails": [
// array of objects you want to extract from the response
]
}
You can try several approaches.
Query the JSON object directly
fetchPeople() {
this.peopleService.fetchPeople().subscribe((res:any)=>{
this.people = res.FarmerDetails;
});
}
Map in the pipe
fetchPeople() {
this.peopleService.fetchPeople().pipe(
.map(res => res.FarmerDetails)
).subscribe(people => {
this.people = people;
});
}
When the people service emits a value that looks like the JSON above, it applies the transform in the map function before returning it to the subscriber.
Pluck in the pipe
fetchPeople() {
this.peopleService.fetchPeople().pipe(
.pluck('FarmerDetails')
).subscribe(people => {
this.people = people;
});
}
Similar to map, when the people service emits a value that looks like the JSON above, it returns the named property in the pluck function before returning it to the subscriber.
Approaches 2 and 3 would be preferable, as you can apply these in you service. Assuming that these come from an HTTP request, you can use the pipe after the get:
export class PeopleService {
fetchPeople() {
return this.http.get(url).pipe(
pluck('FarmerDetails')
);
}
}
Also, the "$" suffix on variables is generally used to denote an observable. In your example, you're just storing the value that is emitted by the observable, so it would be confusing to name it people$. I have named it people for this reason.
When you get response from api, it will return in string format. So, you require to convert it to json format. Parse it to json:
this.people=JSON.parse(res);

Mapping an Object with React.js

I am trying to use the map function to map an object. I've seen similar posts where this has worked using Object.keys but I can't seem to get it to work. I realize that you can't map an object directly.
A portion of my json is below:
{
"orders": [
{
"deadline": 1563046159,
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"id": "83f007d6",
"name": "Work order 83f007d6",
"workerId": 1
},
{
"deadline": 1562752687,
"description": "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
"id": "cb23c526",
"name": "Work order cb23c526",
"workerId": 1
},
]
}
Relevant Code
class App extends Component {
state = {
ordersData: [],
workersData : []
}
// fetch for the first time page loads
componentDidMount() {
fetch("https://www.hatchways.io/api/assessment/work_orders")
.then(response => response.json())
.then(data => {
this.setState({
ordersData: data,
});
console.log(this.state.ordersData)
})
.catch(err => {
console.log(err)
});
}
render() {
let allOrders = this.state.ordersData.orders;
return (
<div>
{Object.keys(allOrders).map((keyName, i) => (
<li className="allOrders" key={i}>
<span className="input-label">key: {i} Name: {allOrders[keyName]}
</span>
</li>
))}
</div>
)}}
I get this error : TypeError: Cannot convert undefined or null to object
You are not waiting for your async to resolve before trying to access your async data (this is the first issue, which is causing the error you are seeing.) The component tries to render with default data while componentDidMount is fired.
ordersData is an array in your default state, not an object, so you are accessing it wrong
allOrders is an array, not an object, so you could just map it directly. Then you can use the .keys() method on the results of that map.
To resolve 1 and 2 you should give a more complete default state so that you don't get the TypeError
state = {
ordersData: {orders:[]},
workersData : []
}
To resolve 3 you will need to change to something along the lines of:
allOrders.map(order => {
//now you have each order, you can use Object.keys(order).map() to get each property of the order
}
I hope this makes sense and clears things up. Comment if you have questions about the individual parts.

react-select - Show different text/label for drop-down and Control?

In my react-select drop-down, the labels are hundreds of characters long. In the Control chips, I would like to show a shorter version of what's in the drop-down. Is this possible?
Edit: I want to set the text of the chip, not pixel width.
SOLUTION 1
You can custom the style of the control chips when using the multiple value Select with the props styles like the following example:
const customStyles = {
multiValue: (base, state) => ({
...base,
// set all chips to a maximum of 100 pixels
maxWidth: "100px"
})
};
Here a live example of shorter versions of long label. I put special (and ugly) background so you can see where each container starts and ends.
SOLUTION 2
Following the comment request this is an alternative solution to cut the selected option. You can overwrite the component MultiValue with the prop components. Inside this component you will have access to the option label and apply substring function to truncate the label as short as you can.
const MultiValue = props => {
// truncate the string to 3 characters
const content = props.children.substring(0, 3);
return <components.MultiValue {...props}>{content}...</components.MultiValue>;
};
Here a live example of this alternative option.
SOLUTION 3
In the same idea as the solution 2 if you know in advance your option labels and the crop you want to display you can set an extra props in your options array like this:
const options = [
{
label:
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi augue sem, bibendum quis mollis amet.",
// you can name this new prop has you want
chipLabel: "Lorem ipsum",
value: "1"
},
{
label:
"Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium.",
chipLabel: "Sed ut perspiciatis",
value: "2"
},
{
label:
"Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur?",
chipLabel: "Ut enim",
value: "3"
}
];
And then overwrite the component with the following code:
const MultiValue = props => (
<components.MultiValue {...props}>
{props.data.chipLabel}
</components.MultiValue>
);
<Select options={options} components={{ MultiValue }} />
Here a live example.
SOLUTION FOR SINGLE VALUE
If you want to display a different value than in options for SingleValue select I recommend to use the solution 3 and change the component like this:
const SingleValue = props => (
<components.SingleValue {...props}>
{props.data.chipLabel}
</components.SingleValue>
);
<Select options={options} components={{ SingleValue }} />
Here a live example.
In React-select V1 , If you ever get into a situation where Selected Value and the Options shown to the User in the drop down have to be different. There is a Props called valueRenderer.
valueRenderer : function which returns a custom way to render the value selected.
its signature is function (option) { return option.value //add any logic to derive the displayed text to user here }
https://github.com/JedWatson/react-select/tree/v1.x
For Multi Value with typescript & react-select 5.7.x:
import Select, { components, MultiValueGenericProps } from "react-select";
type OptionType = {
id: string;
name: string;
description: string;
};
const OptionsArraySample = [
{
name: "test",
id: "1",
description: "test description",
},
{
name: "test 2",
id: "2",
description: "test description 2",
},
];
const MultiValueLabel = (props: MultiValueGenericProps<OptionType>) => {
return <components.MultiValueLabel {...props}>{props.data.name}</components.MultiValueLabel>;
};
// Main Component
export default function FormComponent() {
return (
<form>
<Select
name="sampleSelect"
className="react-select-container"
classNamePrefix="react-select"
components={{ MultiValueLabel }}
isClearable={false}
isSearchable={true}
getOptionValue={(option) => option.id}
getOptionLabel={(option) => `${option.name} ${option.description}`}
isMulti={true}
options={OptionsArraySample}
/>
</form>
);
}

Warning: Each child in an array or iterator should have a unique "key" prop

G'Day.
I want to iterate over a bunch of JSON objects and turn them into React Elements. The objects look like this
"fields":
[
{
key: "testname",
"name": "testname",
"altName": "",
"visible": true,
"groupVisibility": "public",
"type": "text",
"component": "input",
"label": "Test Smart Input",
"placeholder": "Some default Value",
"required": "required",
"validated": false,
"data": []
},
{
key: "password",
"name": "password",
"altName": "",
"visible": true,
"groupVisibility": "public",
"type": "password",
"component": "input",
"label": "Test Smart Input",
"placeholder": "Password",
"required": "required",
"validated": false,
"data": []
}
]
And the code that iterates over them is quite simple. Such:
//--------------------
formFields(fieldsIn) {
const fieldsOut = []; // ARRAY of FORM ELEMENTS to return
console.log('doin fields');
for (var fieldIn in fieldsIn) { // array of FORM ELEMENT descriptions in JSON
console.log(fieldIn);
let field = React.createElement(SmartRender, // go build the React Element
fieldIn,
null); // lowest level, no children, data is in props
console.log('doin fields inside');
fieldsOut.push(field);
}
return(fieldsOut); // this ARRAY is the children of each PAGE
}
And I get the error
Warning: Each child in an array or iterator should have a unique "key" prop.
Any hints?
Cheers
I changed the code to do this.
//--------------------
formFields(fieldsIn) {
const fieldsOut = []; // ARRAY of FORM ELEMENTS to return
console.log('doin fields');
for (var fieldIn in fieldsIn) { // array of FORM ELEMENT descriptions in JSON
console.log(fieldIn);
let field = React.createElement(SmartRender, // go build the React Element
{key: fieldsIn[fieldIn].name, fieldIn},
null); // lowest level, no children, data is in props
console.log('doin fields inside');
fieldsOut.push(field);
}
return(fieldsOut); // this ARRAY is the children of each PAGE
}
And I get the same error. I don't understand why!
Fixed! Thanks for the help.
Here is the code.
//--------------------
formFields(fieldsIn) {
const fieldsOut = []; // ARRAY of FORM ELEMENTS to return
for (var fieldIn in fieldsIn) { // array of FORM ELEMENT descriptions in JSON
console.log(fieldIn);
let field = React.createElement(SmartRender, // go build the React Element
{key: fieldsIn[fieldIn].key, fieldIn},
null); // lowest level, no children, data is in props
fieldsOut.push(field);
}
return(fieldsOut); // this ARRAY is the children of each PAGE
}
//----------------------
pages(pagesIn, format) {
// I tried to do this in JSX, but no syntax I wrestled with would
// allow me to access the childred when building this with the
// BABEL transpiler. Same goes for the METHOD just above, items().
//
// This method returns an array of pages this are React elements
// this are treated as children by the smartForm.
const pagesOut = []; // array of pages to build and return
let Section = {}; // Component to fire in the build
switch(format) {
case 'accordion': {
Section = AccordionSection;
break;
}
case 'workflow': {
Section = null; // I haven't written this yet
break;
}
case 'simple': {
Section = null; // I haven't written this yet
break;
}
}
for (var pageIn in pagesIn) { // pages, any format, any number 1..N
let children = this.formFields(pagesIn[pageIn].fields); // 1..N fields, we don't know beforehand
let page = React.createElement( Section,
pagesIn[pageIn].props,
children);
pagesOut.push(page);
}
return(pagesOut); // this ARRAY is the children of each FORM
}
//--------
render() {
let formIn = this.props.form; // JSON description of FORM
let formOut = null; // contructed REACT/Javascript form
switch (formIn.format) { // what type of operation is this
case 'accordion': { // Accordion in a FORM, OK
let children = this.pages(formIn.pages,
formIn.format); // build the children
formOut = React.createElement(Accordion, // construct the parent with ALL nested CHILDREN after
{key: formIn.formName}, // just a unique key
children); // N ACCORDION pages, N2 input fields
break;
}
case 'workflow': {
let children = this.pages(formIn.pages, // build the children
formIn.format); // build the children
formOut = React.createElement(Workflow, // and create the complex sheet element
{ key: formIn.formName},
children); // N SLIDING pages, N2 input fields
break;
}
case 'simple': {
let children = this.pages(formIn.pages, // build the children
formIn.format); // build the children
formOut = React.createElement(Simple,
{ key: formIn.formName},
children); // One page, N input fields
break;
}
}
return(
<div>
<h2>SmartForm Parser</h2>
<p>"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."</p>
{formOut}
</div>
);
}
}
//-------------------------------------------------------------------------
export default SmartForm;
//----------------- EOF -------------------------------------------------
You need to add a unique key prop to your React element.
According to the React docs:
Keys help React identify which items have changed, are added, or are
removed. Keys should be given to the elements inside the array to give
the elements a stable identity.
The best way to pick a key is to use a string that uniquely identifies
a list item among its siblings. Most often you would use IDs from your
data as keys
When you don’t have stable IDs for rendered items, you may use the
item index as a key as a last resort
You can do it like
for (var fieldIn in fieldsIn) { // array of FORM ELEMENT descriptions in JSON
console.log(fieldIn);
let field = React.createElement(SmartRender, // go build the React Element
{key: fieldsIn[fieldIn].key, fieldIn},
null); // lowest level, no children, data is in props
console.log('doin fields inside');
fieldsOut.push(field);
}
Why are keys necessary?
By default, when recursing on the children of a DOM node, React just iterates over both lists of children at the same time and generates a mutation whenever there’s a difference.
For example, when adding an element at the end of the children, converting between these two trees works well:
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>first</li>
<li>second</li>
<li>third</li>
</ul>
React will match the two <li>first</li> trees, match the two <li>second</li> trees, and then insert the <li>third</li> tree.
If you implement it naively, inserting an element at the beginning has worse performance. For example, converting between these two trees works poorly.
<ul>
<li>first</li>
<li>second</li>
</ul>
<ul>
<li>third</li>
<li>first</li>
<li>second</li>
</ul>
That is where keys come in handy.

MongoDB and NodeJS write array of objects from mongodb to a json file

I need to write an array of places to a JSON file, I take the places from the MongoDB. The code below work
'use strict';
const jsonfile = require('jsonfile');
const debug = require('debug')('myproject:server');
const Place = require('../models/place.js');
const path = './public/places.json';
Place.find({
reviewed: true
}, (err, places) => {
if (err) {
debug(err);
} else {
jsonfile.writeFile(path, places, (err) => {
if (err) {
debug(err);
}
});
}
});
This is what a single object in the JSON file looks like
{ _id: 58b8d11cbaa41f288236d5fa,
__v: 0,
mainImg: 'placeImg-1490464803517.jpg',
reviewed: true,
date: 2017-03-03T02:12:44.313Z,
description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eum illo odit animi architecto.',
coord: { lng: 2.166948616504669, lat: 41.382971076851476 },
type: 'museum',
title: 'Lorem' }
Since I have a lot of objects in the array places it makes sense to remove the properties that are not used on the client side, such as __v and reviewed
I tried doing following before writing array to the file
let shorterPlaces = [];
places.forEach((el, i) => {
delete el['__v'];
delete el['reviewed'];
shorterPlaces.push(el);
});
and then writing shorterPlaces to a file, but the properties remained.
When I tried logging out the object keys inside the for each loop with console.log(Object.keys(el)); I got [ '$__', 'isNew', 'errors', '_doc' ] which does not make any sense to me. Is there something that I am missing here or unaware of?
Based on the file you're requireing it looks like you're using Mongoose. Mongoose collection's find method returns Mongoose documents, which are objects with their own methods and getters/setters, rather than just the plain data in the database.
If you want just the data in a regular JS object, you need to use lean:
Place.find({ reviewed: true })
.lean()
.exec((err, places) => {
// Here, places will be an array of plain objects that you can
// manipulate as normal
})

Resources