Decoupling functionality in React - javascript

I'm trying to figure out how I can decouple some React components -- specifically modals -- so that the components which will open the modals don't need to know how they are opened.
For example, in jQuery I could do something like:
var $modal = $(".modal");
var modals = {};
/* Gets a modal dialog
*/
function _get_modal(modal) {
if(modals[modal] === undefined) {
modals[modal] = $(".modal-dialog[data-modal=" + modal + "]");
}
return modals[modal];
}
/* Shows the modal page dimmer and the modal dialog
*/
function show_modal(modal) {
var $m = _get_modal(modal);
$modal.show();
$m.show();
return $m;
}
// Later in a click event listener somewhere...
show_modal("MyModal");
If I wanted to do this same thing in React it seems like I would need to pass down a function from my parent component to all of the children and the children's children so that they know how to trigger the modal.
Ideally, I'd like to abstract what I'm getting at to be able to implement a Command pattern.

have you looked at react-modal. It encapsulate already the modal. https://github.com/reactjs/react-modal. And even if you are not using it you might get an idea how to encapsulate things in react. One note: Command pattern is an OOP pattern, react has a functional approach. Investigate how to handle things more in the functional way. HTH

Related

Calling "document.getElementByID" in React Component vs. "ref"

I am trying to open a modal by changing the style properties of the modal, so it is visible. I works perfectly when I write a function like this:
modalAction(action) {
switch (action) {
case "open": {
const modal = document.getElementById("login-popup");
modal.style.opacity = 1;
modal.style.visibility = "visible";
}
}
}
Yet I have never seen react code like this. Insead I read a lot about refs that use Code like this:
<div className="login-popup" ref={(ref) => {this.loginPopup = ref}} id="login-popup">
But as far as I see it using refs will only alow me to access a virtual DOM elements in the same (or nested) components. The modal is a component of itself and is not nested it is calles in.
I could solve my problem using redux but writing a reducer-case as well as an action and producing an additional piece of state seems overkill for my sort of problem.
How can I access the modals style properties the right way if the modal is neither nested nor in the same component where it is called from? Thx

React: Programmatically opening modals (and cleaning up automatically)

I have a site with lots of modals which can be opened from anywhere (for example LoginModal). The challenge I'm running into is if I open one programmatically with something like ReactDOM.render, how do I clean it up automatically when the parent component is unmounted without putting it (and all possible modals) in the template.
For example, something like this to open it:
openLoginModal() {
ReactDOM.render(<LoginModal />, document.body);
}
LoginModal can clean itself up when closed. However, if the DOM from the component which opened it is unmounted, how do I let LoginModal know to unmount as well.
One thought I've had is to use an Rx.Subject to notify it when to unmount, but this also sounds like a bit of a wrong approach and a possible anti-pattern.
For example:
// modules/User.js
openLoginModal(unmountSubj) {
const container = document.createElement('div');
ReactDOM.render(<LoginModal />, container);
unmountSubj.subscribe(() => {
ReactDOM.unmountComponentAtNode(container);
});
}
// components/RandomView.jsx
unmountSubject = new Rx.Subject();
componentWillUnmount() {
this.unmountSubject.next();
}
login() {
User.openLoginModal(this.unmountSubject);
}
I'd like to avoid having all the possible modal components in each JSX template they might be used in.
How would you approach this?
Here's the solution I've come up with so far: There's a modal manager module, which will render a modal into the DOM (via ReactDOM.render) and return a function which will unmount it.
Here's a simplified version:
// modalManager.js
export default modalManager = {
openModal(modalClass, props) {
// Create container for the modal to be rendered into
const renderContainer = document.createElement('div');
document.body.appendChild(renderContainer);
// Create & render component
const modalInst = React.createElement(modalClass, { ...props, renderContainer });
ReactDOM.render(modalInst, renderContainer);
// Return unmounting function
return () => this.unmountModal(renderContainer);
},
unmountModal(renderContainer) {
ReactDOM.unmountComponentAtNode(renderContainer);
renderContainer.parentNode.removeChild(renderContainer);
},
}
// TestThing.jsx
class TestThing extends React.Component {
unmountLogin = null;
componentWillUnmount() {
this.unmountLogin();
}
login() {
this.unmountLogin = modalManager.openModal(Login, {});
}
}
You'll also notice that renderContainer is passed to the modal component. This way the modal can call modalManager.unmountModal itself when closed.
Let me know what you think.
In my current React project, I've addressed this by having a multilayered component architecture.
<App>
// the standard starting point for React apps
<DataLayer>
// this is where I make API calls for data that is shared between all components
<DisplayLayer>
// this is where I put the methods to launch display elements that are shared
// by all components (e.g., modals, alerts, notifications, etc.)
<Template>
// this is the first layer that is actually outputting HTML content
<ModuleX>
<ModuleY>
<ModuleZ>
// these modules control the main display area of the screen, they encompass
// major UI functions (e.g., UsersModule, TeamsModule, etc.)
// when one of these modules needs to launch a shared UI element (like a
// modal), they call a method in the <DisplayLayer> template - this means
// that if I have a commonly-used modal (like, LoginModal), it doesn't need
// to be included in every one of the core modules where someone might need
// to initiate a login; these modules are mounted-and-unmounted as the user
// navigates through the app
So when the app loads, <App>, <DataLayer>, <DisplayLayer>, and <Template> all load up (and they will only load one time). As the user navigates around, the <ModuleX/Y/Z/etc> components are mounted-and-unmounted, but all of the "common stuff" stays in place that was mounted/loaded in the higher layers of the app.

