Render fields for Redux Form from values in an array - reactjs

I'm creating a filter for a graph which contains several fields. Most of them are known fields, but one part is dynamically and that is which of the houses the user want to be included in the graph. The houses are contained in my state and is different for each user (basically, the user chooses what they are named). It's the houses part here I want to render dynamically based on the props.
The only example of this that I've found is this, but I haven't found a solution on how I can transition that to my problem. I thought I could just do something like this where every house field is placed in a array (like in that example):
renderHouseFields() {
const { fields: { houseArray } } = this.props;
return this.props.houses.map((house) => {
const houseField = (
<label
{...houseArray}
className="col-xs-9 control-label"
htmlFor="cottageCheckbox"
>
<input type="checkbox" />
</label>
);
houseArray.addField(houseField);
return (
<div key={house.name}>
<label
className="col-xs-3 control-label"
htmlFor="cottage"
>
{house.name}
</label>
{houseField}
</div>
);
});
}
but then I simply get this error message:
Warning: setState(...): Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state.
This is my first project in React so I'm quite sure I'm just overlooking something here, but I can't find the solution and would be grateful if someone could help me here.
(I'm also aware that I can upgrade to redux form 6 and use FieldArray, but I don't really want to do that in the middle of the project.)

Judging by your code I reckon you're getting the error, because you are adding to your houseArray directly in the render method. This will trigger an update to the props of your component, which should not occur in the render method, hence the error.
If you look at the Deep Form link you supplied, you'll notice that the only place modifications to fields are occurring, is within button event handlers.
In your case I think what you want to do is link the entries in your house array, to the actual checkboxes. Right now you're only adding the checkboxes, but it has no reference to a house field:
<input type="checkbox" name={house} />
Or maybe this, depending on the properties of house:
<input type="checkbox" name={`${house}.id`} />
On a side note, I really would recommend to upgrade to version 6, since the API makes a lot more sense and it contains a lot of improvements over the previous version. There's a migration guide: http://redux-form.com/6.6.1/docs/MigrationGuide.md/

Related

React keeps children from previous render

