custom method in nested, dynamic child component doesnt fire React - reactjs

I have a component inside my App that processes data from an API and uses it to dynamically create children (which are defined in their own components) based on one of the parameter values from the API call. I am able to get the data through to render the components, but the onClick methods don't work. Here's some code:
The parent Component(within the return):
{this.state.customInputs.map((input, i) => {
if(input.type === 'radio'){
return(
<div className="preference-section" key={i}>
<h2>{input.name}</h2>
<RadioCustomInput
radioActiveState={this.state.radioActiveState}
handleRadioClick={() => this.handleRadioClick()}
options={input.options} />
</div>
)
} else if ( input.type === 'checkbox'){
return(
<div className="preference-section" key={i}>
<h2>{input.name}</h2>
<CheckboxCustomInput
selectedBoxes={this.state.selectedCheckboxes}
handleOnClick={() => this.handleCheckboxClick()}
options={input.options} />
</div>
)
}
})
}
The children components:
class CheckboxCustomInput extends Component {
handleCheckboxOnClick = (e) => {
e.preventDefault();
let checkboxState = e.target.value;
console.log(checkboxState);
return this.props.handleCheckboxClick(checkboxState);
}
render(){
return(
<div className="preference-options">
{this.props.options.map((item, i) => {
return(
<div key={i} className="checkbox-group">
<label
className={this.props.selectedBoxes.includes(item) ? "active-box checkbox-label" : "checkbox-label"}
htmlFor={item}>
<input
className="checkbox-input"
type="checkbox"
value={item}
id={item + '_checkbox'}
onClick={() => this.handleCheckboxOnClick()} />
{item}
</label>
</div>
);
})}
</div>
)
}
}
export default CheckboxCustomInput;
and the other:
class RadioCustomInput extends Component{
handleRadioOnClick = (e) => {
e.preventDefault();
let radioState = e.target.value;
let radioName = e.target.name;
console.log(radioState);
return this.props.handleRadioClick(radioState, radioName);
}
render(){
return(
<div className="radio-group">
{this.props.options.map((item, i) => {
return(
<label
className={this.props.radioActiveState === item ? "active-box radio-label" : "radio-label"}
htmlFor={item + '-card'} key={i}>
<input
className="radio-input"
type="radio"
name={item}
value={item}
id={item + '_radio'}
onClick={() => this.handleRadioOnClick()} />
{item}
</label>
)
})}
</div>
)
}
}
export default RadioCustomInput;

Is the radio working but the checkbox isn't? It looks like you're passing a prop called handleOnClick to your checkbox component, but expecting a handleCheckboxClick prop.

After editing the method prop invocation in the Checkbox component, I was able to pass the event through using the following tweak to my onClick method for each child: onClick={(e) => this.handleCheckboxOnClick(e)}
I also, as suggested by #larz, removed the function return in the parent and passe dthe method by reference only there.
The rest of my issue was CSS, in that the <input/> had a css rule of visibility:hidden and the <label/> element was handling the CSS visual interaction withthe onClick. To fix this, I moved the onClick method to the <label/> and added a native JS method to the chain for the property definitions within the child-local onClick methods, like so:
let radioState = e.target.childNodes[0].value;

Related

React Hook Form how can I call onSubmit in a child component?

I'm working with React Hook Form. I have a higher order component that is using FormProvider and managing the submitting of the form. I want to trigger this function from a child component. The problem I'm having is that when I call the function in the child component the data in the onSubmit function returns undefined. However it works as expected when I click submit in the same component. What am I doing wrong?
const FormGroup = () {
const onSubmit: SubmitHandler<Inputs> = data => {
console.log('data', data);
dispatch(setEntityInformation({
data
}));
}
};
<FormProvider {...methods}>
<form onSubmit={methods.handleSubmit(onSubmit)}>
<section className="sars__sideBar">
<TabBar
controls={TAB_CONTROLS.controls}
activeIndex={index}
setActiveTab={i => setActiveTab(i)}
onSubmit={onSubmit}
/>
</section>
<PrimaryButton onClick={() => {
setActiveTab(index + 1);
}}
>
<input type="submit" value="Continue" /> // submits form as expected
</PrimaryButton>
</form>
</FormProvider>
}
// Child component
const TabBar = ({ activeIndex, setActiveTab, controls, onSubmit }) => (
<Tabs>
{controls.map((control, i) => (
<div
className={`tab__wrapper ${i === activeIndex ? 'active' : ''}`}
key={control.key}
onClick={() => {
setActiveTab(i);
if (i > activeIndex) {
onSubmit(); // function is called, but data is undefined
}
}}
>
<div className="u-alignCenter">
<p>{control.label}</p>
</div>
<div className="tab__status" />
</div>
))}
</Tabs>
);
Maybe calling onSubmit() through handleSubmit would help? Like this: handleSubmit(onSubmit)();
You need this, because handleSubmit is ran asynchronously and with the second parentheses you actually call the returned function.
You can read more about it here

