I'd like to create a stateless Search component. When the button is clicked, I need to access the value of the search field. Is the following an acceptable way to do this? Are they any drawbacks to this approach?
const Search = ({onBtnClick}) => {
const onClick = (e) => {
const query = e.target.previousSibling.value;
onBtnClick(query);
};
return(
<div>
<input type="search" />
<button onClick={onClick}>Search</button>
</div>
)
}
In stateless components you can use ref with function, like so
const Search = ({ onBtnClick }) => {
let search;
const setNode = (node) => {
search = node;
};
const onClick = () => {
onBtnClick(search.value);
};
return (
<div>
<input type="search" ref={ setNode } />
<button onClick={ onClick }>Search</button>
</div>
);
}
function onBtnClick(value) {
console.log(value);
}
ReactDOM.render(
<Search onBtnClick={onBtnClick} />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container"></div>
I'm not connoisseur of react, but i think that your approach is OK,
because in official react documentation i saw NOT one time something like that {value: event.target.value}:
Here we have component with state, and have this:
this.setState({value: event.target.value});
but i think in your component without state it will be OK use:
let query = e.target.previousSibling.value;
onBtnClick(query);
PS: I also like Alexander T. answer.)
Related
return (
<div>
{
countries.map((country)=>
<div key={country.ccn3}>
{country.name.common}<button className="button" value={country} onClick={ChangeDisplay}>show</button>
</div>
)
}
</div>
);
}
as the code show: how do you get the value of "button", in my case, value should be "country" obj, which can be rendered with onClick function "ChangeDisplay".
Data attributes are a good way.
For a button:
<button data-value={country} onClick={changeDisplay}>show</button>
Click handler:
const changeDisplay = (e) => {
const { value } = e.target.dataset;
console.log(value);
};
You can simply get the value from e.target.value like how you do with form fields. Value needs to be a string.
const App = () => {
const onClick = (e) => alert(e.target.value)
return (<button onClick={onClick} value="my button value">MyButton</button>)
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
If you want to pass an Object. Then you can do without event by simply passing the object to the function.
<button onClick={()=> ChangeDisplay(country)} />
I tried to put a form in a separate reusable component but when used that way I can't type anything into the input. I observed, that after entering one letter (it does not appear in the input box) it seems that React rerender the whole component and the name is updated with the inserted letter.
in the version 2 the same code works correctly.
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = key => event => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = event => {
event.preventDefault();
handleChange();
};
// VERSION 1. doesn't work
const FormEdit = () => (
<form>
<div className="form-group">
<input onChange={handleChange("name")} type="text"/>
</div>
<button onClick={submitEdit}> Submit </button>
</form>
)
return (
<Layout>
<div>
{name} //<-it shows only one letter
<FormEdit />
</div>
</Layout>
);
// VERSION 2 -> works properly
return (
<Layout>
<div>
{name} //<-the updated name is shown immediately
<form>
<div className="form-group">
<input onChange={handleChange("name")} type="text"/>
</div>
<button onClick={submitEdit}> Submit </button>
</form>
</div>
</Layout>
);
};
export default User;
The issue is directly related to declaring the FormEdit component within the other component. Here's why:
In a functional component, everything declared inside gets destroyed and re-created each render. It's no different than a normal function call. This is what makes React's hooks so special. They keep track of values in between renders and make sure they are re-created with the correct values.
You're declaring the FormEdit component inside a function, which means not only is it re-declared every render, but as a side-effect it also un-mounts and remounts each render as well.
This has a few different effects:
The component's input loses focus every render.
It's impossible for it to maintain its own state.
It's not very performant.
Below is a working example to demonstrate.
const {useState, useEffect} = React;
const Example = () => {
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = (key) => (event) => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = (event) => {
event.preventDefault();
handleChange();
};
const FormEdit = () => {
useEffect(() => {
console.log('mount');
return () => console.log('unmount');
}, []);
return (
<form>
<div>
<input onChange={handleChange("name")} type="text"/>
</div>
<button onClick={submitEdit}> Submit </button>
</form>
)
}
return (
<div>
{name}
<FormEdit />
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
As for why you only see the first character; You are not giving the input a value, only an onChange. If the component does not unmount, this just makes it an "uncontrolled" component. The input still gets it's value updated, you just can't programatically control it. But, since it is unmounting and re-mounting every render, it loses its last value every time the user types.
Making it a controlled input would fix this:
const {useState, useEffect} = React;
const Example = () => {
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = (key) => (event) => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = (event) => {
event.preventDefault();
handleChange();
};
const FormEdit = () => {
useEffect(() => {
console.log('mount');
return () => console.log('unmount');
}, []);
return (
<form>
<div>
<input value={name} onChange={handleChange("name")} type="text"/>
// ^ Add this
</div>
<button onClick={submitEdit}> Submit </button>
</form>
)
}
return (
<div>
{name}
<FormEdit />
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This is a little better, but still not ideal. Now it keeps the value each update, but it still loses focus. Not a very good user experience.
This final solution is to never declare a component within another component.
const {useState, useEffect} = React;
const FormEdit = (props) => {
useEffect(() => {
console.log('mount');
return () => console.log('unmount');
}, []);
return (
<form>
<div>
<input value={props.name} onChange={props.handleChange("name")} type="text"/>
</div>
<button onClick={props.submitEdit}> Submit </button>
</form>
)
}
const Example = () => {
// the part same for the both versions
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = (key) => (event) => {
setUser({
...userdata,
[ key ]: event.target.value
});
};
const submitEdit = (event) => {
event.preventDefault();
handleChange();
};
return (
<div>
{name}
<FormEdit name={name} handleChange={handleChange} submitEdit={submitEdit} />
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Now it only mounts once, keeps focus, and updates as expected.
You would have to pass your form handlers to the child component as props so that the lifted state can be manipulated from the child.
// Parent Component
...
const [userdata, setUser] = useState({});
const { name } = userdata
const handleChange = key => event => {
...
};
const submitEdit = event => {
...
};
return (
<Layout>
<div>
{name}
<FormEdit handleChange={handleChange} submitEdit={submitEdit}/>
</div>
</Layout>
);
and then in the child:
// Child Component
const FormEdit = (props) => (
<form>
<div className="form-group">
<input onChange={props.handleChange("name")} type="text"/>
</div>
<button onClick={props.submitEdit}> Submit </button>
</form>
)
Your FormEdit component which is inside the App component is causing the entire App component to re-render when the state gets updated onChange and hence you can only enter only one character at a time. It is generally not a great idea to declare a component within a component. Refer this link for more info. All you have to do is pull the FormEdit component out of the App component in its own separate function and pass the change handlers as props to the FormEdit component. Have a look at the working code below.
import React, { useState } from 'react';
const FormEdit = ({ handleChange, submitEdit, name }) => {
return (
<form>
<div className='form-group'>
<input onChange={handleChange('name')} type='text' value={name || ''} />
</div>
<button onClick={submitEdit} type='submit'>
Submit
</button>
</form>
);
};
export default function App() {
const [userdata, setUser] = useState();
const { name } = userdata || {};
const handleChange = key => event => {
setUser(prevState => {
return { ...prevState, [key]: event.target.value };
});
event.persist();
event.preventDefault();
};
const submitEdit = event => {
event.preventDefault();
handleChange();
};
return (
<div>
<div>
{name || ''}
<FormEdit
handleChange={handleChange}
submitEdit={submitEdit}
name={name}
/>
</div>
</div>
);
}
I'm new to reactjs and I'm trying read data from input. Problem is when I type a sign, my input loose focus. But only when all logic is inside function.
When Input with button and logic is in different file - it's working. I don't really know why...
I have created separate file with same code and import it to sollution - it's ok.
I have tried with onChange={handleChange} - lost focus as well.
export default function MyInput(){
const [citySearch, updateCitySearch] = useState();
function searchCityClick(){
alert(citySearch);
}
const SearchComponent = ()=> (
<div>
<input
value={citySearch}
onChange={(e) => updateCitySearch(e.target.value)}/>
<Button variant="contained" color="primary" onClick={searchCityClick}>
Search
</Button>
</div>
);
return(
<div>
<div>
<SearchComponent />
</div>
</div>
)}
The SearchComponent is a functional component, and shouldn't be defined inside another component. Defining SearchComponent inside MyInput will cause SearchComponent to be recreated (not rerendered), and in essence it's DOM would be removed, and then added back on every click.
The solution is pretty straightforward, extract SearchComponent from MyInput, and pass the functions, and the data via the props object:
const { useState, useCallback } = React;
const SearchComponent = ({ citySearch, updateCitySearch, searchCityClick }) => (
<div>
<input
value={citySearch}
onChange={e => updateCitySearch(e.target.value)} />
<button onClick={searchCityClick}>Search</button>
</div>
);
const MyInput = () => {
const [citySearch, updateCitySearch] = useState('');
const searchCityClick = () => alert(citySearch);
return(
<div>
<SearchComponent
citySearch={citySearch}
updateCitySearch={updateCitySearch}
searchCityClick={searchCityClick} />
</div>
);
};
ReactDOM.render(
<MyInput />,
root
);
<script crossorigin src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
I am also new to React, so take my explanation with a pinch of salt (hopefully someone else can elaborate).. I believe it has something to do with nesting components and how React is re-rendering..
If you use SearchComponent as a variable, instead of an anonymous function, this works as expected.
I am also curious as to why using nested functions like that (when using JSX) causes this behavior... possibly an anti-pattern?
function MyInput() {
const [citySearch, updateCitySearch] = React.useState();
function searchCityClick() {
alert(citySearch);
}
const SearchComponent = (
<div>
<input
value={citySearch}
onChange={(e) => updateCitySearch(e.target.value)}/>
<button variant="contained" color="primary" onClick={searchCityClick}>
Search
</button>
</div>
);
return (
<div>
<div>
{SearchComponent}
</div>
</div>
);
}
let div = document.createElement("div");
div.setAttribute("id", "app");
document.body.append(div);
ReactDOM.render(<MyInput />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.6/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>
Even if you change the nested function to an "actual component", focus is lost after each key press (aka onChange)..
Does not work:
function MyInput() {
const [citySearch, updateCitySearch] = React.useState();
function searchCityClick() {
alert(citySearch);
}
const SearchComponent = () => {
return (
<div>
<input
value={citySearch}
onChange={(e) => updateCitySearch(e.target.value)}/>
<button variant="contained" color="primary" onClick={searchCityClick}>
Search
</button>
</div>
);
}
return (
<div>
<div>
<SearchComponent />
</div>
</div>
);
}
This happens because the useState hook is not "hooked" to your SearchComponent, but your MyInput component. Whenever, you call updateCitySearch() you change the state of MyInput, thus forcing the entire component to re-render.
SearchComponent, is explicitly defined inside MyInput. When citySearch-state is updated, SearchComponent loses focus because the initial virual DOM surrounding it is no longer intact, instead you have a completely new piece of DOM. Essentially, you are creating a brand new SearchComponent each time the MyInput is updated by state.
Consider the following example:
function App() {
const [citySearch, updateCitySearch] = useState("");
console.log("rendered App again"); //always prints
const SearchComponent = () => {
console.log("rendered Search"); //always prints
const searchCityClick = () => {
alert(citySearch);
};
return (
<div>
<input
value={citySearch}
onChange={e => {
updateCitySearch(e.target.value);
}}
/>
<button onClick={searchCityClick}>Search</button>
</div>
);
};
return (
<div>
<div>
<SearchComponent />
</div>
</div>
);
}
Every time you update state, you would trigger both console.log(), the App component re-renders and SearchComponent gets re-created. A new iteration of myInput is rendered each time and a new SearchComponent gets created.
But if you were to define useState inside SearchComponent, then only SearchComponent will re-render whens state changes, thus leaving the original myInput component unchanged, and the current SearchComponent intact.
function App() {
console.log("rendered App again"); //would never print a 2nd time.
const SearchComponent = () => {
const [citySearch, updateCitySearch] = useState("");
console.log("rendered Search"); //would print with new state change
const searchCityClick = () => {
alert(citySearch);
};
return (
<div>
<input
value={citySearch}
onChange={e => {
updateCitySearch(e.target.value);
}}
/>
<button onClick={searchCityClick}>Search</button>
</div>
);
};
return (
<div>
<div>
<SearchComponent />
</div>
</div>
);
}
I am trying to render a component in App.js based on a MobX observable value. I want to define an action that takes a component and some new custom prop assignments. Here's what I have now:
// App.js inside render()
{ ui_store.main_area_sequence }
.
// ui_store.js
// OBSERVABLES / state
main_area_sequence = <ChoosePostType />;
// ACTIONS / state mutators
update_ui_in_main_section(ComponentTag, props) {
this.main_area_sequence = <ComponentTag {props} />
}
The component argument works as intended. But I can't get the props argument working. Ideally I would use it to build something like:
<ChildComponentToBeSwitchedIn name={ui_store.name} handleClick={this.handleClick} />
A button in <ChoosePostType /> might reassign the observable value in main_area_sequence to the above on click, as one example.
Does anyone know how to pass prop assignments as an argument in order to do this?
I suspect you're overthinking this.
Recall that JSX is just JavaScript. When you type this:
<Foo bar={123}>Baz!</Foo>
...you're really writing this:
React.createElement(Foo, { bar: 123 }, 'Baz!');
What if the component you want to render is dynamic, though? That's fine; it still works. For example, suppose we kept the component and props in our React component's state:
render() {
return (
<this.state.dynamicComponent
{...this.state.dynamicComponentProps}
name={this.props.name}
onClick={this.handleClick}
/>
);
}
The above JSX translates to this JavaScript:
React.createElement(this.state.dynamicComponent,
{ ...this.state.dynamicComponentProps,
name: this.props.name,
onClick: this.handleClick,
}
);
As you can see, we get props from this.state.dynamicComponentProps, but we also add a name prop from this.props.name and an onClick prop from this.handleClick. You can imagine this.handleClick calling this.setState to update dynamicComponent and dynamicComponentProps.
Actually, you don't have to imagine it, because you can see it working in the below snippet:
class DynamicComponent extends React.Component {
constructor(props) {
super(props);
this.handleClick = this.handleClick.bind(this);
this.state = {
dynamicComponent: ShowMeFirst,
dynamicComponentProps: { style: { color: 'blue' }},
};
}
render() {
return (
<this.state.dynamicComponent
{...this.state.dynamicComponentProps}
name={this.props.name}
onClick={this.handleClick}
/>
);
}
handleClick() {
this.updateDynamicComponent(ShowMeSecond, { style: { color: 'red' }});
}
updateDynamicComponent(component, props) {
this.setState({
dynamicComponent: component,
dynamicComponentProps: props,
});
}
}
const ShowMeFirst = ({ name, style, onClick }) => (
<button type="button" style={style} onClick={onClick}>
Hello, {name}!
</button>
);
const ShowMeSecond = ({ name, style, onClick }) => (
<button type="button" style={style} onClick={onClick}>
Goodbye, {name}!
</button>
);
ReactDOM.render(<DynamicComponent name="Alice"/>, document.querySelector('div'));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div></div>
I've used plain old React state above, but the same thing works with MobX, as you can see below. (I've never used MobX before, so my code might be less than idiomatic, but hopefully you'll get the idea.)
const { action, decorate, observable } = mobx;
const { observer } = mobxReact;
class UIStore {
dynamicComponent = ShowMeFirst;
dynamicComponentProps = { style: { color: 'blue' }};
updateDynamicComponent(component, props) {
this.dynamicComponent = component;
this.dynamicComponentProps = props;
}
}
decorate(UIStore, {
dynamicComponent: observable,
dynamicComponentProps: observable,
updateDynamicComponent: action,
});
const DynamicComponent = observer(({ store, name }) => (
<store.dynamicComponent
{...store.dynamicComponentProps}
name={name}
onClick={() =>
store.updateDynamicComponent(ShowMeSecond, { style: { color: 'red' }})
}
/>
));
const ShowMeFirst = ({ name, style, onClick }) => (
<button type="button" style={{...style}} onClick={onClick}>
Hello, {name}!
</button>
);
const ShowMeSecond = ({ name, style, onClick }) => (
<button type="button" style={{...style}} onClick={onClick}>
Goodbye, {name}!
</button>
);
ReactDOM.render(<DynamicComponent name="Alice" store={new UIStore()} />, document.querySelector('div'));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/mobx/lib/mobx.umd.js"></script>
<script src="https://unpkg.com/mobx-react#5.2.3/index.js"></script>
<div></div>
I had to use decorate here since Stack Overflow's Babel configuration doesn't include decorators, but I've posted the same thing with decorators on CodeSandbox: https://codesandbox.io/s/qlno63zkw4
If you aren't passing props, then don't include a props parameter.
update_ui_in_main_section(ComponentTag) {
this.main_area_sequence = <ComponentTag handleClick={this.handleClick} />;
}
I have the following TodoApp written in React:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.23/browser.min.js"></script>
<script src="https://unpkg.com/react#15.3.2/dist/react.js"></script>
<script src="https://unpkg.com/react-dom#15.3.2/dist/react-dom.js"></script>
<title>React! React! React!</title>
</head>
<body>
<div class="container">
<div id="container" class="col-md-8 col-md-offset-2"> </div>
</div>
<script type="text/babel">
console.clear();
const Title = () => {
return (
<div>
<div>
<h1>to-do</h1>
</div>
</div>
);
}
const TodoForm = ({addTodo}) => {
// Input Tracker
let input;
// Return JSX
return (
<div>
<input ref={node => {
input = node;
}} />
<button onClick={() => {
addTodo(input.value);
input.value = '';
}}>
+
</button>
</div>
);
};
const Todo = ({todo, remove}) => {
// Each Todo
return (<li onClick={() => {remove(todo.id)}}>{todo.text}</li>);
}
const TodoList = ({todos, remove}) => {
// Map through the todos
const todoNode = todos.map((todo) => {
return (<Todo todo={todo} key={todo.id} remove={remove}/>)
});
return (<ul>{todoNode}</ul>);
}
// Contaner Component
// Todo Id
window.id = 0;
class TodoApp extends React.Component{
constructor(props){
// Pass props to parent class
super(props);
// Set initial state
this.state = {
data: []
}
}
// Add todo handler
addTodo(val){
// Assemble data
const todo = {text: val, id: window.id++}
// Update data
this.state.data.push(todo);
// Update state
this.setState({data: this.state.data});
}
// Handle remove
handleRemove(id){
// Filter all todos except the one to be removed
const remainder = this.state.data.filter((todo) => {
if(todo.id !== id) return todo;
});
// Update state with filter
this.setState({data: remainder});
}
render(){
// Render JSX
return (
<div>
<Title />
<TodoForm addTodo={this.addTodo.bind(this)}/>
<TodoList
todos={this.state.data}
remove={this.handleRemove.bind(this)}
/>
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.getElementById('container'));
</script>
</body>
</html>
Questions:
What is this syntax:
const TodoForm = ({addTodo}) => {
// Input Tracker
let input;
// Return JSX
return (
<div>
<input ref={node => {
input = node;
}} />
I think I get what ref is but what is that node just inside the curly braces? If it's a function declaration, where are the parenthesis around node? What is going on?
Also, at the end, we render the TodoApp which renders TodoForm like this:
<TodoForm addTodo={this.addTodo.bind(this)}/>
Does that just pass addTodo to the functionally declared component, not as props, but merely an argument?
const TodoForm = ({addTodo}) => {
Is this correct? addTodo comes in merely as an argument and not as props?
So in the following function
const TodoForm = ({addTodo}) => {
// Input Tracker
let input;
// Return JSX
return (
<div>
<input ref={node => {
input = node;
}} />
<button onClick={() => {
addTodo(input.value);
input.value = '';
}}>
+
</button>
</div>
);
};
The first line is an example of destructuring in ES6 What happens is that in const TodoForm = ({addTodo}) => { props gets passes to the TodoForm Component which is stateless and in the props you have addTodo as a prop so out of all the props we are extracting addTodo
Also for refs a callback approach is being followed. It is an ES6 style to write a function. Here node is an argument and it doesn't contain any parenthesis because it is a single argument and ES6 gives you flexibility to omit the parenthesis. Also inside the {} you have the body of the function
In your code node refers to the DOM element and you are assigning its reference to the variable input that you have defined. Now you can refer the DOM with input rather than assigning ref as <input ref="myValue"/> and then refering it as this.refs.myValue.
I hope was able to explain it properly.
Read the following documentation on React ref callback approach for a detailed explaination.
let's say we have this one :
const TodoForm = (data) => {
const addTodo = data.addTodo //can be const myFunc = data.addTodo
// Input Tracker
...
as an enhancement we can do it like that :
const TodoForm = (data) => {
const {addTodo} = data //can be const {addTodo as myFunc} = data
// Input Tracker
...
once more !
as an enhancement we can do it like that :
//notice that we moved the {addTodo} directly to replace data
const TodoForm = ({addTodo}) => {
//can be ({addTodo: myFunc}) => {
// Input Tracker
...