How to get React setState to re-render for Foundation 6 Reveal (Modal)?

My issue has been touched in a few questions around the web but I don't think it's been holistically asked.
I am using Foundation 6 with React. Everything works by using
import $ from 'jquery';
window.jQuery = $;
let foundation = require('path/to/foundation.js');
then in componentDidUpdate(), I call $(document).foundation(). Also, the CSS is being called somewhere.
My problem is once I get the modal to open, I can't populate it with data using setState(). I think I understand that the DOM changes when the modal opens, thus causing issues but I was wondering if anyone has had success with the Reveal plugin? My code is like this:
getData() {
Facebook.get('/me/taggable_friends', function(error, response) {
$('#modal').foundation('open');
//setTimeout is just for testing sanity
let _this = this;
setTimeout(function() {
_this.setState({ friends: response.data });
}, 3000);
})
}
Again, everything works. I'm getting data back from Facebook, the state is updating, the modal is opening, I'm just not able to populate the modal and I have this as my markup:
<div id="modal" className="modal-classes-from-foundation" data-reveal>
{
this.state.friends.length > 0 &&
this.state.friends.map((friend, i) => {
return(
<div>{ friend.name }</div>
)
})
}
</div>
Also to note, this.state.friends is being set in the constructor as an empty array.
All the code is valid on my server (no errors), but I wrote this from memory so I didn't remember small details like class/path names
Things I tried
Using componentWillReceiveProps to force update
Setting state before calling modal open
I dont think your answer is so smart. AFAIK the current state of the art is to set a nodeReference using a setNode method and then have
ref={this.setNode}
in your element, so then you can call whatever you are calling with jQuery, passing it via argument the node so you can play with it, and use the React lifecycle hooks to sync your react world with your jQuery playground
componentWillMount() {
window.addEventListener('resize', this.handleWindowResize, false);
}
componentDidMount() {
//initialize your jQuery dom manipulation
}
shouldComponentUpdate(nextProps) {
if (!this.props.store.equals(nextProps)) {
//to update stuff
}
return false; //> dont update
}
componentWillUnmount() {
//
}
setNode(ref) {
this.nodeReference = ref;
}
After banging my head against a virtual desk for a day, I understand why it's tough getting Reveal to work with React, especially compared to the other Foundation elements.
Reveal is an overlay that lives inside of <body></body> but not inside <div id="app"></div>, or whatever you name the root div that your React app renders to. Since the overlay is outside of the "app", React has no control over it and sending states/props to it won't do anything.
What I ended up doing is a bit clever and could be looked down upon, but seemed very necessary. I took inspiration from BlackMutt in 2015 where he basically created a function to use jQuery for appending the modal's code and initializing it. Unfortunately, this means every other thing you do with the modal will need to use jQuery as well but the good news is that it's separated from the rest of the app. Here's a sample of what it looks like:
createListModal(items) {
let content = items.map((item, i) => {
return $('<div class="list-item"><div class="item-name">'+ item.name +'</div></div>');
});
let close = $('<button class="close-button" data-close aria-label="Close modal" type="button"><span aria-hidden="true">×</span></button>');
let modal = $('<div class="reveal" id="list-popup" data-reveal>').append(content).append(pagination).append(close);
$(modal).foundation();
$('#list-popup').foundation('open');
}
So all I did was call that method when I got my data from Facebook. It's pretty simple but if you've been in React for a while, you have to switch your brain back into thinking in jQuery again.

Incrementing the Material Design Lite Progress bar with React