How to handle parameters between parent component and child component

This is working perfectly as a single component. I am trying to remove the search function because I render the component in another component which is embedded in a list. I don't want the search to appear in multiple places. I want to separate it and put it in a parent component.
I don't know how to handle this. I have tried to use props, probably I am doing it the wrong way.
import React, {useState} from 'react'
function TagsInput(props) {
const [tags, setTags] = useState([])
const [search, setSearch] = useState("");
const addTags = event => {
if (event.key === "Enter" && event.target.value !== "") {
setTags([...tags, event.target.value]);
props.selectedTags([...tags, event.target.value]);
event.target.value = "";
}
};
const removeTags = index => {
setTags([...tags.filter(tag => tags.indexOf(tag) !== index)]);
};
const handleFilterChange = e => {
setSearch(e.target.value)
}
function DataSearch(rows) {
return rows.filter((row) => row.toLowerCase().indexOf(search.toLowerCase()) > -1);
}
const searchPosts = DataSearch(tags);
return (
<>
<div>
<input value={search} onChange={handleFilterChange} placeholder={"Search"} />
</div>
<div className="tags-input">
<ul>
{searchPosts.map((tag, index) => (
<li key={index}>
<span>{tag}</span>
<i
className="material-icons"
onClick={() => removeTags(index)}
>x</i>
</li>
))}
</ul>
<input
type="text"
onKeyUp={event => addTags(event)}
placeholder="Press enter to add tags" />
</div>
</>
)
}
export default TagsInput
I am using this component in another component. I want to remove the search input but I don't know how to do it as it is component of a function not defined.
<div>
<input value={search} onChange={handleFilterChange} placeholder={"Search"} />
</div>
In the parent component
<TagsInput /> is embedded in it map function and I want avoid the search being created in multiple li.
Thank you and I am sorry for dump question as I am new to it.
What exactly do you need from this component? If you just need to remove the search input box you can just remove
<div>
<input
value={search}
onChange={handleFilterChange}
placeholder={'Search'}
/>
</div>
It seems you are calling this component inside a parent component loop. If you just need to call this component once you have to use it outside the loop.

React Final Form: make conditions according to form values

I need to generate a certain number of fields according to a value that the user prompts in the first step of the form.
Since I'm doing a multi-step form with a "Wizard" class <Condition /> component doesn't work.
Basically I need to access to values (pull them from the class inside the functional component) in order to tell React the number of "pages" it needs to generate.
Something like this:
export default () => {(
<Wizard
initialValues={{ }}
onSubmit={onSubmit}
>
<Wizard.Page >
<FirstStep />
</Wizard.Page>
{values.items.map((item, index) => (
<Wizard.Page key="index">
<SecondStep stepName="item.name" key="index" />
</Wizard.Page>
) )}
</Wizard>
)}
In order to generate N pages according to the number of items the user created, one page for each item.
This approach does not work because it tells me that the values are not defined.
This is my Wizard React Component:
import React from 'react'
import PropTypes from 'prop-types'
import { Form } from 'react-final-form'
import arrayMutators from 'final-form-arrays'
export default class Wizard extends React.Component {
static propTypes = {
onSubmit: PropTypes.func.isRequired
}
static Page = ({ children }) => children
constructor(props) {
super(props)
this.state = {
page: 0,
values: props.initialValues || {}
}
}
next = values =>
this.setState(state => ({
page: Math.min(state.page + 1, this.props.children.length - 1),
values
}))
previous = () =>
this.setState(state => ({
page: Math.max(state.page - 1, 0)
}))
validate = values => {
const activePage = React.Children.toArray(this.props.children)[
this.state.page
]
return activePage.props.validate ? activePage.props.validate(values) : {}
}
handleSubmit = values => {
const { children, onSubmit } = this.props
const { page } = this.state
const isLastPage = page === React.Children.count(children) - 1
if (isLastPage) {
return onSubmit(values)
} else {
this.next(values)
}
}
render() {
const { children } = this.props
const { page, values } = this.state
const activePage = React.Children.toArray(children)[page]
const isLastPage = page === React.Children.count(children) - 1
return (
<Form
initialValues={values}
validate={this.validate}
onSubmit={this.handleSubmit}
mutators={{...arrayMutators }}
>
{({ handleSubmit, submitting, values }) => (
<form onSubmit={handleSubmit}>
{activePage}
<div className="buttons centered">
{page > 0 && <button type="button" onClick={this.previous} >« Previous </button>}
{!isLastPage && <button type="submit">Next »</button>}
{isLastPage && <button type="submit" disabled={submitting}>Submit</button>}
</div>
<pre>{JSON.stringify(values, 0, 2)}</pre>
</form>
)}
</Form>
)
}
}
And this is the FirstStep component (where the user inserts its first values, it generates and array of items. All I need to do is pull the values of the state in order to generate a number of pages according to the length of that first array):
export default () => (
<FieldArray name="items">
{({ fields }) => (
<div className="centered-items">
{fields.map((name, index) => (
<div key={name} className="item-row">
<label htmlFor={`item-name-${index}`}>item: </label>
<Field
id={`item-name-${index}`}
name={`${name}.item`}
component="input"
type="text"
placeholder="Place Item Name"
/>
<button type="button" onClick={() => fields.remove(index)}>
Remove
</button>
</div>
))}
<button
className="centered"
type="button"
onClick={() => fields.push({ tipologia: '', numero_camere: '1' })}
>
Add Item
</button>
</div>
)}
</FieldArray>
)
I manage to solve this problem calling React.cloneElement() method.
This method needs to wrap either activePage variable and the static method "Page", in this way you can pass properties to children, so at least I manage to access the form values inside every page and so create some conditions.
You can see a working example here: Demo - Codesandbox
I still haven't figured out how to access this values outside the page (inside Wizard Class but before Wizard.Page method, this could be the ideal way because I can create conditions in order to generate/(or not generate) pages.

