I've created a React component that takes inputs from other components to display text of various size in a view. Since it is basically a form, what I want to do is pass the current view into another page where I will then post that view to my database as JSON.
Since the state of the input fields are not set in this component, I'm not sure how I would pass them as props to a new view.
This is a condensed version of what my data input component looks like:
INPUTSHOW.JSX
export default class InputShow extends Component {
componentDidMount() {
autosize(this.textarea);
}
render() {
const { node } = this.props;
...
return (
<div className="editor-div" >
{
(node.type === 'buttonA') ?
<textarea
style={hlArea}
ref={a => (this.textarea = a)}
placeholder="type some text"
rows={1}
defaultValue=""
id={node.id} className='editor-input-hl' type="text" onChange={this.props.inputContentHandler} />
:
(node.type === 'buttonB')
?
<textarea
style={subArea}
ref={b => (this.textarea = b)}
placeholder="type some text"
rows={1}
defaultValue=""
id={node.id} className='editor-input-sub' type="text" onChange={this.props.inputContentHandler} />
:
""
}
</div >
)
}
}
This works fine in creating inputs in a current view. I then pass those values to TextAreaField.JSX
export default (props) => {
return (
<>
<button><Link to={{
pathname: '/edit/preview',
text: props.inputsArray
}}>preview</Link></button>
<div className='view'>
{
props.inputsArray.map(
(node, key) => <InputShow key={key} node={node} inputContentHandler={props.inputContentHandler} />
)
}
</div>
</>
)
}
and then finally that is rendered in my Edit.JSX form:
export default class Edit extends React.Component {
constructor(props) {
super(props)
UniqueID.enableUniqueIds(this);
this.state = {
inputs: [],
text: ''
}
}
...
createPage = async () => {
await this.props.postPage(this.state.text)
}
// Handler for listen from button.
buttonCheck = (e) => {
index++;
const node = {
id: this.nextUniqueId() + index,
type: e.target.id,
text: '',
image: true
}
this.setState(
prev => ({
inputs: [...prev.inputs, node]
})
)
console.log(this.state.inputs);
}
inputContentHandler = (e) => {
let newArray = this.state.inputs;
let newNode = newArray.find((node) => {
return (node.id === e.target.id)
})
newNode.text = e.target.value;
this.setState({ inputs: newArray });
console.log(this.state.inputs);
}
render() {
return (
<div>
<InnerHeader />
<div className='some-page-wrapper'>
<div className='row'>
<div className="dash-card-sm">
<br />
<EditButtonContainer buttonCheck={this.buttonCheck} />
<Route path='/edit/form' render={() => (
<TextAreaField
inputsArray={this.state.inputs}
inputContentHandler={this.inputContentHandler}
/>
)}
/>
<Route path='/edit/preview' render={(props) => (
<Preview
inputs={this.state.inputs}
text={this.state.text}
createPage={this.createPage}
/>
)}
/>
<br /> <br />
{/* Button Header */}
</div>
</div>
</div>
</div>
)
}
}
The problem is that I don't know how I should be passing the rendered view to the Preview.jsxcomponent. I'm still new to react (4 months)...Any help in pointing me in the right direction would be appreciated.
Related
I have an API which with an object and encapsulates errors related with each inputField within it, for example:
{
"success": false,
"message": "The given data was invalid.",
"errors": {
"firstname": [
"Invalid firstname",
"Firstname must be at least 2 characters long"
],
"companyname": ["company name is a required field"]
}
}
based on the errors, I need to display the error right below the input element, for which the code looks like this:
class RegistrationComponent extends Component {
onSubmit = (formProps) => {
this.props.signup(formProps, () => {
this.props.history.push('/signup/complete');
});
};
render() {
const {handleSubmit} = this.props;
return (
<div>
<form
className='form-signup'
onSubmit={handleSubmit(this.onSubmit)}
>
<Field name='companyname' type='text' component={inputField} className='form-control' validate={validation.required} />
<Field name='firstname' type='text' component={inputField} className='form-control' validate={validation.required} />
<Translate
content='button.register'
className='btn btn-primary btn-form'
type='submit'
component='button'
/>
</form>
</div>
);}}
The inputField:
export const inputField = ({
input,
label,
type,
className,
id,
placeholder,
meta: { error, touched },
disabled
}) => {
return (
<div>
{label ? (
<label>
<strong>{label}</strong>
</label>
) : null}
<Translate
{...input}
type={type}
color={"white"}
className={className}
id={id}
disabled={disabled}
component="input"
attributes={{ placeholder: placeholder }}
/>
<InputFieldError
touched={touched}
error={<Translate content={error} />}
/>
</div>
);
};
and finally;
import React, { Component } from "react";
class InputFieldError extends Component {
render() {
return <font color="red">{this.props.touched && this.props.error}</font>;
}
}
export default InputFieldError;
If I validate simply with validate={validation.required} the error property is attached to the correct input field and I can render the error div using InputFieldError right below it.
I am mapping all the errors back from the API response on to props like this:
function mapStateToProps(state) {
return { errorMessage: state.errors, countries: state.countries };
}
which means I can print every error that I encounter at any place like this:
{this.props.errorMessage
? displayServerErrors(this.props.errorMessage)
: null}
rendering all at same place by simply going through each property on errorMessage is easy.
Now when I try to assign the errors back from the API ("errors": {"firstname": []} is linked with Field name="firstname" and so on...), I cannot find a way to attach the error in "firstname" property to the correct InputFieldError component in Field name="firstname"
I hope the question is clear enough, to summarise it I am trying to render error I got from API to their respective input element.
Try to pass errors.firstname to field inner component like this
<Field name='companyname' type='text' component={<inputField apiErrors={this.props.errorMessage.errors.firstname || null} {...props}/>} className='form-control' validate={validation.required} />
and then your inputField will be:
export const inputField = ({
input,
label,
type,
className,
id,
placeholder,
meta: { error, touched },
apiErrors,
disabled
}) => {
return (
<div>
{label ? (
<label>
<strong>{label}</strong>
</label>
) : null}
<Translate
{...input}
type={type}
color={"white"}
className={className}
id={id}
disabled={disabled}
component="input"
attributes={{ placeholder: placeholder }}
/>
<InputFieldError
touched={touched}
error={apiErrors ? apiErrors : <Translate content={error} />}
/>
</div>
);
};
and:
class InputFieldError extends Component {
render() {
const errors = this.props.error
let errorContent;
if (Array.isArray(errors)) {
errorContent = '<ul>'
errors.map(error, idx => errorContent+= <li key={idx}><Translate content={error}/></li>)
errorContent += '</ul>'
} else {
errorContent = errors
}
return <font color="red">{this.props.touched && errorContent}</font>;
}
}
You can also add the main error at the top of your form
class RegistrationComponent extends Component {
onSubmit = (formProps) => {
this.props.signup(formProps, () => {
this.props.history.push('/signup/complete');
});
};
render() {
const {handleSubmit} = this.props;
return (
<div>
{this.props.errorMessage.message &&
<Alert>
<Translate content={this.props.errorMessage.message}/>
</Alert>
}
<form
className='form-signup'
onSubmit={handleSubmit(this.onSubmit)}
>
...
From the App component it passes the variableitem to the Todo component. From the Todo component passes to theSearchResult component. However, the `SearchResult 'component is not displayed.
Demo here: https://stackblitz.com/edit/react-e2zqvd
import {Typeahead} from 'react-bootstrap-typeahead';
class App extends Component {
constructor() {
super();
this.state = {
name: [{id:1, name: 'mario'}, {id:2, name: 'paul'}],
item: 'flower'
};
}
render() {
return (
<div>
<Todo
item = {this.state.item}
name = {this.state.name}
/>
</div>
);
}
}
class Todo extends Component {
constructor(props){
super(props);
}
render () {
return (
<div>
<Typeahead
id={'example4'}
labelKey= 'name'
multiple
options={this.props.name}
onChange={this.handleSelectPeopleToCalendar}
ref={(ref) => this._typeahead = ref}
renderMenuItemChildren={(option, props) => (
<SearchResult
key={option.id}
user={option}
item={props.item}
/>
)}
/>
</div>
)
}
}
const SearchResult = ({user, item}) => (
<div>
<p>{item}</p>
<span>{user.name}</span>
</div>
);
try
<SearchResult
key={option.id}
user={option}
item={this.props.item}
/>
and you need start type something to see this component, there have input with no borders, and without background
I'd like to add a new input everytime the plus icon is clicked but instead it always adds it to the end. I want it to be added next to the item that was clicked.
Here is the React code that I've used.
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add</i>
<i>Remove</i>
</div>
</div>
);
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [Input]
};
}
addInput = index => {
this.setState(prevState => ({
choices: update(prevState.choices, { $splice: [[index, 0, Input]] })
}));
};
render() {
return (
<div>
{this.state.choices.map((Element, index) => {
return (
<Element
key={index}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"));
<div id="app"></div>
<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>
I must admit this get me stuck for a while but there was a problem with how react deals with key props. When you use an index as a key it doesn't work. But if you make sure inputs will always be assigned the same key even when the list changes it will work as expected:
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add </i>
<i>Remove</i>
</div>
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [],
nrOfElements: 0
};
}
addInput = index => {
this.setState(prevState => {
const choicesCopy = [...prevState.choices];
choicesCopy.splice(index, 0, `input_${prevState.nrOfElements}`);
return {
choices: choicesCopy,
nrOfElements: prevState.nrOfElements + 1
};
});
};
componentDidMount() {
this.addInput(0);
}
render() {
return (
<div>
{this.state.choices.map((name, index) => {
return (
<Input
key={name}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
Some reference from the docs:
Keys should be given to the elements inside the array to give the
elements a stable identity...
...We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state.
I am new to both coding as well as React.js, so any assistance in learning what I am doing incorrectly is greatly appreciated! I am creating multiple cards on a page with riddles where the answer is hidden via css. I am using an onClick function ("toggleAnswer") to toggle the state of each answer to change the className so that the answer will either be visible or hidden. Currently, the onClick event is changing the state for all the answers. I realize this is because my code is not targeting a particular element, but I am unsure how this can be done. How can this be achieved? My code is currently like this:
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
isHidden: true
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e) {
this.setState({ isHidden: !this.state.isHidden });
}
componentWillMount() {
this.getPageData();
}
render() {
const answerClass = this.state.isHidden ? "answer-hide" : "answer";
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={answerClass}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={props.onClick}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;
You'd have to keep track of each answer that has been shown in state (in an array or something).
First
Send the index of the answer up in the onclick function. In that function, check if it exists in the "shownAnswers" array and either add or remove it.
onClick={e => props.onClick(e, props.id)}
and
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({
shownAnswers: this.state.shownAnswers.filter(val => val !== index)
});
} else {
this.setState({
shownAnswers: this.state.shownAnswers.concat(index)
});
}
}
Then
When you're passing the class name down to the child component, check if its index is in the "shownAnswers" array to decide which class name to pass.
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
Building off your example, it could look something like this (untested):
// RiddlesPage where toggleAnswer function is defined
class RiddlesPage extends Component {
constructor(props) {
super(props);
this.state = {
questionData: [],
shownAnswers: []
};
this.getPageData = this.getPageData.bind(this);
this.toggleAnswer = this.toggleAnswer.bind(this);
}
getPageData() {
console.log("we hit getPageData function starting --");
helpers.getRiddlesPage().then(data => {
console.log("this is the result", data);
this.setState({
questionData: data[0].questionData,
});
});
}
toggleAnswer(e, index) {
if (this.state.shownAnswers.indexOf(index) > -1) {
this.setState({ shownAnswers: this.state.shownAnswers.filter(val => val !== index) });
} else {
this.setState({ shownAnswers: this.state.shownAnswers.concat(index) });
}
}
componentWillMount() {
this.getPageData();
}
render() {
return (
<div>
<Riddles>
{this.state.questionData.map((data, index) => {
return (
<RiddlesItem
key={index}
id={index}
question={data.question}
answer={data.answer}
button={data.buttonURL}
answerClass={this.state.shownAnswers.indexOf(index) > -1 ? "answer" : "answer-hide"}
onClick={this.toggleAnswer}
/>
);
})}
</Riddles>
</div>
);
}
}
export default RiddlesPage;
// Riddles Component
import React from "react";
import "./riddles.css";
const Riddles = props => (
<div id="riddles-row">
<div className="container">
<div className="row">
<div className="col-12">
<div>{props.children}</div>
</div>
</div>
</div>
</div>
);
export default Riddles;
// RiddlesItem Component where onClick function is set as a prop
import React from "react";
import "./riddles.css";
const RiddlesItem = props => (
<div>
<div className="card-body">
<p id="question">{props.question}</p>
<img
className="img-fluid"
id={props.id}
src={props.button}
onClick={e => props.onClick(e, props.id)}
alt="answer button"
/>
<p className={props.answerClass}> {props.answer} </p>
</div>
</div>
);
export default RiddlesItem;
I have a component of basic editable list items that operates as such:
My problem is that the cursor does not move into the input box onChange, making a hassle for the user to always click twice. I tried https://coderwall.com/p/0iz_zq/how-to-put-focus-at-the-end-of-an-input-with-react-js but it did not work. Component looks like:
import React from 'react';
import MyComponent from '../utils/MyComponent';
export default class BasicList extends MyComponent {
constructor(props) {
let custom_methods = ['renderItemOrEditField', 'toggleEditing', 'moveCaretAtEnd'];
super(props, custom_methods);
this.state = {editing: null};
}
moveCaretAtEnd(e) {
var temp_value = e.target.value
e.target.value = ''
e.target.value = temp_value
}
renderItemOrEditField(item) {
console.log(item);
if (this.state.editing === item.id) {
return (
<input
onKeyDown={ this.handleEditField }
type="text"
className="form-control"
ref={ `${item.type}_name_${ item.id }` }
name="title"
autofocus
onFocus={this.moveCaretAtEnd}
defaultValue={ item.name }
/>
);
} else {
return (
<li
onClick={this.toggleEditing.bind(null, item.id)}
key={item.id}
className="list-group-item">
{item.name}
</li>
);
}
}
toggleEditing(item_id) {
this.setState({editing: item_id});
}
render() {
let li_elements = null;
let items = this.props.items;
if (items.length > 0) {
li_elements = items.map((item) => {
return (
this.renderItemOrEditField(item)
// {/* }<li key={item.id}>
// {item.name} -
// <button onClick={() => {this.props.deleteCallback(this.props.item_type, item.id, item.name)} }>
// Delete
// </button>
// </li> */}
);
});
}
return (
<div>
<h4>{this.props.title}:</h4>
<ul className="list-group">
{li_elements}
</ul>
</div>
);
}
}
The items I'm working with now only have a name and ID (type denotes a 'role' or 'task')
How can I make the cursor start at the end of the input box text on change?
Autofocus triggers when a component is mounted. Not sure that's happening by your conditional render. You could moving your conditional logic to a separate container and that should trigger a mount each time it's shown.
I ended up setting focus as in the callback that causes the input box to show:
toggleEditing(item_id) {
// this syntax runs the function after this.setState is finished
this.setState({editing: item_id}, function() {
this.textInput.focus();
});
}
Then the original solution given worked:
// https://coderwall.com/p/0iz_zq/how-to-put-focus-at-the-end-of-an-input-with-react-js
moveCaretAtEnd(e) {
var temp_value = e.target.value
e.target.value = ''
e.target.value = temp_value
}
and
<input
onKeyDown={ this.handleEditField }
type="text"
className="form-control"
ref={(input) => { this.textInput = input; }}
name="title"
autofocus
onFocus={this.moveCaretAtEnd}
defaultValue={ item.name }
onChange={(event) => this.editItem(event)}
style={ {maxWidth: 500} }
/>