How to use props or state to render or not render 1 out of these 5 forms on click? - javascript

I'm looking for advice on how to render one out of five forms in React on the click on one of their five related buttons. I've got it working actually for one form, but I'm pretty sure this is not how things should be done in React.
There is a sidebar with 5 buttons and a content area that displays some content. One of the forms should appear in the content area after a click on their respective buttons in the sidebar. Only one form should be displayed at a time. Both the sidebar and the content area are functions inside of a class called GraphArea that renders them.
AddNodes (one of five buttons in the sidebar):
const AddNodes = ({ showAdd }) => (
<li role="presentation" className="list-inline">
<a href="" className="nav-link" onClick={showAdd}>
<i className="fa fa-plus" aria-hidden="true"/> Add
</a>
</li>
);
export default AddNodes;
The problem is, there are five forms and buttons... There's no way one could reasonably continue like this.
I'm using Apollo link state. All 5 five forms won't be rendered by default, so they've got a default value of false. The values are written to the cache. Apollo queries and mutates a default value in the cache to true on click.
It would mean a lot to me if you could show me the right way here. I'm missing that 'click' of how things work in React and this would be a great oppertunity for it. This is the sidebar's and content area's parent full component:
GraphArea: (parent)
class GraphArea extends Component {
render() {
const { updateEditGraph, editGraph: { mode } } = this.props;
const showAdd = (e) => {
updateEditGraph(
{
variables: {
index: 'mode',
value: 'addNode'
}
});
e.preventDefault()
};
const showLink = (e) => {
updateEditGraph(
{
variables: {
index: 'mode',
value: 'addLink'
}
});
e.preventDefault()
};
return (
<div className="item">
<GraphSidebar showAdd={showAdd} showLink={showLink}/>
<GraphContent/>
</div>
)
}
}
export default compose(
graphql(updateEditGraph, {name: 'updateEditGraph'}),
graphql(getEditGraph, {
props: ({data: {editGraph}}) => ({
editGraph
})
})
)(GraphArea);
GraphContent:
class GraphContent extends Component {
render() {
let content;
if (this.props.editGraph.mode === 'addNode') {
content = <AddNodesForm/>
} else if (this.props.editGraph.mode === 'addLink') {
content = <LinkNodesForm/>
} else {
content = null;
}
return (
<div className="content">
{content}
<Graph/>
</div>
);
}
}
export default compose(
graphql(getEditGraph, {
props: ({data: {editGraph}}) => ({
editGraph
})
})
)(GraphContent);
GraphSidebar:
const GraphSidebar = ({ showAdd, showLink }) => (
<div className="avatars">
<ul>
<AddNodes showAdd={showAdd} />
<SequenceNodes />
<EditNodes />
<LinkNodes showLink={showLink} />
<DeleteNodes />
</ul>
</div>
);

I will answer what should happen in principle.
You say you have a top level component GraphArea that renders both the component for selecting a form, and the container of the forms:
<div>
<GraphSidebar onClickName={handleClick}/>
<GraphContent/>
</div>
GraphSidebar and GraphContent are related in such a way that GraphSidebar changes the state that should be presented on GraphContent, that is they relate to the same state. Because of that their common parent (GraphArea) should hold that state. You then pass the state to the GraphContent which contains the forms.
Make GraphArea a class:
class GraphArea extends React.Component {
constructor(){
super();
this.state = {formId: 1};
}
handleClick(formId) {
this.setState({fromId: formId});
}
render(){
return (
<div>
<GraphSidebar onClickName={handleClick}/>
<GraphContent formId={this.state.formId}/>
</div>
)
}
}
Also I think you say GraphContent doesn't directly render the forms but renders some other component that renders the form. You pass formId as property through the nested components all the way to the component that selects which form to render. For example if GraphContent renders Graph component that renders (selects) the forms you do like this:
<div className="content">
<Graph formId={this.props.formId}/>
</div>
or just
<div className="content">
<Graph formId={formId}/>
</div>
if GraphContent is not a class but a function like
GraphContent = ({formId}) => {
....
}
Let's say Graph is finally the component that renders (selects) the forms then you can use:
Graph = ({formId}) => {
if (formId === 1) {
return(<div>...code for form1</div>)
} else if (formId === 2) {
return(<div>...code for form2</div>)
} ...
}

Related

