How to better toggle between view and edit mode in reactjs - javascript

I am developing a user profile page, which has many boxes with two modes each - view and edit. Each box is defined as a react class <ProfileBox> ... </ProfileBox>.
For the view and edit mode of each box I defined classes <EditMode> ... </EditMode> and <ViewMode>...</ViewMode>. Eventually I want to render e.g. an address like
<ProfileBox editMode={true}>
<EditMode>
...
</EditMode>
<ViewMode>
...
</ViewMode>
</ProfileBox>
I want the logic to be in the ProfileBox. My current approach is to iterate all children and filter them if they are of type ViewMode or EditMode. Too late, I realized that this breaks down as soon as I do something like:
<ProfileBox editMode={true}>
<EditMode>
...
</EditMode>
<div>
<ViewMode>
...
</ViewMode>
</div>
</ProfileBox>
How could I do it better? What is the standard approach? I don't want to have to manually care about passing an id or a status to the Edit and ViewMode in the definition of the address.
Thank you!

You can continue to render ViewMode and EditMode as children rather than pass them as props by using logic in the render function of ProfileBox similar to this:
render: function() {
var child = this.props.editMode ?
<EditMode>
...
</EditMode> :
<ViewMode>
...
</ViewMode>;
return child;
}

You can do:
<ProfileBox editMode={this.state.editingWhatever} onModeChange={this.updateEditingWhatever}
editView={ <EditMode>...</EditMode> }
viewView={ <div><ViewMode>...</ViewMode></div> }
/>
Or you can conditionally render the EditMode/ViewMode in this code. To make it less ugly, a well designed mixin would do wonders. It's hard to tell what the exact requirements are from your question, but take a look at what all of your <ProfileBox/> uses have in common, and where they differ.
In the simpler case shown above, you probably just want to dynamically create the onModeChange handler, and the onChange handler for any inputs children in edit mode.

Related

Swapping BODY content while keeping state

