How to model dynamic forms as a React Component?
For example I want to create a form shown in an image below:
How can I model this as a React component?
How can I add dynamicity to that component? For example, clicking on "+ Add" button creates another empty textbox and puts it right below the other already rendered textboxes (as shown in an image below).
Can someone help me with the code for the Form below?
In tags I see redux so I can suggest redux-form. Here you have an example of dynamic forms with redux-form.
The difference is in the fact, that beyond the state of form values, we also need to handle the state of form shape/structure.
If you render the inputs by traversing some state object, that is representing the shape of the form, than new input is just a new entry in this state object. You can easily add or remove input fields on the form by managing that state object. E.g. you can write something like this (pseudo react code):
// inputs state of math and algorithms
const state = { math: [obj1, obj2], algorithms: [obj1, obj2] } // obj ~= {id, value}
// render math inputs
const mathMarkup = state.math.map(obj => <input value={obj.value} onChange={...} />)
// add handler for math field
const addMath = () => setState(prevState => ({ math: [...prevState.math, newObj]}))
Here is the example of such form - codesandbox. It's not 100% as on your screen, but the idea should be understandable. Since there are some unclear requirements on your form, I implemented only first two sections, so you can grasp the idea. And, there are no styles :shrug:
Also, you can extract renderXyz methods to separate components, and improve state shape to meet your needs.
I can help you with a reduced way
import React , {Component} from 'react'
import { connect }from 'react-redux'
class main extends Component{
render(){
return(
<div>
<BaselineMath/>
<Algorithms />
</div>
)
}
}
const mapStateToProps = ({}) => {
return{}
}
export default connect (mapStateToProps,{})(main)
class BaselineMath extends Component{
constructor(props){
super(props);
this.state={rows:[1]}
}
_getRows{
return this.state.rows.map((res,key)=>{
return <input placeholder="etc..."/>
})
}
onClickAdd(){
let rows = this.state.rows
rows.push(1)
this.setState({
rows
})
}
render(){
return(
<div>
<Button onClick={this.onClickAdd.bind(this)}>ADD row</Button>
{this._getRows()}
</div>
)
}
}
export default (BaselineMath)
class Algorithms extends Component{
constructor(props){
super(props);
this.state={rows:[1]}
}
_getRows{
return this.state.rows.map((res,key)=>{
return <input placeholder="etc..."/>
})
}
onClickAdd(){
let rows = this.state.rows
rows.push(1)
this.setState({
rows
})
}
render(){
return(
<div>
<Button onClick={this.onClickAdd.bind(this)}>ADD row</Button>
{this._getRows()}
</div>
)
}
}
export default (Algorithms)
you can do the algorithm with anything you want
Related
I'm trying to add a custom HTML attribute on a React component:
const Parent = () => (
<div className="parent">
<Child data-custom="foo" />
</div>
);
Where Child is another React component, but the attribute gets stripped on the output HTML. I'm aware that I could simply add the attribute on the Child root element, like so:
const Child = () => <div className="child" data-custom="foo" />
Or read the attribute in the Child component via props, but that's not what i'm looking since that would couple the attribute with the component, and I'm looking for a more contextual approach (the attribute would be used for test automation purposes).
Is there a clean way to do that in React?
I'm also considering writing a Babel plugin to add the attribute or prevent the stripping, but that would be another question.
Thanks!
React element doesn't necessarily correspond to DOM element that could have HTML attributes. React component can be rendered to multiple DOM elements or no elements at all.
If Child should be able to provide additional props or attributes to child DOM element, it should pass them explicitly:
const Child = props => <div className="child" {...props} />
This way data-custom="foo" will be passed to <div>.
For this, you can try this in your react script.
const MyCompo = () => {
return (
<div>
<p>HTML Code</p>
</div>
);
}
export default About;
Otherwise you can create class and then define your components and then export them.
import React from 'react';
import '../assets/css/style.css';
import '../assets/css/pure-min.css';
import '../assets/css/custom.css';
import 'font-awesome/css/font-awesome.min.css';
import $ from 'jquery';
class FirstScreen extends React.Component {
constructor(props) {
super(props);
this.handleLoad = this.handleLoad.bind(this);
}
componentDidMount() {
window.addEventListener('load', this.handleLoad);
}
handleLoad() {
}
render() {
return <div>HTML Code</div>;
}
}
export default FirstScreen;
You could use the below syntax
const Parent = () => (
<div className="parent">
<Child data-custom="foo"/>
</div>
);
const Child = ({...props}) => (<div className="child" {...props} />)
I'm making a modal in my React project that requires a class to be added to the body when the modal is open and removed when it is closed.
I could do this the old jQuery way by running some vanilla JavaScript which adds / removes a class, however this doesn't feel like the normal React philosophy.
Should I instead setState on my top level component to say whether the modal is open or closed? Even if I did this, as it's rendered into the div on the page it's still a side-effect to edit the body element, so is there any benefit for this extra wiring?
TL;DR use document.body.classList.add and document.body.classList.remove
I would have two functions that toggle a piece of state to show/hide the modal within your outer component.
Inside these functions I would use the document.body.classList.add and document.body.classList.remove methods to manipulate the body class dependant on the modal's state like below:
openModal = (event) => {
document.body.classList.add('modal-open');
this.setState({ showModal: true });
}
hideModal = (event) => {
document.body.classList.remove('modal-open');
this.setState({ showModal: false });
}
With the new React (16.8) this can be solved with hooks:
import {useEffect} from 'react';
const addBodyClass = className => document.body.classList.add(className);
const removeBodyClass = className => document.body.classList.remove(className);
export default function useBodyClass(className) {
useEffect(
() => {
// Set up
className instanceof Array ? className.map(addBodyClass) : addBodyClass(className);
// Clean up
return () => {
className instanceof Array
? className.map(removeBodyClass)
: removeBodyClass(className);
};
},
[className]
);
}
then, in the component
export const Sidebar = ({position = 'left', children}) => {
useBodyClass(`page--sidebar-${position}`);
return (
<aside className="...">
{children}
</aside>
);
};
Actually you don't need 2 functions for opening and closing, you could use document.body.classList.toggle
const [isOpen, setIsOpen] = useState(false)
useEffect(() => {
document.body.classList.toggle('modal-open', isOpen);
},[isOpen])
<button onCLick={()=> setIsOpen(!isOpen)}>Toggle Modal</button>
Like what #brian mentioned, try having a top-level container component that wraps around your other components. (assuming you're not using redux in your app)
In this top-level component:
Add a boolean state (eg. modalOpen) to toggle the CSS class
Add methods (eg. handleOpenModal & handleCloseModal) to modify the boolean state.
Pass the methods created above as props into your <Modal /> component
ReactJS has an official React Modal component, I would just use that: https://github.com/reactjs/react-modal
Able to fetch the data from API.
But the problem is when I try to map the carousel-item inside class carousel.
The properties are not being inherited.
When I inspect, I see there are carousel-item inside carousel, but they are not having the default properties.
This is my Carousel Class
class Carousel extends Component {
constructor(props){
super(props);
this.state = { albums: [] };
}
componentWillMount() {
axios.get('http://rallycoding.herokuapp.com/api/music_albums')
.then(response => this.setState({albums: response.data}));
}
renderAlbums(){
return this.state.albums.map(album =>
<Card song={album.title} singer={album.artist} src={album.thumbnail_image}/>
);
}
render() {
return (
<div className="carousel center">
{this.renderAlbums()}
</div>
);
}
}
export default Carousel;
This is my Card Component
import React, { Component } from 'react';
import '../../App.css';
class Card extends Component {
render() {
return (
<div className="carousel-item center">
<div className="card z-depth-4">
<div>
<img src={this.props.src} className="responsive-img"/>
</div>
<p>{this.props.song}</p>
<div className="singer">{this.props.singer}</div>
</div>
</div>
);
}
}
export default Card;
Please suggest if there is any other way, to import it.
Already have initialised carousel and it's working if I try the Materialize Example
Try performing your API call in componentDidMount instead of componentWillMount.
The latter is called before the initial render, and thus, if your API call returns before the component finishes mounting, the setState statement will not cause a re-render.
With the former, setting the state will always cause a re-render to occur. React documentation recommends initiating data requests from remote endpoints from componentDidMount. See here.
The current official docs only shows how to dynamically change components within an <ng-template> tag. https://angular.io/guide/dynamic-component-loader
What I want to achieve is, let's say I have 3 components: header, section, and footer with the following selectors:
<app-header>
<app-section>
<app-footer>
And then there are 6 buttons that will add or remove each component: Add Header, Add Section, and Add Footer
and when I click Add Header, the page will add <app-header> to the page that renders it, so the page will contain:
<app-header>
And then if I click Add Section twice, the page will now contain:
<app-header>
<app-section>
<app-section>
And if I click Add Footer, the page will now contain all these components:
<app-header>
<app-section>
<app-section>
<app-footer>
Is it possible to achieve this in Angular? Note that ngFor is not the solution I'm looking for, as it only allows to add the same components, not different components to a page.
EDIT: ngIf and ngFor is not the solution I'm looking for as the templates are already predetermined. What I am looking for is something like a stack of components or an array of components where we can add, remove, and change any index of the array easily.
EDIT 2: To make it more clear, let's have another example of why ngFor does not work. Let's say we have the following components:
<app-header>
<app-introduction>
<app-camera>
<app-editor>
<app-footer>
Now here comes a new component, <app-description>, which the user wants to insert in between and <app-editor>. ngFor works only if there is one same component that I want to loop over and over. But for different components, ngFor fails here.
What you're trying to achieve can be done by creating components dynamically using the ComponentFactoryResolver and then injecting them into a ViewContainerRef. One way to do this dynamically is by passing the class of the component as an argument of your function that will create and inject the component.
See example below:
import {
Component,
ComponentFactoryResolver, Type,
ViewChild,
ViewContainerRef
} from '#angular/core';
// Example component (can be any component e.g. app-header app-section)
import { DraggableComponent } from './components/draggable/draggable.component';
#Component({
selector: 'app-root',
template: `
<!-- Pass the component class as an argument to add and remove based on the component class -->
<button (click)="addComponent(draggableComponentClass)">Add</button>
<button (click)="removeComponent(draggableComponentClass)">Remove</button>
<div>
<!-- Use ng-template to ensure that the generated components end up in the right place -->
<ng-template #container>
</ng-template>
</div>
`
})
export class AppComponent {
#ViewChild('container', {read: ViewContainerRef}) container: ViewContainerRef;
// Keep track of list of generated components for removal purposes
components = [];
// Expose class so that it can be used in the template
draggableComponentClass = DraggableComponent;
constructor(private componentFactoryResolver: ComponentFactoryResolver) {
}
addComponent(componentClass: Type<any>) {
// Create component dynamically inside the ng-template
const componentFactory = this.componentFactoryResolver.resolveComponentFactory(componentClass);
const component = this.container.createComponent(componentFactory);
// Push the component so that we can keep track of which components are created
this.components.push(component);
}
removeComponent(componentClass: Type<any>) {
// Find the component
const component = this.components.find((component) => component.instance instanceof componentClass);
const componentIndex = this.components.indexOf(component);
if (componentIndex !== -1) {
// Remove component from both view and array
this.container.remove(this.container.indexOf(component));
this.components.splice(componentIndex, 1);
}
}
}
Notes:
If you want to make it easier to remove the components later on, you can keep track of them in a local variable, see this.components. Alternatively you can loop over all the elements inside the ViewContainerRef.
You have to register your component as an entry component. In your module definition register your component as an entryComponent (entryComponents: [DraggableComponent]).
Running example:
https://plnkr.co/edit/mrXtE1ICw5yeIUke7wl5
For more information:
https://angular.io/guide/dynamic-component-loader
Angular v13 or above - simple way to add dynamic components to DOM
parent.component.html
<ng-template #viewContainerRef></ng-template>
parent.component.ts
#ViewChild("viewContainerRef", { read: ViewContainerRef }) vcr!: ViewContainerRef;
ref!: ComponentRef<YourChildComponent>
addChild() {
this.ref = this.vcr.createComponent(YourChildComponent)
}
removeChild() {
const index = this.vcr.indexOf(this.ref.hostView)
if (index != -1) this.vcr.remove(index)
}
Angular v12 or below
I have created a demo to show the dynamic add and remove process.
The parent component creates the child components dynamically and removes them.
Click for demo
Parent Component
// .ts
export class ParentComponent {
#ViewChild("viewContainerRef", { read: ViewContainerRef })
VCR: ViewContainerRef;
child_unique_key: number = 0;
componentsReferences = Array<ComponentRef<ChildComponent>>()
constructor(private CFR: ComponentFactoryResolver) {}
createComponent() {
let componentFactory = this.CFR.resolveComponentFactory(ChildComponent);
let childComponentRef = this.VCR.createComponent(componentFactory);
let childComponent = childComponentRef.instance;
childComponent.unique_key = ++this.child_unique_key;
childComponent.parentRef = this;
// add reference for newly created component
this.componentsReferences.push(childComponentRef);
}
remove(key: number) {
if (this.VCR.length < 1) return;
let componentRef = this.componentsReferences.filter(
x => x.instance.unique_key == key
)[0];
let vcrIndex: number = this.VCR.indexOf(componentRef as any);
// removing component from container
this.VCR.remove(vcrIndex);
// removing component from the list
this.componentsReferences = this.componentsReferences.filter(
x => x.instance.unique_key !== key
);
}
}
// .html
<button type="button" (click)="createComponent()">
I am Parent, Create Child
</button>
<div>
<ng-template #viewContainerRef></ng-template>
</div>
Child Component
// .ts
export class ChildComponent {
public unique_key: number;
public parentRef: ParentComponent;
constructor() {
}
remove_me() {
console.log(this.unique_key)
this.parentRef.remove(this.unique_key)
}
}
// .html
<button (click)="remove_me()">I am a Child {{unique_key}}, click to Remove</button>
For example you could have a directive in angular like so:
angular.module('app')
.directive('classy', function() {
return {
restrict: 'A',
link: function($scope, $el) {
$el.addClass('stay-classy');
}
}
}
And implement like so:
<div classy></div>
There doesn't seem to be an equivalent in React that I've seen after reading through most the docs and googling. I was hoping for something like:
...
render: function() {
return (
<MyComponent classy></MyComponent>
);
}
Is there something like that possible that I've been missing? Is there a different yet functionally similar equivalent? Or maybe this question just shows that I'm missing some part of the "React way" and I shouldn't ever want to do this. Thanks!
It will be helpful to consider what Angular and React are each doing "behind the scenes."
In your Angular example, when you write <div classy/></div> you're saying "render a DIV element and then attach to it the behaviors defined by the classy directive.
In your React example, when you write <MyComponent classy></MyComponent>, you're saying, "create an instance of MyComponent and pass it the props { classy: true }. The transpiler (Babel or whathaveyou) will turn it into the following JavaScript:
React.createElement(MyComponent, { classy: true });
So the answer to your question is that you can't write <MyComponent classy></MyComponent> because MyComponent component doesn't know what to do with the classy prop. In React, you might write something like this instead:
class ClassyDiv extends React.Component {
render() {
const { className, ...rest } = this.props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
}
}
This works because we know the React.DOM.div component (like most DOM components) knows what to do with the className prop.
Since React 0.14 we can express something like this more simply, as a "pure" stateless functional component, i.e. a function that accepts props and returns the rendered result:
function AlsoClassyDiv(props) {
const { className, ...rest } = props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
};
You can see both approaches in action in the below snippet.
class ClassyDiv extends React.Component {
render() {
const { className, ...rest } = this.props;
return <div className={`${className || ''} stay-classy`} {...rest}/>;
}
}
function AlsoClassyDiv({ className, ...props }) {
return <div className={`${className || ''} stay-classy`} {...props}/>;
};
ReactDOM.render(
<div id="container">
<div>Regular div</div>
<ClassyDiv>ClassyDiv!</ClassyDiv>
<AlsoClassyDiv>AlsoClassyDiv!</AlsoClassyDiv>
</div>,
document.body
);
.stay-classy { font: bold 3em Helvetica; text-shadow: 4px 4px 2px #aaa; }
<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>
One way you could implement similar behavior is using React class mixins
A great example of a useful directive in angular is
Target
The smoothScroll directive would intercept the click event then use window scroll or jquery scrollTo to apply all manner of animation.
Anywhere in the html one could then simply use the directive powered class name.
This sort of thing is not available in React. To do it in React you would have to create a special link component to use instead of:
<a> like ASmooth....
I was looking to find a way to reproduce the directive system for applying style or play with the component.
You can create a component that play with children and then render them :
function TextCenter(props) {
// Iterates over children and clone it with custom props
const children = React.Children.map(
props.children,
(child) => React.cloneElement(child, { className: 'text-center' }
)
// Render the children
return <>{children}</>;
}
function MyComponent() {
return (
<TextCenter>
<div>
<h1>Hello centered world</h1>
<p>Yessss</p>
</div>
</TextCenter>
)
}
Here is a more powerfull example for responsive text alignement :
interface Props extends Breakpoints<'start' | 'center' | 'end'>{}
export const TextAlign: FunctionComponent<Props> = (props) => {
const className = generateClassName('text', props);
const children = React.Children.map(props.children, child => React.cloneElement(child as ReactElement, { className }))
return (
<>
{children}
</>
)
}
export const MyComponent: FunctionComponent<Props> = (props) => {
return (
<div>
<TextCenter xs="center" md="start">
<h1>I am centered on mobile but not on desktop</h1>
</TextCenter>
</div>
)
}
There are two problems with this solution, when the children is a component, it must also have the prop className and it also makes the HTML less clean as it adds a level in hierarchy.
Look my friend i didn't get you well but long story short, angularJS directives is actually a component. So the idea behind angularJs directive is to create component that has its own scope data and it's own method to operate on it. I was thinking the same way you did and found your post here and i couldn't find an answer for that. But thanks for working experience, i thought about it and know how to do it.
I wanted to add an edit button for each link item in a list to toggle the edit form for each one only so each ListItem should be a stand alone component, that way i have standalone state for each one and i toggle it on & off.