React.js: Managing State and Component Rerender - javascript

I've hit a wall as I start my adventure with React.js. I've got the UI of the following time tracking app working on several levels:
http://jsfiddle.net/technotarek/4n8n17tr/
What's working as hoped:
Filtering based on user input
Project clocks can be started and stopped independently
What's not working:
If you start one or more clocks and then try to filter, any clock that's not in the filter result set gets reset once it is re-displayed. (Just click the start on all clocks, then search for a project, then clear your search input.)
I assume this is happening because a setState is run onChange of the filter input, which is re-rendering everything and using the clock getInitialState values.
So, what's the correct way to preserve the 'state' of these clocks and the buttons when the filter re-renders the components? Should I not be storing the clock or the button 'states' as genuine React states? Do I need a function to explicitly save the clock values before the re-render?
I'm not asking for anyone to fix my code. Rather, I'm hoping for a pointer in where my understanding of React is failing.
To satisfy SO's code requirement, below is the component that contains each row in the time tracker. The clocks are started via toggleClock. IncrementClock writes the state that is getting cleared out by the search filter. Please see the complete code in the fiddle link above.
var LogRow = React.createClass({
getInitialState: function() {
return {
status: false,
seconds: 0
};
},
toggleButton: function(status) {
this.setState({
status: !this.state.status
});
this.toggleClock();
},
toggleClock: function() {
var interval = '';
if(this.state.status){
// if clock is running, pause it.
clearInterval(this.interval);
} else {
// otherwise, start it
this.interval = setInterval(this.incrementClock, 1000);
}
},
incrementClock: function() {
this.setState({ seconds: this.state.seconds+1 });
},
render: function() {
var clock = <LogClock seconds={this.state.seconds} />
return (
<div>
<div className="row" key={this.props.id}>
<div className="col-xs-7"><h4>{this.props.project.title}</h4></div>
<div className="col-xs-2 text-right">{clock}</div>
<div className="col-xs-3 text-right"><TriggerButton status={this.state.status} toggleButton={this.toggleButton} /></div>
</div>
<hr />
</div>
);
}
})

