I'm trying to implement a form where I can delete specific inputs using React. The problem is, react doesn't seem to be rendering the information correctly. This is my render function:
render: function(){
var inputItems;
if (this.state.inputs){
inputItems = this.state.inputs.map(function(input){
console.log(input)
return (
<Input
input={input}
onDestroy={this.destroy.bind(this, input)}
onEdit={this.edit.bind(this, input)}
editing={this.state.editing === input.id}
onCancel={this.cancel} />
);
}, this);
}
(...)
// this isn't the actual render return
return {inputItems}
and my destroy function:
destroy: function (input) {
var newInputs = this.state.inputs.filter(function (candidate) {
return candidate.id !== input.id;
});
this.setState({
inputs: newInputs
});
},
The actual destroy function is getting called through a child component via <a href="#" onClick={this.props.onDestroy}>(Remove)</a>. The interesting thing is that when I console log my inputs, as seen in the render function, the correct inputs are shown - the one I called the destroy function on is gone. But the incorrect inputs are rendered - it's always the very last one that disappears, not the one I called the destroy function on. So for example, I'll initially log:
First Name
Last Name
Email
and call the destroy function on the Last Name. The console.log will show:
First Name
Email
but the actual rendered information will show:
First Name
Last Name
Thanks!
Figured it out. Has to do with React child reconciliation. Added a key={input.id} to the <Input> tag and it works.
More information here under child reconciliation and dynamic children.
http://facebook.github.io/react/docs/multiple-components.html
Related
Having a hard time seeing how I could accomplish this. I created some custom number buttons from 0-9 that users can click on instead of using the keyboard. The problem I'm having is I have multiple dynamically created input fields depending on JSON Data so let's say there are 10 dynamically created input fields and a user starts with question one and the user then uses the custom number buttons I created and clicks numbers "145" to answer question one, but what happens is then all 10 inputs have the same number "145" not the problem the user was trying to solve. I'm using the context API to then save the values typed in on a function called getButtonValue that I then call to the parent component and save the values in a state array, so I know that my problem is that all the inputs share the same state array but how could I make sure the correct input the user clicks on is only receiving those values.
Thanks in advance.
My Custom Number Button Component:
import { FormContext } from "../../lib/FormContext";
function ActivityBar() {
const { getButtonValue } = useContext(FormContext);
return (
<div className={`${activity.activity__workSheet__numberButton}`}>
<button value={0} onFocus={(e) => getButtonValue(e)}>
<img
className={`${activity.activity__workSheet__img0}`}
src={"/assets/activityNumber-btn.png"}
alt="activity number button"
/>
.... more code
Parent Component:
const [numberButtonClicked, setNumberButtonClicked] = useState([]);
const getButtonValue = (e) => {
setNumberButtonClicked((prevButtonClicked) => [
...prevButtonClicked,
e?.target?.attributes[0].value
]);
};
return (
<Carousel>
<div ref={imageRef} style={{ height: "100%" }}>
{Object.entries(elements).map((element, i) => {
const { fields } = element[1];
if (fields) {
return (
<Element
key={i}
field={fields[0]}
id={i}
useReff={`answer${i}`}
currentValue={
numberButtonClicked === "" ? null : numberButtonClicked.join("")
}
/>
);
} else {
return;
}
})}
</div>
</Carousel>
Got a good working version figured out for this scenario, what I did was.
I have a onFocus method on my input tags that then takes in the event and calls a handleChange(e) function. Within that function I then save the currentInputId in a variable by using e?.target?.attributes[0]?.value and the previous InputId in a state variable and just check if the previous InputId is equal to the currentId user just focused on. If so then we'll add the next number user clicks into the same field, else if previousInputId !== currentInputId then make my user value state array empty, setNumberButtonClicked([]).
I am trying to set an array to a state hook. Basically I want to keep a track of a per-row (of grid sort of) Edit Dialog Open State. Basically per row, I have a Edit button, launches a . As all seems rendered initially, I am trying to manage the show hide by keeping an array in the parent grid component. When user clicks on the Edit button, per row, I want to pass the rowData as props.data and want to provide the Edit functionality.
To keep the state of the editDialogs (show/hide), I am making a array of objects useState hook as follows:
const [editDialogsModalState, setEditDialogsModalState] = useState([{}]); // every edit dialog has it's own state
...
function initializeEditDialogsModalState(dataSet) {
let newState = [];
dataSet.map((item) => newState.push({ id: item.id, state: false }));
return setEditDialogsModalState(newState); // **PROBLEM->Not setting**
}
function addUDButtons(currentRowDataMovie) { // my edit/delete button UI code
const currRowDataId = currentRowDataMovie.id;
return (
<span>
<button
type="button"
className="btn btn-info"
onClick={() => setEditDialogsState(currRowDataId)}
>
Edit
</button>
{editDialogsModalState[currRowDataId].state && ( // **PROBLEM->null data even after set call**
<EditMovieComponent
open={editDialogsModalState[currRowDataId].state}
onToggle={toggleEditDialogsModalState(currentRowDataMovie)}
movie={currentRowDataMovie}
/>
)}
}
......
function buildGrid() {
{
if (!ready) {
// data is not there, why to build the grid
return;
}
initializeEditDialogsModalState(movies);
...........
}
However not able to get the editStates. A screen shot from debugger where I can see the movies (REST output), ready, but not the editDialogsModalState state array.
In general, is there a better ways of implementing such per-row basis functionality where on click of a button I want to open a React-bootstrap and pass the row-specific dataitem for doing operations ? (I am learning React, so may not not yet fully aware of all pointers).
Thanks,
Pradip
In the spec for my app it says (developerified translation): When tabbing to a time element, it should update with the current time before you can change it.
So I have:
<input type="time" ref="myTimeEl" onFocus={this.handleTimeFocus.bind(null, 'myTimeEl')} name="myTimeEl" value={this.model.myTimeEl} id="myTimeEl" onChange={this.changes} />
Also relevant
changes(evt) {
let ch = {};
ch[evt.target.name] = evt.target.value;
this.model.set(ch);
},
handleTimeFocus(elName, event)
{
if (this.model[elName].length === 0) {
let set = {};
set[elName] = moment().format('HH:mm');
this.model.set(set);
}
},
The component will update when the model changes. This works well, except that the input loses focus when tabbing to it (because it gets rerendered).
Please note, if I would use an input type="text" this works out of the box. However I MUST use type="time".
So far I have tried a number of tricks trying to focus back on the element after the re-render but nothing seems to work.
I'm on react 0.14.6
Please help.
For this to work, you would need to:
Add a focusedElement parameter to the components state
In getInitialState(): set this parameter to null
In handleTimeFocus(): set focusElement to 'timeElem` or similar
Add a componentDidUpdate() lifecycle method, where you check if state has focusedElement set, and if so, focus the element - by applying a standard javascript focus() command.
That way, whenever your component updates (this is not needed in initial render), react checks if the element needs focus (by checking state), and if so, gives the element focus.
A solution for savages, but I would rather not
handleTimeFocus(elName, event)
{
if (this.model[elName].length === 0) {
let set = {};
set[elName] = moment().format('HH:mm');
this.model.set(set);
this.forceUpdate(function(){
event.target.select();
});
}
},
try using autoFocus attrribute.
follow the first 3 steps mention by wintvelt.
then in render function check if the element was focused, based on that set the autoFocus attribute to true or false.
example:
render(){
var isTimeFocused = this.state.focusedElement === 'timeElem' ? true : false;
return(
<input type="time" ref="myTimeEl" onFocus={this.handleTimeFocus.bind(null, 'myTimeEl')} name="myTimeEl" value={this.model.myTimeEl} id="myTimeEl" onChange={this.changes} autoFocus={isTimeFocused} />
);
}
New to ember and this is super dumb but I've wasted my day on it.
I'm creating an array of objects in my controller which i'm using to building radio buttons in my view.
when the button is clicked, i want to toggle the clicked attribute on the radio input so that it will appear clicked. Very simple but Ember keeps throwing me errors.
here's my code (edited some typos):
IndexController = Ember.ObjectController.extend({
radioArray : function () {
var a = [], i = 0;
while (a.push({ index: i++, clicked: false }), i <= 10);
return a;
}.property('radioArray'),
actions : {
assignClick : function (item, index) {
this.toggleProperty(item.clicked);
// some unrelated business logic here
}
}
});
this hooks up to:
{{#each radioArray}}
<label {{action "assignClick" this index}}>
<input type="radio" {{bind-attr value=index checked=clicked}} /> foo
</label>
{{/each}}
All i want is to show that the correct radio button has been clicked. But when i try and set clicked to true in my ctrl, i get "Uncaught Error: Assertion Failed: Cannot call get with false key."
If you're trying to use Em.Object.toggleProperty, then you need Em.Object. :)
There are functions like Em.get and Em.set, which you can use for ember and non-ember objects, but there is no function Em.toggleProperty for a non-ember objects.
However, you can use Em.set with Em.get to implement toggle behavior:
Em.set(item, 'clicked', !Em.get(item, 'clicked'));
P.S. Setting property dependence from property itself doesn't make sense. (I'm talking about radioArray : function () {...}.property('radioArray')).
P.P.S. Working example: http://emberjs.jsbin.com/memuwi/2/edit?html,js,output
I use Jest to test my React Components. However, I have no idea (or haven't seen anything) how to test components that pass (as prop) on methods to sub components. For instance, I have: Form, MemberList, Member, FormButton. Something similar to this in code:
Form:
<MemberList members={this.state.members} remove={this.remove} add={this.add} />
<FormButton data={this.state.members} />
MemberList:
<span onClick={this.add}> <!-- add button --> </span>
{this.props.members.map(function(member, index) {
<Member key={index} data={member} remove={this.props.remove} />
})}
Member:
// some input like name and so, and a remove itself button.
FormButton:
var submit = function() {
this.setState({ loading: true });
// xhr
}
<button type="button" onClick={submit} disabled={this.state.loading}>Submit</button>
Am I thinking in the right mindset? To add, are there any practical examples out there?
*I've never tested before trying out React and Jest.
The solution is to pass a mocked function directly to the sub components and test them. Anything involving more than one "sub-component" is usually not truly a unit test as you are testing multiple units of functionality.
So I would create MemberList-test.js:
describe('MemberList', function () {
it('calls the add handler when add is clicked', function () {
var Component = TestUtils.renderIntoDocument(
<MemberList add={ jest.genMockFn() } />
);
const btn = TestUtils.findRenderedDOMComponentWithTag(Component, 'span')
TestUtils.Simulate.change(btn);
expect(Component.add.mock.calls.length).toBe(1)
})
})
Then rather than trying to test your member component directly within the same test your should create Member-test.js:
describe('Member', function () {
it('calls the add handler when add is clicked', function () {
var Component = TestUtils.renderIntoDocument(
<Member remove={ jest.genMockFn() } />
);
const btn = TestUtils.findRenderedDOMComponentWithTag(Component,
'HOWEVER YOU FIND YOUR REMOVE BUTTON')
TestUtils.Simulate.change(btn);
expect(Component.remove.mock.calls.length).toBe(1)
})
})
Now the assertion your are missing is that the remove handler that is passed into the member list is correctly getting passed down to the Member component. So let's add another test to the MemberList-test.js
it('passes correct information to the children', function () {
var MemberMock = require('../Member')
var removeFn = jest.genMockFn();
var testMember = {WHATEVER YOUR MEMBER OBJECT LOOKS LIKE}
var Component = TestUtils.renderIntoDocument(
<MemberList members={ [testMember] }
remove={ removeFn } />
);
// We expect the member component to be instantiated 1 time and
// passed the remove function we defined
// as well as a key and the data
expect(MemberMock.mock.calls).toEqual([[{key: 0, data: testMember,
remove: removeFn}]])
})
Then you simply do the same pattern with the form component. Mocking the member list and the button and testing them separately and seeing that the correct handlers and data are passed down.
Hopefully this makes sense and if not just reply and maybe I can walk you through it on Skype or something.