Delay in Rendering a Component? - javascript

I'm attempting to make my own personal website, and trying to use React to do so. In the process, I intend to make each section a different React Component. My plan is to have the navbar at the top be able to select which component is currently "active", and actually gets rendered and shown. In addition, when switching to a new section, I would like the old component to have a "leaving" animation, and the new component to have an "entering" animation (these are done with react-motion). However, currently both the entering and leaving are done at the same time, because I'm changing the active state for both components at the same time. Is there any way to delay one component becomes active after another one becoming inactive?
The parent component that houses each section looks like so:
class Website extends React.Component {
constructor(props){
super(props)
this.state = {
homeActive: true,
aboutActive: false
}
homeActivator(){
this.setState({
homeActive: true,
aboutActive: false
})
}
aboutActivator(){
this.setState({
homeActive: false,
aboutActive: true
})
}
render(){
return (
<div>
<NavBar handleHome={this.homeActivator.bind(this)} handleAbout=
{this.aboutActivator.bind(this)}/>
<Home active={this.state.homeActive} />
<About active={this.state.aboutActive} />
</div>
}
And then one of the "sections" would look like so:
class Home extends React.Component{
render() {
let content = (
<div>
Home
</div>
)
if (!this.props.active){
return (
//Some jsx that results in the content leaving the page
)
}
return(
//Some jsx that results in the content entering the page
)
}
}

I did not have a ton of time to answer this, but came up with the best example I could. It's not an exact replica of what you are looking to do, but is very similar, so if you understand it, you will be able to figure out your problem quite easily.
To make things a little easier to understand, I am mimicking components with methods placed inside the React Class. Obviously in the real world, you would be importing your components from other files. I'm sure you'll understand what's going on.
export default class Example extends Component {
constructor(props) {
super(props)
this.state = {
c1: true,
c2: false
}
}
// Component One
renderc1() {
return (
<div>
I am component one
</div>
)
}
// Component Two
renderc2() {
return (
<div>
I am component two
</div>
)
}
changeComponents = () => {
this.setState({ c1: false })
setTimeout(() => {
this.setState({ c2: true })
}, 1500)
}
render() {
return (
<div className="example">
{this.state.c1 ? this.renderc1() : null}
{this.state.c2 ? this.renderc2() : null}
<button onClick={this.changeComponents}>Click me</button>
</div>
)
}
}
Clicking the button will fire off the changeComponents function, which will then immediately set the state of "c1" to false. A setTimeout after that ensures that component 2 will be delayed rendering to the screen.
Notice the arrow syntax, I used, which binds the this keyword to the class, so you don't have to worry about writing bind this everywhere.

Related

React render JSX from method

In order to keep the render method of my component shorter and also avoid creating additional components I was hoping to render the HTML from a class method depending on the state like so:
class ExampleComponent extends React.Component {
constructor (props) {
super(props);
this.state = {
step: 'step1'
}
}
handleChangeStep(step) {
this.setState({ step: step })
}
step1 () {
return (
<>
<h2>Step 1</h2>
<div onClick={this.handleChangeStep('step2')}>Next Step</div>
</>
)
}
step2 () {
return (
<>
<h2>Step 2</h2>
<div onClick={this.handleChangeStep('step1')}>Previous Step</div>
</>
)
}
render () {
return this.state.step === 'step2' ? this.step2() : this.step1();
}
}
However this gives the error: Warning: Cannot update during an existing state transition (such as within render). Render methods should be a pure function of props and state..
Seems you can't do this and instead need to do something like (from: https://reactjs.org/docs/conditional-rendering.html)
return this.state.step === 'step2' ? <Step1 /> : <Step2 />
However I don't understand why the former example isn't allowed as those two methods just return JSX so in theory should allow you to return one or the other depending on the state.
The problem has nothing to do with what you're describing. You're trying to update state during a render:
onClick={this.handleChangeStep('step2')}
When this.handleChangeStep('step2') is invoked, state is updated. Updating state triggers a re-render. Which in this case would then update state again, and again, indefinitely.
I suspect you meant to pass a function reference to onClick, not invoke a function:
onClick={() => this.handleChangeStep('step2')}

Manipulating DOM in componentDidMount() without setTimeout

I want to manipulate the DOM in ReactJS in the componentDidMount() method. My problem is that at this time the DOM isn't fully rendered somehow and I need a setTimeout function, which I would rather omit.
When I console.log the scrollHeight of the rendered element in componentDidMount() it gives me a different number as when I wait for let's say 100 milliseconds.
What I want to achieve is to scroll down to the end of an element which is described here How to scroll to bottom in react?
The component is a modal-window which renders {this.props.children} of another component. The modal-window is rendered into the DOM with visibility: hidden and opacity: 0 and it has the height of the window, when it first appears on the page. By clicking on a button it shows up and still has the height of the window until I wait some milliseconds.
I guess, I do something wrong here when setTimeout is needed, but I didn't found out what.
I also tried to change the DOM in the componentDidUpdate() method with the same results.
I wrote this code in the modal-window component:
componentDidMount() {
console.log(document.querySelector('.myModal').scrollHeight);
setTimeout(function() {
console.log(document.querySelector('.myModal').scrollHeight);
}, 100);
}
First console.log gives me for example 497 and the second one something like 952.
Update
I have a modal-window component which renders a child like this for example for my inbox-thread:
<Modal>
<InboxThread />
</Modal>
The problem was, that I needed to wait until the modal-window component rendered its children like this in the Modal.js:
render() {
return (
<React.Fragment>
{this.props.children}
</React.Fragment>
);
}
So my solution in the end was to hand over a method in the props from the parent component where I call the modal to check if componentDidUpdate() in Modal.js.
My code looks now like this in the parent component:
...
export default class InboxThreadList extends React.Component {
constructor(props) {
super(props);
this.scrollToModalBottom = this.scrollToModalBottom.bind(this);
}
render() {
return (
<React.Fragment>
...
<Modal onRender={this.scrollToModalBottom}>
<InboxThread/>
</Modal>
</React.Fragment>
)
}
scrollToModalBottom() {
const myModalObject = document.querySelector('.myModal');
myModalObject.scrollTop = myModalObject.scrollHeight;
}
}
And in the Modal.js:
...
export default class Modal extends React.Component {
...
componentDidUpdate() {
if ('onRender' in this.props) {
this.props.onRender();
}
}
render() {
return (
<div className={'myModal'}>
{this.props.children}
</div>
);
}
I know! I still should work with refs instead of document.querySelector and I will do as described here React - Passing ref from dumb component(child) to smart component(parent).
If you use a ref - as long as the element is always rendered in render() - it is guaranteed to resolve before componentDidMount runs:
componentDidMount() {
// can use any refs here
}
componentDidUpdate() {
// can use any refs here
}
render() {
// as long as those refs were rendered!
return <div ref={/* ... */} />;
}
componentDidMount called BEFORE ref callback
So in your case, you might code it a bit like this:
componentDidMount() {
console.log(this.mymodal.scrollHeight)
}
render() {
return <div className="mymodal" ref={ref => this.mymodal = ref} />
}

componentWillReceiveProps in Vue

I am new pretty new to Vue, and coming from a rather React-y suburb. I am rebuilding my SideNav ("drawer") component from the latter. There, when one clicked the button (not being related to the navigation per se), it setStateed this.state.toggle that was tied to appropriate
class thePage extends React.Component {
...
this.handleToggleClick = this.handleToggleClick.bind(this);
this.state ={
toggleState: false
};
}
// Slide out buttons event handlers
handleToggleClick(){
this.setState({
toggleState: !this.state.toggleState
})
}
render() {
const button = <a href="#" onClick={this.handleToggleClick}>here</a>
const isOpenWithButton = this.state.toggleState;
return (
<div>
{button}
<SideNav logo="logo.png" isOpenWithButton={isOpenWithButton}>
. . .
</SideNav>
</div>
);
}
}
export default SideNavPage;
the SideNav looks as follows:
class SideNav extends React.Component {
constructor(props){
super(props);
this.state = {
isThere: false,
showOverlay: false,
}
this.handleOverlayClick = this.handleOverlayClick.bind(this);
}
componentWillReceiveProps(NextProps) {
if (this.props.isOpenWithButton !== NextProps.isOpenWithButton) {
this.setState({
isThere: true,
showOverlay: true
})
}
}
handleOverlayClick(){
this.setState({
isThere: false,
showOverlay: false
});
}
render() {
const {
tag: Tag,
...
isOpenWithButton,
} = this.props;
let isThere = this.state.isThere;
let showOverlay = this.state.showOverlay;
const overlay = <div class="overlay" onClick={this.handleOverlayClick}></div>
const sidenav = (
<Tag>
<ul>
{logo &&
<li>
<div className="logo-wrapper">
<a href={href}>
<img src={logo} className="img-fluid flex-center d-block"/>
</a>
</div>
</li>
}
{children}
</ul>
</Tag>
);
return (
<div>
{isThere && sidenav}
{showOverlay && overlay}
</div>
);
}
}
export default SideNav;
So, as you can see, clicking the button causes the isOpenWithButton props to change, and whenever it happens (componentWillReceiveProps), the sidenav with overlay appear.
I did some work on porting it to Vue, but as it lacks this lifecycle hook I am stuck with props. I have a following problem: clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change, what necessitates clicking the button twice if the sidenav has been already open. I know I must be missing a vital part in Vue logic, I just cannot grasp which.
Using .sync modifier
What you are looking for is called in vue a .sync modifier.
When a child component mutates a prop that has .sync, the value change will be reflected in the parent.
With this you can achive what you described:
clicking the button opens the overlay, but as you close it with clicking in the overlay, the Boolean prop sent by button does not change
Using a centralised store - (like vuex)
The same could also be achieved if you have a centralised state/store, in this case both of your components could rely on that state property.
See state management on Vue documentation:
Large applications can often grow in complexity, due to multiple pieces of state scattered across many components and the interactions between them
You could simple toogle the same property, for example:
$store.commit('overlayToggle');

Race condition with load event in Javascript

So this is more or less the code
Sorry for the syntax, I typed it from my phone
export default class Main extends React.Component {
componentDidMount() {
axios.get('/user?ID=12345')
.then(function (response) {
if (response){
document.addEventListener('load', () => {/* remove spinner using jquery */});
} else { /* redirect to somewhere else */}
})
}
render() {
return (
<SomeComponent />
);
}
}
I used addEventListener with React because I couldn't find any other way to bind the removal of the loading spinner to the load event.
The issue is that there is a race here, for slow network stations or fast CPUs ones, the load event may be launched long before the request is resolved, which results in the loading spinner to remain.
Is there maybe a way to check if the load event was already lanched?
If I can do that, I'll be able to check it after adding the event listener and in case it was already launched, I'll remove the spinner manually.
I would't use jquery for this task (or at all in react) as you can do it in a more "reactish" way.
You can store the data in your state and conditionally render the component in your render method when the state has changed.
You can't avoid the first render by the way.
Small example:
const Loader = () => <div>Loading...</div>
const MyComponent = ({message}) => <div>{message}</div>
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
message: ''
};
}
componentDidMount(){
// mimic async operation
setTimeout(()=>{
this.setState({message: 'Hi there!'})
}, 1500);
}
render() {
const {message} = this.state;
return (
<div>
{message ? <MyComponent message={message} /> : <Loader />}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Edit
As a followup to your comment:
But then you re-render the entire component just to change the display
style of the preloader element, right?
Not entirely true i think you should read more about Reconciliation and The Diffing Algorithm and look at this example:
React DOM compares the element and its children to the previous one,
and only applies the DOM updates necessary to bring the DOM to the
desired state.

ReactJS keeping a single "Active" state between multiple components

I am attempting to keep with best practices, while adhering to the documentation. Without creating to many one-off methods to handle things for a maintainability standpoint.
Anyway all in all, I am trying to achieve a state between sibling elements that is in sorts an "active" state visually at the least. With something like jQuery I would simply do..
$(document).on('.nav-component', 'click', function(e) {
$('.nav-component').removeClass('active');
$(this).addClass('active');
});
However in react, each component in it of itself is independent of the next and previous, and should remain as such per the documents.
That said, when I am handling a click event for a component I can successfully give it a state of active and inactive, toggling it on and off respectively. But I end up in a place where I have multiple "active" elements when I don't need them as such.
This is for setting up a navigation of sorts. So I want the one in use at the moment to have that active class while the rest won't
I use an app.store with reflux to set state for multiple pages/components. You can do the same passing state up to a common component but using the flux pattern is cleaner.
class AppCtrlRender extends Component {
render() {
let page = this.state.appState.currentPage;
let hideAbout = (page != 'about');
let hideHome = (page != 'home');
return (
<div id='AppCtrlSty' style={AppCtrlSty}>
<div id='allPageSty' style={allPageSty}>
<AboutPage hide={hideAbout} />
<HomePage hide={hideHome} />
</div>
</div>
);
}
}
let getState = function() { return {appState: AppStore.getAppState(),}; };
export default class AppCtrl extends AppCtrlRender {
constructor() {
super();
this.state = getState();
}
componentDidMount = () => { this.unsubscribe = AppStore.listen(this.storeDidChange); }
componentWillUnmount = () => { this.unsubscribe(); }
storeDidChange = () => { this.setState(getState()); }
}
In the page/component check for this.props.hide.
export default class AboutPage extends Component {
render() {
if (this.props.hide) return null;
return (
<div style={AboutPageSty}>
React 1.4 ReFlux used for app state. This is the About Page.
<NavMenu />
</div>
);
}
}
Siblings needing to share some sort of state in React is usually a clue that you need to pull state further up the component hierarchy and have a common parent manage it (or pull it out into a state management solution such as Redux).
For sibling components where only one can be active at a time, the key piece of state you need is something which lets you identify which one is currently active and either:
pass that state to each component as a prop (so the component itself can check if it's currently active - e.g. if each item has an associated id, store the id of the currently active one in a parent component and pass it to each of them as an activeId prop)
e.g.:
var Nav1 = React.createClass({
getInitialState() {
return {activeId: null}
},
handleChange(activeId) {
this.setState({activeId})
},
render() {
return <div className="Nav">
{this.props.items.map(item =>
<NavItem
activeId={this.state.activeId}
item={item}
onClick={this.handleChange}
/>
)}
</div>
}
})
or use it to derive a new prop which is passed to each component (such as an active prop to tell each component whether or not it's currently active - e.g. in the id example above, check the id of each component while rendering it: active={activeId === someObj.id})
e.g.:
var Nav2 = React.createClass({
// ... rest as per Nav1...
render() {
return <div className="Nav">
{this.props.items.map(item =>
<NavItem
active={this.state.activeId === item.id}
item={item}
onClick={this.handleChange}
/>
)}
</div>
}
})
The trick with React is to think of your UI in terms of the state you need to render if from scratch (as if you were rendering on the server), instead of thinking in terms of individual DOM changes needed to make the UI reflect state changes (as in your jQuery example), as React handles making those individual DOM changes for you based on complete renderings from two different states.

Categories

Resources