When you filter, you're removing LogRow components from the rendered output - when this happens, React unmounts the component and disposes of its state. When you subsequently change the filter and a row is once again displayed, you're getting an entirely new LogRow component, so getInitialState() is called again.
(You also have a leak here because you're not clearing the interval when these components unmount using the componentWillUnmount() lifecycle hook - those intervals are still running away the background)
To solve this, you could move the timer state and the methods which control and increment it up out of the LogRow component, so its job is just to display and control the current state but not to own it.
You're currently using the LogRow component to tie the state and behaviour of a project timer together. You could either move this state and behaviour management up to a parent component which will manage it the same way, or out into another object, e.g.:
function Project(props) {
this.id = props.id
this.title = props.title
this.ticking = false
this.seconds = 0
this._interval = null
}
Project.prototype.notifyChange = function() {
if (this.onChange) {
this.onChange()
}
}
Project.prototype.tick = function() {
this.seconds++
this.notifyChange()
}
Project.prototype.toggleClock = function() {
this.ticking = !this.ticking
if (this.ticking) {
this.startClock()
}
else {
this.stopClock()
}
this.notifyChange()
}
Project.prototype.startClock = function() {
if (this._interval == null) {
this._interval = setInterval(this.tick.bind(this), 1000)
}
}
Project.prototype.stopClock = function() {
if (this._interval != null) {
clearInterval(this._interval)
this._interval = null
}
}
Since the clearIntervals being used are an external source of change, you'd need to subscribe to them somehow, so I've implemented the ability to register a single onChange callback, which the LogRow component is doing when it mounts in the snippet below.
The working code snippet below does the most simple and direct thing possible to achieve this and as a result the solution has some discouraged practices (modifying props) and caveats (you can only have one "listener" on a Project) but it works. (This is generally my experience with React - it works first, then you make it "right" afterwards).
Next steps could be:
PROJECTS is effectively a singleton Store - you could make it an object which allows registration of listeners for changes to project state. You could then add an Action object to encapsulate triggering changes to project state so LogRow never touches its project prop directly, only reads from it and calls sideways to an Action to change it. (This is just indirection, but helps with thinking about data flow). See the Less Simple Communication example in the react-trainig repo for a worked example of this.
You could make LogRow completely dumb by listening for all project changes at a higher level and re-rendering everything on change. Passing individual project props to LowRow would then allow you to implement shouldComponentUpdate() so only rows which need to display a change actually re-render.
<meta charset="UTF-8">
<script src="http://fb.me/react-with-addons-0.12.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.2/css/bootstrap.min.css" rel="stylesheet">
<link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css" rel="stylesheet">
<div class="container">
<div class="row">
<div id="worklog" class="col-md-12">
</div>
</div>
</div>
<script type="text/jsx;harmony=true">void function() { "use strict";
/* Convert seconds input to hh:mm:ss */
Number.prototype.toHHMMSS = function () {
var sec_num = parseInt(this, 10);
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
var seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10) {hours = "0"+hours;}
if (minutes < 10) {minutes = "0"+minutes;}
if (seconds < 10) {seconds = "0"+seconds;}
var time = hours+':'+minutes+':'+seconds;
return time;
}
function Project(props) {
this.id = props.id
this.title = props.title
this.ticking = false
this.seconds = 0
this._interval = null
}
Project.prototype.notifyChange = function() {
if (typeof this.onChange == 'function') {
this.onChange()
}
}
Project.prototype.tick = function() {
this.seconds++
this.notifyChange()
}
Project.prototype.toggleClock = function() {
this.ticking = !this.ticking
if (this.ticking) {
this.startClock()
}
else {
this.stopClock()
}
this.notifyChange()
}
Project.prototype.startClock = function() {
if (this._interval == null) {
this._interval = setInterval(this.tick.bind(this), 1000)
}
}
Project.prototype.stopClock = function() {
if (this._interval != null) {
clearInterval(this._interval)
this._interval = null
}
}
var PROJECTS = [
new Project({id: "1", title: "Project ABC"}),
new Project({id: "2", title: "Project XYZ"}),
new Project({id: "3", title: "Project ACME"}),
new Project({id: "4", title: "Project BB"}),
new Project({id: "5", title: "Admin"})
];
var Worklog = React.createClass({
getInitialState: function() {
return {
filterText: '',
};
},
componentWillUnmount: function() {
this.props.projects.forEach(function(project) {
project.stopClock()
})
},
handleSearch: function(filterText) {
this.setState({
filterText: filterText,
});
},
render: function() {
var propsSearchBar = {
filterText: this.state.filterText,
onSearch: this.handleSearch
};
var propsLogTable = {
filterText: this.state.filterText,
projects: this.props.projects
}
return (
<div>
<h2>Worklog</h2>
<SearchBar {...propsSearchBar} />
<LogTable {...propsLogTable} />
</div>
);
}
});
var SearchBar = React.createClass({
handleSearch: function() {
this.props.onSearch(
this.refs.filterTextInput.getDOMNode().value
);
},
render: function() {
return (
<div className="form-group">
<input type="text" className="form-control" placeholder="Search for a project..." value={this.props.filterText} onChange={this.handleSearch} ref="filterTextInput" />
</div>
);
}
})
var LogTable = React.createClass({
render: function() {
var rows = [];
this.props.projects.forEach(function(project) {
if (project.title.toLowerCase().indexOf(this.props.filterText.toLowerCase()) === -1) {
return;
}
rows.push(<LogRow key={project.id} project={project} />);
}, this);
return (
<div>{rows}</div>
);
}
})
var LogRow = React.createClass({
componentDidMount: function() {
this.props.project.onChange = this.forceUpdate.bind(this)
},
componentWillUnmount: function() {
this.props.project.onChange = null
},
onToggle: function() {
this.props.project.toggleClock()
},
render: function() {
return <div>
<div className="row" key={this.props.id}>
<div className="col-xs-7">
<h4>{this.props.project.title}</h4>
</div>
<div className="col-xs-2 text-right">
<LogClock seconds={this.props.project.seconds}/>
</div>
<div className="col-xs-3 text-right">
<TriggerButton status={this.props.project.ticking} toggleButton={this.onToggle}/>
</div>
</div>
<hr />
</div>
}
})
var LogClock = React.createClass({
render: function() {
return (
<div>{this.props.seconds.toHHMMSS()}</div>
);
}
});
var TriggerButton = React.createClass({
render: function() {
var button;
button = this.props.status != false
? <button className="btn btn-warning" key={this.props.id} onClick={this.props.toggleButton}><i className="fa fa-pause"></i></button>
: <button className="btn btn-success" key={this.props.id} onClick={this.props.toggleButton}><i className="fa fa-play"></i></button>
return (
<div>
{button}
</div>
);
}
})
React.render(<Worklog projects={PROJECTS} />, document.getElementById("worklog"));
}()</script>

