I have a form and when it is submitted I take the values with the function saveEntity.
Now in this form, I have some fields that are showed after some other values are chosen. From these fields, I don't receive any values on my saveEntity function.
export const MyFunctionName = (props) => {
// code...
const saveEntity = (event, errors, values) => {
console.log('values ', values);
// other code
}
const RuoliChoosen = ruolo => {
if (!ruolo.ruolo) {
return <div />;
}
return (
<div>
<Row>
<Col md="6">
<AvGroup>
<Label for="accessNumber">{'Access Number'}</Label>
<AvInput
id="accessNumber"
data-cy="accessNumber"
type="text"
className="form-control"
name="accessNumber"
/>
</AvGroup>
</Col>
//.....
}
return(
<AvForm model={isNew ? {} : userClientAuthorityEntity} onSubmit={saveEntity}>
<AvInput
id="user-client-authority-application"
data-cy="application"
type="select"
className="form-control"
name="applicationId"
onChange={handleChange}
required
>
<option value="" key="0">
Select Application
</option>
{applicationList
? applicationList.map(value => {
return (
<option value={value.appCod} key={value.appCod}>
{value.appDesapplicazione}
</option>
);
})
: null}
</AvInput>
// this RuoliChoosen receive "ruoli" from another function (it works)
<RuoliChoosen ruolo={ruoli} />
)}
When I submit the form, I expect to see the values in the saveEntity, in this case only values for application and accessNumber, but I receive only value for application.
How can I fix it? Thank you.
Please format your code well when coding, since normally the format keeps up with your logic. Sometimes people are picky about the format, but really what they are saying is that they are not comfortable to read your code. Trust me, you don't want to get uncomfortable reading code ;)
const RuoliChoosen = ruolo => {
This isn't a component, instead
const RuoliChoosen = ({ ruolo }) => {
Because ruolo is a prop, not the entire props
You sent ruolo as a prop where the component is called. But You sent it as an object. And then in that component where you receive this ruolo as prop it comes as an object. If you want to access it you have to destructure it. So change it.
from
const RuoliChoosen = ruolo => {
return()
}
to
const RuoliChoosen = ({ruolo}) => {
return()
}
thanks.
Related
export default function RenderPages({storage, setStorage, state, setState}){
let elRefs= useRef()
if(!storage) return
if(!state.currentFileId || !state.currentFolderId) return
const content = storage[state.currentFolderId][state.currentFileId].content
return (
<div className="writing">
<input ref={elRefs}/>
{content.map((page, index)=>
<div className='textarea'>
<textarea placeholder='write here' value={page} id={"page"+index} onChange={(e)=>onChange(e, index)} rows={rows} cols={cols}></textarea>
</div>)}
</div>
)
}
There are some props(state, storage) and they are sometimes null value. What I am doing now is checking the values of state and storage, returning blank early if those values are null. If I don't return in advance, the variable "content" get error because it needs state and storage value. Now this is the problem. I want to use "useRef", and if the component return early "elRefs" is assigned null value, so I can't get DOM element. What should I do?
I put your booleans into single function and seperated the renderable part of component. If bool is false, that component is simply not rendered. Of course you can put in there other component which shows error data or loading gif or something like that. Hope this works!
export default function RenderPages({ storage, setStorage, state, setState }) {
const elRefs = useRef()
const content = storage[state.currentFolderId][state.currentFileId].content
// content to render in seperate component
const Pages = () => (
<div className="writing">
<input ref={elRefs} />
{
content.map((page, index) =>
<div className='textarea'>
<textarea
placeholder='write here'
value={page} id={"page" + index}
onChange={(e) => onChange(e, index)}
rows={rows}
cols={cols}
/>
</div>
)
}
</div>
)
// decide to or not to render component
const renderable = storage &&
state.currentFileId &&
state.currentFolderId
return (
<>
{
renderable
? <Pages />
: <></> // returns empty component
}
</>
)
}
I am creating an app to keep track of expenses.
Before creating the server side. What I am trying to do is to render all information held in the state in a list format.
As I am not storing this data in a database. I wondered if there would be a way to do it without creating the database. I mean, just do this task dealing just with react.(I have to learn server side yet)
What I've done:
I sent the data, data is the state created in , as props to component, and
and in there, I created an empty array to push each property of the data object.
After created the array, in the return of the List component, I map through that array to print all the information.
But obviously, once I type something, the previous data disappears.
What I am trying to achieve is what I've explained at the beginning.
How to keep track of all inputs and print all of them without losing the previous enter.
I am new to this. So, please, go easy on me :)
export default function Forms() {
const [data, setData] = useState({
concept:"",
amount:"$",
date:getCurrentDate(),
select: "Income"
})
function handleChange(event){
let name = event.target.name;
let value = event.target.value;
setData({...data,[name]:value})
}
return (
<>
<h2>Incomes - Outcomes</h2>
<form className="form">
<label>Concept:</label>
<input type="text" name="concept" value={data.concept} onChange={handleChange} />
<label>Amount:</label>
<input type="text" name="amount" value={data.amount} onChange={handleChange}/>
<label>Date:</label>
<input name="date" value={data.date} onChange={handleChange}/>
<label>Select type of expense:</label>
<select name="select" value={data.type} onChange={handleChange}>
<option value="Income">Income</option>
<option value="Outcome">Outcome</option>
</select>
</form>
<List data={data}/>
</>
);
}
export default function List({ data }) {
const arrayOfExpenses = [];
Object.keys(data).forEach((key) => {
arrayOfExpenses.push(key + ": " + data[key]);
})
function handleSubmit(event) {
event.preventDefault();
}
return (
<div>
<button onClick={handleSubmit}>Submit</button>
{arrayOfExpenses.map((element, index) =>(
<ul key={index}>
<li>{element}</li>
</ul>
)
)}
</div>
);
}
I'm building a controlled form with dynamic fields.
The Parent component get data from a redux store and then set state with the values.
I don't want to make it with too much code lines so I turn the dynamic fields into a component.
States stay in the parent component and I use props to pass the handlechange function.
Parent :
function EditAbout(props) {
const [img, setImg] = useState("");
const [body, setBody] = useState(props.about.body);
const [instagram, setInstagram] = useState(props.about.links.instagram);
const [linkedin, setLinkedIn] = useState(props.about.links.linkedin);
const [press, setPress] = useState(props.about.press)
const handleSubmit = (e) => {
// Submit the change to redux
};
// set states with redux store
useEffect(() => {
setBody(props.about.body);
setInstagram(props.about.links.instagram);
setLinkedIn(props.about.links.linkedin);
setPress(props.about.press);
}, []);
const handleChangeChild = (e, index) => {
e.preventDefault();
let articles = press
const {value, name } = e.target
if (name === "title") {
articles[index].title = value;
} else {
articles[index].link = value;
}
setPress(articles)
console.log(articles[index])
}
return (
<Box>
<h1>CHANGE ABOUT ME</h1>
<Input
label="Image"
name="img"
type="file"
variant="outlined"
margin="normal"
onChange={(e) => setImg(e.target.files)}
/>
<Input
label="body"
value={body}
name="body"
onChange={(e) => setBody(e.target.value)}
variant="outlined"
multiline
rowsMax={12}
margin="normal"
/>
<Input
label="instagram"
value={instagram}
name="instagram"
variant="outlined"
margin="normal"
onChange={(e) => setInstagram(e.target.value)}
/>
<Input
label="Linkedin"
value={linkedin}
name="linkedin"
variant="outlined"
margin="normal"
onChange={(e) => setLinkedIn(e.target.value)}
/>
<Child press={press} onChange={handleChangeChild} />
{props.loading ? (
<CircularProgress color="black" />
) : (
<Button onClick={handleSubmit} variant="contained">
Send
</Button>
)}
</Box>
);
}
Child :
function Child(props) {
const { press, onChange } = props;
const inputsMarkup = () =>
press.map((article, index) => (
<div key={`press${index}`} style={{ display: "flex" }}>
<input
name="title"
value={press[index].title}
onChange={(e) => onChange(e, index)}
/>
<input
name="link"
value={press[index].link}
onChange={(e) => onChange(e, index)}
/>
<button>Delete</button>
</div>
));
return (
<div>
<h1>Press :</h1>
{inputsMarkup()}
</div>
);
}
Everything is fine when I'm typing in the Parent inputs. But when I'm using Child fields state update for one character but come back at its previous state right after.
It also doesn't display the character change. I can only see it in the console.
Thanks you in advance for your help
The problem is that you're mutating the state directly. When you create the articles variable (let articles = press) you don't actually create a copy and articles doesn't actually contain the value. It's only a reference to that value, which points to the object’s location in memory.
So when you update articles[index].title in your handleChangeChild function, you're actually changing the press state too. You might think that's fine, but without calling setPress() React will not be aware of the change. So, although the state value is changed, you won't see it because React won't re-render it.
You need to create a copy of the press array using .map() and create a copy of the updated array element. You can find the updated handleChangeChild() below:
const handleChangeChild = (e, index) => {
e.preventDefault();
const { value, name } = e.target;
setPress(
// .map() returns a new array
press.map((item, i) => {
// if the current item is not the one we need to update, just return it
if (i !== index) {
return item;
}
// create a new object by copying the item
const updatedItem = {
...item,
};
// we can safely update the properties now it won't affect the state
if (name === 'title') {
updatedItem.title = value;
} else {
updatedItem.link = value;
}
return updatedItem;
}),
);
};
I was creating a component that returns a label and a children, this child is a function that evaluates if the field has type 'input' or 'textarea' and returns it:
export const Field = ({
fieldType,
}) => {
return (
<>
<label htmlFor={name}> {label}</label>
{() => {
switch (fieldType) {
case 'textarea':
return (
<textarea
/>
);
default:
return (
<input/>
);
}
}}
</>
);
};
I like to start my test by creating a snapshot of the component
describe('Unit testing: <Field /> component', () => {
test('Should render correctly ', () => {
const wrapper = shallow(<Field fieldType='textarea' />);
expect(wrapper).toMatchSnapshot();
});
});
This is the result of my snapshot (I'm using enzyme-to-json):
exports[`Unit testing for Field component Should render correctly 1`] = `
<Fragment>
<label
htmlFor="testField"
>
Test Label
</label>
<Component />
</Fragment>
`;
As you can see, the child has been rendered just as and this is very fuzzy to me... I would like to know how can I exactly test that my component is really rendering either an input or a textarea...
I've found a possible solution that actually it's good for me:
const innerWrapper = shallow(wrapper.prop('children')[1]());
This innerWrapper creates a shallow render from the children.
The snapshot shows what I wanted:
exports[`Unit testing for Field component Function as children should render correctly 1`] = `
<textarea
autoComplete="off"
id="testField"
name="testField"
value=""
/>
`;
The complete test that I've implemented:
test('Function as children should render correctly', () => {
const innerWrapper = shallow(wrapper.prop('children')[1]());
expect(innerWrapper).toMatchSnapshot();
expect(innerWrapper.find(props.fieldType).exists()).toBe(true);
});
And yes, I've ran the test and it passed.
You mentioned in your answer to your question :
I've found a possible solution that actually it's good for me:
But it's a wrong solution. You have a wrong component, and you changed your test to ignore it. your component is like:
export const Field = ({fieldType,}) => {
return (
<>
<label htmlFor={name}> {label}</label>
{() => {return <input />}} <---- it's just a component defination.
</>
);
};
And if you use it like:
<Field />
It will only render label, not the textarea nor the input. (Because a function inside the render function is considered as a component definition, you should call it in order to get an element from it to render.)
So the test was correct, but your component is wrong. Change your component to:
export const Field = ({fieldType,}) => {
const input = () => {
return <input />
}
return (
<>
<label htmlFor={name}> {label}</label>
{input()}
</>
);
};
To render the input component, not just defining it.
Sorry for the general title but I don't know what else to call it at this point. I have a form that uses widgets. Im using the react-jsonschema-form package. Id use the github for the project but I don't think this is bug so I want to check here first. I think it's just how to use this React thing question. Possibly Redux as well.
So...
I have these widgets for some of the form elements in my component.
dropdownWidget = (props, type, id) => {
return (
<div>
<input id={id} type="text" className="form-control" list={type} placeholder="Select one..." onChange={(event) => { props.onChange(event.target.value); this.hideResultTables(event.target.id) }} />
<datalist id={type}>
{this.props.actionsObj.filterJsonData(type, this.props.convertDropDownDataObj).map((value, index) => { return <option key={index} value={value}>{value}</option> })}
</datalist>
</div>
)
}
multiFileWidget = (props) => {
return (
<div>
<OverlayTrigger trigger={['hover', 'focus']} placement="right" overlay={fileWidgetPopup}>
<input type="file" id="multiFileName" required={props.required} onChange={(event) => { props.onChange(event.target.value); this.getFileNames(); this.hideResultTables(event.target.id) }} multiple />
</OverlayTrigger>
<textarea id="multiFileNameList" className="form-control" rows="4" style={{ marginTop: "2%" }} readOnly />
</div>
)
}
dropdownTooltipWidget = (props, type, id, tooltip) => {
return (
<div>
<OverlayTrigger trigger={['hover', 'focus']} placement="right" overlay={tooltip} hidden>
<input id={id} type="text" className="form-control" list={type} placeholder="Select one..." onChange={(event) => { props.onChange(event.target.value); this.hideResultTables(event.target.id) }} />
</OverlayTrigger>
<datalist id={type}>
{this.props.actionsObj.filterJsonData(type, this.props.convertDropDownDataObj).map((value, index) => { return <option key={index} value={value}>{value}</option> })}
</datalist>
</div>
)
}
They are stuffed in a object like so:
multiUISchema = {
file: {
'ui:widget': this.multiFileWidget,
classNames: "uiSchema",
},
convertTo: {
'ui:widget': (props) => this.dropdownWidget(props, "ConvertToTypes", "multiConvertTo"),
classNames: "uiSchema"
},
notification: {
'ui:widget': (props) => this.dropdownTooltipWidget(props, "NotificationType", "notification", websNotificationPopup),
classNames: "uiSchema"
}
}
Where things go bad!! This function is injected in the widgets onChange handler.
hideResultTables(targetId) {
debugger;
//disable Retrieve button
if (targetId === 'multiFileName' || targetId === 'multiConvertTo') {
console.log("BEFORE::", this.state.formData)
selectedFiles = [];
document.getElementById("convertResultTable").setAttribute("hidden", true);
document.getElementById("retrieveResultTable").setAttribute("hidden", true);
this.setState({ disabled: true });
}
//leave Retrieve Button enabled
else if (targetId !== 'appIDs') {
console.log("BEFORE::", this.state.formData)
selectedFiles = [];
document.getElementById("convertResultTable").setAttribute("hidden", true);
document.getElementById("retrieveResultTable").setAttribute("hidden", true);
}
}
Basically if I hit the first if statement which has the setState for disabled to True, my onChange doesn't seem to retain the selected dropdown data to the Form Data object. It's like setState for Disabled true, which is for a button unrelated to the field does something to in the reconciliation tree possibly and/or my form data never retains the selected field in the formdata object at all?? idk. The field just stays undefined in all console logs if it's a field that hits that first if statement with the setState in it. But that setState has nothing to do with the fields being selected. It's disabling a button further down the page is all. Im sure im missing some React lifecycle, reconciliation async something or other. Im thinking of just reduxing this all but this simple bool value seems like it should stay as local state to the component. Any thoughts on things I should be checking before i redux this simple bool for my form?
To Further clarify my question, why does setState in the injected function in onChange seem to disrupt the onChange value update for the field on the form in my Widget when I select a field. That's really what this is all about. We cannot figure out why this is happening.
footnote** The dropdown data in the form is redux store data. So im selecting store data in my onchange in the widgets. Not sure if that's relevant.