How can I make this react input component idiomatic? - javascript

Here's a react input component:
function Input({ value, setValue }) {
return (
<div>
<input value={value} onChange={event => setValue(event.target.value)} />
<button onClick={() => setValue(value.toUpperCase())}>Capitalize</button>
</div>
);
}
It's just a vanilla input component together with a button that capitalizes the input's value. It's meant to be controlled by some parent component:
function Parent() {
let [value, setValue] = useState("");
return <Input value={value} setValue={setValue} />;
}
This works fine, but it's not idiomatic. To be useable as a "drop-in replacement" for a vanilla input component, it should take an onChange prop, not setValue, the relevant difference being that onChange takes a synthetic event as an argument while setValue takes a string. (I'd like the presence of the capitalize button to be "opaque" to a developer using this Input component.)
I tried to idiomaticize this (see snippet below) by having the input element fire off a change event when the button is clicked, but the this doesn't cause onChange to execute. (I assume that this is due to details of react's synthetic event system that I don't understand. I browsed a bunch of posts on this topic, but couldn't get the ideas I found to work.)
function AnotherInput({ value, onChange }) {
let input = useRef();
let handleClick = () => {
input.current.value = value.toUpperCase();
var event = new Event("change" /* also tried "input" */, {
bubbles: true
});
input.current.dispatchEvent(event); // onChange doesn't fire!
};
return (
<div>
<input value={value} ref={input} onChange={onChange} />
<button onClick={handleClick}>Capitalize</button>
</div>
);
}
Also, I feel I shouldn't have to use a ref here because I don't want to modify the DOM directly; I just want to change the value in the controlling parent component.
Here's a CodePen.

I made it work by simulating the event Object on the Capitalize Button.
Parent Component:
function Parent() {
let [value, setValue] = useState("");
return <Input value={value} onChange={(e) => setValue(e.target.value)} />;
}
Input Component:
EDITED: I've managed to came up with a more elegant solution to the Input Component:
function Input({ value, onChange: inheritedOnChange }) {
return (
<div>
<input value={value} onChange={inheritedOnChange} />
<button value={value.toUpperCase()} onClick={inheritedOnChange}>Capitalize</button>
</div>
);
}
Note that i renamed the onChange prop to inheritedOnChange just for readability purposes. Preserving the onChange name at the destructuring should still work.

Related

Is it possible for my custom input not to work in shadow-dom?

I have a very simple react component with input:
const Search = ({ onchange, t }) => {
const [value, setValue] = useState('');
useEffect(() => {
console.log('value', value);
}, [value]);
return (
<div className={'user-search'}>
<input
placeholder={t('placeholder')}
className={'search-input'}
onChange={(e) => setValue(e.target.value)}
type="text"
/>
<div className={'search-icon'}>
<icon className={'icon'} size={'normal'} name="search" />
</div>
</div>
);
};
At the same time, I use a library with client components - most of them create a shadow-root in the DOM. Nevertheless, I can drop my components inside them via {children} or sometimes props.
Well, and I have a problem with this Search component: when I use it loosely anywhere in the project I get a console.log with "value" after typing something in the input. On the other hand, when I put it into the component with modal and accordion (from the client library) - input completely stops working and doesn't respond.
Could the non-functioning input have something to do with shadow-dom? Or do I need to provide more information?

Impossible to update state while typing in input in fresh

I am new using Fresh by Deno, and I am trying to update the state of a Preact component while typing text into an input.
export default function Calculous(props: CalculousProps) {
const [input, setInput] = useState<string>('');
const onSubmit = (event: h.JSX.TargetedEvent<HTMLFormElement>) => {
event.preventDefault();
console.log(input);
}
useEffect(() => {
console.log(input);
}, [input])
return (
<form onSubmit={onSubmit}>
<input type="text" value={input} onChange={(e) => setInput(e.target.value)} />
<button disabled={isNaN(Number(input))} type="submit">Next</button>
</form>
);
}
However, when I type text on my component, it doesn't re-render until I unfocus the input. Do you know how to trigger re-rendering to update my state ?
Thanks for your help
You should generally use onInput instead of onChange when using Preact with an input element.
React chose to use the name onChange for the event listener callback property for both the change and input events. Preact's API is more semantic and better aligned with the spec in this case.

React how to re-render component only when the user clicks a button

