I have child Component with input, and in the parent I've a button that disabled based on this input.
I thought about creating 'top level' state and pass the values like this:
const Parent = () => {
const [projectName, setProjectName] = useState('')
return (
<>
<Child projectName={projectName} setProjectName={setProjectName} />
<button disabled={projectName.length === 6}/>
</>
)
}
My question is, Is it solid react way to implement this?
In the child component I'm getting undefined for both projectName and setProjectName, why is it happening and how can I solve this?
Child Component:
const Child= ({projectName, setProjectName}) => {
return (
<>
<h2><StyledInlineSpan>Projects / </StyledInlineSpan>Create New Project</h2>
<Input autoFocus placeholder="Project Name" value={projectName} onChange={({ target }) => { setProjectName(target.value) }} />
</>
)
}
You shouldn't get an undefined, I think it is a problem with your Input component. I created a codesandbox with the same use case. As you type into the input, the Child component will change the state variable and the parent uses it.
It is completely fine to create a controlled Child component like this.
Seems like you have mis-spelled Input.
In React if the first letter of any HTML tag is capital, then it is treated as a custom React component.
Simply rename this
<Input autoFocus placeholder="Project Name" value={projectName} onChange={({ target }) => { setProjectName(target.value) }} />
to
<input autoFocus placeholder="Project Name" value={projectName} onChange={({ target }) => { setProjectName(target.value) }} />
Related
I have a functional SearchBar child component that is linked to the parent homepage. I pass it props of id and label from the parent component. The child component reads the props id and label as "undefined." I have this exact same code for another dropdown component and it works. My guess is that this may be because I am passing the props to a hoisted (above the functional component body) renderInput function? Can you pass props to a hoisted helper function? I am also using Redux Form, which I'm not sure if it complicates things. Input, which is a prop from Redux Form, works fine. This is my code:
//helper render function hoisted to prevent re-render of searchbar with every key stroke
const renderInput = ({ id, label input }) => {
return (
<div className="container position-relative" id={id}>
{label}
<input
{...input}
type="text"
placeholder="Search..."
className="py-4 px-5 border rounded-sm form-control"
/>
</div>
);
};
const SearchBar = ({ handleSubmit, submitSearch }) => {
const onSubmit = (formValues, dispatch) => {
submitSearch(formValues); //calls search action creator
dispatch(reset("SearchBar")); //clears search form after submission
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Field
name="search"
component={renderInput}
/>
</form>
);
}
This is the working code, if anyone else runs into the same issue. You have to pass the custom props of id and label to the Field component in Redux Form for the Redux Form component to read it.
<Field name="search" component={renderInput} id={id} label={label} />
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.
I want to access a nested component from parent component.
This is Bill Form.jsx
import BillDetailForm from './BillDetailForm';
render(){
return (
<form onSubmit={handleSubmit}>
<FieldArray
name= 'detail'
component={BillDetailForm}
placeholder= '...detail'
label='Detail'
/>
</form>
);
}
}
BillForm is the parent component.
This is a nested component or child component of BillForm: BillDetailForm.jsx
render(){
return(
<form onSubmit={ handleSubmit }>
<div>Detail:</div>
<FieldArray
name= 'detail'
component={RenderDetail}
label='Detail'
/>
</form>
)
}
Inside BillDetailForm is RenderDetail:
const RenderDetail = ({fields, meta: { error,submitFailed}},props) => (
<dl>
<dt>
<button type="button" className= 'btn btn-primary' onClick={() => fields.push()}>Add
Detail</button>
{submitFailed && error && <span>{error}</span>}
</dt>
{ fields.map((registerDetail, index) =>
//In the following line renderDetail is accesing Detail component.
<Detail detailItem={registerDetail} fields={fields} index={index} key={index}/>
)
}
{error && <dt className="error">{error}</dt>}
</dl>
);
This is Detail Class Component:
class Detail extends Component{
render(){
const{detailItem,index,fields,isSubtotal} = this.props;
return(
<dd key={index}>
<br></br>
<button className= 'btn btn-light mr-2'
type="button"
title="Remove detail"
onClick={() => { fields.remove(index)
if(fields.length == 0 || fields.length === undefined){
}
try{
for(let x in fields){
fields.remove(index)
let d = fields.selectedIndex;
if(fields.remove(index) && d >= 1 && d< fields.length ){
fields.removeAll(index);
}
}
}catch{console.info("deletes non numerical index")}
}}> Delete </button>
<h4>DetailRegister #{index + 1}</h4>
<Field
id={`${detailItem}._id`}
name={`${detailItem}.quantity`}
component= {NumberPickerInteger}
placeholder= '...quantity'
label = "Quantity"
/>
<br></br>
<h3><b>Product</b></h3>
<Field
id={`${detailItem}._id`}
name={`${detailItem}.product.code`}
type="number"
component= {RenderFieldNumeric}
placeholder='...Product's code'
label = "Product's code"
/>
<Field
id={`${detailItem}._id`}
name={`${detailItem}.product.name`}
type="text"
component= {RenderField}
placeholder='...Product's name'
label = "Product's name"
/>
<Field
id={`${detailItem}._id`}
name={`${detailItem}.product.price`}
component= {NumberPickerr}
placeholder= '...Price'
label = "Product's price"
/>
<br></br>
<h3><b>Subtotal</b></h3>
<Field
id={`${detailItem}._id`}
name={`${detailItem}.subtotal`}
component= {SubtotalWidget}
placeholder= '...subtotal'
label = "Subtotal"
>
{isSubtotal}
</Field>
</dd>
);
}
}
I want to access e.g ${props.detailItem}.subtotal that is in Detail from BillForm. BillForm accesses to BillDetailForm, BillDetailForm accesses to renderDetail, and last renderDetail acceses to Detail.
The question is: How can I access and use props like quantity and subtotal with dynamic index (props.index) from BillForm? I want to access Detail component from BillForm, respecting the following secuence in order access: BillForm -> BillDetailForm -> RenderDetail -> Detail
If I understand correctly what you are saying, it seems you are going against the ethos of React. If your parent component wants access to a piece of data, then that data should start in the parent and be passed down. This way, if the data changes it will call a re-render of components and update all necessary components.
Some other advice. Try not o have so much logic inside your component handlers, it looks messy and will run every render cycle. Abstract this into a method on the class and call it when required.
My example will hopefully help you with your issue, but I recommend having a read of the React documentation as it is very good with simple examples.
The use of class will be deprecated eventually in favour of function components and the Hooks API.
class ParentComponent {
state = {
value: 0,
}
methodToDoSomething = (passedVal) => {
this.setState({
value: passVal,
});
}
render() {
const myState = this.state;
return (
<Component {...myState} />
)
}
}
class Component {
state = {}
render() {
const { value , methodToDoSomething } = this.props;
return (
<div onClick={methodToDoSomething}>
{value}
</div>
)
}
}
// Hooks API
const ParentComponent = () => {
const [stateVal, updateState] = React.useState('myString');
return (
<div>
{stateVal}
<Component passedVal={stateVal} passedHandler={updateState} />
</div>
)
}
const Component = ({ stateVal, passedHandler }) => {
function updateMyValue() {
passedHandler('menewvalue');
}
return (
<div onClick={updateMyValue}>
{stateValue}
<div/>
)
}
To avoid passing lots down all the children components, I would recommend reading up on the Context Hook.
*** UPDATE ***
The above example is rudimentary and tries to answer the question presented, there are always many ways to solve a problem.
Passing props can be messy and a maintenance overhead. Most larger applications will benefit from using a state library to manage their global state. The Context API is a good tool to use to wrap a cohesive set of components so they can share data/props without prop-drilling (passing props down many child components).
Custom hooks are another good way to share data. Create a hook containing your data and any other methods for the task and use this hook inside parent and child components to share said data.
I developed a React App using Material-UI then I tried to create independent Components,
check the below independent components(<PanelDiv/>),
render() {
return (
<div className="panelDiv-component" style={{display:this.props.display}}>
<div className="panel-field-content">
<TextField
floatingLabelText={this.props.heading}
type={this.props.inputType}
value={this.props.value}
/>
{/* <input defaultValue className="form-control"></input>*/}
</div>
</div>
);
}
I tried to use the component like this,
<PanelDiv
heading='name'
inputType="text"
value={this.state.userData.name}
display={this.state.display[0]}
/>
But I can't update input field in this way.Also there is no error. How can i solve this? I want to update my input field.
Please check my input filed in the below image :
Because you are controlling the value of TextField by using value attribute but you are not updating the value by using onChange function, Since value of TextField is not changing so it becomes read only.
Solution:
Specify the onChange function with TextField and update the value inside that, Like this:
<TextField
floatingLabelText={this.props.heading}
type={this.props.inputType}
value={this.props.value}
onChange={this.props._change}
/>
Inside parent component:
_Change(event, value){
//update the value here
}
<PanelDiv
heading='name'
inputType="text"
value={this.state.userData.name}
_change={this._change}
display={this.state.display[0]}
/>
If you pass value as a prop to TextField you can't change that text!
On Material-UI official documentation they have used defaultValue="default val" as a prop.
So I used defaultValue as a prop! It worked fine for me!
<TextField
type="text"
defaultValue={this.props.val}
onChange={handleChange}
/>
Had the same error. I was not able to key in anything and it was because there was no name props included. example:
<TextField
type="email"
name='email'/>
Look at this example - https://jsfiddle.net/y857yeLq/
You should define a function, which is handles of text change and pass it to onChange property. In this example I used state for storing current text field value. I see, that you use props for that, but the principle is the same - function should update props in your case.
const { TextField, MuiThemeProvider, getMuiTheme } = MaterialUI;
class SliderExampleControlled extends React.Component {
state = {
value: '',
}
render() {
console.log(this.state);
return (
<div style={{width: '50%', margin: '0 auto'}}>
<TextField
hintText="Type here"
value={this.state.value}
onChange={(e) => this.setState(e.target.value)}
/>
</div>
);
}
}
const App = () => (
<MuiThemeProvider muiTheme={getMuiTheme()}>
<SliderExampleControlled />
</MuiThemeProvider>
);
ReactDOM.render(
<App />,
document.getElementById('container')
);
I was running into this problem as well and I needed the onClick function to take more than just the event I also needed it to take the row number because my state had an array representing the rows in a table. I was able to do that using this chunk of code
onChange={(e) => this.nameChange(e.target.value, row.row_num)}
then in my function called nameChange I was able to update the value
The idea is to make a form that will validate the inputs and put all the logic for validating inside the MyForm component. so the only thing that comes from the onsubmit call is a error object and a object with the form values.
i would also like to validate when the user clicks on submit and when the user touched a field with onBlur.
i would like to keep all the logic in MyForm
The problem is that i'm trying to figure out how to change the InputGroup props from MyForm component and display validation errors for example.
how can i change to props of the childs when the user clicks the submit button or a field is touched and call the handleBlur in InputGroup.
also tell me is this is a wrong approach
i managed to change the props of InputGroup from MyForm at runtime.
this will change the label of every child.
children = React.Children.map(this.props.children, (child) =>
React.cloneElement(child, {
label: 'foo'
})
)
...
return (
<form onSubmit={this.validate}>
{children}
</form>
)
NewCustomerContainer:
class NewCustomerContainer extends Component {
handleSubmit(errors, customer) {
// if errors is empty do something with customer
}
render() {
return (
<div className={shared.flexRow}>
<div className={shared.flexColumn}>
<ColumnHeader
headerTitle="New Customer" />
<MyForm formHandler={this.handleSubmit}>
<InputGroup
type="text"
label="Customer Name"
placeholder="Customer Name"
name="customerName"
isRequired={true} />
<InputGroup
type="text"
label="Customer Number"
placeholder="Customer Number"
name="customerNumber" />
<InputGroup
type="text"
label="Customer Email"
placeholder="Customer Email"
name="customerEmail"
isRequired={true}
validateOption="email" />
</MyForm>
</div>
</div>
)
}
}
export default NewCustomerContainer
MyForm
export class MyForm extends Component {
validate(e) {
e.preventDefault()
children = React.Children.map(this.props.children, (child) =>
React.cloneElement(child, {
label: 'foo'
})
)
// handle validation login here
// then call the parent function with the errors and values
this.props.testForm(errors, values)
}
render() {
const children = React.Children.map(this.props.children, (child) =>
React.cloneElement(child, {
})
)
return (
<form onSubmit={this.validate}>
{children}
</form>
)
}
}
InputGroup:
export class InputGroup extends Component {
handleBlur() {
// handle blur here
}
render() {
return (
<div className={shared.formGroup}>
<label className={shared.formLabel}>{this.props.label}</label>
<input className={shared.formError} type={this.props.type} name={this.props.name} placeholder={this.props.placeholder} onBlur={this.handleBlur} />
<span className={shared.formError}>{this.props.foo}</span>
</div>
)
}
}
i'm very new to React and i know i can use redux-form or a other plugin, but i want to challenge myself and understand the concept how children works. there are plenty of examples out there but they do not answer my questions
thanks in advance.