functions passed as props are invoked regardless

I'm developing an application that has the following component hierarchy (courtesy ProReact)
KanbanContainer => KanbanBoard => List => Card => CheckList
KanbanContainer contains methods that need to be passed down to the CheckList component (as that component has all the ui controls). The methods in the KanbanContainer are defined as follows
class KanbanBoardContainer extends Component {
state = { cards: [] };
componentDidMount() {
this.setState({ cards: API.getTasks() });
}
addTask = (cardId, taskName) => {
console.log(taskName, " invoked for cardId =", cardId);
};
deleteTask = (cardId, taskId, taskIndex) => {
console.log("deleteTask invoked for cardId = ", cardId);
};
toggleTask = (cardId, taskId, taskIndex) => {
console.log("toggleTask invoked fpr cardId = ", cardId);
};
render() {
return (
<KanbanBoard
cards={this.state.cards}
taskCallbacks={{
toggleTask: this.toggleTask,
addTask: this.addTask,
deleteTask: this.deleteTask
}}
/>
);
}
}
In all the other components, the taskCallbacks is simply passed on via props. For example:
class List extends React.Component {
render() {
let cards = this.props.cards.map(c => {
return (
<Card
id={c.id}
key={c.id}
title={c.title}
description={c.description}
color={c.color}
tasks={c.tasks}
taskCallbacks={this.props.taskCallbacks}
/>
);
});
return (
<div className="list">
<h1>{this.props.title}</h1>
{cards}
</div>
);
}
}
In the final component, the functions passed in via props are attached to the ui controls such as checkbox and a link.
class CheckList extends Component {
checkInputKeyPress = event => {
if (event.key === "Enter") {
this.props.taskCallbacks.addTask(this.props.cardId, event.target.value);
event.target.value = "";
}
};
render() {
const { deleteTask, toggleTask } = this.props.taskCallbacks;
let tasks = this.props.tasks.map((t, index) => (
<li key={t.id}>
<input
type="checkbox"
name=""
id=""
defaultChecked={t.done}
onChange={toggleTask(this.props.cardId, t.id, index)}
/>{" "}
{t.name}{" "}
<a
href="#"
onClick={deleteTask(this.props.cardId, t.id, index)}
/>
</li>
));
return (
<div>
<ul>{tasks}</ul>
<input
type="text"
placeholder="Key in a task and hit enter"
onKeyPress={this.checkInputKeyPress}
/>
</div>
);
}
}
However when I load the application, the functions get called "on load" and nothing happens when the controls are clicked. Only the addTask() gets called when you type in the textfield and hit enter. What am I missing?
By using:
onClick={deleteTask(this.props.cardId, t.id, index)}
The function will be invoked in place. Try switching for:
onClick={() => deleteTask(this.props.cardId, t.id, index)}
For clarity, deleteTask is a reference to a function, deleteTask() invokes a function. In a situation where you need to invoke the function (for example, to pass arguments) then the pattern above is an anonymous function that calls your deleteTask function.
Create a function in the last component that calls the prop function with the appropriate arguments. That is, don't invoke a function directly from onClick/onChange, becase those props expect a reference to a function, not the result of invoking a function.
Most importantly, you should check out the Context API in order to avoid passing down so many props.

How to get the values of `textField` in the "Father" component

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/

Resources