How do I reference a input element from higher order component - javascript

I would like to access the input element from my HOC. How can I achieve this?
const TextInput = (props) => {
const allowed = ['readOnly','tabIndex','placeholder'];
const filteredProps = filterProps(props,allowed);
return (
<div>
<label>{props.field.Name}</label>
<input type="text" ref={props.inputRef} key={props.field.Id} className="form-control" id={props.field.Id} name={props.field.Name}
value={props.value}
onChange={props.onChange}
onKeyDown={props.onKeyDown}
{...filteredProps}
/>
</div>
);
}
TextInput.propTypes = {
fieldMetadata: PropTypes.object,
isValid: PropTypes.bool
}
export default withInputMask(withRequired(withReadOnly(withMinMax(withHidden(TextInput)))));
I have tried a few things but this is the latest attempt.
Inside the withInputMask render method I have inserted the following.
return (
<div>
<Component {...this.props} inputRef={el=> this.inputElement=el} isValid={isValid} onChange={this.onChange} placeholder={inputMaskPattern} />
{hasErrors && <span className={hasErrors}>{error}</span>}
</div>
);
}
}
};
export default withInputMask;
when I open the react dev tools and click on withInputMask component this is what I see.

Related

React strange behavior when calling as function and when calling as component, focus on input disapears after typing one character