I tried my best to search for a similar question before posting. I've got a Summary component in my project that accepts three user selected props (part #, start date, and end date), calls an API, then displays the fetched data. My problem is that the component re-renders every time the user changes one of the parameters (e.g. picks a new start date).
Ideally, the user would instead click an "Apply" button that would re-render the component using the set of three props. I tried using React.useRef() to create a reference to the component that I would use to update the Summary's state in a button's onClick event but no luck. I would greatly appreciate any advice on how to structure this situation. I'm editing the question to provide an extremely simple example below.
This is a sample application with an App.js and a Summary.jsx component. The code for App.js is as follows:
import React from "react";
import Summary from "./Components/Summary";
function App() {
const [input1, setInput1] = React.useState("");
const [input2, setInput2] = React.useState("");
const [input3, setInput3] = React.useState("");
return (
<>
<input
type="text"
id="input1"
onChange={(e) => setInput1(e.target.value)}
/>
<input
type="text"
id="input2"
onChange={(e) => setInput2(e.target.value)}
/>
<input
type="text"
id="input3"
onChange={(e) => setInput3(e.target.value)}
/>
<button
type="button"
onClick={() => {
alert("button has been clicked.");
}}
>
Apply
</button>
<Summary i1={input1} i2={input2} i3={input3} />
</>
);
}
export default App;
The code for Summary.jsx (contained in a Components folder) is as follows:
import React from "react";
const Summary = (props) => {
return (
<h1>{`Input 1: ${props.i1} Input 2: ${props.i2} Input 3:
${props.i3}`}</h1>
);
};
export default Summary;
You can see that as the user types into any of the inputs, it automatically re-renders the components as the state changes and thus the props that are supplied to the Summary component. Ideally, I would like no change to occur until the user hits the Apply button (I just supplied a bogus alert message as the onClick functionality for now).
If you don't want the Summary component to be re-rendered every time the parent component changes, I suggest using conditional rendering
Have a state isSubmited that defaults False, and set to True when user clicks Apply
Only render Summary when isSubmmited is True. If false, renders nothing (null)
If you want to switch isSummited back, pass a handler function setBack = () => setSubmited(false) as a props to the appropriate component
Something like this:
// App.js
function App() {
const [input1, setInput1] = React.useState("");
const [input2, setInput2] = React.useState("");
const [isSubmitted, setSubmitted] = React.useState(false);
return (
<>
<input
type="text"
id="input1"
value={input1}
onChange={(e) => setInput1(e.target.value)}
/>
<input
type="text"
id="input2"
value={input2}
onChange={(e) => setInput2(e.target.value)}
/>
<button
type="button"
onClick={() => {
alert("button has been clicked.");
setSubmitted(true);
}}
>
Apply
</button>
{/* Ternary operator */}
{isSumitted ? (
<Summary
i1={input1}
i2={input2}
afterDataFetch={() => setSubmitted(false)}
/>
) : null}
</>
);
}
// Summary.js
function Summary(props) {
const { i1, i2, i3, afterDataFetch } = props;
useEffect(() => {
fetchData();
// This will trigger 'setSubmitted(false)'
afterDataFetch();
});
}
Edit: As per request, to implement "keeping the old state and only send new state to Summary when click Submit", I have come up with a solution:
Besides the 3 input states, I also have a data state that is responsible for keeping the old states of the individual input fields (states from the previous Submit)
Therefore, the data state will only get updated when user clicks Submit
// App.js
function App() {
const initial = {
input1: "",
input2: "",
};
const [input1, setInput1] = useState("");
const [input2, setInput2] = useState("");
const [data, setData] = useState(initial);
return (
<>
<input
type="text"
id="input1"
value={input1}
onChange={(e) => setInput1(e.target.value)}
/>
<input
type="text"
id="input2"
value={input2}
onChange={(e) => setInput2(e.target.value)}
/>
<button
type="button"
onClick={() => {
setData({
input1: input1,
input2: input2,
});
}}
>
Apply
</button>
<Summary i1={data.input1} i2={data.input2} />
</>
);
}
// Summary.js
function Summary(props) {
const { i1, i2 } = props;
return <pre>{JSON.stringify({ i1, i2 })}</pre>;
}
export default React.memo(Summary);
Note the use of React.memo in the Summary.js file. This is for some rendering optimization. As you can imagine, the Summary component may get re-rendered (through setInput1 or setInput2), even though the data state has not changed. Therefore, using React.memo, per the docs:
If your component renders the same result given the same props, you can wrap it in a call to React.memo for a performance boost in some cases by memoizing the result. This means that React will skip rendering the component, and reuse the last rendered result.
If your Summary component fetches API every time it re-renders, that could be a pretty good optimization (prevent refetching when data has not changed - although you could use solution like "useSWR" to cache response anyway)

Search input onchange causes crashing - how to make it a button to submit search