How do I keep the state of a React Component after removing others from an array?

I'm new to React and am not sure what I'm doing wrong here. I have a component called Blocks that contains an array of sub-components in state. Right now, when I add the sub-component Paragraph, I do so like this. This is in the parent component Blocks.
handleAddBlock(block) {
let new_block = null;
let last_block_id = this.state.last_block_id;
last_block_id++;
new_block = {
component: <Paragraph
key={last_block_id}
id={last_block_id}
/>,
id: last_block_id,
value: null
}
this.setState({ last_block_id: last_block_id });
this.setState({ blocks: [...this.state.blocks, new_block] });
}
The Paragraph component has a state variable "value", that is updated when a user types into a text box. However, when I go to remove an item from this.state.blocks, any components that come after the component I'm removing all get re-rendered, and lose their state. The components that come before the item I've removed keep theirs.The question is why, and how can I stop that from happening? Is this a bad design pattern?
Here's the code that handles the removal of a sub-component. This is in the parent component Blocks.
handleRemoveBlock(id) {
const blocks = [...this.state.blocks].filter(block => {
return block.id !== id;
});
this.setState({ blocks: blocks });
}
And finally, this is part of the render() method in the parent component Blocks.
render() {
const blocks = this.state.blocks.map(block => {
return <div
key={block.key}
className="col p-1"
>{block.component}
<button
className="delete-button"
onClick={() => this.handleRemoveBlock(block.id)}
type="button">X
</button>
</div>
})
return <section className="row">
<div className="col">
<div className="col">
{blocks}
</div>
</div>
</section>
}
I have a component called Blocks that contains an array of sub-components in state.
You shouldn't. Components should contain as little data in their state as possible. The main React design concept is that component's render method is a pure function of props and the state. Based on this, you should move <Paragraph/> instances (because you should render components only in render) and last_block_id (because it's computable from the blocks state) from state to render:
class Block extends React.Component {
handleAddBlock(block) {
const new_block = { ... }
this.setState('blocks', [...this.state.blocks, new_block])
}
get last_block_id() {
return this.state.blocks.at(-1).id
}
render() {
// your markup
return <...>
// create Paragraph here
{this.state.blocks.map(block => <Paragraph key={block.id} id={block.id} />)
<.../>
}
}

Render unique divs for each hovered element

minimum reproducible example: https://codesandbox.io/s/react-hover-example-tu1eu?file=/index.js
I currently have a new element being rendered when either of 2 other elements are hovered over. But i would like to render different things based upon which element is hovered.
In the example below and in the codepen, there are 2 hoverable divs that are rendered; when they are hovered over, it changes the state and another div is rendered. I would like for the HoverMe2 div to render text "hello2". Currently, whether i hover hoverme1 or 2, they both just render the text "hello".
import React, { Component } from "react";
import { render } from "react-dom";
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false
};
}
handleMouseHover() {
this.setState(this.toggleHoverState);
}
toggleHoverState(state) {
return {
isHovering: !state.isHovering
};
}
render() {
return (
<div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me
</div>
<div
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me2
</div>
{this.state.isHovering && <div>hello</div>}
</div>
);
}
}
render(<HoverExample />, document.getElementById("root"));
You need to keep the state of item which you have hovered that's for sure
const { Component, useState, useEffect } = React;
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false,
values: ['hello', 'hello2'],
value: 'hello'
};
}
handleMouseHover({target: {dataset: {id}}}) {
this.setState(state => {
return {
...state,
isHovering: !state.isHovering,
value: state.values[id]
};
});
}
render() {
return (
<div>
<div
data-id="0"
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me
</div>
<div
data-id="1"
onMouseEnter={this.handleMouseHover}
onMouseLeave={this.handleMouseHover}
>
Hover Me2
</div>
{this.state.isHovering && <div>{this.state.value}</div>}
</div>
);
}
}
ReactDOM.render(
<HoverExample />,
document.getElementById('root')
);
<script src="https://unpkg.com/react/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom/umd/react-dom.development.js"></script>
<script src="https://unpkg.com/babel-standalone#6/babel.min.js"></script>
<div id="root"></div>
You can pass the context text as shown in example. This is working code:
import React, { Component } from "react";
import { render } from "react-dom";
// Drive this using some configuration. You can set based on your requirement.
export const HOVER_Hello1 = "Hello1";
export const HOVER_Hello2 = "Hello2";
class HoverExample extends Component {
constructor(props) {
super(props);
this.handleMouseHover = this.handleMouseHover.bind(this);
this.state = {
isHovering: false,
contextText: ""
};
}
handleMouseHover = (e, currentText) => {
this.setState({
isHovering: !this.state.isHovering,
contextText: currentText
});
}
toggleHoverState(state) {
//
}
render() {
return (
<div>
<div
onMouseEnter={e => this.handleMouseHover(e, HOVER_Hello1)}
onMouseLeave={e => this.handleMouseHover(e, HOVER_Hello1)}
>
Hover Me
</div>
<div
onMouseEnter={e => this.handleMouseHover(e, HOVER_Hello2)}
onMouseLeave={e => this.handleMouseHover(e, HOVER_Hello2)}
>
Hover Me2
</div>
{this.state.isHovering && <div>{this.state.contextText}</div>}
</div>
);
}
}
export default HoverExample;
If the whole point is about linking dynamically messages to JSX-element you're hovering, you may store that binding (e.g. within an object).
Upon rendering, you simply pass some anchor (e.g. id property of corresponding object) within a custom attribute (data-*), so that later on you may retrieve that, look up for the matching object, put linked message into state and render the message.
Following is a quick demo:
const { Component } = React,
{ render } = ReactDOM,
rootNode = document.getElementById('root')
const data = [
{id:0, text: 'Hover me', message: 'Thanks for hovering'},
{id:1, text: 'Hover me too', message: 'Great job'}
]
class HoverableDivs extends Component {
state = {
messageToShow: null
}
enterHandler = ({target:{dataset:{id:recordId}}}) => {
const {message} = this.props.data.find(({id}) => id == recordId)
this.setState({messageToShow: message})
}
leaveHandler = () => this.setState({messageToShow: null})
render(){
return (
<div>
{
this.props.data.map(({text,id}) => (
<div
key={id}
data-id={id}
onMouseEnter={this.enterHandler}
onMouseLeave={this.leaveHandler}
>
{text}
</div>
))
}
{
this.state.messageToShow && <div>{this.state.messageToShow}</div>
}
</div>
)
}
}
render (
<HoverableDivs {...{data}} />,
rootNode
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.11.0/umd/react-dom.production.min.js"></script><div id="root"></div>
As #CevaComic pointed out, you can do this with CSS. But if you want to use React, for example, because your actual problem is more complex, here is the answer.
You will need a way to tell apart the two elements. It could be done with some neat tricks, like setting an unique id to each element, passing a custom argument, or something else.
But I would advise against "cool tricks" as it's more difficult to understand what is going on, and the code is more prone to errors. I think the best way it to use a dumb approach of unique functions for unique elements.
Each onMouseEnter and onMouseLeave has to be an unique function (e.g. handleMouseHover1 and handleMouseHover2), and each of those functions need to control unique state (for example, isHovering1 and isHovering2). Then you have to render the element you want based on the state. Of course, for a real-world code, you will probably want to use more descriptive names to make the code more comprehensible. The full code would look something like this.
class HoverExample extends Component {
state = {
isHovering1: false,
isHovering2: false
};
handleMouseHover1 = () => {
this.setState(({ isHovering1 }) => ({ isHovering1: !isHovering1 }));
};
handleMouseHover2 = () => {
this.setState(({ isHovering2 }) => ({ isHovering2: !isHovering2 }));
};
render() {
const { isHovering1, isHovering2 } = this.state;
return (
<div>
<div
onMouseEnter={this.handleMouseHover1}
onMouseLeave={this.handleMouseHover1}
>
Hover Me1
</div>
<div
onMouseEnter={this.handleMouseHover2}
onMouseLeave={this.handleMouseHover2}
>
Hover Me2
</div>
{isHovering1 && <div>hello1</div>}
{isHovering2 && <div>hello2</div>}
</div>
);
}
}
Also, updated example: https://codesandbox.io/s/react-hover-example-rc3h0
Note: I have also edited the code to add some syntax sugar which exists with newer ECMAScript versions. Instead of binding the function, you can use the arrow function format, e.g. fn = () => { ... }. The arrow function means the this context is automatically bound to the function, so you don't have to do it manually. Also, you don't have to initialize this.state inside the constructor, you can define it as a class instance property. With those two things together, you do not need the constructor at all, and it makes the code a bit cleaner.

componentWillReceiveProps in Vue

I am new pretty new to Vue, and coming from a rather React-y suburb. I am rebuilding my SideNav ("drawer") component from the latter. There, when one clicked the button (not being related to the navigation per se), it setStateed this.state.toggle that was tied to appropriate
class thePage extends React.Component {
...
this.handleToggleClick = this.handleToggleClick.bind(this);
this.state ={
toggleState: false
};
}
// Slide out buttons event handlers
handleToggleClick(){
this.setState({
toggleState: !this.state.toggleState
})
}
render() {
const button = <a href="#" onClick={this.handleToggleClick}>here</a>
const isOpenWithButton = this.state.toggleState;
return (
<div>
{button}
<SideNav logo="logo.png" isOpenWithButton={isOpenWithButton}>
. . .
</SideNav>
</div>
);
}
}
export default SideNavPage;
the SideNav looks as follows:
class SideNav extends React.Component {
constructor(props){
super(props);
this.state = {
isThere: false,
showOverlay: false,
}
this.handleOverlayClick = this.handleOverlayClick.bind(this);
}
componentWillReceiveProps(NextProps) {
if (this.props.isOpenWithButton !== NextProps.isOpenWithButton) {
this.setState({
isThere: true,
showOverlay: true
})
}
}
handleOverlayClick(){
this.setState({
isThere: false,
showOverlay: false
});
}
render() {
const {
tag: Tag,
...
isOpenWithButton,
} = this.props;
let isThere = this.state.isThere;
let showOverlay = this.state.showOverlay;
const overlay = <div class="overlay" onClick={this.handleOverlayClick}></div>
const sidenav = (
<Tag>
<ul>
{logo &&
<li>
<div className="logo-wrapper">
<a href={href}>
<img src={logo} className="img-fluid flex-center d-block"/>
</a>
</div>
</li>
}
{children}
</ul>
</Tag>
);
return (
<div>
{isThere && sidenav}
{showOverlay && overlay}
</div>
);
}
}
export default SideNav;
So, as you can see, clicking the button causes the isOpenWithButton props to change, and whenever it happens (componentWillReceiveProps), the sidenav with overlay appear.
I did some work on porting it to Vue, but as it lacks this lifecycle hook I am stuck with props. I have a following problem: clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change, what necessitates clicking the button twice if the sidenav has been already open. I know I must be missing a vital part in Vue logic, I just cannot grasp which.
Using .sync modifier
What you are looking for is called in vue a .sync modifier.
When a child component mutates a prop that has .sync, the value change will be reflected in the parent.
With this you can achive what you described:
clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change
Using a centralised store - (like vuex)
The same could also be achieved if you have a centralised state/store, in this case both of your components could rely on that state property.
See state management on Vue documentation:
Large applications can often grow in complexity, due to multiple pieces of state scattered across many components and the interactions between them
You could simple toogle the same property, for example:
$store.commit('overlayToggle');

