My question, while related to a specific use case, is more about a general concept.
I use a library Informed for form management and it uses higher order components to allow creating custom fields. Here's the HOC:
import React from 'react';
import Field from '../components/Field';
function getDisplayName(WrappedComponent) {
return WrappedComponent.displayName || WrappedComponent.name || 'Component';
}
const asField = Component => {
const displayName = getDisplayName(Component);
Component.displayName = 'Wrapper';
const AsField = props => <Field component={Component} {...props} />;
AsField.displayName = displayName;
return AsField;
};
export default asField;
I took this from the official repo. So here's how it's supposed to be used:
// Definition
const CustomInput = asField(
({
fieldState: {value},
fieldApi: {setTouched, setValue},
forwardedRef
}) =>
<input onFocus={() => setTouched()} onChange={e => setValue(e.target.value)}
ref={forwardedRef} value={!value && value !== 0 ? '' : value} />
)
// Usage
<CustomInput field="firstName" />
So in my case, first, I'm not a big fan of higher order components, and I don't like specifying the field name using field prop, I prefer name. So with that in mind I create a component that duplicates the functionality of the asField HOC and uses name instead of field:
//#flow
import React from 'react'
import {asField} from 'informed'
export const Field = ({children, name, ...props}) => React.createElement(
asField(children),
{
field: name,
...props
}
)
Then I use it like this:
// Definition
const CustomInput = ({name}) =>
<Field field={name}>
{({
fieldState: {value},
fieldApi: {setTouched, setValue},
forwardedRef
}) =>
<input onFocus={() => setTouched()} onChange={e => setValue(e.target.value)}
ref={forwardedRef} value={!value && value !== 0 ? '' : value} />
}
</Field>
// Usage
<CustomInput name="firstName" />
This works, but interestingly, it causes something in the state to endlessly update something in the state which blocks the whole UI. The documented way works just fine.
I cannot find anything in my component. The component that asField returns is the one that accepts the field prop, it gets it through my function along with other props.
What could be the issue here? Did I miss something or is there a problem with the core concept?
you are using the wrong syntax for createElement
instead of createElement({}) use createElement(comp, props, children)
I have made a working code for your case
import React from "react";
import ReactDOM from "react-dom";
import "./styles.css";
class HoC extends React.Component {
constructor(props) {
super(props);
}
render() {
return <App fromHOC={this.props.fromHOC} field={this.props.field} />;
}
}
const Field = ({ children, name, ...props }) =>
React.createElement(HoC, { field: name, ...props }, children);
export default function App(props) {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<div>
I am getting props {props.fromHOC} <div />
<div> The name is {props.field} </div>
</div>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<Field name="New name" fromHOC="YES!!!" />, rootElement);
Full code here
For your updated question, I have a probable solution; see it it works
import React from "react";
import ReactDOM from "react-dom";
import { Text, Form } from "informed";
const Func = props => {
console.log(props);
return (
<Form id="simple-form">
<label htmlFor="name-field">First name:</label>
{/*(<Text field="name" id="name-field" /> */}
<Text {...props} />
<button type="submit">Submit</button>
</Form>
);
};
const HOC = ({ children, name, ...props }) => {
return React.createElement(Func, { ...props, field: name }, children);
};
const rootElement = document.getElementById("root");
ReactDOM.render(<HOC name="name" id="name-field" />, rootElement);
Related
I am trying to write a reusable component for the input field for URL
import React from 'react';
const InputURL = ({ nam, ...rest}) => {
return (
<>
<input
name={name}
{...rest}
/>
</>
);
}
export default InputURL;
I want when user input facebook.com then in onChange it should return with http like it will be https//facebook.com
I will use only this <InputURL onChange={(e) => console.log(e)} />
Can anyone please tell me how can i override onChange to replace non http to http?
Note: I want to write the condition once in the InputURL component only, dont want to write everywhere where i will use it
onChange event may trigger many time so it would be better if you make your logic to add "http" on blur event like this.
import React from 'react';
const InputURL = ({ name, callback, ...rest }) => {
const [value, setValue] = React.useState('');
return (
<>
<input
onBlur={() => callback(`http://${value}`)}
name={name}
onChange={(e) => setValue(e.target.value)}
{...rest}
/>
</>
);
};
export default InputURL;
and in the component where you are using this component find parameter coming in callback.
for example I am logging here.
import React from 'react';
import './style.css';
import InputURL from './InputURL';
export default function App() {
const callback = (val) => {
console.log('>>>>>', val);
};
return (
<div>
<InputURL callback={callback} name="My Input" />
</div>
);
}
See SS hope you are looking the same.
import React from 'react';
const InputURL = ({ nam, prefix, ...rest}) => {
return (
<>
<input
name={nam}
id={"input-"+nam}
onChange={(e) => {
const value = e.target.value;
const input = document.querySelector('#input-'+nam);
if(value.substring(0,prefix.length) != prefix)
{
input.value = prefix;
}
}}
{...rest}
/>
</>
);
}
export default InputURL;
This should work, check it out the reference of Substring here.
I would like to implement Algolia search with Ant Design Autocomplete. But I get Cannot read property 'focus' of null error when I try to extract the SearchInput component (without extraction, i. e. when I leave it in the same file, it works fine). Here is the working code:
import React, { useState } from 'react'
import { AutoComplete, Input } from 'antd'
const SearchAutocomplete = connectAutoComplete(
({ hits, currentRefinement, refine }) => {
...
return (
<AutoComplete
options={options}
onSelect={onSelect}
onSearch={handleSearch}
open={open}
>
<Input
value={currentRefinement}
onChange={e => refine(e.currentTarget.value)}
/>
</AutoComplete>
);
}
);
But when I move Input to a separate component like this it doesn't work:
import React, { useState } from 'react'
import { AutoComplete } from 'antd'
import SearchInput from './SearchInput'
const SearchAutocomplete = connectAutoComplete(
({ hits, currentRefinement, refine }) => {
...
return (
<AutoComplete
options={options}
onSelect={onSelect}
onSearch={handleSearch}
open={open}
>
<SearchInput value={currentRefinement} onChange={e => refine(e.currentTarget.value)}/>
</AutoComplete>
);
}
);
And the SearchInput component itself:
import React from 'react'
import { Input } from 'antd'
const SearchInput = props => {
const { value, onChange} = props;
return (
<Input
value={value}
onChange={onChange}
/>
)
}
Here is the link to codesandbox with the extracted component. How can I fix this error?
Adding React.forwardRef() to SearchInput solved the issue:
const SearchInput = React.forwardRef((props, ref) => {
const { onChange } = props;
return (
<Input.Search
onChange={onChange}
ref={ref}
/>
)
})
I'm trying to get a reaction from onInputChange function when I use it with onChange on input, but I didn't get why it's not console.log the value. Any help?
App.js
import React, { Component } from 'react';
import Output from './Components/Output';
import NumberInput from './Components/NumberInput';
import './App.css';
class App extends Component {
constructor() {
super()
this.state = {
decimal: 0,
inputString: ''
}
}
onInputChange = (event) => {
console.log(event.target.value)
}
render() {
const { decimal, inputString } = this.state;
return (
<fragment>
<h1>Binary to Decimal convertor</h1>
<NumberInput InputChange={this.onInputChange} />
<button>Submit</button>
<Output string={inputString}/>
</fragment>
);
}
}
export default App;
NumberInput component:
import React from 'react';
const NumberInput = ({ inputChange }) => {
return (
<fragment>
<input type='search' onChange={inputChange}></input>
</fragment>
)
}
export default NumberInput;
InputChange should be camelcase
change
<NumberInput InputChange={this.onInputChange} />
to
<NumberInput inputChange={this.onInputChange} />
you may also need to bind
<NumberInput inputChange={(e) => this.onInputChange(e)} />
Need some changes
Change your InputChange={this.onInputChange}
to inputChange={this.onInputChange} in App component.
because you are accessing inputChange in your NumberInput component.
And also change your fragment tag as well because you used fragment instead of Fragment tag.
Either use <Fragment>, <React.Fragment> or <> in NumberInput, App component like-
<Fragment>
<input type='search' onChange={inputChange}></input>
</Fragment>
I'm using the React Color lib to create a custom component in my application. My child component is composed by two components of React Colors: CirclePicker and ChromePicker. Both share the colorPicker state variable as value. So, if one change his value, the other will change too.
As you can see on my Child Component, I put one invisible input that shares the same colorPicker value (I don't know if it's the right wat to do it). And my goal here is: when the input change, I could do something, for example: alert('COLOR CHANGED') (this is on my code in handleOnChange function)
Child component:
import React from 'react';
import { CirclePicker, ChromePicker } from 'react-color';
import { Icon } from './../Icon/Icon';
import './color-picker.scss';
export interface ColorPickerProps {
onChange: (value: string) => void;
}
export function ColorPicker(props: ColorPickerProps) {
const [colorPicker, showColorPicker] = React.useState(false);
const [finalColor, changeColor] = React.useState('#fff');
function handleOnChange() {
alert('COLOR CHANGED');
props.onChange(finalColor);
}
return (
<div className="relative-position">
<input type="text" value={finalColor} onChange={() => handleOnChange} style={{display: "none"}}/>
<CirclePicker
color={finalColor}
onChangeComplete={colore => changeColor(colore.hex)}
colors={[
'#004de8',
'#2ecc71',
'#ff9300',
'#62708b',
'#ff003a',
'#20396a'
]}
circleSize={24}
></CirclePicker>
<a
className="btn-select-color"
onClick={() => showColorPicker(!colorPicker)}
>
<Icon viewIcone="ArrowDropDown" style={{ margin: '5px' }} />
</a>
{colorPicker ? (
<span className="chrome-picker">
<ChromePicker
color={finalColor}
onChangeComplete={colore => changeColor(colore.hex)}
disableAlpha={true}
/>
</span>
) : (
''
)}
</div>
);
}
Situations:
I've tried more or less how they've explained here, but no matter what I do, the handleOnChange function is not called and I can't see the alert.
Furthermore, my goal is to use this component in parent, more or less like this:
**Parent component: **
<ColorPicker onChange={e => this.state.color = e} />
So, on this way, I could have on parent state the color picked.
I can't get nothing in the Child function, neither having the state updated on the parent component.
Could someone help me? I'm a new user of React :(
Explanation
The input's value is linked to state. So when state changes, the value changes. BUT, no event was fired on the input. You were trying to use a react event handler on an input that wasn't firing any events; meaning handleOnChange never got called so props.onChange never got called...
Solution
Use useEffect to listen for when the input's value/state value changes. If you use useRef, you can stop your props.onChange from running when the component mounts. Check out the DEMO.
import * as React from "react";
import { FunctionComponent, useState, useEffect, useRef } from "react";
import { render } from "react-dom";
import { CirclePicker, ChromePicker } from "react-color";
const colors = [
"#004de8",
"#2ecc71",
"#ff9300",
"#62708b",
"#ff003a",
"#20396a"
];
export interface ColorPickerProps {
onChange: (value: string) => void;
}
const ColorPicker: FunctionComponent<ColorPickerProps> = ({ onChange }) => {
const [colorPicker, showColorPicker] = useState(false);
const [finalColor, changeColor] = useState("#fff");
const componentMounted = useRef(true);
useEffect(() => {
if (componentMounted.current) {
componentMounted.current = false;
console.log(
"Don't run props.onChange when the component mounts with useRef"
);
} else {
onChange(finalColor);
alert("finalColor changed via useEffect");
}
return () => undefined;
}, [finalColor]);
return (
<div className="relative-position">
<input type="text" value={finalColor} style={{ display: "none" }} />
<CirclePicker
color={finalColor}
onChangeComplete={colore => changeColor(colore.hex)}
colors={colors}
circleSize={24}
/>
<br />
<button onClick={() => showColorPicker(!colorPicker)}>click me</button>
{colorPicker && (
<span className="chrome-picker">
<ChromePicker
color={finalColor}
onChangeComplete={colore => changeColor(colore.hex)}
disableAlpha={true}
/>
</span>
)}
</div>
);
};
const rootElement = document.getElementById("root");
render(
<ColorPicker
onChange={() => {
console.log("onChange");
}}
/>,
rootElement
);
useCallback may be a good option, refer to this QA, and document
import React from "react";
import ReactDOM from "react-dom";
import { CirclePicker, ChromePicker } from "react-color";
import "./styles.css";
function App(props) {
const [colorPicker, showColorPicker] = React.useState(false);
const [finalColor, changeColor] = React.useState("#fff");
const handleColorChange = React.useCallback(console.log("Work via callback"));
return (
<div className="relative-position">
<input value={finalColor} onChange={console.log("Work directly")} />
<input value={finalColor} onChange={handleColorChange} />
<CirclePicker
color={finalColor}
onChangeComplete={colore => changeColor(colore.hex)}
colors={[
"#004de8",
"#2ecc71",
"#ff9300",
"#62708b",
"#ff003a",
"#20396a"
]}
circleSize={24}
/>
{colorPicker ? (
<span className="chrome-picker">
<ChromePicker
color={finalColor}
onChangeComplete={colore => changeColor(colore.hex)}
disableAlpha={true}
/>
</span>
) : (
""
)}
<p>{finalColor}</p>
</div>
);
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
I am attempting to add additional properties onto the return type of Reacts forwardRef function. In this case it's the types property. I have it working, but as I am relatively new to Typescript I'm sure there's a better way of doing it (or not 100% sure my implementation is correct).
import * as React from "react";
import {
forwardRef,
ReactNode,
ForwardRefExoticComponent,
PropsWithoutRef,
RefAttributes
} from "react";
import { render } from "react-dom";
import "./styles.css";
enum Types {
button = "button",
a = "a"
}
interface Props extends React.ComponentPropsWithoutRef<"div"> {
children?: ReactNode;
className?: string;
type?: Types;
}
interface Button<T> extends ForwardRefExoticComponent<T> {
types: typeof Types;
}
const Button = forwardRef<HTMLDivElement, Props>(
({ children, className, type, ...props }, ref) => {
return (
<div {...props} className={className} ref={ref}>
{children}
<div>{type}</div>
</div>
);
}
) as Button<PropsWithoutRef<Props> & RefAttributes<HTMLDivElement>>;
Button.types = Types;
function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<Button title="foo" type={Button.types.button}>
Hello
</Button>
</div>
);
}
const rootElement = document.getElementById("root");
render(<App />, rootElement);
Is there a cleaner way of extending the returned type from forwardRef to add the types property?
Working sandbox here: https://codesandbox.io/s/cocky-ishizaka-2s8vy
I'm not sure to understand what you want to do using forwardRef like this.
But a React ref is actually a ref to a HTML DOM element.
So, you can use tagName to get the type of tag of the inner component having his ref forwarded (to his parent).
EDIT 2 If you want to add some information to a ref, add it in the attributes, for example:
<div ref={ref} data-type="a">something</div>
Once you have the ref, get the attribute with ref.getAttribute("data-type")
Making the component to forward back his ref:
const Input = React.forwardRef((props, ref) => <input type="text" ref={ref} />);
Getting the type:
handleSubmit = e => {
e.preventDefault();
this.setState({
value: this.inputRef.current.value,
typeOfRef: this.inputRef.current.tagName
});
};
Displaying it:
render() {
return (
<div>
<h1>React Ref - createRef</h1>
<h3>Value: {this.state.value}</h3>
<h3>Type: {this.state.typeOfRef}</h3>
<form onSubmit={this.handleSubmit}>
<Input ref={this.inputRef} />
<button>Submit</button>
</form>
</div>
);
}
}
https://codepen.io/bonjour123/pen/bGGQQKG
It should be noted that createRef cannot (can, see edit below) be used in functional components, which, with hooks, are the new recommended way to do things in React. It can nevertheless easily be replaced by a callback passed to the children, and called at the ref creation.
Components declaration
const Input = ({ returnBackRef }) => <input type="text" ref={returnBackRef} />;
const Button = ({ returnBackRef, text, handler }) => (
<button onClick={handler} ref={returnBackRef}>{text}</button>
);
The app (functional component):
const App = () => {
let [refTo1, setRefTo1] = React.useState({});
let [refTo2, setRefTo2] = React.useState({});
let [info, setInfo] = React.useState({
val1:"",
tag1:"",
val2:"",
tag2:""
});
const handleSubmit = () => {
console.error(info);
setInfo({
val1:refTo1.value,
tag1:refTo1.tagName,
val2:refTo2.textContent,
tag2:refTo2.tagName
});
};
return (
<div >
<h1>React Ref - createRef</h1>
<h3>Value1: {info.val1}</h3>
<h3>Type1: {info.tag1}</h3>
<h3>Value2: {info.val2}</h3>
<h3>Type1: {info.tag2}</h3>
<Input returnBackRef={setRefTo1} />
<Button
handler={handleSubmit}
text={"Submit"}
returnBackRef={setRefTo2}
/>
</div>
);
};
https://codepen.io/bonjour123/pen/VwwVqYR
EDIT My bad, you can actually use useRef, which is like createRef for functional components. But it's still cool to see how to implement it on another way, isn't it ^^