I have problem rendering page based on array of templates stored in redux. I'm unable to provide the actual code as it is too complex to reproduce in sandbox, but I will try with simplified structure.
I have pinterest-style grid based on flex-grow and js calculations for each item based on its dimensions so I need to have all of this inside one container.
I have 3 groups of items in same div:
blank_item + templates.map + tailItems.map
<div className="grid">
{shouldRenderBlankItem && <BlankItem />}
{templates.map((template) => (
<Template
key={template.uuid}
template={template}
/>
))}
{shouldRenderTail &&
tailItems.map(item, i) => (
<TailItem
key={i}
item={item}
/>
))}
</div>
The problem is - sometimes after render I have EXTRA children left from previous render and React puts them in front of other elements within div.grid so the result I have will look like:
3-4 of EXTRA <Template/> + blank_item + templates.map + tailItems.map
As a key for Template I use template.uuid that is coming from backend and I know they're unique and react also doesn't show any warnings for duplicated keys - so I know I'm not getting any duplicated items from API what I thought might be an issue.
Redux is fine - I see correct number of templates in it e.g. 50, grid and react dev tools shows same 50 templates coming as a prop when I inspect parent component, but the actual DOM has e.g. 53 elements in the same moment.
How can I debug issue like this? I suppose something is wrong during reconciliation but I don't know where exactly to start debugging this.
I have React/ReactDOM 16.13.1
Ok so in my case the problem was broken backend api which might return same template twice - so uuid's that I use for keys were unique for each template but they are not really unique in terms of the page and DOM elements.
I faced this only in production build that is why I didn't have duplicated key warning (on dev build I have another DB with much less templates so I was unable to reproduce this).
So anyone facing similar issue: go check your keys are really unique within page, because what happens after new templates load comes:
React wants to remove old item
It searches corresponding DOM element for each key and finds item with key="XXX" and removes it from DOM
Their duplicated items stays in DOM as corresponding key was processed and React doesn't care about this particular key anymore
We load new templates and they get appended to the parent container
Voila! We have old items before newly loaded
What I did here - my key now is:
templates.map((template, i) => <Template key={template.uuid + i} />
So in this case I am safe even if backend returns duplicated items.
Of course we will fix backend as well. Thanks for reading and helping!
Just quick example how to search for items with same id in data:
var duplicates = new Set();
data.map(t => t.id).forEach((id, i, arr) => {if(arr.filter(item => item == id).length > 1){
duplicates.add(id);
}});
console.log("DUPLICATED IDs:", duplicates)

How to get value from a dropdown to a button?

I'm using react JS and I have a problem. I don't know how to get the value from my dropdown and put that value into a onclick button. I have read lots of topics but I haven't find anything really useful for a beginner like me.
I am using "scheduler" that helped me built my dropdown and some other stuffs.
So, my dropdown get data from a local file and looks like this:
{values.map(v => (
<option value={this.value}>{v.value}</option>
))}
console.log(ref)
And my button is like this:
<Button onClick={() => this.decrement()}>
Ajouetr la réservation
</Button>
The decrement method was only there to test if it was working, and it is.
Actually, what I want to do is quite simple: I have some values in my dropdown (from 1 to 7). And I have a state that says there is 30 places available. What I want is when I choose a specified item in my dropdown AND validate with my button and then my state to decrement with the specified number. Because right now it only decrement with 1.
I hope it's clear enough for someone to help me, because I spent 2 days on that problem and I don't know what to do.
Thank you :)
Next time, it's nice to provide an interactive example with your question. Here's a CodeSandbox I made that (I hope) illustrates your example (link). If you want to fiddle with the example, just click "Fork" in the top right corner.
Back to the solution:
I think what you're missing is storing the selected value in your state along with the 30 "places". What you want is to make your <select /> tag into a "controlled component". When someone interacts with the <select /> you want to change the internal state so that it matches the selected value. That way, when you call decrement() from your button, you can use the internal state's value rather than getting it from a ref (I think that's what you were trying to do).
Here's a link to the React doc that explains how to use forms, specifically the <select /> tag: (link).
Take care!
I would say that you can think about this in 2 different steps:
SET THE QUANTITY STATE
Set the state with the current dropdown value - For achieving this, you can just use the onChange method in your select:
<select name="quantity"
value={this.state.quantity}
onChange={this.onSelectQuantity}
>
In your constructor, you create a variable quantity inside your state
Create a function called onSelectQuantity where you will set the quantity state with setState.
Do not forget to bind the function onSelectQuantity on the constructor.
With this, every time that you change the value on select, your state would capture its value. You can log it from the function if you want to test if it works.
DECREMENT FROM THE BUTTON
After this, you can just decrease the value of the state again from decrement function
<Button onClick={this.decrement}>
Ajouetr la réservation
</Button>
You will have a function...
decrement() {
const newQuantity = this.state.quantity - 1;
this.setState({
quantity: newQuantity
})
}
Hope it helps!

AngularJS - Validate a part of a form

I'm using Angular 1.5.9
I have a big form, which I scattered through different Bootstrap Accordion.
When there is an error in the form, I want to be able to change the class of my accordions to show in which accordions the error is located.
To check for errors in a whole form, I can check
myFormName.$error
And to check errors for an element, I can simply do
myFormName.myInputName.$error
But I don't know if there is a way to do this for multiple element at once, without having to check each element individually.
My first thought was to change the name of my inputs like so:
<input name="accordion1.fieldName">
But this didn't give me the expected result: I don't have myFormName.accordion1.$error, actually, I don't even have myFormName.accordion1.fieldName, since my data is actually stored in myFormName['accordion1.fieldName'] which is pretty much useless.
Has anyone found an answer to this problem? I think I'll have to check each field, which is kinda ugly, and a mess to maintain whenever we add / remove fields...
Maybe there is a directive out there that could do that for me, but as a non-native English speaker, I can't find which key words to use for my search in this situation.
One approach is to nest with the ng-form directive:
<form name=form1>
<div ng-form=set1>
<input name=input1 />
<input name=input2 />
</div>
</form>
{{form1.set1.$error}}
You could name the fields with a prefix such as 'accordion1_' then add a controller function that will filter your fields.
ctrl.fieldGroup = function(form, fieldPrefix) {
var fieldGroup = {};
angular.forEach(form, function(value, key) {
if (key.indexOf(prefix) === 0) {
fieldGroup[key] = value;
}
});
return fieldGroup;
}
Then ctrl.fieldGroup('accordion1') will return an object with the appriopriate fields on it. You could extend the function further to add an aggregate $error property to the resulting fieldGroup object.

Disable / workaround React key requirement?

I'm getting the warning Each child in an array or iterator should have a unique "key" prop. Check the render method of Login
I want to pass an array of elements back, without using keys. I have to believe there's a workaround for this, without adding a pointless wrapper?
Note the return [<div/>, <div/>];
render () {
return (
<div className='login'>
{this.mobileWeb()}
{this.pcWeb()}
</div>
);
}
mobileWeb () {
if (this.state.isMobileWeb === true) {
return [
<div className='sky-container'></div>,
<div className='sea-container'></div>
];
}
}
pcWeb () {
if (this.state.isMobileWeb !== true) {
return [
<div className='sky-container'></div>,
<div className='sea-container'>
<LoginForm onChange={this.onChange} ref='loginForm' onEmailChange={this.onEmailChange} onPasswordChange={this.onPasswordChange} />
<input type='submit' value='Submit' onClick={this.login} />
</div>
];
}
}
There is a valid use case for not wanting to use keys, if you e.g. render strongly differing trees (think blog posts) or have a function that returns always the same array of items.
When you don’t provide an explicit key, React will use the index as key and emit a warning – however you would want to silence the warning, as using the index here is perfectly valid.
This doesn’t seem to be possible however, which has the sad consequence that you either have to make your code more complex for no reason (adding useless keys) or ignore the warnings (which means that valid warnings might be lost among the noise).
As of now, probably since 16.2.0 (November 28, 2017) you can wrap the elements inside <React.Fragment> instead of an array. The child components do not need keys although array of Fragments will still need keys.
you should not be passing the child element without key, React uses key for diff diff using virtual dom (it is lightweight javascript object). Never use key as Date.now() or Math.random(), because during render the react will see different key value and it will create new dom object.
A few years later, I will answer with newfound knowledge: the 'pointless wrapper' actually has semantic meaning to describe the collection and it's pretty normal to use a fragment eg <> ... </>, or a wrapper <div> to describe it, just not an array.
Edit: Do not do this, as the comment by #Jason Miller suggests.
Whenever it is straightforward, it is best to simply use the indices
of the array or some other ID. Sometimes, however, this is
impractical, for example, when a complex layout is generated
dynamically. A workaround for suppressing the warning is to generate a
random number as a key using key={Math.random()}. There are around 15 safe digits in the returned float and thus >10^15
possible outcomes, which should be safe enough for the most
applications.

Submittable HTML5 Web Component

How do I create a form-submittable web component?
Background:
I have a customer-picker component that is basically a text input plus a tiny button to the right of the text box plus a dialog that pops up when the button is clicked. It is used in the same way as one might use an html select.
This component is used as part of an html form that is submitted in the old fashioned (non-ajax) kind of way. The actual html input is encapsulated privately inside of the customer-picker component.
Problem:
The text input's value is not submitted
I guess I can understand why this is. I suppose this is the desired behavior (otherwise we are breaking encapsulation).
So with all that said, how do I create a submittable web component?
For example, suppose I have a form like this:
<form action="action.jsp">
<input name="date-start"/>
<input name="date-end"/>
<input name="name-first"/>
<input name="name-last"/>
</form>
that gets submitted like this:
action.jsp?date-start=2016-06-01&date-end=2016-06-30&name-first=Joe&name-last=Smith
I would like to create the same form using components like this:
<form action="action.jsp">
<date-range name="date">
<full-name name="name">
</form>
that gets submitted exactly the same way as the example above:
action.jsp?date-start=2016-06-01&date-end=2016-06-30&name-first=Joe&name-last=Smith
I am aware of iron-form. But this solution has some problems.
For one, it does not emulate native form submission very well. In a normal html form, when you submit, the current page is automatically replaced by whatever is returned by the action URL. This doesn't seem to happen with iron-form.
Second. It only allows your component to contribute a single value to the submitted data. In the above mentioned date-range example, I would like two values to be submitted for one component.

Resources