I have next code:
<Wrapper>
<InputsGroup />
<Controls />
</Wrapper>
In InputsGroup component I have a list of inputs:
// InputsGroup
<>
<input ... />
<input ... />
<input ... />
...
</>
In Controls component I have two buttons: Next and Previously.
// Controls
<>
<button> Prev </button>
<button> Next </button>
</>
The idea, is I need to toggle focus between inputs when click 'Next' or 'Prev' button.
What is the best way to do it, and how can I manage the focus of inputs?
This is how it's looks like:
I would use the local state to track the focused input field.
In the outer componenent:
state = {
focused: 'input_name' || defaultValue
};
handleFocusChange = focused => {
// Use your own logic to change the focus
this.setState({ focused });
};
render() {
const { focused } = this.state;
return (
<Wrapper>
<InputsGroup focused={focused} />
<Controls onClick={this.handleFocusChange} />
</Wrapper>
);
}
Inside your InputsGroup component where you render your inputs:
const { focused } = this.props;
<>
{/* .... */}
<input
{/* ... */}
autoFocus={focused === inputName}
/>
</>
At the end in your Controls component:
const { onClick } = this.props;
<>
<button onClick={() => onClick(/* you need your own logic here, too */)}> Prev </button>
<button onClick={() => onClick(/* you need your own logic here, too */)}> Next </button>
</>
Usually I keep my input field options in a separated constant file, therefore the logic can be super easy to implement. You can use this to walk through the inputs with button click or render the input fields.
Example of a possible field map:
const fields = [
{ name: 'input_name_1', type: 'text', ... },
{ name: 'input_name_2', type: 'text', ... }
];
You can solve this using Refs concept, this is a working solution:-
class App extends React.Component{
constructor(props) {
super(props);
this.state = {
focus: 0
};
this.textInput0 = React.createRef();
this.textInput1 = React.createRef();
this.textInput2 = React.createRef();
}
componentDidMount() {
this.textInput0.current.focus();
}
clickHandler = btnType => {
let focus = this.state.focus;
if (btnType === "prev") {
if (focus !== 0) focus--;
} else {
if (focus !== 2) focus++;
}
this.focusInputField(focus);
this.setState({ focus: focus });
};
focusInputField = id => {
//console.log(id);
switch (id) {
case 0:
this.textInput0.current.focus();
break;
case 1:
this.textInput1.current.focus();
break;
case 2:
this.textInput2.current.focus();
break;
default:
this.textInput0.current.focus();
}
};
render() {
return (
<React.Fragment>
<input placeholder="textInput0" ref={this.textInput0} />
<br />
<input placeholder="textInput1" ref={this.textInput1} />
<br />
<input placeholder="textInput2" ref={this.textInput2} />
<br />
<button onClick={() => this.clickHandler("prev")}>Prev</button>
<button style={{marginLeft: "20px"}} onClick={() => this.clickHandler("next")}>Next</button>
</React.Fragment>
);
}
}
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>
Related
I'm trying to control inputs with state.
But I'm having a problem with this case which input keep losing it's focus when I hit the keyboard.
See picture below:
https://codesandbox.io/s/how-to-solve-this-7kwj6
export default class App extends React.Component {
constructor(props) {
super(props);
this.onBchange = this.onBchange.bind(this);
}
state = {
a: true,
b: "",
c: ""
};
onBchange(e) {
const thisClass = this;
return (async function() {
await thisClass.setState({ b: e.target.value });
console.log(thisClass.state.b);
})();
}
onCchange = e => {
this.setState({ c: e.target.value });
};
render() {
// this cause problem why?
const Test = ({ b, c, onBchange, onCchange }) => (
<Fragment>
<span>B: </span>
<input value={b} onChange={onBchange} />
<span>C: </span>
<input value={c} onChange={onCchange} />
</Fragment>
);
return (
<div className="App">
{this.state.a && (
<Test
b={this.state.b}
c={this.state.c}
onBchange={this.onBchange}
onCchange={this.onCchange}
/>
)}
</div>
);
}
}
Please let me know if you need more info about this.
Any ideas?
Your app is rerendering those elements on each change because each keystroke causes new props to be passed to <Test/> within your App's render method.
Notice that changing the return method in render to this stops the issue:
<div className="App">
<span>B: </span>
<input value={this.state.b} onChange={this.onBchange} />
<span>C: </span>
<input value={this.state.c} onChange={this.onCchange} />
{/* {this.state.a && (
<Test
b={this.state.b}
c={this.state.c}
onBchange={this.onBchange}
onCchange={this.onCchange}
/>
)} */}
</div>
Set the attributes of a input field or any component by taking input from the user dynamically?
I would like to know if there is any way, where I would give user an option to choose a component from the list of components i would mention, and allow him to customize the components attributes. For example if the user chooses a Input component, he must be able to set the attributes of that particular component, like "required", "type", "placeholder".
You can achieve it by passing all attributes you want as props to the child component.
You should also add them to state of parent component with change handler.
Each time the user change something of the attributes, the state should update.
As the state updates, the new state will pass as props to child Component and it'll update.
I made a simple example to input: You can change its placeholder, minLength, and requierd.
Check This Example
in the render, method you can do something like this
render() {
// change the name and values base on your user input
userInputtedAttribName = "placeholder";
userInputtedAttribValue = "the placeholder";
// the object to contain your user defined attribute name and values
const dynamicAttributes = {
[userInputtedAttribName]: userInputtedAttribValue
};
return (
<div>
<input type="text" {...dynamicAttributes}></input>
</div>
)
}
the spread operator, {...dynamicAttributes}, will build the attributes and their values dynamically
Probably not even what you're looking for, but I made a medium-sized prototype that can show you how to create Components (input, button, textarea), dynamically.
It's like filling out a form. Choose a type of component you want to make from the select-list. Then define the attributes you want in the proceeding textboxes. Once you're done adding all the attributes, hit Generate to render your customized component.
Sandbox: https://codesandbox.io/s/dynamic-component-generator-mhuh5
Working code:
import React from "react";
import ReactDOM from "react-dom";
import Input from "./Input";
import Button from "./Button";
import TextArea from "./TextArea";
import "./styles.css";
class ComponentGenerator extends React.Component {
state = {
componentInProgress: null,
customizeMode: false,
attributeName: "",
attributeSetting: "",
boolean: false,
builtComponents: []
};
handleSelection = e => {
this.setState({
componentInProgress: { componentType: e.target.value },
customizeMode: true
});
};
createOptions = () => {
const { customizeMode, componentInProgress } = this.state;
return (
<div>
<h4>Choose a Component:</h4>
<select
onChange={this.handleSelection}
value={!customizeMode ? "Select" : componentInProgress.componentType}
>
<option>Select</option>
<option>Input</option>
<option>TextArea</option>
<option>Button</option>
</select>
</div>
);
};
handleOnChange = e => {
this.setState({
[e.target.name]: e.target.value
});
};
handleOnSubmit = e => {
const {
attributeName,
attributeSetting,
boolean,
componentInProgress
} = this.state;
e.preventDefault();
let componentCopy = JSON.parse(JSON.stringify(componentInProgress));
componentCopy.props = {
...componentCopy.props,
[attributeName]: boolean ? boolean : attributeSetting
};
this.setState({
componentInProgress: componentCopy,
attributeName: "",
attributeSetting: "",
boolean: false
});
};
setBoolean = boolean => {
this.setState({
boolean: boolean
});
};
generateComponent = () => {
const { componentInProgress, builtComponents } = this.state;
this.setState({
componentInProgress: null,
customizeMode: false,
builtComponents: [...builtComponents, componentInProgress]
});
};
defineComponentAttributes = () => {
const {
componentInProgress,
attributeName,
attributeSetting,
boolean
} = this.state;
return (
<div>
<h4>
Customizing:{" "}
<span className="highlight">{componentInProgress.componentType}</span>
</h4>
{/*Render form */}
<form onSubmit={this.handleOnSubmit}>
<label>Attribute: </label>
<input
className="form-group"
onChange={this.handleOnChange}
value={attributeName}
name="attributeName"
placeholder="Choose attribute (type)"
/>
<label>Definition: </label>
<input
className="form-group"
onChange={this.handleOnChange}
value={attributeSetting}
name="attributeSetting"
placeholder="Define attribute (text)"
/>
<label>This is a Boolean type: </label>
<input
type="radio"
name="boolean"
onChange={() => this.setBoolean(true)}
/>
True
<input
type="radio"
name="boolean"
checked={boolean === false}
onChange={() => this.setBoolean(false)}
/>
False
<button className="form-group" type="submit">
Add
</button>
</form>
{/*Create List of attributes */}
{componentInProgress.props && (
<div>
<h4>Defined Attributes:</h4>
{Object.entries(componentInProgress.props).map(
([propName, propValue]) => {
return (
<div key={propName}>
<span>{propName}: </span>
<span>{propValue + ""}</span>
</div>
);
}
)}
</div>
)}
<div>
<h4>Click to finish and generate:</h4>
<button onClick={this.generateComponent}>Generate</button>
</div>
</div>
);
};
renderComponents = () => {
const { builtComponents } = this.state;
return builtComponents.map((component, index) => {
let renderedComponent = () => {
switch (component.componentType) {
case "Input":
return <Input {...component.props} />;
case "Button":
return <Button {...component.props} />;
case "TextArea":
return <TextArea {...component.props} />;
default:
return null;
}
};
return (
<div key={index} className="componentSection">
<h4>{component.componentType}</h4>
{renderedComponent()}
<div>
<p>Attributes: </p>
{Object.entries(component.props).map(([propName, propValue]) => {
return (
<div key={propName}>
<span>{propName}: </span>
<span>{propValue + ""}</span>
</div>
);
})}
</div>
</div>
);
});
};
render() {
const { customizeMode } = this.state;
return (
<div>
{this.createOptions()}
{customizeMode && this.defineComponentAttributes()}
<div className="components">{this.renderComponents()}</div>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<ComponentGenerator />, rootElement);
I want to display an input field for every click on a button "Add Input Field". Now it is showing only one time when I click on button "Add Input Field". How Can I achieve this.
Here below I have created a codesandbox.
https://codesandbox.io/s/6lr8w994vn
To achieve this, you will have to store an array in your state containing the information to generate each field.
Then, in your add function, set your array to your deconstructed previous array, and your additional element:
class InputAdder extends React.Component {
constructor(props) {
super(props)
this.state = {
inputs: []
}
}
addInput = ev => {
this.setState(prev => ({ inputs: [...prev.inputs, 'Hi'] }))
}
render() {
return (
<div>
<button onClick={this.addInput}>Add input</button>
{this.state.inputs.map(node => <input type="text"/>)}
</div>
)
}
}
ReactDOM.render(<InputAdder/>, document.getElementById('root'))
input {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.5.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.5.2/umd/react-dom.production.min.js"></script>
<div id='root'>
You will then simply have to map over your array in your render function
One way is to store the number of inputs in the state, which will be incremented by one on every click. Then pass this value to props of your component and use map function to create the corresponding number of inputs.
To make this work, Change your state from this:
this.state = {
add: false,
addChild: false
};
to :
this.state = {
add: [],
addChild: []
};
and in addInputField function increase add array size every time it clicked, then use this.state.add to render your input fields. Same applies to Child.
Demo Component:
class Demo extends Component {
constructor() {
super();
this.state = {
add: [],
addChild: []
};
}
addInputField = event => {
const add = this.state.add;
const size = add.length + 1;
add.push(size);
this.setState({
add
});
event.preventDefault();
};
addChildInputField = event => {
const addChild = this.state.addChild;
const size = addChild.length + 1;
addChild.push(size);
this.setState({
addChild
});
event.preventDefault();
};
handleChange = event => {
this.setState({
[event.target.name]: event.target.value
});
};
render() {
return (
<div>
<Grid>
<Grid>
<Button onClick={this.addInputField} style={{ marginTop: 30 }}>
{" "}
<Icon>
<AddCircle />
</Icon>{" "}
Add Input Field
</Button>
{this.state.add.map(index => {
return (
<Grid>
<TextField
id="preference"
label="Preference"
name="points"
value={this.state.name}
onChange={this.handleChange}
margin="normal"
/>
<Button onClick={this.addChildInputField}> Add Child</Button>
</Grid>
);
})}
{this.state.addChild.map(index => {
return (
<Grid style={{ marginLeft: 50 }}>
<TextField
id="preference"
label="Child Preference"
name="points"
value={this.state.name}
onChange={this.handleChange}
margin="normal"
/>
</Grid>
);
})}
</Grid>
<Grid>
<Button
color="primary"
variant="contained"
type="submit"
style={{ margin: "0.2rem", marginTop: 30 }}
>
Save
</Button>
</Grid>
</Grid>
</div>
);
}
}
export default Demo;
Full code available here
Hope it helps.
Ok, so the problem is pretty simple, but hard to explain.
I'm making an InputGenerator component, which generates a list of inputs.
Each generated input has a corresponding "remove" button next to it. The two elements (the input and the button) are wrapped in a div inside a map function. The div has a unique key prop. It looks like this (this is the whole component's jsx):
<div style={[InputGenerator.style.wrapper]}>
<div className="container" style={InputGenerator.style.container}>
{this.state.inputs.map((input, idx) => {
return (
<div key={idx} style={[
InputGenerator.style.row,
InputGenerator.count > 1 && idx > 0 ? InputGenerator.style.input.pushTop : {},
]}>
<InputText
id={input.id}
name={input.name}
placeholder={input.placeholder}
style={input.style}
/>
<Button
style={InputGenerator.style.remove}
type={Button.TYPES.BASIC}
icon="ion-close-round"
onClick={this.remove.bind(this, input.id)}
/>
</div>
);
})}
</div>
<div className="controls" style={InputGenerator.style.controls}>
<Button icon="ion-plus" type={Button.TYPES.PRIMARY} title="Add ingredient" onClick={this.add.bind(this)}/>
</div>
</div>
As you may see, all the inputs are kept in the this.state object and each one is given an unique id.
Here are the are add and remove methods:
add():
add() {
InputGenerator.count++;
const newInput = {
id: this.id,
name: this.props.name,
placeholder: this.props.placeholder,
style: this.style,
value: '',
};
const inputs = this.state.inputs;
inputs.push(newInput);
this.setState({ inputs });
}
remove():
remove(id) {
this.setState({
inputs: this.state.inputs.filter(i => i.id !== id),
});
}
The problem is:
I generate three inputs (using the add button)
I put random values in the inputs (e.g: 1, 2, 3)
I click on the remove button, corresponding to the first element (with value 1)
Result: Two input items with values 1 and 2
Expected: Two input items with values 2 and 3
The problem: I suggest that the key prop on the wrapping div is not enough for react to keep track of the input's values.
So, I'm open for ideas and suggestions how to proceed.
Here's an isolated sandbox to play around with my component and see the "bug" in action: https://codesandbox.io/s/5985AKxRB
Thanks in advance! :)
The issue you facing is because you are not handling state properly. You need to update state when you change input value.
handleChange(index,event) {
let inputs = this.state.inputs;
inputs[index].value = event.target.value;
this.setState({inputs:inputs})
}
DEMO : DEMO
Here is the updated code:
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
const styles = {
fontFamily: 'sans-serif',
textAlign: 'center',
};
const App = () =>
<div style={styles}>
<InputGenerator />
</div>;
class InputGenerator extends Component {
constructor() {
super();
this.state = {
inputs: [],
};
}
componentWillMount() {
this.add();
}
handleChange(index,event) {
let inputs = this.state.inputs;
inputs[index].value = event.target.value;
this.setState({inputs:inputs})
}
add() {
InputGenerator.count++;
const newInput = {
id: this.id,
name: this.props.name,
placeholder: this.props.placeholder,
style: this.style,
value: '',
};
const inputs = this.state.inputs;
inputs.push(newInput);
this.setState({ inputs });
}
get id() {
if (this.props.id) {
return `${this.props.id}-${InputGenerator.count}`;
}
return `InputGeneratorItem-${InputGenerator.count}`;
}
get style() {
return [];
}
remove(id) {
var state = this.state;
state.inputs = state.inputs.filter(i => i.id !== id);
this.setState({
inputs: state.inputs
});
}
render() {
return (
<div>
<div className="container">
{this.state.inputs.map((input, idx) => {
return (
<div key={idx}>
<input
id={input.id}
name={input.name}
value = {input.value}
onChange={this.handleChange.bind(this,idx)}
placeholder={input.placeholder}
/>
<button onClick={this.remove.bind(this, input.id)}>
Remove
</button>
</div>
);
})}
</div>
<div className="controls">
<button onClick={this.add.bind(this)}>Add</button>
</div>
</div>
);
}
}
InputGenerator.count = 0;
render(<App />, document.getElementById('root'));
I set a material-ui/TextField in my user-defined component. The user-defined component is named LabelTextField. I render several LabelTextField in my user-defined component which is named TextList. My question is how to get the values of textField in the TextList component.
A button is next to the TextList component in the View component. I will save all the TextField values when someone clicks the button.
I will post a network request in the TextList component to save the value to the backend.
I am using Redux. Does every material-ui/TextField should dispatch the value in the onChange callback function?
The onChange is at the bottom of this website:
http://www.material-ui.com/#/components/text-field
My central code:
LabelTextField:
textChangeFun = (value) => {
}
render() {
return (
<div>
<div style={{fontSize:0}}>
<div style={inlineStyle}>
<FlatButton disableTouchRipple={true} disabled={true} label={this.props.labelValue} />
</div>
<div style={inlineStyle}>
<TextField
hintText={this.props.textValue}
/>
</div>
</div>
</div>
);
}
TextList:
render(){
return (
<div>
{demoData.map((item,id) =>
<LabelTextField key={id} labelValue={item.label} textValue={item.text} ></LabelTextField>
)}
</div>
)
}
You need to give LabelTextField a handler for the change event.
class LabelTextField extends React.Component {
onChange(e) {
this.props.onChange({ id: this.props.id, value: e.currentTarget.value })
}
render() {
return (
<div>
<div style={{fontSize:0}}>
<div style={inlineStyle}>
<FlatButton disableTouchRipple={true} disabled={true} label={this.props.labelValue} />
</div>
<div style={inlineStyle}>
<TextField
hintText={this.props.textValue}
onChange={this.onChange.bind(this)}
/>
</div>
</div>
</div>
);
}
}
class TextList extends React.Component {
constructor() {
super();
this.state.textFields = {}; // TODO: get initial state from demoData
this.onTextFieldChange = this.onTextFieldChange.bind(this);
}
onTextFieldChange = ({ id, value }) {
const { textFields } = this.state;
textFields[id] = value;
this.setState({ textFields });
}
render(){
return (
<div>
{demoData.map((item,id) =>
<LabelTextField key={id} labelValue={item.label} textValue={item.text} onChange={this.onTextFieldChange} ></LabelTextField>
)}
</div>
)
}
}
This way any time a textField changes, it causes the onTextFieldChange handler to be called and the state of TextList to update.
If you have a more complicated situation, you might consider using redux or even http://redux-form.com/6.5.0/