Display some text in react depending on the switch case

I have a dropdown populated from a Web Service, what I want is to display some text according to the selection made. For example the first option in the Dropdown is Buy n and Save m so in a p tag I want to display Buy 2 and Save $1.5 I know this is work for a switch and the position of the array is going to be my "CASE" in order to know what to display or not but I'm new to react and also in programming so I need help..
import React from 'react';
import DropDownMenu from 'material-ui/DropDownMenu';
import MenuItem from 'material-ui/MenuItem';
import cr from '../styles/general.css';
export default class Example extends React.Component {
constructor(props) {
super(props);
this.state = {
OfferTypeData: [],
OfferTypeState: '',
};
this.handleChange = this.handleChange.bind(this);
this.renderOfferTypeOptions = this.renderOfferTypeOptions.bind(this);
}
componentDidMount() {
const offerTypeWS = 'http://localhost:8080/services/OfferType/getAll';
fetch(offerTypeWS)
.then(Response => Response.json())
.then(findResponse => {
console.log(findResponse);
this.setState({
OfferTypeData: findResponse
});
});
}
handleChange(event, index, value) {this.setState({value});}
handleChangeDiscountType(event, index, value) {
this.setState({ OfferTypeState: (value) });
}
renderOfferTypeOptions() {
return this.state.OfferTypeData.map((dt, i) => {
return (
<MenuItem
key={i}
value={dt.offerTypeDesc}
primaryText={dt.offerTypeDesc} />
);
});
}
render() {
return (
<div className={cr.container}>
<div className={cr.rows}>
<div>
<DropDownMenu
value={this.state.OfferTypeState}
onChange={this.handleChangeDiscountType}>
<MenuItem value={''} primaryText={'Select Offer Type'} />
{this.renderOfferTypeOptions()}
</DropDownMenu>
<br/>
<p>{DISPLAY SOME TEXT HERE}</p>
</div>
</div>
</div>
);
}
}
Thanks in advance!
Regards.
Create a component which passes a callback to the dropdown, this callback will update the state of the container which will in turn set the props of the display. This is very common in React and is the basis of how the compositional pattern works. If you need to share data between two components just put them in a container and lift the state to the parent component. These components are usually called containers and there is a bunch of documentation on it.
This is a good starting point: https://reactjs.org/docs/lifting-state-up.html
A rough layout would be something like this.
class Container extends React.Component {
constructor(props) {
super(props);
// Don't forget to bind the handler to the correct context
this.changeText = this.changeText.bind(this);
}
changeText(text) {
this.setState({text: text});
}
render() {
return (
<DropDown callback={this.changeText} />
<Display text={this.state.text} />
)
}
}
Display component...
const Display = (props) => (
<p>{this.props.text}</p>
)

