React: add dynamically fields - javascript

I have a class in which I should add dynamically fields when the user click on "Add Button" or delete the fields if the user click on the "Remove Button".
export default class myClass extends Component
{
constructor(props){
super(props);
}
Fields = [
{
name: '$ClassName',
fields: [
{
name: 'ClassName.FirstField',
col: ["lg-6", 'md-6', 'sm-12'],
required: true,
label: "First Field"
}
]
},
]
render()
{
const self = this
return(
<Column col={'12'} className="mt-2 mb-2">
<Row>
{
this.Fields.map((group, i) => (
<Column key={`${group.name}_i`} col="12" className="mt-4 mb-2 form-fields-wrap">
<h5 className="form-section-title">{group.label}</h5>
<Row>
{
Start.renderFieldsGroup(group.fields, group.name, this.props.preview)
}
</Row>
</Column>
))
}
</Row>
</Column>
)
}
Now I should create the possibility to add (and remove) the Fields array when an user click on Add Button (or Remove Button).
How can I do to add dynamically add this field?
EDIT:
export default class myClass extends Component
{
constructor(props){
super(props);
this.state = { inputs: ['input-0'] };
}
tryFunction(){
const self = this
return(
<Column col={'12'} className="mt-2 mb-2">
<Row>
{
this.Fields.map((group, i) => (
<Column key={`${group.name}_i`} col="12" className="mt-4 mb-2 form-fields-wrap">
<h5 className="form-section-title">{group.label}</h5>
<Row>
{
Start.renderFieldsGroup(group.fields, group.name, this.props.preview)
}
</Row>
</Column>
))
}
</Row>
</Column>
)
}
appendInput() {
console.log("11111")
var newInput = `input-${this.state.inputs.length}`;
this.setState(prevState => ({ inputs: prevState.inputs.concat([newInput]) }));
}
render()
{
const self = this
return(
<div>
<div id="dynamicInput">
{console.log("this.state.input ", this.state.input)}
{this.state.inputs.map(input => this.tryFunction())}
</div>
<button onClick={ () => this.appendInput() }>
CLICK ME TO ADD AN INPUT
</button>
</div>
);
}

You call this.Fields.map() in your edit but as far as i can tell you dont actually declare Fields. I made an example how i would solve such a situation, you should be able to use the same technique for your situation.
export default class MyClass extends React.Component{
constructor(props){
super(props);
this.state = {
dynamicItems: []
};
}
handleClick(){
//Add a new item component to state.dynamicItems
this.setState(prevState => ({
dynamicItems: [...prevState.dynamicItems, <Item text="text" key="key" />]
}));
}
render(){
return(
<div className="page">
<div className="dynamic-container">
{/*Render item components*/}
{this.state.dynamicItems.map((item) => {return item})}
</div>
<button onclick={() => this.handleClick()}>Add Item</button>
</div>
);
}
}
//Item component
class Item extends React.Component{
render(){
return(
<div className="item" key={this.props.key}>
<p>{this.props.text}</p>
</div>
);
}
}

Related

switch icons on button click in react

I am using react-full-screen library.
Link to code sandbox
I have a navbar, where I have placed the JSX for the button with icons.
class AdminNavbar extends React.Component {
constructor(props) {
super(props);
this.state = {
isFfull: false
};
}
render() {
return (
<Navbar className="navbar" expand="lg">
<Container fluid>
<div className="navbar-wrapper">
<div className="navbar-minimize d-inline">
<button className="btn-fullscreen" onClick={this.props.goFull}>
<i className="fa fa-expand-arrows-alt"></i>
<i className="fa compress-arrows-alt"></i>
</button>
</div>
</div>
</Container>
</Navbar>
);
}
}
And then in my another Admin Component, I am using it as props and performing the onClick()
class Admin extends React.Component {
constructor(props) {
super(props);
this.state = {
isFull: false
};
}
goFull = () => {
if (document.body.classList.contains("btn-fullscreen")) {
this.setState({ isFull: true });
} else {
this.setState({ isFull: false });
}
document.body.classList.toggle("btn-fullscreen");
};
render() {
return (
<Fullscreen
enabled={this.state.isFull}
onChange={(isFull) => this.setState({ isFull })}
>
<div className="wrapper">
<div className="main-panel">
<AdminNavbar {...this.props} goFull={this.goFull} />
</div>
</div>
</Fullscreen>
);
}
}
Problem: the icons are not changing on click of the button. I also tried using the active class. but no luck.
You don't have to check the classList on body. The icon toggle can be achieved by state change.Please have a look at the code.
import React from "react";
import AdminNavbar from "./AdminNavbar";
import Fullscreen from "react-full-screen";
class Admin extends React.Component {
constructor(props) {
super(props);
this.state = {
isFull: false
};
}
goFull = () => {
this.setState({ isFull: !this.state.isFull });
};
render() {
return (
<Fullscreen
enabled={this.state.isFull}
onChange={(isFull) => this.setState({ isFull })}
>
<div className="wrapper">
<div className="main-panel">
<AdminNavbar
{...this.props}
isFull={this.state.isFull}
goFull={this.goFull}
/>
</div>
</div>
</Fullscreen>
);
}
}
export default Admin;
The admin Navbar code
import React from "react";
// reactstrap components
import { Navbar, Container } from "reactstrap";
class AdminNavbar extends React.Component {
constructor(props) {
super(props);
this.state = {
isFfull: false
};
}
render() {
return (
<Navbar className="navbar" expand="lg">
<Container fluid>
<div className="navbar-wrapper">
<div className="navbar-minimize d-inline">
<button className="btn-fullscreen" onClick={this.props.goFull}>
{!this.props.isFull ? (
<i className="fa fa-expand-arrows-alt"></i>
) : (
<i className="fa compress-arrows-alt"></i>
)}
</button>
</div>
</div>
</Container>
</Navbar>
);
}
}
export default AdminNavbar;

How to call child component method from parent

In my Reactjs app, I need to have a parent component (a wizard) named Wizard.js and a number of child components (steps of the wizard) named PrimaryForm.js, SecondaryForm.js etc. They all are Class based components with some local validation functions.
Previous and Next buttons to advance the steps, reside in the Wizard.js.
To advance the next step of the wizard, I'm trying to call a method from PrimaryForm. I checked similar questions in Stackoverflow; tried using ref or forwardRef, but I could not make it work. I currently receive "TypeError: Cannot read property 'handleCheckServer' of null" error.
Below are my parent and child classes. Any help about what I would be doing wrong is appreciated.
Wizard.js:
import React, { Component } from 'react';
...
const getSteps = () => {
return [
'Info',
'Source Details',
'Target Details',
'Configuration'
];
}
class Wizard extends Component {
constructor(props) {
super(props);
this.firstRef = React.createRef();
this.handleNext = this.handleNext.bind(this);
this.state = {
activeStep: 1,
}
}
componentDidMount() {}
handleNext = () => {
if (this.state.activeStep === 1) {
this.firstRef.current.handleCheckServer(); <<<<<<<<<<<<<<<<< This is where I try to call child method
}
this.setState(state => ({
activeStep: state.activeStep + 1,
}));
};
handleBack = () => {
this.setState(state => ({
activeStep: state.activeStep - 1,
}));
};
handleReset = () => {
this.setState({
activeStep: 0,
});
};
render() {
const steps = getSteps();
const currentPath = this.props.location.pathname;
const { classes } = this.props;
return (
<React.Fragment>
<CssBaseline />
<Topbar currentPath={currentPath} />
<div className={classes.root}>
<Grid container spacing={2} justify="center" direction="row">
<Grid container spacing={2} className={classes.grid} justify="center" direction="row">
<Grid item xs={12}>
<div className={classes.topBar}>
<div className={classes.block}>
<Typography variant="h6" gutterBottom>Wizard</Typography>
<Typography variant="body1">Follow the wizard steps to create a configuration.</Typography>
</div>
</div>
</Grid>
</Grid>
<Grid container spacing={2} alignItems="center" justify="center" className={classes.grid}>
<Grid item xs={12}>
<div className={classes.stepContainer}>
<div className={classes.bigContainer}>
<Stepper classes={{ root: classes.stepper }} activeStep={this.state.activeStep} alternativeLabel>
{steps.map(label => {
return (
<Step key={label}>
<StepLabel>{label}</StepLabel>
</Step>
);
})}
</Stepper>
</div>
<PrimaryForm ref={this.firstRef} />
</div>
</Grid>
</Grid>
<Grid container spacing={2} className={classes.grid}>
<Grid item xs={12}>
<div className={classes.flexBar}>
<Tooltip title="Back to previous step">
<div>
<Button variant="contained"
disabled={(this.state.activeStep === 0)}
className={classes.actionButton}
onClick={this.handleBack}
size='large'>
<BackIcon className={classes.rightIcon} />Back
</Button>
</div>
</Tooltip>
<Tooltip title="Proceed the next step">
<div>
<Button
variant="contained" className={classes.actionButton}
color="primary"
size='large'
disabled={!(!this.state.isFormValid || this.state.isTestWaiting)}
onClick={this.handleNext}>
<ForwardIcon className={this.props.classes.rightIcon}/>Next</Button>
</div>
</Tooltip>
<Tooltip title="Cancel creating new configuration">
<Button variant="contained" color="default" className={classes.actionButton}
component={Link} to={'/configs'} style={{ marginLeft: 'auto' }}>
<CancelIcon className={classes.rightIcon} />Cancel
</Button>
</Tooltip>
</div>
</Grid>
</Grid>
</Grid>
</div>
</React.Fragment>
)
}
}
export default withRouter(withStyles(styles)(Wizard));
PrimaryForm.js:
import React, { Component } from 'react';
...
class PrimaryForm extends Component {
constructor(props) {
super(props);
this.handleCheckServer = this.handleCheckServer.bind(this);
this.state = {
hostname: {
value: "localhost",
isError: false,
errorText: "",
},
serverIp: {
value: "127.0.0.1",
isError: false,
errorText: "",
},
isFormValid: true,
isTestValid: true,
testErrorMessage: "",
isTestWaiting: false,
};
}
componentDidMount() { }
handleCheckServer() {
alert('Alert from Child. Server check will be done here');
}
evaluateFormValid = (prevState) => {
return ((prevState.hostname.value !== "" && !prevState.hostname.isError) &&
(prevState.serverIp.value !== "" && !prevState.serverIp.isError));
};
handleChange = event => {
var valResult;
switch (event.target.id) {
case 'hostname':
valResult = PrimaryFormValidator.validateHostname(event.target.value, event.target.labels[0].textContent);
this.setState({
...this.state,
hostname:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
},
});
break;
case 'serverIp':
valResult = PrimaryFormValidator.validateIpAddress(event.target.value, event.target.labels[0].textContent);
this.setState({
...this.state,
serverIp:
{
value: event.target.value,
isError: valResult.isError,
errorText: valResult.errorText,
}
});
break;
default:
}
this.setState(prevState => ({
...prevState,
isFormValid: this.evaluateFormValid(prevState),
}));
}
render() {
const { classes } = this.props;
return (
<React.Fragment>
<div className={classes.bigContainer}>
<Paper className={classes.paper}>
<div>
<div>
<Typography variant="subtitle1" gutterBottom className={classes.subtitle1} color='secondary'>
Primary System
</Typography>
<Typography variant="body1" gutterBottom>
Information related with the primary system.
</Typography>
</div>
<div className={classes.bigContainer}>
<form className={classes.formArea}>
<TextField className={classes.formControl}
id="hostname"
label="FQDN Hostname *"
onChange={this.handleChange}
value={this.state.hostname.value}
error={this.state.hostname.isError}
helperText={this.state.hostname.errorText}
variant="outlined" autoComplete="off" />
<TextField className={classes.formControl}
id="serverIp"
label="Server Ip Address *"
onChange={this.handleChange}
value={this.state.serverIp.value}
error={this.state.serverIp.isError}
helperText={this.state.serverIp.errorText}
variant="outlined" autoComplete="off" />
</form>
</div>
</div>
</Paper>
</div>
</React.Fragment>
)
}
}
export default withRouter(withStyles(styles)(PrimaryForm));
(ps: I would like to solve this without another framework like Redux, etc if possible)
Example in Typescript.
The idea is that the parent passes its callback to the child. The child calls the parent's callback supplying its own e.g. child callback as the argument. The parent stores what it got (child callback) in a class member variable and calls it later.
import * as React from 'react'
interface ICallback {
(num: number): string
}
type ChildProps = {
parent_callback: (f: ICallback) => void;
}
class Child extends React.Component {
constructor(props: ChildProps) {
super(props);
props.parent_callback(this.childCallback);
}
childCallback: ICallback = (num: number) => {
if (num == 5) return "hello";
return "bye";
}
render() {
return (
<>
<div>Child</div>
</>
)
}
}
class Parent extends React.Component {
readonly state = { msg: "<not yet set>" };
letChildRegisterItsCallback = (fun: ICallback) => {
this.m_ChildCallback = fun;
}
callChildCallback() {
const str = this.m_ChildCallback? this.m_ChildCallback(5) : "<callback not set>";
console.log("Child callback returned string: " + str);
return str;
}
componentDidMount() {
this.setState((prevState) => { return {...prevState, msg: this.callChildCallback()} });
}
render() {
return (
<>
<Child {...{ parent_callback: this.letChildRegisterItsCallback }} />
<div>{this.state.msg}</div>
</>
)
}
m_ChildCallback: ICallback | undefined = undefined;
}
P.S.
The same code in Javascript. The only difference is that interface, type, readonly and type annotations are taken out. Pasting into here confirms it's a valid ES2015 stage-2 code.
class Child extends React.Component {
constructor(props) {
super(props);
props.parent_callback(this.childCallback);
}
childCallback = (num) => {
if (num == 5) return "hello";
return "bye";
}
render() {
return (
<>
<div>Child</div>
</>
)
}
}
class Parent extends React.Component {
state = { msg: "<not yet set>" };
letChildRegisterItsCallback = (fun) => {
this.m_ChildCallback = fun;
}
callChildCallback() {
const str = this.m_ChildCallback? this.m_ChildCallback(5) : "<callback not set>";
console.log("Child callback returned string: " + str);
return str;
}
componentDidMount() {
this.setState((prevState) => { return {...prevState, msg: this.callChildCallback()} });
}
render() {
return (
<>
<Child {...{ parent_callback: this.letChildRegisterItsCallback }} />
<div>{this.state.msg}</div>
</>
)
}
m_ChildCallback = undefined;
}

React - How to Populate one Dropdowns based on selection from another Dropdown by Passing State as props

I am creating a bar with two dropdown. The second dropdown depends of the selection from the first dropdown. I have 3 Components :
1. Dropdown Bar : Contains FirstDropdown and Second Dropdown
2. FirstDropdown
3. SecondDropdown
Trying to pass State -> Practice that appears in the FirstDropdown Component as props to SecondDropdown Component. Clearly I'm not doing this correctly. Any Help will be appreciate. Thank you in advance!
class DropdownBar extends React.Component {
constructor(props) {
super(props);
}
render () {
return (
<div>
<div className="top-bar">
<Row>
<div style={{marginTop: 15, marginBottom:15}}>
<Col span={8}><FirstDropdown practice={this.props.practice} /></Col>
<Col span={8}><SecondDropdown /></Col>
</div>
</Row>
</div>
</div>
)
}
class FirstDropdown extends React.Component {
constructor() {
super();
this.state = {
practices: [
name = 'Jon',
name = 'potato',
name = 'stark',
],
practice: 'stark'
}
}
onChangePractice(value) {
console.log(`selected ${value}`);
this.setState({
practice: value
})
}
render () {
const {practices} = this.state
return (
<div>
<Row>
<div className="First-dropdown">
<Col span={8}><div className="dropdown-title">Research: </div></Col>
<Col span={14}>
<Select
showSearch
style={{ width: '100%' }}
placeholder="Select a Something"
optionFilterProp="children"
onChange={this.onChangePractice.bind(this)}
onFocus={onFocus}
onBlur={onBlur}
onSearch={onSearch}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{practices.map(practice => (
<Option
value={practice}
key={practice}
data-automation={practice.name}
>{practice}</Option>
))}
</Select>
</Col>
</div>
</Row>
</div>
)
}
class SecondDropdown extends React.Component {
constructor(props) {
super(props);
this.state = {
modules: [
name = 'Drogon',
name = 'Rhaegal',
name = 'Viserion',
]
}
}
componentDidUpdate(prevProps) {
console.log(this.props.practice)
if (!equal(this.props.practice, prevProps.practice))
{
this.updatePractice();
}
}
render () {
const {modules} = this.state
console.log(this.props.practice )
let practice = this.props.practice;
if (practice === 'stark') {
return (
<div>
<Row>
<div className="benchmark-dropdown">
<Col span={4}><div className="dropdown-title">Module: </div></Col>
<Col span={16}>
<Select
showSearch
style={{ width: '100%' }}
placeholder="Select Something"
optionFilterProp="children"
onChange={onChange}
onFocus={onFocus}
onBlur={onBlur}
onSearch={onSearch}
filterOption={(input, option) =>
option.props.children.toLowerCase().indexOf(input.toLowerCase()) >= 0
}
>
{modules.map(item => (
<Option
value={item}
key={item}
>{item}</Option>
))}
</Select>
</Col>
</div>
</Row>
</div>
)
} else {
return <div> NOOOOO </div>
}
}
In order for both dropdowns to have access to the practice prop, you need to lift it up to the DropdownBar's state, and pass down both practice and a way to update practice.
class DropdownBar extends Component {
state = {
practice: '',
}
handlePracticeChange = (value) => {
setState({ practice: value });
}
render() {
return (
<div>
<FirstDropdown
practice={this.state.practice}
onPracticeChange={this.handlePracticeChange}
/>
<SecondDropdown practice={this.state.practice} />
</div>
)
}
}
So, practice only lives in DropdownBar, and the practices array should live in FirstDropdown child.
In FirstDropdown, you should pass props.onPracticeChange to your Select's onChange:
class FirstDropdown extends Component {
render() {
...
<Select
onChange={this.props.onPracticeChange}
...
}
}
From your code example, it looks like Select passes the currently selected value to onChange.
I'd pull the state into the parent.
class MainBar extends React.Component {
state = {
practice: null
};
handleChange = practice => {
this.setState({ practice });
}
render() {
return (
<div className="top-bar">
<Row>
<div style={{marginTop: 15, marginBottom:15}}>
<Col span={8}>
<FirstDropdown
onChange={this.handleChange}
practice={this.state.practice}
/>
</Col>
<Col span={8}>
<SecondDropdown practice={this.state.practice} />
</Col>
</div>
</Row>
</div>
);
}
}

How to show Data results on click in React?

I am trying to show my results from a JSON file only when the search button is clicked. What is the correct way to do it?
Right now as the user types a product the results are show. I have a simple filter, that is filtering the results, but I would like to make that only appear when the button is clicked. I only want to show results when the search button is clicked.
class App extends Component {
constructor(props){
super(props);
this.state = {
value: '',
list: []
}
this.handleChange = this.handleChange.bind(this);
this.handleSearch = this.handleSearch.bind(this);
this.refresh();
}
handleChange(event){
this.setState({ ...this.state, value: event.target.value })
}
refresh(){
axios.get(`${URL}`)
.then(resp => this.setState({...this.state, value: '', list: resp.data}));
}
handleSearch(product){
this.refresh();
}
render(){
return(
<div className="outer-wrapper">
<Header />
<main>
<Container>
<Row>
<Col xs={12} md={12} lg={12} className="pl-0 pr-0">
<SearchBar
handleChange={this.handleChange}
handleToggle={this.handleToggle}
handleSearch={this.handleSearch}
value={this.state.value}
/>
<SearchResultBar
value={this.state.value}
/>
<Filter />
</Col>
</Row>
<ProductList
value={this.state.value}
list={this.state.list}
/>
</Container>
</main>
</div>
)
}
}
export default App;
class Search extends Component {
constructor(props){
super(props);
}
render(){
return(
<div className="search-input">
<InputGroup>
<Input placeholder='Enter Search'
onChange={this.props.handleChange}
value={this.props.value}
/>
<InputGroupAddon className='input-group-append'
onClick={this.props.handleSearch}>
<span className='input-group-text'>
<i className="fa fa-search fa-lg fa-flip-horizontal"></i>
</span>
</InputGroupAddon>
</InputGroup>
</div>
)
}
}
export default Search;
class ProductList extends Component {
constructor(props){
super(props);
this.state = {
}
}
render(){
let filteredSearch = this.props.list.filter(
(product) => {
return product.title.indexOf(this.props.value) !== -1
}
)
return(
<Container>
<Row>
{
filteredSearch.map(item => {
return <Product {...item} key={item._id} />
})
}
</Row>
</Container>
);
}
}
export default ProductList;
As it stands, my list of products is being displayed in the app as soon as it loads. This seems something trivial, but I have been scratching my head in trying to solve it.
You're calling this.refresh() inside the constructor. So it gets run on mount.
Just remove it from the constructor and you should be fine.

Add Class to the element where Clicked event happens in React JS

I have 5 such list items i.e self , parents , siblings , relative, friend. Clicking on any item , I am adding a class called active-option . Below is my code , what I have done so far. To note , I am a new to React JS.
import React, { Component } from 'react';
import {Grid, Col, Row, Button} from 'react-bootstrap';
import facebook_login_img from '../../assets/common/facebook-social-login.png';
const profilesCreatedBy = ['Self' , 'Parents' , 'Siblings' , 'Relative' , 'Friend'];
class Register extends Component {
constructor(props) {
super(props);
this.state = { addClass: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ addClass: !this.state.addClass });
}
render() {
let selectOption = ["option"];
if (this.state.addClass) {
selectOption.push("active-option");
}
return (
<section className="get-data__block" style={{padding: '80px 0 24px 0'}}>
<Grid>
<Row>
<Col sm={10} md={8} mdOffset={2} smOffset={1}>
<p className="grey-text small-text m-b-32"><i>
STEP 1 OF 6 </i>
</p>
<div className="data__block">
<div className="step-1">
<p className="m-b-32">This profile is being created by</p>
<Row>
{profilesCreatedBy.map((profileCreatedBy, index) => {
return <Col className="col-md-15">
<div onClick={this.handleClick} className={selectOption.join(" ")}>
{profileCreatedBy}
</div>
</Col>;
})}
</Row>
</div>
<Row className="text-center">
<Col xs={12} className="text-center">
<Button href="#" bsStyle="primary" className="m-t-96 m-b-16 has-box__shadow" >
Continue
</Button>
</Col>
</Row>
</div>
</Col>
</Row>
</Grid>
</section>
);
}
}
export default Register;
I am using a map function to display all items. I have tried to add a class called active-option to option. But clicking on any item is adding the class to every other item also. (Attached) Any suggestion ? I want to add active-option class to the one where click event happens, not to every other element. Siblings should not contain active-option class. Please help !
You can achieve this with keeping active item id in the state of component, for example:
class Test extends React.Component{
constructor(){
super();
this.state = {
activeId: null
}
this.setActiveElement = this.setActiveElement.bind(this);
}
setActiveElement(id){
this.setState({activeId: id})
}
render(){
return(
<div>
{
[1,2,3,4,5].map((el, index) =>
<div className={index === this.state.activeId? "active" : ""} onClick={() => this.setActiveElement(index)}>click me</div>
)
}
</div>
)
}
}
https://jsfiddle.net/69z2wepo/85095/

Categories