I have the below common component
import PropTypes from 'prop-types';
import Input from '../component/Input'; //internal component
const CustomInput = props => {
const { label, updateInputField } = props;
return (
<Input
label={label}
changeField={updateInputField}
)
}
CustomInput.propTypes = {
label: PropTypes.string,
updateInputField: PropTypes.func
}
export default CustomInput;
Now i am using these component at several places like below
<CustomInput
label="401Balance"
updateInputField={inputFieldHandler}
/>
so this one works.. but there are some places where I am not using the updateInputField as a prop. for e.g.
<CustomInput
label="savingsBalance"
/>
How do i not pass the prop and ensure it doesnot fail. Can someone please suggest.
You could either set a default value to secure the case if no function was passed:
const { label, updateInputField = () => {} } = props;
or (probably a better approach) - create a separate function and add a simple condition:
const CustomInput = props => {
const { label, updateInputField } = props;
const fn = () => {
if (updateInputField) {
updateInputField();
}
};
return (
<Input
label={label}
changeField={fn}
/>
);
}
Related
I have existing component called TextField. I would like to use this component as base to create NumberField component. Reason is I don't want to type out exact same code when there are only minor tweaks in the NumberField component. There are 3 parts that I would like to change:
For interface, TextField has type = 'text' | 'search', but I would like to update it to type = 'text' for NumberField component.
I would like to add new prop inputMode = 'numeric' for NumberField.
I would like to update handleChange function for onChange prop.
So I have coded up the following.
TextField.tsx
import React, { useState } from "react";
export interface ITextFieldProps {
type?: "text" | "search";
onChange?: (value: string) => void;
required?: boolean;
}
export const TextField: React.FunctionComponent<ITextFieldProps> = React.forwardRef<
HTMLInputElement, ITextFieldProps>((props, ref) => {
const { type = "text", onChange, required = true } = props;
const [value, setValue] = useState("");
function handleChange(e: React.FormEvent<HTMLInputElement>) {
setValue(e.currentTarget.value);
onChange?.(e.currentTarget.value);
}
return (
<input
type={type}
value={value}
onChange={handleChange}
required={required}
/>
);
});
NumberField.tsx
import React, { useState } from "react";
import { ITextFieldProps, TextField } from "./TextField";
export interface INumberFieldProps extends ITextFieldProps {
type?: "text";
inputMode?: "numeric";
}
export const NumberField: React.FunctionComponent<INumberFieldProps> = React.forwardRef<
HTMLInputElement, INumberFieldProps>((props, ref) => {
const { inputMode = "numeric", onChange } = props;
const [value, setValue] = useState("");
function handleChange(e: React.FormEvent<HTMLInputElement>) {
const updatedValue = e.currentTarget.value.replace(/\D/g, "");
setValue(updatedValue);
onChange?.(updatedValue);
}
return (
<TextField inputMode={inputMode} onChange={handleChange} value={value} />
);
});
App.tsx
import "./styles.css";
import { TextField } from "./TextField";
import { NumberField } from "./NumberField";
export default function App() {
function onChange(e: any) {
console.log(e);
}
return (
<div className="App">
<NumberField onChange={(e) => onChange(e)} />
</div>
);
}
With this code when I go to chrome dev tools and check input element, I only see <input type="text" />, but I expect to see <input type="text" inputmode="numeric" />, since I added inputMode prop. Also with regex in the handleChange function, I should be able to only type numbers in the input, but I get "Cannot read properties of undefined (reading 'value')" error. What's the best way to solve this issue? Source: https://codesandbox.io/s/numberfield-component-ts-wohlok?file=/src/NumberField.tsx
Having a monaco-editor inside a React component:
<Editor defaultValue={defaultValue} defaultLanguage='python' onChange={onChangeCode} />
The defaultValue, the default code inside of the editor, is sent via props to the component:
const MyComponent = ({
originalCode
}: MyComponentProps) => {
const [defaultValue, setDefaultValue] = useState(originalCode);
When the user edits the code, onChange={onChangeCode} is called:
const onChangeCode = (input: string | undefined) => {
if (input) {
setCode(input);
}
};
My question is, how to reset the code to the original one when the user clicks on Cancel?
Initially it was like:
const handleCancel = () => {
onChangeCode(defaultValue);
};
but it didn't work, probably because useState is asynchronous, any ideas how to fix this?
Here is the whole component for more context:
import Editor from '#monaco-editor/react';
import { useState, useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { Button, HeaderWithButtons } from '../shared/ui-components';
import { ICalculationEngine } from '../../../lib/constants/types';
import { usePostScript } from '../../../lib/hooks/use-post-script';
import { scriptPayload } from '../../../mocks/scriptPayload';
import { editorDefaultValue } from '../../../utils/utils';
export interface ScriptDefinitionProps {
realInputDetails: Array<ICalculationEngine['RealInputDetails']>;
realOutputDetails: ICalculationEngine['RealInputDetails'];
originalCode: string;
scriptLibId: string;
data: ICalculationEngine['ScriptPayload'];
}
const ScriptDefinition = ({
realInputDetails,
realOutputDetails,
originalCode
}: ScriptDefinitionProps) => {
const [defaultValue, setDefaultValue] = useState(originalCode);
const [code, setCode] = useState(defaultValue);
const { handleSubmit } = useForm({});
const { mutate: postScript } = usePostScript();
const handleSubmitClick = handleSubmit(() => {
postScript(scriptPayload);
});
const handleCancel = () => {
onChangeCode(defaultValue);
};
const onChangeCode = (input: string | undefined) => {
if (input) {
setCode(input);
}
};
useEffect(() => {
setDefaultValue(editorDefaultValue(realInputDetails, realOutputDetails));
}, [realInputDetails, realOutputDetails, originalCode]);
return (
<div>
<HeaderWithButtons>
<div>
<Button title='cancel' onClick={handleCancel} />
<Button title='save' onClick={handleSubmitClick} />
</div>
</HeaderWithButtons>
<Editor defaultValue={defaultValue} defaultLanguage='python' onChange={onChangeCode} />
</div>
);
};
export default ScriptDefinition;
If you need the ability to change the value externally, you'll need to use the Editor as a controlled component by passing the value prop (sandbox):
For example:
const defaultValue = "// let's write some broken code 😈";
function App() {
const [value, setValue] = useState(defaultValue);
const handleCancel = () => {
setValue(defaultValue);
};
return (
<>
<button title="cancel" onClick={handleCancel}>
Cancel
</button>
<Editor
value={value}
onChange={setValue}
height="90vh"
defaultLanguage="javascript"
/>
</>
);
}
import React, { useState } from "react";
import Child from "./Child";
import "./styles.css";
export default function App() {
let [state, setState] = useState({
value: ""
});
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
import React from "react";
function Child(props) {
return (
<input
type="text"
placeholder="type..."
onChange={e => {
let newValue = e.target.value;
props.handleChange(newValue);
}}
value={props.value}
/>
);
}
export default Child;
Here I am passing the data from the input field to the parent component. However, while displaying it on the page with the h1 tag, I am able to see the latest state. But while using console.log() the output is the previous state. How do I solve this in the functional React component?
React state updates are asynchronous, i.e. queued up for the next render, so the log is displaying the state value from the current render cycle. You can use an effect to log the value when it updates. This way you log the same state.value as is being rendered, in the same render cycle.
export default function App() {
const [state, setState] = useState({
value: ""
});
useEffect(() => {
console.log(state.value);
}, [state.value]);
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
};
return (
<div className="App">
<h1>{state.value}</h1>
<Child handleChange={handleChange} value={state.value} />
</div>
);
}
Two solution for you:
- use input value in the handleChange function
let handleChange = input => {
setState(prevValue => {
return { value: input };
});
console.log(state.value);
};
use a useEffect on the state
useEffect(()=>{
console.log(state.value)
},[state])
Maybe it is helpful for others I found this way...
I want all updated projects in my state as soon as I added them
so that I use use effect hook like this.
useEffect(() => {
[temp_variable] = projects //projects get from useSelector
let newFormValues = {...data}; //data from useState
newFormValues.Projects = pro; //update my data object
setData(newFormValues); //set data using useState
},[projects])
I'm new to React and am tripping over this issue.
Have read couple of tutorials and questions here to find out about how Parent & Child Components should communicate. However, I am unable to get the data to populate the fields + make it editable at the same time. I'll try explain further in code below:
Parent Component:
...imports...
export default class Parent extends Component {
constructor(props) {
this.state = {
data: null
};
}
componentDidMount() {
API.getData()
.then((response) => {
this.setState({ data: response });
// returns an object: { name: 'Name goes here' }
})
}
render() {
return (
<Fragment>
<ChildComponentA data={this.state.data} />
<ChildComponentB data={this.state.data} />
</Fragment>
);
}
}
Input Hook: (source: https://rangle.io/blog/simplifying-controlled-inputs-with-hooks/)
import { useState } from "react";
export const useInput = initialValue => {
const [value, setValue] = useState(initialValue);
return {
value,
setValue,
reset: () => setValue(""),
bind: {
value,
onChange: event => {
setValue(event.target.value);
}
}
};
};
ChildComponent:* (This works to allow me to type input)
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput('');
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
ChildComponent Component:
(Trying to bind API data - Input still editable but data is still not populated even though it is correctly received.. The API data takes awhile to be received, so the initial value is undefined)
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput(props.data && props.data.name || '');
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
ChildComponent Component:
(Trying to use useEffect to bind the data works but input field cannot be typed..)
I believe this is because useEffect() is trigged every time we type.. and props.data.name is rebinding its original value
import { Input } from 'reactstrap';
import { useInput } from './input-hook';
export default function(props) {
const { value, setValue, bind, reset } = useInput(props.data && props.data.name || '');
useEffect(() => {
if(props.data) {
setValue(props.data.name);
}
});
return (
<Fragment>
<Input type="input" name="name" {...bind} />
</Fragment>
);
}
I can think of a few tricks like making sure it binds only once etc.. But I'm not sure if it is the correct approach. Could someone share some insights of what I could be doing wrong? And what should be the correct practice to do this.
To iterate, I'm trying to bind API data (which takes awhile to load) in parent, and passing them down as props to its children. These children have forms and I would like to populate them with these API data when it becomes available and yet remain editable after.
Thanks!
Basic way to create your Parent/Child Component structure is below, I believe. You don't need a class-based component for what you are trying to achieve. Just add an empty array as a second argument to your useEffect hook and it will work as a componentDidMount life-cycle method.
Parent component:
import React, {useState, useEffect} from 'react';
export default const Parent = () => {
const [data, setData] = useState({});
const [input, setInput] = useState({});
const inputHandler = input => setInput(input);
useEffect(() => {
axios.get('url')
.then(response => setData(response))
.catch(error => console.log(error));
}, []);
return <ChildComponent data={data} input={input} inputHandler={inputHandler} />;
};
Child Component:
import React from 'react';
export default const ChildComponent = props => {
return (
<div>
<h1>{props.data.name}</h1>
<input onChange={(e) => props.inputHandler(e.target.value)} value={props.input} />
</div>
);
};
I'm trying to set up a HOC in React to able to apply text selection detection to any Input component. However I seem to be missing something when I was trying to put it together.
I was following this article here on how to create a HOC:
https://levelup.gitconnected.com/understanding-react-higher-order-components-by-example-95e8c47c8006
My code (before the article looked like this):
import { func } from 'prop-types';
import React, { PureComponent } from 'react';
import { Input } from 'reactstrap';
class SelectableInput extends PureComponent {
handleMouseUp = () => {
const selection = window.getSelection();
if (selection) {
this.props.onSelectionChanged(selection.toString());
}
};
render() {
// eslint-disable-next-line
const { onSelectionChanged, ...rest } = this.props;
return <Input onMouseUp={this.handleMouseUp} {...rest} />;
}
}
SelectableInput.propTypes = {
onSelectionChanged: func
};
export default SelectableInput;
And I was using it like this:
render() {
return (
<SelectableInput
type="textarea"
name="textarea-input"
value={'This is some txt'}
onSelectionChanged={onTextSelectionChanged}
id="textarea-input"
onChange={e => this.onPageDataChanged(e)}
dir="rtl"
rows="14"
placeholder="Placeholder..."
/>
);
}
After reading the article I changed the above code to:
const SelectableInput = WrappedInput => {
class SelectableInputHOC extends PureComponent {
handleMouseUp = () => {
const selection = window.getSelection();
if (selection) {
this.props.onSelectionChanged(selection.toString());
}
};
render() {
// eslint-disable-next-line
const { onSelectionChanged, ...rest } = this.props;
return <WrappedInput onMouseUp={this.handleMouseUp} {...rest} />;
}
}
SelectableInputHOC.propTypes = {
onSelectionChanged: func
};
};
export default SelectableInput;
My question is how do I actually go about using it now in a render() function?
Thank you for your advance for your help.
SelectableInput is a function that returns a function that takes a component as a parameter and returns another component. You can use it like this:
const ResultComponent = ({...props}) =>
SelectableInput({...props})(YourParamComponent);
Then render ResultComponent wherever you want.
Here you have an example of using a HOC and passing props to it:
https://jsfiddle.net/58c7tmx2/
HTML:
<div id="root"></div>
JS
const YourParamComponent = ({ name }) => <div>Name: {name}</div>
const SelectableInput = ({...props}) =>
WrappedInput => <WrappedInput {...props} />
const ResultComponent = ({...props}) =>
SelectableInput({...props})(YourParamComponent);
const App = () => <ResultComponent name="textarea-input" />
ReactDOM.render(
<App />,
document.getElementById('root')
)