Programmatically open a route with state in react

I have two types of item, one of which can contain data similar to the other.
Currently when form is used to save an item it saves it then uses browserHistory.push to show the next page.
But I wish add a button that will
save the currently item
redirect them to the form to add the other item type,
partially fill out this form with the data from the first item.
Is there a way to do this using react and not using local storage or session variables?
You should take a look to Redux (or other Flux based libraries) to store data between components and routes, avoiding the excessive prop nesting.
browserHistory.push won't work. It only moves you to a certain location but it doesn't update the application state. You need to update application state, which then will reflect into location update, but not in the opposite direction. Keep in mind that, in React, data comes first, and its representation, even though mutable, doesn't change the data back. The same applies to the location.
To make the redirect alone work, I'd recommend wrapping your component into withRouter higher-order component.
import React, { Component } from 'react';
import { withRouter } from 'react-router';
class MyComponent extends Component {
render() {
return (
<div>
<button
onClick={() => this.props.router.push('/new-location')}>
Click me to go to /new-location
</button>
</div>
);
}
}
But if you need to pass data from one component to another, and the two aren't in hierarchy, I'd agree with Alomsimoy and recommend using Redux. But if, for some reason, it's not an option, you can store this data in a component that is parent to both forms:
class FormA extends Component {
render() {
return (
<form onSubmit={() => this.props.onSubmit()}>
<input
type="text"
value={this.props.inputA}
onChange={(event) => this.props.handleChangeA(event)} />
</form>
);
}
}
class FormB extends Component {
render() {
return (
<form onSubmit={() => this.props.onSubmit()}>
<input
type="text"
value={this.props.inputB}
onChange={(event) => this.props.handleChangeB(event)} />
</form>
);
}
}
while their parent would rule the location and state updates:
class Forms extends Component {
constructor() {
super();
this.state = {};
}
handleChange(name, value) {
this.setState({
[name]: value
});
}
renderForm() {
const {
params: {
stepId
}
} = this.props;
if (stepId === 'step-a') { // <- will be returned for location /form/step-a
return (
<FormA
inputA={this.state.inputA}
handleChangeA={(event) => this.handleChange('inputA', event.target.value)}
onSubmit={() => this.props.router.push('/form/step-b')} />
);
} else if (stepId === 'step-b') { // <- will be returned for location /form/step-b
return (
<FormB
inputB={this.state.inputB}
handleChangeB={{(event) => this.handleChange('inputA', event.target.value)} />
);
}
}
render() {
const {
children
} = this.props;
console.log(this.state); // track changes
return (
<div>
{this.renderForm()}
<button
onClick={() => this.props.router.push('/new-location')}>
Click me to go to /new-location
</button>
</div>
);
}
}
export default withRouter(Forms);
so the route for them would look like
<Route path="form/:stepId" component={Forms} />

Categories

Resources