I am react beginner and I would like to create 2 inputs for first and last name.
When i call <Item/> the focus on input disappears after typing one character, but when i call {Item()} everything works fine as expected. It looks like some strange behavior to me and my question is why? Any ideas?
const { useState } = React;
function Item() {
const [firstName, setFirstName] = useState("John");
const [lastName, setLastName] = useState("Rambo");
function HandleFirstNameChange(event) {
setFirstName(event.target.value);
}
function HandleLastNameChange(event) {
setLastName(event.target.value);
}
// display
function Display(props) {
return (
<div>
{firstName}, {lastName}
</div>
);
}
// edit
function Edit(props) {
return (
<div>
<form>
<input
type="text"
value={firstName}
onChange={HandleFirstNameChange}
/>
<input type="text" value={lastName} onChange={HandleLastNameChange} />
</form>
</div>
);
}
return (
<div>
<Display />
{/* here after typing one character focus of input disappears */}
<Edit />
{/* here everything works fine as expected */}
{Edit()}
{/* whyyyy?? */}
</div>
);
}
ReactDOM.render(<Item />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
This is because you are defining the Edit component inside the Item component and so on every render, Edit function will be defined again and because of that react will try and replace it in the DOM tree on every render.
When you are calling the function, you are not actually using the Edit function as a React component. The below code will work just fine.
import React, { useState } from "react";
// display
function Display(props) {
const { firstName, lastName } = props;
return (
<div>
{firstName}, {lastName}
</div>
);
}
// edit
function Edit(props) {
const {
firstName,
HandleFirstNameChange,
lastName,
HandleLastNameChange
} = props;
return (
<div>
<form>
<input type="text" value={firstName} onChange={HandleFirstNameChange} />
<input type="text" value={lastName} onChange={HandleLastNameChange} />
</form>
</div>
);
}
function Item() {
const [firstName, setFirstName] = useState("John");
const [lastName, setLastName] = useState("Rambo");
function HandleFirstNameChange(event) {
setFirstName(event.target.value);
}
function HandleLastNameChange(event) {
setLastName(event.target.value);
}
return (
<div>
<Display firstName={firstName} lastName={lastName} />
{/* here after typing one character focus of input disappears */}
<Edit
firstName={firstName}
lastName={lastName}
HandleFirstNameChange={HandleFirstNameChange}
HandleLastNameChange={HandleLastNameChange}
/>
</div>
);
}
export default Item;

ref.current is null in gatsby react app when trying to execute recaptcha

I am trying to use this https://react-hook-form.com/get-started npm package with this package https://www.npmjs.com/package/react-google-recaptcha in gatsby and react. I want to use the invisible recaptcha it looks like I have to execute the recaptcha which I am trying to do by creating a react ref but it says the rec.current is null, Not quote sure what to do. The onSubmit function is where I am getting the null result, I was assuming I would be able to fire the captcha here and then get back the captcha value to later send off to google in my lambda function for verification.
Thanks ahead of time
Here is my code thus far
import React, { useState } from "react"
import Layout from "../components/layout"
import Img from "gatsby-image"
import { graphql, Link } from "gatsby"
import { CartItems } from "../components/cart"
import { useForm } from "react-hook-form"
import ReCAPTCHA from "react-google-recaptcha"
const StoreDetails = ({ data }) => {
const { register, handleSubmit, watch, errors } = useForm()
const recaptchaRef = React.createRef()
const onSubmit = data => {
console.log(recaptchaRef)
recaptchaRef.current.execute() //this shows up null
}
function onChange(value) {
console.log("Captcha value:", value)
}
function error(value) {
alert(value)
}
return (
<>
{data.allSanityProducts.edges.map(({ node: product }, i) => {
return (
<React.Fragment key={i}>
<Item>
<Img
fluid={product.featureImage && product.featureImage.asset.fluid}
/>
<div>
...
<form onSubmit={handleSubmit(onSubmit)}>
{/* register your input into the hook by invoking the "register" function */}
<input name="example" defaultValue="test" ref={register} />
{/* include validation with required or other standard HTML validation rules */}
<input
name="exampleRequired"
ref={register({ required: true })}
/>
{/* errors will return when field validation fails */}
{errors.exampleRequired && (
<span>This field is required</span>
)}
<ReCAPTCHA
className="captchaStyle"
sitekey="obsf"
onChange={onChange}
onErrored={error}
badge={"bottomright"}
size={"invisible"}
ref={recaptchaRef}
/>
<input type="submit" />
</form>
</div>
</Item>
</React.Fragment>
)
})}
{close && <CartItems />}
</>
)
}
const WithLayout = Component => {
return props => (
<>
<Layout>
<Component {...props} />
</Layout>
...
</>
)
}
export default WithLayout(StoreDetails)
export const query = graphql`
query StoreDeatailsQuery($slug: String!) {
...
}
`
You are never populating the reference with any value. Initially is set to null in:
const recaptchaRef = React.createRef()
You have to wait for the Google response to fill the recaptchaRef with a value. In other words, you need to use a promise-based approach to fill it using an executeAsync() and using an async function:
const onSubmit = async (data) => {
const yourValue = await recaptchaRef.current.executeAsync();
console.log(yourValue)
}
You can check for further details about the props exposed in react-google-recaptcha documentation.

How can I test functions as child in React? The snapshot is

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.

React: array as props shows as undefined

I'm trying to pass an array called myitems as props to a child component, but I get an error saying that options is undefined in the Child component. Not sure what's going on here. Any help will be highly appreciated.
Child component:
import React from 'react';
const Dropdown = ({className, options}) => {
return (
<>
<select className={className}>
{options.map((el,i) => (<option key={i}>{el.type}</option>))}
</select>
</>
)
}
export default Dropdown;
Parent component:
import React from 'react';
import Dropdown from './Dropdown'
const BudgetInput = ({ descValue, budgetValue, onDescChange }) => {
const myItems = [{ type: '+' }, { type: '-' }];
return (
<>
<Dropdown
className="add__type"
options={myItems}
/>
<input
type="text"
className="add__description"
placeholder="Add description"
value={descValue}
onChange={onDescChange}
/>
<input
type="number"
className="add__value"
placeholder="Value"
value={budgetValue}
//onChange={}
/>
<Dropdown
className="add__category"
/>
<button onClick={handleInput}>Enter</button>
</>
)
}
export default BudgetInput;
You're not passing an options prop to the second Dropdown instance, which is why you're getting the error
<Dropdown
className="add__category"
/>

Store Values from Material UI's form in TypeScript

What's the best way to store values typed into the text fields here?
const AddUserPage = () => (
<div>
<PermanentDrawerLeft></PermanentDrawerLeft>
<div className='main-content'>
<form className="ROOT" noValidate autoComplete="off">
<TextField id="standard-basic" label="Standard" />
</form>
</div>
</div>
);
export default AddUserPage;
I want to find a way such that I can use the stored values in my GraphQL mutations as well, without having to modify the const() structure of my page. I don't want to use the Class Component Extend or function structure here.
What is your const() structuremakes:
=> (This is the auto return syntax.)
If you want to store/reuse your value, you will have to define some state/variable to store the data.
You can also do it in upper component like:
import React, { useState } from "react";
const Parent = props => {
const [state, setState] = useState({ text: "" });
return <AddUserPage value={state.text} onChange={e => setState(prev => ({ ...prev, text: e.target.value || "" }))} />
}
const AddUserPage = ({ value = "" , onChange }) => (
<div>
<PermanentDrawerLeft></PermanentDrawerLeft>
<div className='main-content'>
<form className="ROOT" noValidate autoComplete="off">
<TextField id="standard-basic" value={value} onChange={onChange} label="Standard" />
// value, and Onchange comes from an upper component
</form>
</div>
</div>
);

Categories