Related

Why does React rerender when the state is set to the same value the first time via an onClick event on DOM, but not react-native?

I have what I thought would be a simple test to prove state changes, I have another test which does the change by timer and it worked correctly (at least I am assuming so) but this one is trigged by a click event and it's failing my rerender check.
it("should not rerender when setting state to the same value via click", async () => {
const callback = jest.fn();
function MyComponent() {
const [foo, setFoo] = useState("bir");
callback();
return (<div data-testid="test" onClick={() => setFoo("bar")}>{foo}</div>);
}
const { getByTestId } = render(<MyComponent />)
const testElement = getByTestId("test");
expect(testElement.textContent).toEqual("bir");
expect(callback).toBeCalledTimes(1);
act(() => { fireEvent.click(testElement); });
expect(testElement.textContent).toEqual("bar");
expect(callback).toBeCalledTimes(2);
act(() => { fireEvent.click(testElement); });
expect(testElement.textContent).toEqual("bar");
expect(callback).toBeCalledTimes(2); // gets 3 here
})
I tried to do the same using codesandbox https://codesandbox.io/s/rerender-on-first-two-clicks-700c0
What I had discovered looking at the logs is it re-renders on the first two clicks, but my expectation was it on re-renders on the first click as the value is the same.
I also did something similar on React native via a snack and it works correcty. Only one re-render. So it may be something specifically onClick on React-DOM #22940
Implement shouldComponentUpdate to render only when state or
properties change.
Here's an example that uses shouldComponentUpdate, which works
only for this simple use case and demonstration purposes. When this
is used, the component no longer re-renders itself on each click, and
is rendered when first displayed, and after it's been clicked once.
var TimeInChild = React.createClass({
render: function() {
var t = new Date().getTime();
return (
<p>Time in child:{t}</p>
);
}
});
var Main = React.createClass({
onTest: function() {
this.setState({'test':'me'});
},
shouldComponentUpdate: function(nextProps, nextState) {
if (this.state == null)
return true;
if (this.state.test == nextState.test)
return false;
return true;
},
render: function() {
var currentTime = new Date().getTime();
return (
<div onClick={this.onTest}>
<p>Time in main:{currentTime}</p>
<p>Click me to update time</p>
<TimeInChild/>
</div>
);
}
});
ReactDOM.render(<Main/>, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>

What is the best way to implement undo state change (undo store/history implementation) in React Redux

I'm looking out for a history redo/ store reset to the previous state in a redux react application.
I've found a blog telling it can be done by storing present, future and past states in a stack and resetting accordingly.
I've also found a similar question in StackOverflow, but it doesn't give me a proper answer or maybe it's difficult for me to understand.
I've built a demo ToDo app and have used redux-logger to log store details with previous state and updated state. You can find the code here.
Do we have a store reset method in redux, so that we can get the previous state and update the store other that having a store with the present, the past and future states?
For anyone looking for a solution to this in 2020. You don't have to store the entire state object as the present, past, and future.
Instead, you can just store details about what has changed. This can be implemented using ImmerJS. It records all changes done on the state object and generates something called a patch.
Example: If age is updated from 32 to 40, then the generated patch will be:
Patch:
[ { op: 'replace', path: [ 'age' ], value: 40 } ]
Inverse Patch: [ { op: 'replace', path: [ 'age' ], value: 32 } ]
It also exposes a method to apply these patches/inverse patches to the state - applyPatch. So, to undo we can apply an inverse patch and to redo we can apply a patch.
You can find details of full implementation here: Implementing Undo-Redo Functionality in Redux using Immer
What is the best way ...
Best way is always difficult to define, it really depends of your use-case and requirements included client and server.
But to get start you you could consider using a library or looking how they approach this problem, some example:
https://github.com/omniscientjs/immstruct
https://www.npmjs.com/package/redux-undo
https://github.com/PowToon/redux-undo-redo
Tutorial with example of todo undo/redo in redux:
https://github.com/reactjs/redux/tree/master/examples/todos-with-undo
Or you could implement your own, as redux you could store all your application state. A simple stack could be a simple and efficient way to store your app state at any given time.
let yourHistory = [state1, state2, state3];
I created a state undo/redo snapshot manager class, which would be great for tracking the change history on an HTML element.
<div id="buttons">
<button type="button" id="undo_btn">Undo</button>
<button type="button" id="redo_btn">Redo</button>
</div>
<br/><br/>
<div id="content">
<label>
Input1:
<input type="text" value="" />
</label>
<br/><br/>
<label>
Input2:
<input type="text" value="" />
</label>
<br/><br/>
<label>
Input3:
<input type="text" value="" />
</label>
<br/><br/>
<label>
Input4:
<input type="text" value="" />
</label>
<br/><br/>
</div>
<script type="text/javascript">
var StateUndoRedo = function() {
var init = function(opts) {
var self = this;
self.opts = opts;
if(typeof(self.opts['undo_disabled']) == 'undefined') {
self.opts['undo_disabled'] = function() {};
}
if(typeof(self.opts['undo_enabled']) == 'undefined') {
self.opts['undo_enabled'] = function() {};
}
if(typeof(self.opts['redo_disabled']) == 'undefined') {
self.opts['redo_disabled'] = function() {};
}
if(typeof(self.opts['redo_enabled']) == 'undefined') {
self.opts['redo_enabled'] = function() {};
}
if(typeof(self.opts['restore']) == 'undefined') {
self.opts['restore'] = function() {};
}
self.opts['undo_disabled']();
self.opts['redo_disabled']();
}
var add = function(state) {
var self = this;
if(typeof(self.states) == 'undefined') {
self.states = [];
}
if(typeof(self.state_index) == 'undefined') {
self.state_index = -1;
}
self.state_index++;
self.states[self.state_index] = state;
self.states.length = self.state_index + 1;
if(self.state_index > 0) {
self.opts['undo_enabled']();
}
self.opts['redo_disabled']();
}
var undo = function() {
var self = this;
if(self.state_index > 0) {
self.state_index--;
if(self.state_index == 0) {
self.opts['undo_disabled']();
} else {
self.opts['undo_enabled']();
}
self.opts['redo_enabled']();
self.opts['restore'](self.states[self.state_index]);
}
}
var redo = function() {
var self = this;
if(self.state_index < self.states.length) {
self.state_index++;
if(self.state_index == self.states.length - 1) {
self.opts['redo_disabled']();
} else {
self.opts['redo_enabled']();
}
self.opts['undo_enabled']();
self.opts['restore'](self.states[self.state_index]);
}
}
var restore = function() {
var self = this;
self.opts['restore'](self.states[self.state_index]);
}
var clear = function() {
var self = this;
self.state_index = 0;
//self.states = [];
}
return {
init: init,
add: add,
undo: undo,
redo: redo,
restore: restore,
clear: clear
};
};
//initialize object
var o = new StateUndoRedo();
o.init({
'undo_disabled': function() {
//make the undo button hidden
document.getElementById("undo_btn").disabled = true;
},
'undo_enabled': function() {
//make the undo button visible
document.getElementById("undo_btn").disabled = false;
},
'redo_disabled': function() {
//make the redo button hidden
document.getElementById("redo_btn").disabled = true;
},
'redo_enabled': function() {
//make the redo button visible
document.getElementById("redo_btn").disabled = false;
},
'restore': function(state) {
//replace the current content with the restored state content
document.getElementById("content").innerHTML = state;
}
});
//initialize first state
o.add(document.getElementById("content").innerHTML);
o.restore();
o.clear();
//bind click events for undo/redo buttons
document.getElementById("undo_btn").addEventListener("click", function() {
o.undo();
});
document.getElementById("redo_btn").addEventListener("click", function() {
o.redo();
});
//bind change events for content element
document.getElementById('content').addEventListener("change", function(event) {
// the following is required since vanilla JS innerHTML
// does not capture user-changed values of inputs
// so we set the attributes explicitly (use jQuery to avoid this)
var elems = document.querySelectorAll("#content input");
for(var i = 0; i < elems.length; i++) {
elems[i].setAttribute("value", elems[i].value);
}
//take a snapshot of the current state of the content element
o.add(document.getElementById("content").innerHTML);
});
</script>
See this JSFiddle: https://jsfiddle.net/up73q4t0/56/

Make my code more modular to run multiple components at one time

I have created a HTTP Response tracker which looks like a Traffic Light. Currently if the response is not a "200" response the traffic light flashes red. There is a request to a specific URL happening every 10 seconds and the traffic light will change accordingly e.g. Green for success, Flashing red for error.
The above works great but I have now ran into a problem where if I wanted to monitor multiple URLS with Multiple traffic lights on the same page I am restricted unfortunately as I can only pass a URL Parameter and it will target all the .cp_trafficLight components.
I need to find a way to structure my code where I can pass an additional argument on the initializer to target specific .cp_trafficLight components.
var requestResponses = {
greenLight: $('.cp_trafficLight_Light--greenDimmed'),
redLight: $('.cp_trafficLight_Light--redDimmed'),
greenBright: 'cp_trafficLight_Light--greenBright',
redBright: 'cp_trafficLight_Light--redBright',
settings: {
flashError: 400,
requestTime: 10000
},
init: function (url) {
requestResponses.url = url;
requestResponses.getResponse(requestResponses.url);
setInterval(function () {
if (requestResponses.errorCode === true) {
requestResponses.redLight.toggleClass(requestResponses.redBright);
}
}, requestResponses.settings.flashError);
},
successResponse: function () {
requestResponses.errorCode = false;
requestResponses.redLight.removeClass(requestResponses.redBright);
requestResponses.greenLight.addClass(requestResponses.greenBright);
},
errorResponse: function () {
requestResponses.greenLight.removeClass(requestResponses.greenBright);
},
getResponse: function (serverURL) {
$.ajax(serverURL, {
success: function () {
requestResponses.errorCode = false;
requestResponses.successResponse();
},
error: function () {
requestResponses.errorCode = true;
requestResponses.errorResponse();
},
complete: function () {
setTimeout(function () {
requestResponses.getResponse(requestResponses.url);
}, requestResponses.settings.requestTime);
}
});
},
errorCode: false
}
requestResponses.init('/status');
https://jsfiddle.net/73tex200/1/
My code is below. Any help would be greatly appreciated!
Make a factory function :
function flashlight(el){
//your code using el, instead of the green/yellow/red static identifiers thing
return init;
}
Now you can do:
var url1=flashlight("test");
url(1"/testurl");
However, you need to modify your flashlight, so that it can be easily appended to an object,through using sth like document.createElement...
Add a second parameter to the init and set it to the html element that contains the lights.
Then, change the greenLight/redLight etc selectors to an object, so you can save multiple versions, one for each set of lights.
Have a function to get the 4 lights inside of this div.
Then, edit all your functions so you can pass the name around and change every peice of code that says requestResponses.colorLight to this.instances[name].colorLight.
Creating a class for this would also be a good solution, but if you don't like working with prototypes, something like this suffices.
This snippet isn't compelte, but it shows the basics of this style of solution.
<html>
<head></head>
<body>
<section>
<div class="container">
<div class="cp_trafficLight" id="one">
<div class="in">
<div class="cp_trafficLight_Light cp_trafficLight_Light--greenDimmed"></div>
<div class="cp_trafficLight_Light cp_trafficLight_Light--redDimmed"></div>
</div>
</div><div class="cp_trafficLight" id="two">
<div class="in">
<div class="cp_trafficLight_Light cp_trafficLight_Light--greenDimmed"></div>
<div class="cp_trafficLight_Light cp_trafficLight_Light--redDimmed"></div>
</div>
</div>
</div>
</section>
<script>
var requestResponses = {
instances : {},
settings : {
flashError: 400,
requestTime: 10000
},
gather : function gather( container, url ) {
var name = container.id;
this.instances[name] = {
callInterval: null,
blinkInterval: null,
url : url,
errorCode: false,
greenLight: $('#' + name + ' .cp_trafficLight_Light--greenDimmed'),
redLight: $('#' + name + ' .cp_trafficLight_Light--redDimmed')
};
return this.instances[name];
},
init : function ( url, container ) {
var instance = this.gather( container, url );
this.getResponse( instance );
},
successResponse : function ( instance ) {
instance.errorCode = false;
instance.redLight.removeClass( 'redBright' );
instance.greenLight.addClass( 'greenBright' );
if (instance.blinkInterval) {
clearInterval( instance.blinkInterval );
}
},
errorResponse : function ( instance ) {
instance.errorCode = true;
instance.greenLight.removeClass( 'greenBright' );
instance.blinkInterval = setInterval( function () {
console.log( instance.redLight );
if (instance.redLight.hasClass( 'redBright' )) {
instance.redLight.removeClass( 'redBright' );
instance.redLight.addClass( 'redDimmed ');
}
else {
instance.redLight.removeClass( 'redDimmed' );
instance.redLight.addClass( 'redBright ');
}
}.bind( instance ), this.settings.flashError );
},
getResponse : function ( instance ) {
var module = this;
setInterval(function() {
$.ajax(instance.url, {
success: function() {
module.successResponse( instance );
},
error: function() {
module.errorResponse( instance );
}
});
}, this.settings.requestTime);
}
}
requestResponses.init('/status1', $('#one') );
requestResponses.init('/status2', $('#two') );
</script>
</body>
</html>

Update sibling state based on another clicked sibling ReactJS

Good afternoon,
I am having trouble changing the state of components which are siblings. Basically they look like this:
<Navigation>
<NavEndpt /> About <--- no
<NavEndpt /> Blog
<NavEndpt /> Projects
</Navigation>
Each <NavEndpt /> is a link that routes to a different 'page' or route on my website unless it is the first one listed, "About". About I want to be a drop down/lightbox feature something pretty to show off my CSS knowledge. So when one of the links is clicked I want it to go where I want; additionally, I'd like it to reflect which link is currently "active" (or not) by both changing it's state.active to true or false and adding an additional class active for CSS purposes. I want to new "active" class to only be present on a <NavEndpt /> that's state.active is true.
Yet everything I have tried so far hasn't worked and I've been at this for two days. I'd appreciate someone who is more experienced with React to show me how to accomplish this.
Here is what I am working with:
var MasterLayout = React.createClass({
mixins: [History],
render: function(){
var childrenWithProps = React.Children.map(this.props.children,
(child) => React.cloneElement(child, {
doSomething: this.doSomething
})
);
return(
<div id="container">
<Navigation activeRoute={this.props.location.pathname} />
<div id="content">
{childrenWithProps}
</div>
</div>
)
}
});
var Navigation = React.createClass({
getInitialState: function(){
var endpoints = require("./data/naviEnd.js");
return {
endpoints: endpoints,
activeRoute: this.props.activeRoute
}
},
renderEndpoints: function(key){
var endpointDetails = this.state.endpoints[key];
return(
<NavEndpt id={endpointDetails.id} key={endpointDetails.title} url={endpointDetails.url} title={endpointDetails.title}/>
)
},
render: function(){
return(
<div id="navigation">
{Object.keys(this.state.endpoints).map(this.renderEndpoints)}
</div>
)
}
});
// Created child not a this.props.child of <Navigation /> component
// as pointed out by #BenHare
var NavEndpt = React.createClass({
handleClick: function(){
this.setState({
active: true
})
},
render: function(){
return (
<div onClick={this.handleClick} className="navLink" id={this.props.id}>
<Link id={this.props.id + "-link"} to={this.props.url}>{this.props.title}</Link>
</div>
)
}
})
Currently this only changes creates and set states for each <NavEndpt /> I tried to make this mess as clean as possible for Stack Overflow.
The best fix I have come up with so far uses a lot of DOM selection and hardcoded if/else statements. It also doesn't light up my "About" component because it doesn't have a url property. That's significant because I have the below solution tied up to the pathname of my entire layout component.
var MasterLayout = React.createClass({
mixins: [History],
render: function(){
var childrenWithProps = React.Children.map(this.props.children,
(child) => React.cloneElement(child, {
doSomething: this.doSomething
})
);
return(
<div id="container">
<Navigation activeRoute={this.props.location.pathname} />
<div id="content">
{childrenWithProps}
</div>
</div>
)
}
});
// This is the parent component that sits on the side or the top depending on the
// broswer size, contains components NavEndpt
var Navigation = React.createClass({
getInitialState: function(){
var endpoints = require("./data/naviEnd.js");
return {
endpoints: endpoints,
activeRoute: this.props.activeRoute
}
},
// Always makes the website's initial view the home route
componentDidMount: function(){
var cover = document.getElementById("cover");
var projects = document.getElementById("projects");
var about = document.getElementById("about");
var active = this.props.activeRoute
this.setActive();
},
// resets the hard coded CSS class
resetClasses: function(){
var active = this.props.activeRoute
var cover = document.getElementById("cover");
var projects = document.getElementById("projects");
var about = document.getElementById("about");
cover.className = "navLink";
projects.className = "navLink";
about.className = "navLink";
},
// checks pathname of <MasterLayout/>
// also somehow makes it so a refresh does not
// return you to "/"
setActive: function(){
var active = this.props.activeRoute
var cover = document.getElementById("cover");
var projects = document.getElementById("projects");
var about = document.getElementById("about");
if (active === "/"){
cover.className += " active";
} else if (active === "/projects"){
projects.className += " active"
} else if (active === "/about"){
about.className += " active"
}
},
// listens for updates, resets active first and sets it
componentDidUpdate: function(){
this.resetClasses();
this.setActive();
},
renderEndpoints: function(key){
var endpointDetails = this.state.endpoints[key];
return(
<NavEndpt id={endpointDetails.id} key={endpointDetails.title} url={endpointDetails.url} title={endpointDetails.title}/>
)
},
render: function(){
return(
<div id="navigation">
{Object.keys(this.state.endpoints).map(this.renderEndpoints)}
</div>
)
}
});
var NavEndpt = React.createClass({
handleClick: function(){
this.setState({
active: true
})
},
render: function(){
return (
<div onClick={this.handleClick} className="navLink" id={this.props.id}>
<Link id={this.props.id + "-link"} to={this.props.url}>{this.props.title}</Link>
</div>
)
}
})

markedjs from React component - stopped capturing the headers

Strange problem with marked executed within my React component.
Has been working, but cannot see why it's stopped.
This is my React component
** #jsx React.DOM */
var React = require('react');
var marked = require('marked');
var Panel = require('react-bootstrap').Panel;
var Highlight = require('highlight.js');
var Markdown = React.createClass({
componentWillMount:function( ){
marked.setOptions({
highlight: function (code) {
return Highlight.highlightAuto(code).value;
},
sanitize: true
});
},
render: function () {
var rawMarkup;
if (_.isString(this.props.content)) {
rawMarkup = marked(this.props.content);
}
else if (_.isArray(this.props.content)) {
rawMarkup = marked(this.props.content.join("\n"));
}
return (
<div>
<span dangerouslySetInnerHTML={{__html: rawMarkup}} />
</div>
);
}
});
exports.Markdown = Markdown;
Tracing it through, the problem appear to lie here:
(this is extract of marked js)
// heading
if (cap = this.rules.heading.exec(src)) {
src = src.substring(cap[0].length);
this.tokens.push({
type: 'heading',
depth: cap[1].length,
text: cap[2]
});
continue;
}
Where this.rules.heading == /^ *(#{1,6}) +([^\n]+?) *#* *(?:\n+|$)/
and src == "#heading"
Nothing is found.
I have updated npm packages, but don't see how this would have stopped working?
Any ideas?

Categories