Dynamically swapping BODY content using jQuery html function works as expected with 'static' content.
But if forms are being used, current state of inputs is lost.
The jQuery detach function, which should keep page state, seems to be blanking out the whole page.
The following code is the initial idea using jQuery html but of course the text input value will always empty.
function swap1( ) {
$("body").html('<button onclick="swap2();">SWAP2</button><input type="text" placeholder="swap2"/>');
}
function swap2( ) {
$("body").html('<button onclick="swap1();">SWAP1</button><input type="text" placeholder="swap1"/>');
}
With not knowing what form inputs there are, how would one swap in and out these forms in the BODY and keep track of the form states?
Demo of two text inputs which should keep state when they come back into the BODY:
https://jsfiddle.net/g7hksfne/3/
Edit: missunderstood use case. This covers saving the state while manipulating the DOM, instead of switching between different states.
Instead of replacing the <body>'s content by serializing and parsing HTML, learn how to use the DOM. Only replace the parts of the DOM you actually change, so the rest of it can keep its state (which is stored in the DOM).
In your case: you might want to use something like this:
function swap1( ) {
document.querySelector("button").onclick = swap2;
document.querySelector("button").textContent = "SWAP2";
document.querySelector("input").placeholder = "swap2";
}
function swap2( ) {
document.querySelector("button").onclick = swap1;
document.querySelector("button").textContent = "SWAP1";
document.querySelector("input").placeholder = "swap1";
}
<button onclick="swap1();">SWAP1</button><input type="text" placeholder="swap1"/>
(This is not optimized and should only serve as an example.)
Put the content you want to save in a node below <body>, like a simple ยด` if you don't already have a container. When you want to save and replace the container, use something like:
var saved_container = document.body.removeChild(document.querySelector("#app_container");
// or document.getElementById/getElementsByClassName, depends on container
The element is now detached and you can add your secondary to document.body. When you want to get back, save the secondary content (without overwriting the first container of course), then reattach the primary content it with:
document.body.appendChild(savedContainer);

Override a function in 'MultiSelect' component from PrimeNG

I'm new in JS. I have a small front-end task and I don't know how to solve it after few hours of googling.
I need to override this function in PrimeNG MultiSelect component: MultiSelect.prototype.updateLabel.
In project, I'm working on the label should be static, but alt text (when hovering) should be dynamic as in original realization.
It would be great if you point me to the right direction. I have found this page, but it didn't help me because I don't know how to implement it correctly.
Thanks in advance for any help.
I had to do the same thing in my project.
Here's what I did to get it to work:
In your component's .html file add something like:
<p-multiSelect #multiselect
[options]="someOptions"
[(ngModel)]="someModel.options"
[defaultLabel]="Did this work?"
(onChange)="onChange($event)"
>
</p-multiSelect>
Below the constructor in your component file, add:
#ViewChild('multiselect') multi: MultiSelect;
I put mine in a setter that is always called, but wherever you need to change the label - perhaps in a subscribe function - you can override using:
this.multi.updateLabel = function () {
var label = this.value.length.toString() + " Data Points Selected";
this.valuesAsString = label;
}
Hope that helps!

How do you reliably measure React components for computed position of popovers and callouts?

I'm using react and react-bootstrap. I want to know whether there's a better, cleaner, more idiomatic approach than what I've come up with.
The Problem
I want to position a popover callout that points to a button on my page that provides a hint for where the user should start their workflow. The user can subsequently dismiss this popover. It took me a long time to figure out this hack-y approach to measure the elements' dimensions and positions to properly position the callout:
The target of the callout:
<Button
bsClass="button"
className="button-blue"
id="connectParentsButton"
onClick={( event ) => { this.handleOpenInviteParents( event );}}
ref={( input ) => { this.connectParentsButtonRef = input; }}
>
+ Connect Parents
</Button>
The idea is to use ref to store the target as a property of the parent React component, and then access it from the sibling callout component to perform the measurements.
The callout component:
<RightBottomHintBox
isShow={this.state.isShowConnectParentsHint && !this.state.isDismissHideConnectParentsHint}
target={this.connectParentsButtonRef}
id="connectParentsHint"
onHide={this.handleHideConnectParentsHint}
/>
In my RightBottomHintBox component, I have to
use document.getElementById() to get dimenions and positions
use guards to avoid trying to access nodes that don't exist yet
store dimensions and coordinates in class properties rather than component state
And I have to do this in the callout's render() to ensure that my callout component gets mounted even if it's not displayed, so that the DOM node exists and can be correctly measured.
let myStyle = this.props.isShow ? {} : { visibility: 'hidden' };
Failed approaches
computing dimensions when the callout's componentDidMount() fires. The DOM nodes of either the callout or the target are often not yet there
computing dimensions when the parent component's componentDidMount() fires. The same problem exists, you can't be sure that the DOM nodes exist at this point.
The above two approaches led me to this thread: When exactly is `componentDidMount` fired?
One post suggests that to be sure that all DOM nodes are ready, you'd have to go up to the top component. I don't like this approach because it would break encapsulation and separation of concerns.
Another thread, How can I respond to the width of an auto-sized DOM element in React?, suggests using the react-measure library to do measurements, which might work, but for my particular case, I wanted to avoid having another library to manage because this app is small.
Background context
I'm creating a page that allows the user to input a list of students' parents' email addresses. The initial page lists students and parents' email addresses that have already been input. However, the first time the user comes to this page, there may be a list of students, with no parent email addresses. I want to provide a hint to the user that they should click on 'Connect Parents' button to open up a wizard to guide the user in data input.
I have another failed attempt here that may spur someone to the correct solution.
export function getSize(cpt) {
var elem = document.getElementById("ruler");
const html = ReactDOMServer.renderToStaticMarkup(cpt);
elem.innerHTML = html;
return [elem.scrollWidth, elem.scrollHeight];
}
var Foo = React.createClass({
componentDidMount() {
const sz = this.elem.getBoundingClientRect();
ReactDOM.render(<span>{ [sz.width,sz.height].join("x") }</span>,
document.getElementById('actual'));
},
render() {
return <div ref={e => { this.elem = e; }} className="content">
<p> Bar!! BAZ </p> <p> another line </p> </div>;
}
})
Setting up getSize to render into any div (onscreen or off) gives about 76x84, while the actual size reported by componentDidMount (and confirmed by dev tools) is 188x52.

Polymer, evaluate element based off object

I am using the tile example from polymers neon elements - and I am trying to make each expanded tile unique. My first try on how to do this was to pass a string in with the grid items like
{
value: 1,
color: 'blue',
template: 'slide-1'
}
And have that element be evaluated when rendered in a new element something like this. (this is the card template itself)
<template>
<div id="fixed" class$="[[_computeFixedBackgroundClass(color)]]"></div>
<div id="card" class$="[[_computeCardClass(color)]]">
<[[item.template]]></[[item.template]]>
</div>
This does not work - however I am wondering if there is some way to do this so I can load custom elements for the content of each card. For reference -https://elements.polymer-project.org/elements/neon-animation?view=demo:demo/index.html&active=neon-animated-pages , it is the grid example and I am trying to replace the content of each card once it is clicked on ( the fullsize-page-with-card.html, here is all the html for it - https://github.com/PolymerElements/neon-animation/tree/master/demo/grid ). Is this the wrong way of approaching this? Or maybe I have some syntax wrong here. Thanks!
Edit : OK, So I can send it through if i add it to the click to open the card like so
scope._onTileClick = function(event) {
this.$['fullsize-card'].color = event.detail.data.color;
this.$['fullsize-card'].template = event.detail.data.template;
this.$.pages.selected = 1;
};
and in the card's properties like so
template: {
type: String
},
So I can then evaluate it as [[template]] , however - the question still remains how to call a custom element (dynamically) using this string. I could pass a couple of properties and fill in a card or form so they are unique, but i think I would have much more creative freedom if I could call custom elements inside each card.
I have an element that allows referenced templates. There are a couple of others other there, but this one also allows data bindings to work: https://github.com/Trakkasure/dom-bindref

Show/Hide ReactJS components without losing their internal state?

I've been hiding/showing react components by not rendering them, for example:
render: function() {
var partial;
if (this.state.currentPage === 'home') {
partial = <Home />;
} else if (this.state.currentPage === 'bio') {
partial = <Bio />;
} else {
partial = <h1>Not found</h1>
}
return (
<div>
<div>I am a menu that stays here</div>
Home Bio
{partial}
</div>
);
}
but just say that the <Bio/> component has lots of internal state. Everytime I recreate the component, it loses it's internal state, and resets to it's original state.
I know of course that I could store the data for it somewhere, and pass it in via props or just globally access it, but this data doesn't really need to live outside of the component. I could also hide/show components using CSS (display:none), but I'd prefer to hide/show them as above.
What's the best practice here?
EDIT: Maybe a better way to state the problem is to use an example:
Ignore React, and assume you were just using a desktop app that had a configuration dialog with a Tab component called A, which has 2 tabs, named 1 and 2.
Say that tab A.1 has an email text field and you fill in your email address. Then you click on Tab A.2 for a second, then click back to Tab A.1. What's happened? Your email address wouldn't be there anymore, it would've been reset to nothing because the internal state wasn't stored anywhere.
Internalizing the state works as suggested in one of the answers below, but only for the component and it's immediate children. If you had components arbitrarily nested in other components, say Tabs in Tabs in Tabs, the only way for them to keep their internal state around is to either externalize it somewhere, or use the display:none approach which actually keeps all the child components around at all times.
It just seems to me that this type of data isn't data you want dirtying up your app state... or even want to even have to think about. It seems like data you should be able to control at a parent component level, and choose to either keep or discard, without using the display:none approach and without concerning yourself with details on how it's stored.
One option would be to move the conditional inside the component itself:
Bio = React.createClass({
render: function() {
if(this.props.show) {
return <p>bio comp</p>
} else {
return null;
}
}
});
<Bio show={isBioPage} />
Whether this is "best practise" or not probably depends on the exact situation.
Unfortunately, style={{display: 'none'}} trick only works on normal DOM element, not React component. I have to wrap component inside a div. So I don't have to cascade the state to subcomponent.
<div className="content">
<div className={this.state.curTab == 'securities' ? 'active' : ''}>
<Securities />
</div>
<div className={this.state.curTab == 'plugins' ? 'active' : ''}>
<Plugins />
</div>
</div>
Looks like official documentation suggests hiding stateful children with style={{display: 'none'}}
The fundamental problem here is that in React you're only allowed to mount component to its parent, which is not always the desired behavior. But how to address this issue?
I propose the solution, addressed to fix this issue. More detailed problem definition, src and examples can be found here: https://github.com/fckt/react-layer-stack#rationale
Rationale
react/react-dom comes comes with 2 basic assumptions/ideas:
every UI is hierarchical naturally. This why we have the idea of components which wrap each other
react-dom mounts (physically) child component to its parent DOM node by default
The problem is that sometimes the second property isn't what you want
in your case. Sometimes you want to mount your component into
different physical DOM node and hold logical connection between
parent and child at the same time.
Canonical example is Tooltip-like component: at some point of
development process you could find that you need to add some
description for your UI element: it'll render in fixed layer and
should know its coordinates (which are that UI element coord or
mouse coords) and at the same time it needs information whether it
needs to be shown right now or not, its content and some context from
parent components. This example shows that sometimes logical hierarchy
isn't match with the physical DOM hierarchy.
Take a look at https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example to see the concrete example which is answer to your question (take a look at the "use" property):
import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
return (
<Cell {...props}>
// the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
<Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
hideMe, // alias for `hide(modalId)`
index } // useful to know to set zIndex, for example
, e) => // access to the arguments (click event data in this example)
<Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
<ConfirmationDialog
title={ 'Delete' }
message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
confirmButton={ <Button type="primary">DELETE</Button> }
onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
close={ hideMe } />
</Modal> }
</Layer>
// this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
<LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
<div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
<Icon type="trash" />
</div> }
</LayerContext>
</Cell>)
// ...

Categories

Resources