I've got MDL running with React at the moment and it seems to be working fine at the moment.
I've got the Progress Bar appearing on the page as needed and it loads up with the specified 'progress' on page load when either entering in a number directly:
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(10);
})
or when passing in a number via a Variable:
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(value);
})
It stops working after this though. I try to update the value via the Variable and it doesn't update. I've been advised to use this:
document.querySelector('.mdl-js-progress').MaterialProgress.setProgress(45);
to update the value but it doesn't work. Even when trying it directly in the console.
When trying via the Console I get the following Error:
Uncaught TypeError: document.querySelector(...).MaterialProgress.setProgress is not a function(…)
When I try to increment the value via the Variable I get no errors and when I console.log(value) I am presented the correct number (1,2,3,4...) after each click event that fires the function (it fires when an answer is chosen in a questionnaire)
What I want to know is if there's something obvious that I'm missing when using MTL and React to make components to work? There was an issue with scope but I seem to have it fixed with the following:
updateProgressBar: function(value) {
// fixes scope in side function below
var _this = this;
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(value);
})
},
In React I've got the parent feeding the child with the data via props and I'm using "componentWillReceiveProps" to call the function that updates the progress bar.
I've used the "componentDidMount" function too to see if it makes a difference but it still only works on page load. From what I've read, it seems that I should be using "componentWillReceiveProps" over "componentDidMount".
It's being fed from the parent due to components sending data between each other. I've used their doc's and some internet help to correctly update the parent function to then update the progress bar in the separate component.
updateProgressBarTotal: function(questionsAnsweredTotal) {
this.props.updateProgressBarValue(questionsAnsweredTotal);
}
The parent function looks like the following (I think this may be the culprit):
// this is passed down to the Questions component
updateProgressBarTotal: function(questionsAnsweredTotal) {
this.setState({
progressBarQuestionsAnswered : questionsAnsweredTotal
})
}
I can post up some more of the code if needed.
Thank you
Looks I needed a fresh set of eyes on this.
I moved the function to the child of the parent. It seems that using document.querySelector... when in the parent doesn't find the element but when it's moved to the child where I do all the question logic it seems to be fine. It increments the progress correctly etc now :)
// goes to Questionnaire.jsx (parent) to update the props
updateProgressBarTotal: function(questionsAnsweredTotal) {
// updates the state in the parent props
this.props.updateProgressBarValue(questionsAnsweredTotal);
// update the progress bar with Value from questionsAnsweredTotal
document.querySelector('.mdl-js-progress').MaterialProgress.setProgress(questionsAnsweredTotal);
},
I had same problem in angular2 application.
You don't necessary need to move to the child component.
I found after struggling to find a reasonable fix that you simply have to be sure mdl-componentupgradedevent already occurred before being able to use MaterialProgress.setProgress(VALUE). Then it can be updated with dynamic value.
That is why moving to the child works. In the parent component mdl-componentupgraded event had time to occur before you update progress value
My solution for angular2 in this article
Adapted in a React JS application :
in componentDidMount, place a flag mdlProgressInitDone (initiated to false) in mdl-componentupgraded callback :
// this.ProgBar/nativeElement
// is angular2 = document.querySelector('.mdl-js-progress')
var self = this;
this.ProgBar.nativeElement.addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(0);
self.mdlProgressInitDone = true; //flag to keep in state for exemple
});
Then in componentWillReceiveProps test the flag before trying to update progress value :
this.mdlProgressInitDone ? this.updateProgress() : false;
updateProgress() {
this.ProgBar.nativeElement.MaterialProgress.setProgress(this.currentProgress);
}
After attaching the progress bar to the document, execute:
function updateProgress(id) {
var e = document.querySelector(id);
componentHandler.upgradeElement(e);
e.MaterialProgress.setProgress(10);
}
updateProgress('#questionnaireProgressBar');

How can I access child components values from a parent component when they are added dynamically?

Current Working Example
I am creating a search form that has a varying number of input elements based on the users selection from a select box.
I have broken this up into three components, a wrapper called SearchContainer, a select box called SearchSelect, and the inputs within components called SearchWithTwo and SearchWithOne just for the sake of the example.
App
└─SearchContainer Form
│ SearchSelect
│ ... any one of the multiple search inputs (SearchWithOne, SearchWithTwo)
When a user changes the value of the select box the related component which contains the inputs is loaded. The component could have anywhere from one to ten inputs. All the examples I've seen mention using ref which would be great if my inputs weren't changing.
I currently have it working by using the following in the onSubmit handler for SearchContainer
handleSubmit: function(e) {
e.preventDefault();
var form = this.getDOMNode();
[].forEach.call(form.elements, function(e){
// get the values
});
// submit the values to get results.
}
However this doesn't feel like the proper way to be doing this. Is there a better recommended way to iterate through the children components and read their state? Or can I somehow pass the children into the parent state and get the values that way?
I think I have a solution in the form of a fork of your fiddle, and I'll cover the main ideas below.
First, I'm no React expert, but I like the idea of it, and I know it's gaining popularity so I want to learn more. What I don't know is the right way to use composition or inheritance to reduce the code duplication shown below.
Basically, my idea is to add a method to each search class that exposes its state to calling classes. This is implemented here as a very simple function inside the createClass call:
getData: function() {
return this.state;
},
It's so simple, there has to be a way to create a base class or mixin class with this method and then inherit/compose over it with other classes. I'm just not sure how. For now, you can just copy-paste these lines wherever it makes sense to expose a component's state.
Keep in mind, this is anathema to the Flux architecture everyone loves. In Flux, state always comes from a source object which lives outside the React components.
Anyway, abandoning larger architecture concerns for now, you can just grab that state variable by calling getData in the handleSubmit method. No DOM traversal required:
handleSubmit: function(e) {
e.preventDefault();
var form = this.getDOMNode(),
fd = new FormData(form);
var submitData = this.state.SearchBox.getData();
// submit the values to get results.
},

Categories

Resources