I'm trying to learn the basics of facebook's react.js library and I've been lately thinking about a few stuff I can do with it just to get used to the way it works . I'm trying to make a div that contains 2 buttons one is OPEN and the other is CLOSE, when you click the OPEN the react will render a div containing anything (Like a msg saying "You clicked"), this is fine up to now but I cannot figure out how to make it disappear once clicked on the CLOSE button, does anyone know how to do that ? Thanks ^^
There are at least four ways, that depends on the real problem you need to solve:
1) Add #your-div-id.hidden { display:none } styles and add/remove hidden class on click (maybe not React way)
2) Change view state (i.e. opened flag). That's a React way and maybe the simplest choice
onOpen() {
this.setState({ opened: true });
}
onClose() {
this.setState({ opened: false });
}
render() {
var div = (this.state.opened) ? (<div>Your Div Content</div>) : undefined;
return (
//some your view content besides div
{div}
);
}
3) If you use Flux. Move state to Store and subscribe to changes. That maybe useful if you gonna show your div at many parts of your app (i.e. implement error popups which may be shown at any part of an application).
So, first of all let's keep warnings at the store:
var CHANGE_EVENT = 'change';
const warnings = [];
var WarningsStore = assign({}, EventEmitter.prototype, {
getWarnings: () => return warnings,
emitChange: () => this.emit(CHANGE_EVENT),
addChangeListener: callback => this.on(CHANGE_EVENT, callback),
removeChangeListener: callback => this.removeListener(CHANGE_EVENT, callback)
});
WarningsStore.dispatchToken = AppDispatcher.register(action => {
switch(action.type) {
case ActionTypes.ADD_WARNING:
warnings.push(action.warning);
WarningsStore.emitChange();
break;
case ActionTypes.DISMISS_WARNING:
_.remove(warnings, {type: action.warningType}); //that's lodash or underscore method
WarningsStore.emitChange();
break;
}
});
After we have a warnings store, you may subscribe to it from YourView and show popup on each AppDispatcher.dispatch({type: ADD_WARNING, warningType: 'Error'});
var YourView = React.createClass({
componentDidMount: function() {
WarningsStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() {
WarningsStore.removeChangeListener(this._onChange);
},
_onChange() {
this.setState({ warnings: WarningsStore.getWarnings() });
},
render() {
//if we have warnigns - show popup
const warnings = this.state.warnings,
onCloseCallback = () => AppDispatcher.dispatch({type: DISSMISS_WARNING, warningType: warnings[0].warningType});
popup = (warnings.length) ? <YourPopupComponent warning={warnings[0]} onClose={onCloseCallback}> : undefined;
return (
//here the main content of your view
{popup}
);
}
})
4) If you simplified your example, but actually instead of div you need to show/hide another page - you should use react-router
Here's how I would do this:
var foo = React.CreateClass({
getInitialState: function() {
return {
showDiv: false, //Or true if you need it displayed immediately on open
}
},
showIt: function() {
this.setState({showDiv: true});
},
hideIt: function() {
this.setState({showDiv: false});
},
render: function() {
return (<div style={{display: this.state.showDiv ? 'block' : 'none'}}>Your content here</div>);
}
});
What this will do is on state change, the style block of the div will be re-evaluated. If the showDiv state variable is true, it'll display as a block element. Otherwise it'll display none. You could, in theory, do this with CSS as well.
Here's a jsFiddle showing this being done with both CSS classes AND the style attribute on the div element.
Related
I have a dialog message in a modal that asks the user to save changes if there is an attempt to navigate to another screen without saving.
It has 3 buttons (Save, Proceed without Saving, and Close). Obviously, the modal window looks exactly the same on all screens but the Save and Go back without Saving perform different actions depending on the screen.
I tried creating a separate Redux reducer for the modal but I learnt that you aren't allowed to store functions in Redux.
So my question is what is the recommended approach in a situation where you have the same modals in terms of UI but different in terms of the actions they perform?
I would really want to have it at the root level so that I can just dispatch an action and not be bothered with importing the modal to each component and controlling its state inside the component.
Here is an example of the action that I initially was planning to dispatch to show the modal
showModal: (state, { payload }) => {
state.modal.isOpen = true;
state.modal.header = payload.header;
state.modal.title = payload.title;
state.modal.btnText1 = payload.btnText1;
state.modal.btnText2 = payload.btnText2;
state.modal.btnText3 = payload.btnText3;
state.modal.btnAction1 = payload.btnAction1;
state.modal.btnAction2 = payload.btnAction2;
state.modal.btnAction3 = payload.btnAction3;
}
I would create a "wrapper" component like PageWithConfirmModal, and use it in each page component:
const MyCurrentPage = function( props ){
return <PageWithConfirmModal
showModal= { props.showModal }
save= { props.callSpecificSaveAction }
proceed= { props.callSpecificProceedAction }
close= { props.callSpecificCloseAction }
>
... page content ...
</PageWithConfirmModal>;
}
It depends where your payload.btnAction1 etc. come from, but you will figure out how to adapt my example, I guess.
Alternatively there are several possible variants, depending on your situation, e.g. pass some property that describes what to do instead of the actions, and decide which action to use inside the PageWithConfirmModal or inside the modal, e.g.:
const MyCurrentPage = function( props ){
return <PageWithConfirmModal
showModal= { props.showModal }
whatToDo= { DO_ACTIONS_FROM_MAIN_PAGE }
>
... page content ...
</PageWithConfirmModal>;
}
// e.g. inside PageWithConfirmModal.jsx
let actions;
if( DO_ACTIONS_FROM_MAIN_PAGE ){
actions = {
save: callSpecificSaveAction,
proceed: callSpecificProceedAction,
close: callSpecificCloseAction,
}
} else if( ...
Example 1:
click on tab1. Tab 1 is active tab. Click again on tab1(tab1 is active tab) I don't want to call function start() in component 'Details'. Click on tab 2 or tab 3 I want to call function start() in another component 'Details'.
Example 2:
click on tab2. Tab 2 is active tab. Click again on tab2(tab 2 is active tab) I don't want to call function start() in component 'Details'. Click on tab 1 or tab 3 I want to call function start() in another components 'Details'.
Tries to create a condition in the 'Details' component. If the tab is not active, call the start () function. Move the function isActive () and the variable selectedTabId from the component 'Tabs' to 'Details' and call the start function there if the tab is not active. Is my reasoning correct? Is this a good approach?
Code here: https://codepen.io/kaka-milan/pen/oKXJma?editors=1111
Drawing: https://imgur.com/VZ3N5lM
Tabs
var Tabs = React.createClass({
getInitialState: function() {
return { selectedTabId: 1 }
},
isActive: function (id) {
return this.state.selectedTabId === id;
},
setActiveTab: function (selectedTabId) {
this.setState({ selectedTabId });
},
render: function() {
var total = this.props.data.points.total,
tabs = total.map(function (el, i) {
return <Tab
key={ i }
content={ el.name }
id={el.id}
isActive={ this.isActive(el.id) }
onActiveTab={ this.setActiveTab.bind(this, el.id) }
/>
}, this);
return
<div>
<ul className="navigation">
{ tabs }
</ul>
<Details
isActive={ this.isActive}
selectedTabId={this.state.selectedTabId}
/>
</div>
}
});
Details
var Details = React.createClass({
componentDidMount() {
if(!this.props.isActive(this.props.selectedTabId)){
this.start();
}
},
start: function() {
return console.log('aaa')
},
render: function() {
return
<p></p>
}
});
Currently the problem is that Details is rendered when Tabs is rendered so start is only called on first render. Also Details has no way of knowing when state is changed.
One solution is to move start into Tabs and call it in setActiveTab if needed, like below. This allows us to remove the Details component altogether:
var Tabs = React.createClass({
...
setActiveTab: function (selectedTabId) {
if(selectedTabId !==
this.setState({ selectedTabId });
},
start: function () {
console.log('aaa')
},
...
})
Updated codepen: https://codepen.io/sentaipadas/pen/JgYPmq
I haven't worked with react classes for a while, but if you have access to React 16.8 and want to separate state handling logic from rendering (it looks like that is the aim of Details component) you can use React hooks (with classes you could possibly use refs).
Example codepen: https://codepen.io/sentaipadas/pen/mNeboE
I'm currently using react router to build my site, but I have .dropdowns on several pages which I initialise using $('.dropdown').dropdown();
How can I keep initialising my dropdowns on every page I visit using react router? The onhashchange doesn't seem to register the URL changing.
This is what my dropdown function looks like.
jQuery
$(function() {
$('.dropdown').dropdown();
$(window).on('hashchange', function(e){
$('.dropdown').dropdown();
});
});
$.fn.extend({
dropdown: function(o){
var o = $.extend({ maxHeight: 600, buffer: 100, delay: 500 }, o);
return this.each(function(){
var dropdown = $(this),
toggle = dropdown.find('.toggle'),
menu = dropdown.find('.menu'),
a = dropdown.find('a'),
liheight = dropdown.height();
if(!dropdown.length){ return; }
toggle.click(function(e){
if(!menu.is(':visible')) {
$('.menu').parent().removeClass('open');
}
menu.parent().addClass('open');
});
$(document).bind('click', function(e){
if (! $(e.target).parents().hasClass('dropdown'))
menu.parent().removeClass('open');
});
});
}
});
I have also tried this, but still no luck:
$('body').on('focus', '.dropdown' function(e){
$('.dropdown').dropdown();
});
Edit: The solution
Used the componentDidMount() function in react router to initialise the dropdown every time the dropdown was rendered. This is what I added:
export const elDropdown = React.createClass({
componentDidMount: function() {
$(document).ready(function() {
$('.dropdown').dropdown();
})
},
render() {
return(
<div>...
you need to initialise the dropdowns after they become available on the page. The right place to do that is in React's lifecycle methods. In that case, componentDidMount - https://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount
You can create a component that wraps the dropdown and initialises it. Here is a simple example of how you could achieve that:
class DropdownWrapper extends React.Component {
constructor(){
super()
this.id = 'someUniqueID'
}
componentDidMount(){
$('#' + this.id).dropdown();
}
render(){
return <select id={this.id} otherProps={this.otherProps} ></select>
}
}
Notice that you will need to use unique identifiers. You can pass it to the component manually or create it randomly.
I am building a todo list, and one of the functionalities is to display a list of completed tasks when the "completed" text is clicked and hide the list when it is clicked again. However, I can't get the first step - saving the state of the list, whether shown or hidden - to work.
There are several of these "CompletedRow" components (for different categories of tasks), and while clicking the text does toggle the state successfully, it seems that all these "CompletedRow" components share the same state - they are not independent. Why is this the case when I have already assigned keys to these components, and how can I solve this issue?
var CompletedRow = React.createClass({
getInitialState: function () {
return {
show: false
};
},
handleChange: function() {
this.setState.show = !this.setState.show;
console.log(this.state.show);
},
render: function() {
return (<tr>
<td
className="completed" colSpan="3" onClick={this.handleChange}
> {this.props.count} tasks completed
</td>
</tr>);
}
});
var TaskTable = React.createClass({
// other code omitted for simplicity
render: function() {
var rows = [];
var completedTasks = 0;
this.state.taskList.forEach(function(task, index) {
// do something
if (completedTasks > 0) {
rows.push(<CompletedRow
count={completedTasks}
key={getKey()}
taskList={this.state.taskList}
/>);
completedTasks = 0;
}
}.bind(this));
return (
<div>
<table>
<tbody>{rows}</tbody>
</table>
</div>
);
}
});
This may be a typo, but you have a line in handleChange that is toggling a property attached to the setState function. This is shared by all the components
this.setState.show = !this.setState.show;
That line should be
this.setState({show: !this.state.show});
Also, the console.log on the following line will reflect the old state because setState is asynchronous to allow batching of state changes.
I have a react component rendering on page load. The content includes lots of rich media that I want to lazy load only when the content is on screen and subsequently unload it when it's not. More content is loaded as the user scrolls.
I'm using a combination of techniques to handle lazy loading iframes, videos, and images and it works well outside of content rendered via React. Mostly custom jQuery and the Lazy Load Anything library.
My main issue is that I can't get my lazy load function to trigger on content just placed into the dom. It works once the user resizes/scrolls (I have a events for this that are triggered appropriately). How do I get it to trigger when the content is available?
I've tried triggering it from componentDidMount but this doesn't seem to work as the content has yet to be placed into the DOM.
I suppose I could just check for content every n seconds but I'd like to avoid this for performance reasons.
Here's my simplified code:
var EntriesList = React.createClass({
render: function() {
var entries = this.props.items.map(function(entry) {
return (
<div className="entry list-group-item" key={entry.id}>
// lazy items video, image, iframe...
<img src="1px.gif" className="lazy" datasource="/path/to/original" />
<video poster="1px.gif" data-poster-orig="/path/to/original" preload="none">{entry.sources}</video>
</div>
);
});
return(<div>{entries}</div>);
}
});
var App = React.createClass({
componentDidMount: function() {
$.get('/path/to/json', function(data) {
this.setState({entryItems: data.entries});
}.bind(this));
// What do I put here to trigger lazy load? for the rendered content?
myLazyLoad(); // does not work on the new content.
},
getInitialState: function() {
return ({
entryItems: []
});
},
render: function() {
return (<div><EntriesList items={this.state.entryItems} /></div>);
}
});
React.render(<App />, document.getElementById('entries'));
With the npm package react-lazy-load-image-component, you just have to wrap the components that you want to lazy load with <LazyLoadComponent> and it will work without any other configuration.
import { LazyLoadComponent } from 'react-lazy-load-image-component';
var EntriesList = React.createClass({
render: function() {
var entries = this.props.items.map(function(entry) {
return (
<LazyLoadComponent>
<div className="entry list-group-item" key={entry.id}>
// lazy items video, image, iframe...
<img src="1px.gif" className="lazy" />
<video poster="1px.gif" data-poster-orig="/path/to/original" preload="none">{entry.sources}</video>
</div>
</LazyLoadComponent>
);
});
return(<div>{entries}</div>);
}
});
var App = React.createClass({
componentDidMount: function() {
$.get('/path/to/json', function(data) {
this.setState({entryItems: data.entries});
}.bind(this));
},
getInitialState: function() {
return ({
entryItems: []
});
},
render: function() {
return (<div><EntriesList items={this.state.entryItems} /></div>);
}
});
React.render(<App />, document.getElementById('entries'));
Disclaimer: I'm the author of the package.
If you are trying to use the jquery plugin you may end with a DOM out of sync with that rendered by React. Also in your case the lazy load function should be called in the EntriesList component, not from its parent.
You could use a very simple component as react-lazy-load:
https://github.com/loktar00/react-lazy-load
or just take inspiration from its source code to implement your own.
var EntriesList = React.createClass({
render: function() {
var entries = this.props.items.map(function(entry) {
return (
<div className="entry list-group-item" key={entry.id}>
// lazy items video, image, iframe...
<LazyLoad>
<img src="1px.gif" datasource="/path/to/original" />
<video poster="1px.gif" data-poster-orig="/path/to/original" preload="none">{entry.sources}</video>
</LazyLoad>
</div>
);
});
return(<div>{entries}</div>);
}
});
Try to check on scroll event to your div parent container (the div that you render on App class:
var App = React.createClass({
componentDidMount: function() {
$.get('/path/to/json', function(data) {
this.setState({entryItems: data.entries});
}.bind(this));
},
myLazyLoad: function(e) {
// here do actions that you need: load new content, do ajax request, ...
// example: check you scrolling and load new content from page 1, 2, 3 ... N
var self = this;
$.get('path/to/json/?page=N', function(data) {
self.setState({entryItems: data.entries});
});
},
getInitialState: function() {
return ({
entryItems: []
});
},
render: function() {
return (<div onScroll={this.myLazyLoad}><EntriesList items={this.state.entryItems} /></div>);
}
});
Lazy Loading React
A component can lazily load dependencies without its consumer knowing using higher order functions, or a consumer can lazily load its children without its children knowing using a component that takes a function and collection of modules, or some combination of both.
https://webpack.js.org/guides/lazy-load-react/