I have an app in MeteorJS, which makes use of React (I am ok with JavaScript, but am on a learning curve starting with React). The current search input makes use of the onchange function of the input box BUT this is actually not desired as this slows the app considerably - making requests every time the user types.
I basically want the input to be basic input and then have a button to trigger the search.
Inline code, for calling the searchinput where needed;
<div className="col-md-4 col-xs-12" style={disabledStyling.control}>
<SearchInput placeholder="Search" onChange={this.filterGames} value={filter} />
</div>
searchinput component;
import PropTypes from 'prop-types';
import Icon from '../Icon';
import Styles from './styles';
const SearchInput = ({ placeholder, value, onChange }) => (
<Styles.SearchInput className="SearchInput">
<Icon iconStyle="solid" icon="search" />
<input
type="text"
name="search"
className="form-control"
placeholder={placeholder}
value={value}
onChange={onChange}
/>
</Styles.SearchInput>
);
SearchInput.defaultProps = {
placeholder: 'Search...',
value: '',
};
SearchInput.propTypes = {
placeholder: PropTypes.string,
value: PropTypes.string,
onChange: PropTypes.func.isRequired,
};
export default SearchInput;
Hoping you all could help ;)
Basically what you need to do is use a state to store the value from the onChange event and later, on button click/form submit action you pass this value to the function that will actually fetch data.
Here is a small example on code sandbox where you can see this being applied both on a functional component and on a class component
lets assume your "wrapper" component is something like this:
const Form = () => {
const filterGames = (event) => {
// handle event and calls API
}
return (
<div className="col-md-4 col-xs-12" style={disabledStyling.control}>
<SearchInput placeholder="Search" onChange={filterGames} value={filter} />
</div>
)
}
What we need to do here, is basically adding the state and handle it without calling the API and a button to actually call the API.
const Form = () => {
const [inputValue, setInputValue] = useState('');
const filterGames = (event) => {
// handle event and calls API
}
// this will store the value locally on the state
const handleInputOnChange = (event) => {
setInputValue(event.target.value);
}
return (
<div className="col-md-4 col-xs-12" style={disabledStyling.control}>
<SearchInput placeholder="Search" onChange={handleInputOnChange} value={inputValue} />
<button type='submit' onClick={filterGames}>Submit</button>
</div>
)
}
ps: you can also wrap the input + button with a form and use form.onSubmit instead of button.onClick.

Why isn't child component re-rendered even though parent state is updated using hooks

I am a newbie in React and have got a problem using hook.
I am going to build a basic form as a child component, but I am wondering why input element on the child form is not rendered when changing its value.
Here is my code.
'use strict';
function SearchForm({form, handleSearchFormChange}) {
return (
<div className="card border-0 bg-transparent">
<div className="card-body p-0 pt-1">
<div className="form-row">
<div className="col-auto">
<label className="mr-1" htmlFor="number">Number:</label>
<input type="text" className="form-control form-control-sm w-auto d-inline-block"
name="number" id="number" value={form.number} onChange={handleSearchFormChange}/>
</div>
</div>
</div>
</div>
);
}
function App() {
const formDefault = {
number: 'Initial Value'
};
const [form, setForm] = React.useState(formDefault);
const handleSearchFormChange = (e) => {
setForm(Object.assign(form, {[e.target.name]: e.target.value}));
console.log('Handle Search Form Change', e.target.name, "=", e.target.value);
};
return (
<React.Fragment>
<div>Number : {form.number}</div>
<SearchForm form={form} handleSearchFormChange={handleSearchFormChange} />
</React.Fragment>
);
}
const domContainer = document.querySelector('#root');
ReactDOM.render((
<React.Fragment>
<App/>
</React.Fragment>
), domContainer);
I defined an onChange event handler for 'number' element in parent component and tried to transfer the value to child component using props.
The problem is that when I am going to change 'number', 'number' input element is not changed at all. (Not rendered at all). So that input element has always 'Initial Value'.
Could you advise on this?
And I'd like to know if this approach is reasonable or not.
Thanks in advance.
One of the philosophies of react is that state is immutable. Ie, you don't change properties of the existing state object, but instead you create a new state object. This allows react to tell that the state changed by a simple === check from before and after.
This line of code mutates the existing form object, then passes that into setForm:
setForm(Object.assign(form, {[e.target.name]: e.target.value}));
So react compares the object before setForm and after, and sees that they're the same object. Therefore it concludes that nothing changed, and so the component does not rerender.
Instead, you need to make a copy of the object and make your changes on the copy. If you're used to Object.assign, that can be accomplished by putting an empty object as the first argument to Object.assign:
setForm(Object.assign({}, form, {[e.target.name]: e.target.value}));
Alternatively, the spread syntax is a convenient way to make a shallow copy of an object:
setForm({
...form,
[e.target.name]: e.target.value
})
Try replacing setForm(Object.assign(form, {[e.target.name]: e.target.value})); with
setForm(prevState => ({
...prevstate,
[e.target.name]: e.target.value
}));

Categories