I am using react and redux for my current project. I have a button and whenever user click on that it first call the server and load some data and then manipulate the css of some dom elements.
Here is my code:
var allClickableStories = document.getElementById("dummyClickStory" + this.props.story.id);
$(allClickableStories).click(function () {
if (!$("#" + this.id + " .expansion-handler").hasClass("show")) {
var storyId = this.id.replace("dummyClickStory", "");
thisRef.props.getStoryDetail(storyId, thisRef.props.channel);
$("#" + this.id + " .expansion-handler").addClass("show");
$("#" + this.id + " .cutline").addClass("show");
}
});
Also it is noteworthy that the above code in in componentDidMount to make sure that first render happens. However this does not guarantee that ajax call ( thisRef.props.getStoryDetail) happens before css manipulation and this is exactly where I am stuck at. what is happenning is the ajax call is sent and then css manipulation fires however ajax call may return after and render will happend and hide the manipulated dom element again.An easy way to fix it is to set asynch to false in jquery ajax call but not a good solution. So how can I can make sure that first ajax call finishes and render happens then css manipulation takes place?
Also just for more info here are my code in Action and reducer:
Action:
export function getStoryDetail(storyId,channel){
return dispatch => {
$.ajax({
url: "http://localhost:3003/json5.txt",
dataType: 'json',
cache: false,
success: function(data) {
var storyDetatil=[];
for (var key in data) {
storyDetatil.push(data[key]);
}
var storyDetailObj={"storyArray":storyDetatil,"storyId":storyId, "channel":channel};
dispatch({
type: "STORY_EXPANSION",
payload: storyDetailObj
});
}.bind(this)
});
};
}
Reducer:
case "STORY_EXPANSION":
var tempStateExpansion = state.slice();
if (action.payload.storyId > -1 && state[0].channel !=undefined) {
for(var i=0;i<state.length;i++){
if(state[i].channel.toLowerCase()===action.payload.channel.toLowerCase()){
for(var j=0;j<state[i].storiesSnippet.length;j++){
if(action.payload.storyId===state[i].storiesSnippet[j].id){
tempStateExpansion[i].storiesSnippet[j]=action.payload.storyArray[0];
break;
}
}
break;
}
}
}
else{
tempStateExpansion[0].storiesSnippet[0]=action.payload.storyArray[0];
}
state=tempStateExpansion;
break;
In short, you can not do this with React. You are using jQuery to manipulate the DOM which is basically the opposite approach to React. React manipulates the DOM for you.
What you should do instead is to have a component (you could look at inline styles perhaps: https://facebook.github.io/react/docs/dom-elements.html) that will rerender based on an updated state.
App renders with whatever defaults and fires off ajax request.
Ajax response updates the redux store, which through connect and a mapStateToProps updates the props of a component that should change when the ajax request is fulfilled.
Component rerenders based on the new state. The render path has the new styles (possibly inline)
I would recommend running through the TODO List example with redux and React here: http://redux.js.org/docs/basics/UsageWithReact.html. Your Redux usage looks alright, it's the React that is problematic.
Here is an example of using a React component which would rerender based on a data property of the redux state. It assumes that you would have an application wide CSS that contains definitions for foo and bar CSS classes.
import React from 'react';
import { connect } from 'react-redux';
class Example extends React.Component {
render() {
const { data } = this.props;
// If data is present (render a div using CSS from class foo)
// and put the data in the div asuming it's a string
if (data) {
return <div className='foo'>{ data }</div>;
}
// If data is not present (render a div using CSS from class bar)
// Display No Content
return <div className='bar'>No Content</div>;
}
}
// Data is an object, but not required as initially it will be undefined
Example.propTypes = {
data: React.PropTypes.object
};
// Map redux state to react props
const mapStateToProps = state => ({
data: state.data
});
// Connect to redux store helper using our mapping function
export default connect(mapStateToProps)(Example);
Related
in a react UI I have a table component. You can edit one row of the table by clicking a edit button or you can add a new record by clicking a "new-record-button". When clicking the edit button an redux-action is triggered which takes the row and sets a visible property of a modal dialog. When the "new-record-button" is clicked an action is triggered which creates a new empty data item and the same modal dialog is triggered.
In the modal dialog I have several text components with onChange method.
in this onChange-method the data-item is written.
When to user clicks a save-button the edited dataItem is saved to the database.
So my code looks like:
const mapStateToProps = (state) => ({
dataItem: state.datItemToEdit || {},
...
});
...
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
dataItem.carId = text;
break;
}
...
}
this.forceUpdate();
}
...
<TextField
...
onChange={event => this.handleTextChange(event)}
/>
I have several question regarding this approach. First I do not understand why in handleTextChange we can write to dataItem. It does work apparently.
dataItem.carId is set in the example code but I thought
const {dataItem} = this.props;
gives us a local read-only variable dataItem just to read from the props...
Next thing I think is a poor design. After reading in a book about react I think we should not write to props but only set a state.
In my example I get the the dataItem from the redux-state. The mapStateToProps maps it to the (read-only) props of the component, right?!. But I want to EDIT it. So I would have to copy it to the state of my component?
But where to do it?
Once in the state of my component I could simply call this.setState for the various text-fields and the component would render and I could abstain from forceUpdate(), right?!
Can someone explain how the redux status plays together with the component status and props for this example?
In redux or react, you shouldn't write to the props directly because you should keep your props as immutable. Redux forces us to use immutable state because state is a source of truth for the application. If the reference to state changes then only your app should render. If you'll mutate your state (objects) then the references don't get changed and your app doesn't know whether some state has been changed or not. React/Redux doesn't give you read-only objects automatically. You can mutate them anytime but as I told you, it can cause problems that Your app won't know when to re-render. If you want to have this read-only property inherently, you should probably use immutable.js
About your second question that you'll have to copy the props to the component's state and where you should do it. You should do it in the constructor of the component and you should use immutibility helper
import React from React;
import update from 'immutibility-helper';
class Modal extends React.Component {
constructor(props){
this.state = {
dataItem: dataItem,
};
}
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.state.dataItem, text); //fire a redux action to update state in redux
this.setState(update(this.state, {
dataItem: {
carId: {$set: text},
}
});
break;
}
...
}
}
}
You wouldn't have to do forceUpdate in such case because the reference to state will change and the component will re-render itself.
Also, you can use forceUpdate in your application but personally I don't find it a great idea because when React/Redux is giving you the flow of state, by using forceUpdate, you're breaking the flow.
The last question is how redux and react state plays together. That is also a matter of choice. If I have a app level state, e.g., in your case you've some app level data, you should put that in your redux state and if you have a component level things, such as opening a modal or opening a third pane. That's the convention I follow but that can really depend on how you want to exploit react and redux state.
Also, in above code, I put the redux state in component state too (because you asked where to put that) but Ideally you should fire a redux action and update in redux state. In this way, you will restrict yourself from state duplication in react and redux.
import React from React;
import {updateItem} from './actions';
class Modal extends React.Component {
...other methods
handleTextChange(event) {
const {
dataItem
} = this.props;
const id = event.target.id;
const text = event.target.value;
switch (id) {
case 'carId': {
this.props.updateItem(this.props.dataItem, text); //fire a redux action to update state in redux
break;
}
...
}
}
}
const mapStateToProps = (state) => ({
dataItem: getDataItem(state), //get Data Item gets Data from redux state
});
export default connect(mapStateToProps, {updateItem: updateItem})(Modal);
in Actions:
updateItem = (dataItem, text) => dispatch => {
dispatch({type: 'UPDATE_ITEM', payLoad: {dataItem, text});
};
in Reducer:
export default (state = {}, action) => {
switch(action){
case 'UPDATE_ITEM': {
return {
...state,
dataItem: {
...action.dataItem,
carId: action.text,
}
};
}
}
}
In this way, your state will be pure and you don't have to worry about immutibility.
EDIT:
As constructor will be called only once, you should probably use componentWillReceiveProps so that whenever you render the component, you get the next updated props of the component. You can check whether the carId of dataItem is same or not and then update the state.
componentWillReceiveProps(nextProps){
if(nextProps.dataItem.carId !== this.props.dataItem.carId){
this.setState({dataItem: nextProps.dataItem});
}
}
You should only use redux when you want different, unrelated components in your app to know and share the specific state.
e.g. - When a user logs in to your app, you might want all components to know that user so you'll connect your different containers to the user reducer and then propagate the user to the components.
Sounds like in this case you have a classic use case for using the inner state.
You can use the parent of all TextFields to maintain all rows, edit them by index, etc.
Once you start using redux, it's really easy to make the mistake of transferring the entire state of the components to the reducers, I've been there and stopped doing it a while ago :)
How should one access state (just state, not the React State) of child components in React?
I've built a small React UI. In it, at one point, I have a Component displaying a list of selected options and a button to allow them to be edited. Clicking the button opens a Modal with a bunch of checkboxes in, one for each option. The Modal is it's own React component. The top level component showing the selected options and the button to edit them owns the state, the Modal renders with props instead. Once the Modal is dismissed I want to get the state of the checkboxes to update the state of the parent object. I am doing this by using refs to call a function on the child object 'getSelectedOptions' which returns some JSON for me identifying those options selected. So when the Modal is selected it calls a callback function passed in from the parent which then asks the Modal for the new set of options selected.
Here's a simplified version of my code
OptionsChooser = React.createClass({
//function passed to Modal, called when user "OK's" their new selection
optionsSelected: function() {
var optsSelected = this.refs.modal.getOptionsSelected();
//setState locally and save to server...
},
render: function() {
return (
<UneditableOptions />
<button onClick={this.showModal}>Select options</button>
<div>
<Modal
ref="modal"
options={this.state.options}
optionsSelected={this.optionsSelected}
/>
</div>
);
}
});
Modal = React.createClass({
getOptionsSelected: function() {
return $(React.findDOMNode(this.refs.optionsselector))
.find('input[type="checkbox"]:checked').map(function(i, input){
return {
normalisedName: input.value
};
}
);
},
render: function() {
return (
//Modal with list of checkboxes, dismissing calls optionsSelected function passed in
);
}
});
This keeps the implementation details of the UI of the Modal hidden from the parent, which seems to me to be a good coding practice. I have however been advised that using refs in this manner may be incorrect and I should be passing state around somehow else, or indeed having the parent component access the checkboxes itself. I'm still relatively new to React so was wondering if there is a better approach in this situation?
Yeah, you don't want to use refs like this really. Instead, one way would be to pass a callback to the Modal:
OptionsChooser = React.createClass({
onOptionSelect: function(data) {
},
render: function() {
return <Modal onClose={this.onOptionSelect} />
}
});
Modal = React.createClass({
onClose: function() {
var selectedOptions = this.state.selectedOptions;
this.props.onClose(selectedOptions);
},
render: function() {
return ();
}
});
I.e., the child calls a function that is passed in via props. Also the way you're getting the selected options looks over-fussy. Instead you could have a function that runs when the checkboxes are ticked and store the selections in the Modal state.
Another solution to this problem could be to use the Flux pattern, where your child component fires off an action with data and relays it to a store, which your top-level component would listen to. It's a bit out of scope of this question though.
I am lost between the possibilities offered to handle this case: let's say we have the following constraints:
Knockout
SPA with Sammy.js - Html loaded via Ajax
My page:
+-------------------------------+
| #navigation |
+---------+---------------------+
| #sidebar| #content |
| | |
| | |
| | |
+---------+---------------------+
Currently, I have one appViewModel which handle the data-bind for all the shared elements of my website: #navigation and #sidebar. This appViewModel has observable used on every pages of my website.
appViewModel = function () {
var self = this;
self.sidebarItemArray = ko.observableArray([x, y, z]);
self.currentRoute = ko.observable();
...
self.updateView = function(path, currentRoute) {
return $.get(path, function( data ) {
var $data = $(data);
// Updates #content, TITLE and update the currentRoute observable.
$( '#content' ).replaceWith($data.find('#content'));
document.title = $data.filter('title').text();
self.currentRoute(currentRoute);
}, 'html');
}
Sammy(function() {
this.get(':link'', function() {
self.updateView(this.path, this.params.link);
});
}).run();
}
ko.applyBindings(new appViewModel());
Now, let's say that #content is a piece of DOM loaded through an Ajax Call. Each time a user click a link inside #navigation or #sidebar, Sammy.js intercept it and then update #content. The problem is that the new DOM inside #content has data-bindings itself.
1) First, should I use the html data-bind on #content, replaceWith(as above) or the template binding with custom function to get the template?
(http://knockoutjs.com/documentation/template-binding.html#note-5-dynamically-choosing-which-template-is-used)? What is the best practice here?
2) Should Sammy necessary lives inside the appViewModel as in the documentation or elsewhere is just fine?
3) Once the updateView method is completed, how would you bind the new DOM? Like below? Isn't there a risk of rebinding some DOM because ko.applyBindings has already been called without second argument?
ko.applyBindings(new routeSpecificViewModel() , document.getElementById("content"));
I am thankful for your help.
One simple solution is to make the page's viewmodel an observable, and load it ondemand. Use a variable to record if ko.applyBindings has been called. Example from the knockout-spa framework:
/*! knockout-spa (https://github.com/onlyurei/knockout-spa) * Copyright 2015-2016 Cheng Fan * MIT Licensed (https://raw.githubusercontent.com/onlyurei/knockout-spa/master/LICENSE) */
define(['app/shared/root-bindings', 'framework/page-disposer', 'ko', 'sugar'], function (
RootBindings, PageDisposer, ko) {
var initialRun = true;
var Page = {
init: function (name, data, controller, path) {
Page.loading(false);
name = name.toLowerCase();
if ((Page.page().name == name) && (Page.page().data == data)) { // if the requested page is the same page, immediately call controller without going further
if (controller) {
controller(data);
}
document.title = Page.title();
if (Page.initExtra) {
Page.initExtra(name, data, controller);
}
return data;
}
var autoDispose = (Page.page().data.dispose && Page.page().data.dispose(Page)) || true; // if the requested page is not the same page, dispose current page first before swap to the new page
if (autoDispose !== false) {
// auto-dispose page's exposed observables and primitive properties to initial values. if not desired, return
// false in dispose function to suppress auto-disposal for all public properties of the page, or make the
// particular properties private
PageDisposer.dispose(Page.page().data);
}
PageDisposer.init(data); //store initial observable and primitive properties values of the page
var initialized = (data.init && data.init(Page)) || true; // init view model and call controller (optional) before template is swapped-in
if (initialized === false) {
return false; // stop initialization if page's init function return false (access control, etc.)
}
if (controller) {
controller(data);
}
Page.pageClass([name, ('ontouchstart' in document.documentElement) ? 'touch' : 'no-touch'].join(' '));
Page.page({
name: name,
data: data,
path: path
}); // to test if template finished rendering, use afterRender binding in the template binding
document.title = Page.title();
if (Page.initExtra) {
Page.initExtra(name, data, controller); // useful for common init tasks for all pages such as anaylitics page view tracking, can be set in RootBindings
}
if (initialRun) {
ko.applyBindings(Page, document.getElementsByTagName('html')[0]); // apply binding at root node to be able to bind to anywhere
initialRun = false;
}
return data;
},
page: ko.observable({
name: '', // name of the page - auto-set by the framework, no need to worry
data: {
init: function () {}, // preparation before the page's template is rendered, such as checking access control, init/instantiate modules used by the page, etc.
dispose: function () {} // properly dispose the page to prevent memory leaks and UI leftovers (important for SPA since page doesn't refresh between page views) - remove DOM element event listeners, dispose knockout manual subscriptions, etc.
}
}),
pageClass: ko.observable(''),
loading: ko.observable(false),
title: function () {
return Page.page().name.titleize(); // override in RootBindings as needed
}
};
Object.merge(Page, RootBindings); // additional root bindings as needed by the app
return Page;
});
A mini but full-fledged SPA framework built on top of Knockout, Require, Director, jQuery, Sugar.
https://github.com/onlyurei/knockout-spa
Live Demo: https://knockout-spa.mybluemix.net
As far as I can tell, Backbone.js view represents DOM element. I take it from existing DOM or create it on the fly in el attribute.
In my case, I want to take it from server with AJAX request because I'm using Django templates and don't want to rewrite everything to JavaScript templates.
So I define el function that performs AJAX request.
el: function() {
model.fetch().success(function(response) {
return response.template
})
}
Of course, it does NOT work because AJAX request is executed asynchronous.
This means that I don't have el attribute and events does NOT work neither. Can I fix it?
Maybe the Backbone.js framework isn't the right tool for my needs? The reason I want to use that was to have some structure for the code.
P.S. I'm new to Backbone.js.
Do your ajax request from another view, or directly after the page load using jquery directly, and after you've downloaded your template, THEN instantiate your backbone view class with the proper id/el or whatever (depending on where you stored your ajax fetched template). Depending on your use-case, this may or may not be a sensible approach.
Another, perhaps more typical approach, would be to set up your view with some placeholder element (saying "loading" or whatever), then fire off the ajax, and after the updated template has been retrieved, then update your view accordingly (replace the placeholder with the actual template you requested).
When/if you update your view with new/other DOM elements, you need to call the view's delegateEvents method to rebind your events to the new elements, see:
http://backbonejs.org/#View-delegateEvents
I came across a similar requirement. In my instance, I was running asp.net and wanted to pull my templates from user controls. The first thing I would recommend is looking into Marionette because it will save you from writing a lot of boiler plate code in Backbone. The next step is to override how your templates are loaded. In this case I created a function that uses Ajax to retrieve the HTML from the server. I found an example of this function where they were using it to pull down html pages so I did a little modification so I can make MVC type requests. I can't remember where I found the idea from; otherwise, I would give the link here.
function JackTemplateLoader(params) {
if (typeof params === 'undefined') params = {};
var TEMPLATE_DIR = params.dir || '';
var file_cache = {};
function get_filename(name) {
if (name.indexOf('-') > -1) name = name.substring(0, name.indexOf('-'));
return TEMPLATE_DIR + name;
}
this.get_template = function (name) {
var template;
var file = get_filename(name);
var file_content;
var result;
if (!(file_content = file_cache[name])) {
$.ajax({
url: file,
async: false,
success: function (data) {
file_content = data; // wrap top-level templates for selection
file_cache[name] = file_content;
}
});
}
//return file_content.find('#' + name).html();
return file_content;
}
this.clear_cache = function () {
template_cache = {};
};
}
The third step would be to override Marionette's method to load templates. I did this in the app.addInitializer method. Here I am initializing my template loader and setting it's directory to a route handler. So when I want to load a template, I just set the template: "templatename" in my view and Backbone will load the template from api/ApplicationScreens/templatename. I am also overriding my template compiling to use Handlebars because ASP.net is not impressed with the <%= %> syntax.
app.JackTemplateLoader = new JackTemplateLoader({ dir: "/api/ApplicationScreens/", ext: '' });
Backbone.Marionette.TemplateCache.prototype.loadTemplate = function (name) {
if (name == undefined) {
return "";
} else {
var template = app.JackTemplateLoader.get_template(name);
return template;
}
};
// compiling
Backbone.Marionette.TemplateCache.prototype.compileTemplate = function (rawTemplate) {
var compiled = Handlebars.compile(rawTemplate);
return compiled;
};
// rendering
Backbone.Marionette.Renderer.render = function (template, data) {
var template = Marionette.TemplateCache.get(template);
return template(data);
}
Hopefully this helps. I've been working on a large dynamic website and it is coming along very nicely. I am constantly being surprised by the overall functionality and flow of using Marionette and Backbone.js.
There is a new Tooltip Widget in jQuery UI 1.9, whose API docs hint that AJAX content can be displayed in it, but without any further details. I guess I can accomplish something like that with a synchronous and blocking request, but this isn't what I want.
How do I make it display any content that was retrieved with an asynchronous AJAX request?
Here is a ajax example of jqueryui tootip widget from my blog.hope it helps.
$(document).tooltip({
items:'.tooltip',
tooltipClass:'preview-tip',
position: { my: "left+15 top", at: "right center" },
content:function(callback) {
$.get('preview.php', {
id:id
}, function(data) {
callback(data); //**call the callback function to return the value**
});
},
});
This isn't a complete solution obviously, but it shows the basic technique of getting data dynamically during the open event:
$('#tippy').tooltip({
content: '... waiting on ajax ...',
open: function(evt, ui) {
var elem = $(this);
$.ajax('/echo/html').always(function() {
elem.tooltip('option', 'content', 'Ajax call complete');
});
}
});
See the Fiddle
One thing to lookout for when using the tooltip "content" option to "AJAX" the text into the tooltip, is that the text retrieval introduces a delay into the tooltip initialization.
In the event that the mouse moves quickly across the tooltip-ed dom node, the mouse-out event may occur before the initialization has completed, in which case the tooltip isn't yet listening for the event.
The result is that the tooltip is displayed and does not close until the mouse is moved back over the node and out again.
Whilst it incurs some network overhead that may not be required, consider retrieving tooltip text prior to configuring the tooltip.
In my application, I use my own jquery extensions to make the AJAX call, parse the resutls and initialise ALL tooltips, obviously you can use jquery and/or your own extensions but the gist of it is:
Use image tags as tooltip anchors, the text to be retrieved is identified by the name atrribute:
<img class="tooltipclassname" name="tooltipIdentifier" />
Use invoke extension method to configure all tooltips:
$(".tooltipclassname").extension("tooltip");
Inside the extension's tooltip method:
var ids = "";
var nodes = this;
// Collect all tooltip identifiers into a comma separated string
this.each(function() {
ids = ids + $(this).attr("name") + ",";
});
// Use extension method to call server
$().extension("invoke",
{
// Model and method identify a server class/method to retrieve the tip texts
"model": "ToolTips",
"method": "Get",
// Send tooltipIds parameter
"parms": [ new myParmClass("tipIds", ids ) ],
// Function to call on success. data is a JSON object that my extension builds
// from the server's response
"successFn": function(msg, data) {
$(nodes).each(function(){
// Configure each tooltip:
// - set image source
// - set image title (getstring is my extension method to pull a string from the JSON object, remember that the image's name attribute identifies the text)
// - initialise the tooltip
$(this).attr("src", "images/tooltip.png")
.prop("title", $(data).extension("getstring", $(this).attr("name")))
.tooltip();
});
},
"errorFn": function(msg, data) {
// Do stuff
}
});
// Return the jquery object
return this;
Here is an example that uses the jsfiddle "/echo/html/" AJAX call with a jQuery UI tooltip.
HTML:
<body>
<input id="tooltip" title="tooltip here" value="place mouse here">
</body>
JavaScript:
// (1) Define HTML string to be echo'ed by dummy AJAX API
var html_data = "<b>I am a tooltip</b>";
// (2) Attach tooltip functionality to element with id == tooltip
// (3) Bind results of AJAX call to the tooltip
// (4) Specify items: "*" because only the element with id == tooltip will be matched
$( "#tooltip" ).tooltip({
content: function( response ) {
$.ajax({
url: "/echo/html/",
data: {
'html': html_data
},
type: "POST"
})
.then(function( data ) {
response( data );
});
},
items: "*"
});
here is this example on jsfiddle: