Reactjs: Fade-in data not working properly - reactjs

I have a button in App Component if i click in this button i get next item in array in the Component but the problem now the Fade-in Transition work only the fist item and not work's for the next item. how can i let Fade-in Transition work for next items?
My code:
import React, { Component } from 'react';
import FadeIn from 'react-fade-in';
class App extends Component {
constructor(props){
super(props);
this.state={indexTeam:0}
}
nextTeam=() => {
this.setState({ indexTeam: (this.state.indexTeam + 1) % teamList.length });
};
render() {
const teams = teamList[this.state.indexTeam];
return (
<div>
<FadeIn><h3>{teams.name}</h3></FadeIn>
<br/>
<button onClick={this.nextTeam}>Next Team</button>
</div>
);
}
}
const teamList = [
{
name: "1- Manchester United"
},
{
name: "2- Fc Barcelona"
},
{
name: "3- Inter Milan"
},
{
name: "4- Liverpool"
}
];
export default App;

Don't use that library. It does exactly that it should, fade in elements one by one when component (page in your case) mounts, but you need your transition on each rerender
If you will look through library that you are using (react-fade-in), you will notice that it reinits it's state on componentDidMount, so it doesn't work when you set state (so, just rerender it, not unmount and mount again).
I didn't come up with any fast solution how to fix or rewrite this library, so just think about yours.
Look through their realization (Which is simply based on css transition) and create your solution.
react-fade-in:
https://github.com/gkaemmer/react-fade-in/blob/master/src/FadeIn.js

Related

How to pass a State to a multilevel component - React

I am building a shopping cart which should have the number of cart items automatically updated on the top right corner. Currently the number comes from localStorage, so the page must be reloaded in order to see it working. I need it updated automatically so I think the State is the best approach, but have no idea how to use in this case.
I have created a State in single item (ProductItem component) which is updated for every action "Add to cart".
The current structure is:
Home (with CartButton) > Products > ProductItem
How can I pass the single State (ProductItem) to the up level (Products), sum up all single components states, then pass it again to up level (Home), then finally pass as a prop to CartButton?
How can I achieve that? Any help would be greatly appreaciated.
Structure
ProductItem.jsx
import PropTypes from 'prop-types';
import React from 'react';
import { Link } from 'react-router-dom';
class ProductsItem extends React.Component {
constructor() {
super();
this.state = {
qtyState: 1,
};
}
addCart = () => {
const {
title,
thumbnail,
price,
id,
available,
} = this.props;
this.setState((prevState) => ({ qtyState: prevState.qtyState + 1 }));
const cart = localStorage.cart ? JSON.parse(localStorage.cart) : [];
cart.push({
title,
thumbnail,
price,
id,
qtd: 1,
available,
});
localStorage.setItem('cart', JSON.stringify(cart));
}
render() {
const {
title,
thumbnail,
price,
id,
} = this.props;
return (
<div>
<p>{title}</p>
<img src={ thumbnail } alt={ title } />
<p>{ `$ ${price}` }</p>
<button type="button" onClick={ this.addCart }>Add to Cart</button>
</div>
);
}
}
ProductsItem.propTypes = {
title: PropTypes.string,
thumbnail: PropTypes.string,
id: PropTypes.string,
price: PropTypes.number,
available: PropTypes.number,
}.isRequired;
export default ProductsItem;
You should use a context to share data between component. Here is one of my answer to another post with a clean example on how to use the context API from React.
There are three ways
(As mentioned by "zillBoy")
React context API
Redux
(One addition)
You can pass callback function from Home to Product and another callback from Product to ProductItem. (passing data from child to parent component - react - via callback function)
Yes, I also had the same kind of problem. You can solve this problem by using any of the following:
React Context API (https://reactjs.org/docs/context.html)
Redux (https://redux-toolkit.js.org/)
I like redux, but context is easy to get started with so you can use that, so whenever you add an item to the cart your context state would update and show the updated state count on the cart icon.

How to know where to add states in react.js

New to react and did not know how to structure a google search for this so decided to ask it here. Was taking a react tutorial and the instructor did this:
#App.js
import React, { Component } from 'react';
import Ninjas from './Ninjas.js'
class App extends Component {
state = {
ninjas : [
{ name: 'Ryu', age:30, belt:'black', id:1 },
{ name: 'Jacy', age:34, belt:'yellow', id:2 },
{ name: 'Ikenna', age:20, belt:'green', id:3 },
{ name: 'Cole', age:50, belt:'red', id:4 }
]
}
render() {
return (
<div className="App">
<p>My First React App</p>
<hr/>
<Ninjas ninjas={ this.state.ninjas } />
</div>
);
}
}
export default App;
#Ninjas.js
import React, { Component } from 'react';
const Ninjas = (props) => {
const { ninjas } = props;
const ninjaList = ninjas.map(ninja => {
return (
<div className="ninja" key={ ninja.id }>
<div>Name: { ninja.name }</div>
<div>Age: { ninja.age }</div>
<div>Belt: { ninja.belt }</div>
<hr/>
</div>
)
})
return(
<div className="ninja-list">
{ ninjaList }
</div>
)
}
export default Ninjas
But then I tried this and it gave the same result:
#App.js
import React, { Component } from 'react';
import Ninjas from './Ninjas.js'
class App extends Component {
render() {
return (
<div className="App">
<p>My First React App</p>
<hr/>
<Ninjas />
</div>
);
}
}
export default App;
#Ninjas.js
class Ninjas extends Component {
state = {
ninjas : [
{ name: 'Ryu', age:30, belt:'black', id:1 },
{ name: 'Jacy', age:34, belt:'yellow', id:2 },
{ name: 'Ikenna', age:20, belt:'green', id:3 },
{ name: 'Cole', age:50, belt:'red', id:4 }
]
}
render() {
const ninjaList = this.state.ninjas.map(ninja => {
return(
<div className="ninja" key={ ninja.id }>
<div>Name: { ninja.name }</div>
<div>Age: { ninja.age }</div>
<div>Belt: { ninja.belt }</div>
<hr/>
</div>
)
})
return (
<div className="ninja-list">
{ ninjaList }
</div>
)
}
}
export default Ninjas
Why did he put the state in the parent App component and not in the nested Ninjas component?
And how do you know when to pass data down as props and not use it as a state in the component that needs the data?
First of all, congratulations on noticing this ;) You're 1 step closer to React Thinking
In your example, it doesn't make a difference whether ninjas state lives in App, or in <Ninjas/> component. It only matters when this app grows more complicated.
Smart Container vs Dumb Component
The tutorial example is building <Ninjas/> as a dumb/presentational component, which is why it did not use class, but was written as a Stateless Functional Component. It is merely used for displaying data in certain way.
But why? Because we might want to reuse <Ninjas/> component with different data set.
In an actual app, most likely you wouldn't hardcode the ninja's data as state. What usually happen is, a smart container (in this case, App) will make API call to backend server to retrieve all the ninja data, then save them as state.
Using the tutorial's structure, you now have the flexibility to:
Pass down ninjas state to other components that might need the data. For example, a <BeltCount/> component that displays the count for each belt color. Not the best example, but the point here is reusability.
<Ninjas> components can be reused as well! Now that it doesn't have any hardcoded state in it, anyone can reuse <Ninjas> by passing down different ninjas props.
In your second example you are passing an undefined state.ninjas it has no effect whatsoever. The reason why your second example works is because you define the state with the props from the first example.
Try to call it like in the first example with const { ninjas } = props and it won't work anymore.
The reason why you would take the first approach is that you can define an arbitrary list of ninjas while in the second one you have always the same ninjas.
I would like to answer the specific part:
how do you know when to pass data down as props and not use it as a state in the component that needs the data?
It probabaly is because the data in the state is being used / manipulated by some other elements as well. Example could be 'sort'/ 'delete' etc.
As a general rule, you should keep your state as local as possible, i.e, where the state data is being used. Think of the concept encapsulation.
Hope that helps.
With the example as is, there isn't any compelling reason for the state to be at the App level. I would expect that as the tutorial progresses and the example gets more complicated (state being changed in some manner and potentially used/displayed by multiple components), that the reasons for the state being where it is will become more clear.
There are two types of components in React: Container Component and Presentation Component.
Container component is the top level component and has the information about state(and other advanced things like Redux store etc.).
Presentation component are only responsible for representing your content.
The instructor has used functional component for your 'Ninjas' class and it accepts props from the top layer. This is the standard practice in React and I would recommend to follow it. As you progress in your learning, you will better understand why only top level component needs to have the knowledge of state. Good luck!

React TransitionGroup and React.cloneElement do not send updated props

I am following Chang Wang's tutorial for making reusable React transitions with HOCs and ReactTransitionGroup(Part 1 Part 2) in conjunction with Huan Ji's tutorial on page transitions (Link).
The problem I am facing is that React.cloneElementdoes not seem to be passing updated props into one of its children, while other children do properly receive updated props.
First, some code:
TransitionContainer.js
TransitionContainer is a container component that is akin to App in Huan Ji's tutorial. It injects a slice of the state to it's children.
The children of the TransitionGroup are all an instance of an HOC called Transition (code further down)
import React from 'react';
import TransitionGroup from 'react-addons-transition-group';
import {connect} from 'react-redux';
class TransitionContainer extends React.Component{
render(){
console.log(this.props.transitionState);
console.log("transitionContainer");
return(
<div>
<TransitionGroup>
{
React.Children.map(this.props.children,
(child) => React.cloneElement(child, //These children are all instances of the Transition HOC
{ key: child.props.route.path + "//" + child.type.displayName,
dispatch: this.props.dispatch,
transitionState: this.props.transitionState
}
)
)
}
</TransitionGroup>
</div>
)
}
}
export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)
Transition.js
Transition is akin to Chang Wang's HOC. It takes some options, defines the componentWillEnter + componentWillLeave hooks, and wraps a component. TransitionContainer (above) injects props.transitionState into this HOC. However, sometimes the props do not update even if state changes (see The Problem below)
import React from 'react';
import getDisplayName from 'react-display-name';
import merge from 'lodash/merge'
import classnames from 'classnames'
import * as actions from './actions/transitions'
export function transition(WrappedComponent, options) {
return class Transition extends React.Component {
static displayName = `Transition(${getDisplayName(WrappedComponent)})`;
constructor(props) {
super(props);
this.state = {
willLeave:false,
willEnter:false,
key: options.key
};
}
componentWillMount(){
this.props.dispatch(actions.registerComponent(this.state.key))
}
componentWillUnmount(){
this.props.dispatch(actions.destroyComponent(this.state.key))
}
resetState(){
this.setState(merge(this.state,{
willLeave: false,
willEnter: false
}));
}
doTransition(callback,optionSlice,willLeave,willEnter){
let {transitionState,dispatch} = this.props;
if(optionSlice.transitionBegin){
optionSlice.transitionBegin(transitionState,dispatch)
}
if(willLeave){
dispatch(actions.willLeave(this.state.key))
}
else if(willEnter){
dispatch(actions.willEnter(this.state.key))
}
this.setState(merge(this.state,{
willLeave: willLeave,
willEnter: willEnter
}));
setTimeout(()=>{
if(optionSlice.transitionComplete){
optionSlice.transitionEnd(transitionState,dispatch);
}
dispatch(actions.transitionComplete(this.state.key))
this.resetState();
callback();
},optionSlice.duration);
}
componentWillLeave(callback){
this.doTransition(callback,options.willLeave,true,false)
}
componentWillEnter(callback){
this.doTransition(callback,options.willEnter,false,true)
}
render() {
console.log(this.props.transitionState);
console.log(this.state.key);
var willEnterClasses = options.willEnter.classNames
var willLeaveClasses = options.willLeave.classNames
var classes = classnames(
{[willEnterClasses] : this.state.willEnter},
{[willLeaveClasses] : this.state.willLeave},
)
return <WrappedComponent animationClasses={classes} {...this.props}/>
}
}
}
options
Options have the following structure:
{
willEnter:{
classNames : "a b c",
duration: 1000,
transitionBegin: (state,dispatch) => {//some custom logic.},
transitionEnd: (state,dispatch) => {//some custom logic.}
// I currently am not passing anything here, but I hope to make this a library
// and am adding the feature to cover any use case that may require it.
},
willLeave:{
classNames : "a b c",
duration: 1000,
transitionBegin: (state,dispatch) => {//some custom logic.},
transitionEnd: (state,dispatch) => {//some custom logic.}
}
}
Transition Lifecycle (onEnter or onLeave)
When the component is mounted, actions.registerComponent is dispatched
componentWillMount
When the component's componentWillLeave or componentWillEnter hook is called, the corresponding slice of the options is sent to doTransition
In doTransition:
The user supplied transitionBegin function is called (optionSlice.transitionBegin)
The default action.willLeave or action.willEnter is dispatched
A timeout is set for the duration of the animation (optionSlice.duration). When the timeout is complete:
The user supplied transitionEnd function is called (optionSlice.transitionEnd)
The default actions.transitionComplete is dispatched
Essentially, optionSlice just allows the user to pass in some options. optionSlice.transitionBegin and optionSlice.transitionEnd are just optional functions that are executed while the animation is going, if that suits a use case. I'm not passing anything in currently for my components, but I hope to make this a library soon, so I'm just covering my bases.
Why Am I tracking transition states anyway?
Depending on the element that is entering, the exiting animation changes, and vice versa.
For example, in the image above, when the blue enters, red moves right, and when the blue exits, red moves left. However when the green enters, red moves left and when the green exits, red moves right. To control this is why I need to know the state of current transitions.
The Problem:
The TransitionGroup contains two elements, one entering, one exiting (controlled by react-router). It passes a prop called transitionState to its children. The Transition HOC (children of TransitionGroup) dispatches certain redux actions through the course of an animation. The Transition component that is entering receives the props change as expected, but the component that is exiting is frozen. It's props do not change.
It is always the one that is exiting that does not receive updated props. I have tried switching the wrapped components (exiting and entering), and the issues is not due to the wrapped components.
Images
On-Screen Transition:
Transition in React DOM
The exiting component Transition(Connect(Home))), in this case, is not receiving updated props.
Any ideas why this is the case? Thanks in advance for all the help.
Update 1:
import React from 'react';
import TransitionGroup from 'react-addons-transition-group';
import {connect} from 'react-redux';
var childFactoryMaker = (transitionState,dispatch) => (child) => {
console.log(child)
return React.cloneElement(child, {
key: (child.props.route.path + "//" + child.type.displayName),
transitionState: transitionState,
dispatch: dispatch
})
}
class TransitionContainer extends React.Component{
render(){
let{
transitionState,
dispatch,
children
} = this.props
return(
<div>
<TransitionGroup childFactory={childFactoryMaker(transitionState,dispatch)}>
{
children
}
</TransitionGroup>
</div>
)
}
}
export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)
I've updated my TransitionContainer to the above. Now, the componentWillEnter and componentWillLeave hooks are not being called. I logged the React.cloneElement(child, {...}) in the childFactory function, and the hooks (as well as my defined functions like doTransition) are present in the prototype attribute. Only constructor, componentWillMount and componentWillUnmount are called. I suspect this is because the key prop is not being injected through React.cloneElement. transitionState and dispatch are being injected though.
Update 2:
import React from 'react';
import TransitionGroup from 'react-addons-transition-group';
import {connect} from 'react-redux';
var childFactoryMaker = (transitionState,dispatch) => (child) => {
console.log(React.cloneElement(child, {
transitionState: transitionState,
dispatch: dispatch
}));
return React.cloneElement(child, {
key: (child.props.route.path + "//" + child.type.displayName),
transitionState: transitionState,
dispatch: dispatch
})
}
class TransitionContainer extends React.Component{
render(){
let{
transitionState,
dispatch,
children
} = this.props
return(
<div>
<TransitionGroup childFactory={childFactoryMaker(transitionState,dispatch)}>
{
React.Children.map(this.props.children,
(child) => React.cloneElement(child, //These children are all instances of the Transition HOC
{ key: child.props.route.path + "//" + child.type.displayName}
)
)
}
</TransitionGroup>
</div>
)
}
}
export default connect((state)=>({transitionState:state.transitions}),(dispatch)=>({dispatch:dispatch}))(TransitionContainer)
After further inspection of the TransitionGroup source, I realized that I put the key in the wrong place. All is well now. Thanks so much for the help!!
Determining Entering and Leaving Children
Imagine rendering the sample JSX below:
<TransitionGroup>
<div key="one">Foo</div>
<div key="two">Bar</div>
</TransitionGroup>
The <TransitionGroup>'s children prop would be made up of the elements:
[
{ type: 'div', props: { key: 'one', children: 'Foo' }},
{ type: 'div', props: { key: 'two', children: 'Bar' }}
]
The above elements will be stored as state.children. Then, we update the <TransitionGroup> to:
<TransitionGroup>
<div key="two">Bar</div>
<div key="three">Baz</div>
</TransitionGroup>
When componentWillReceiveProps is called, its nextProps.children will be:
[
{ type: 'div', props: { key: 'two', children: 'Bar' }},
{ type: 'div', props: { key: 'three', children: 'Baz' }}
]
Comparing state.children and nextProps.children, we can determine that:
1.
{ type: 'div', props: { key: 'one', children: 'Foo' }} is leaving
2.
{ type: 'div', props: { key: 'three', children: 'Baz' }} is entering.
In a regular React application, this means that <div>Foo</div> would no longer be rendered, but that is not the case for the children of a <TransitionGroup>.
How <TransitionGroup> Works
So how exactly is <TransitionGroup> able to continue rendering components that no longer exist in props.children?
What <TransitionGroup> does is that it maintains a children array in its state. Whenever the <TransitionGroup> receives new props, this array is updated by merging the current state.children and the nextProps.children. (The initial array is created in the constructor using the initial children prop).
Now, when the <TransitionGroup> renders, it renders every child in the state.children array. After it has rendered, it calls performEnter and performLeave on any entering or leaving children. This in turn will perform the transitioning methods of the components.
After a leaving component's componentWillLeave method (if it has one) has finished executing, it will remove itself from the state.children array so that it no longer renders (assuming it didn't re-enter while it was leaving).
Passing Props to Leaving Children?
Now the question is, why aren't updated props being passed to the leaving element? Well, how would it receive props? Props are passed from a parent component to a child component. If you look at the example JSX above, you can see that the leaving element is in a detached state. It has no parent and it is only rendered because the <TransitionGroup> is storing it in its state.
When you are attempting to inject the state to the children of your <TransitionGroup> through React.cloneElement, the leaving component is not one of those children.
The Good News
You can pass a childFactory prop to your <TransitionGroup>. The default childFactory just returns the child, but you can take a look at the <CSSTransitionGroup> for a more advanced child factory.
You can inject the correct props into the children (even the leaving ones) through this child wrapper.
function childFactory(child) {
return React.cloneElement(child, {
transitionState,
dispatch
})
}
Usage:
var ConnectedTransitionGroup = connect(
store => ({
transitionState: state.transitions
}),
dispatch => ({ dispatch })
)(TransitionGroup)
render() {
return (
<ConnectedTransitionGroup childFactory={childFactory}>
{children}
</ConnectedTransitionGroup>
)
}
React Transition Group was somewhat recently split out of the main React repo and you can view its source code here. It is pretty straightforward to read through.

ReactJS - Add custom event listener to component

In plain old HTML I have the DIV
<div class="movie" id="my_movie">
and the following javascript code
var myMovie = document.getElementById('my_movie');
myMovie.addEventListener('nv-enter', function (event) {
console.log('change scope');
});
Now I have a React Component, inside this component, in the render method, I am returning my div. How can I add an event listener for my custom event? (I am using this library for TV apps - navigation )
import React, { Component } from 'react';
class MovieItem extends Component {
render() {
if(this.props.index === 0) {
return (
<div aria-nv-el aria-nv-el-current className="menu_item nv-default">
<div className="indicator selected"></div>
<div className="category">
<span className="title">{this.props.movieItem.caption.toUpperCase()}</span>
</div>
</div>
);
}
else {
return (
<div aria-nv-el className="menu_item nv-default">
<div className="indicator selected"></div>
<div className="category">
<span className="title">{this.props.movieItem.caption.toUpperCase()}</span>
</div>
</div>
);
}
}
}
export default MovieItem;
Update #1:
I applied all the ideas provided in the answers. I set the navigation library to debug mode and I am able to navigate on my menu items only based on the keyboard (as you can see in the screenshot I was able to navigate to Movies 4) but when I focus an item in the menu or press enter, I dont see anything in the console.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MenuItem extends Component {
constructor(props) {
super(props);
// Pre-bind your event handler, or define it as a fat arrow in ES7/TS
this.handleNVFocus = this.handleNVFocus.bind(this);
this.handleNVEnter = this.handleNVEnter.bind(this);
this.handleNVRight = this.handleNVRight.bind(this);
}
handleNVFocus = event => {
console.log('Focused: ' + this.props.menuItem.caption.toUpperCase());
}
handleNVEnter = event => {
console.log('Enter: ' + this.props.menuItem.caption.toUpperCase());
}
handleNVRight = event => {
console.log('Right: ' + this.props.menuItem.caption.toUpperCase());
}
componentDidMount() {
ReactDOM.findDOMNode(this).addEventListener('nv-focus', this.handleNVFocus);
ReactDOM.findDOMNode(this).addEventListener('nv-enter', this.handleNVEnter);
ReactDOM.findDOMNode(this).addEventListener('nv-right', this.handleNVEnter);
//this.refs.nv.addEventListener('nv-focus', this.handleNVFocus);
//this.refs.nv.addEventListener('nv-enter', this.handleNVEnter);
//this.refs.nv.addEventListener('nv-right', this.handleNVEnter);
}
componentWillUnmount() {
ReactDOM.findDOMNode(this).removeEventListener('nv-focus', this.handleNVFocus);
ReactDOM.findDOMNode(this).removeEventListener('nv-enter', this.handleNVEnter);
ReactDOM.findDOMNode(this).removeEventListener('nv-right', this.handleNVRight);
//this.refs.nv.removeEventListener('nv-focus', this.handleNVFocus);
//this.refs.nv.removeEventListener('nv-enter', this.handleNVEnter);
//this.refs.nv.removeEventListener('nv-right', this.handleNVEnter);
}
render() {
var attrs = this.props.index === 0 ? {"aria-nv-el-current": true} : {};
return (
<div ref="nv" aria-nv-el {...attrs} className="menu_item nv-default">
<div className="indicator selected"></div>
<div className="category">
<span className="title">{this.props.menuItem.caption.toUpperCase()}</span>
</div>
</div>
)
}
}
export default MenuItem;
I left some lines commented because in both cases I am not able to get the console lines to be logged.
Update #2: This navigation library does not work well with React with its original Html Tags, so I had to set the options and rename the tags to use aria-* so it would not impact React.
navigation.setOption('prefix','aria-nv-el');
navigation.setOption('attrScope','aria-nv-scope');
navigation.setOption('attrScopeFOV','aria-nv-scope-fov');
navigation.setOption('attrScopeCurrent','aria-nv-scope-current');
navigation.setOption('attrElement','aria-nv-el');
navigation.setOption('attrElementFOV','aria-nv-el-fov');
navigation.setOption('attrElementCurrent','aria-nv-el-current');
If you need to handle DOM events not already provided by React you have to add DOM listeners after the component is mounted:
Update: Between React 13, 14, and 15 changes were made to the API that affect my answer. Below is the latest way using React 15 and ES7. See answer history for older versions.
class MovieItem extends React.Component {
componentDidMount() {
// When the component is mounted, add your DOM listener to the "nv" elem.
// (The "nv" elem is assigned in the render function.)
this.nv.addEventListener("nv-enter", this.handleNvEnter);
}
componentWillUnmount() {
// Make sure to remove the DOM listener when the component is unmounted.
this.nv.removeEventListener("nv-enter", this.handleNvEnter);
}
// Use a class arrow function (ES7) for the handler. In ES6 you could bind()
// a handler in the constructor.
handleNvEnter = (event) => {
console.log("Nv Enter:", event);
}
render() {
// Here we render a single <div> and toggle the "aria-nv-el-current" attribute
// using the attribute spread operator. This way only a single <div>
// is ever mounted and we don't have to worry about adding/removing
// a DOM listener every time the current index changes. The attrs
// are "spread" onto the <div> in the render function: {...attrs}
const attrs = this.props.index === 0 ? {"aria-nv-el-current": true} : {};
// Finally, render the div using a "ref" callback which assigns the mounted
// elem to a class property "nv" used to add the DOM listener to.
return (
<div ref={elem => this.nv = elem} aria-nv-el {...attrs} className="menu_item nv-default">
...
</div>
);
}
}
Example on Codepen.io
You could use componentDidMount and componentWillUnmount methods:
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MovieItem extends Component
{
_handleNVEvent = event => {
...
};
componentDidMount() {
ReactDOM.findDOMNode(this).addEventListener('nv-event', this._handleNVEvent);
}
componentWillUnmount() {
ReactDOM.findDOMNode(this).removeEventListener('nv-event', this._handleNVEvent);
}
[...]
}
export default MovieItem;
First off, custom events don't play well with React components natively. So you cant just say <div onMyCustomEvent={something}> in the render function, and have to think around the problem.
Secondly, after taking a peek at the documentation for the library you're using, the event is actually fired on document.body, so even if it did work, your event handler would never trigger.
Instead, inside componentDidMount somewhere in your application, you can listen to nv-enter by adding
document.body.addEventListener('nv-enter', function (event) {
// logic
});
Then, inside the callback function, hit a function that changes the state of the component, or whatever you want to do.
I recommend using React.createRef() and ref=this.elementRef to get the DOM element reference instead of ReactDOM.findDOMNode(this). This way you can get the reference to the DOM element as an instance variable.
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
class MenuItem extends Component {
constructor(props) {
super(props);
this.elementRef = React.createRef();
}
handleNVFocus = event => {
console.log('Focused: ' + this.props.menuItem.caption.toUpperCase());
}
componentDidMount() {
this.elementRef.addEventListener('nv-focus', this.handleNVFocus);
}
componentWillUnmount() {
this.elementRef.removeEventListener('nv-focus', this.handleNVFocus);
}
render() {
return (
<element ref={this.elementRef} />
)
}
}
export default MenuItem;
Here is a dannyjolie more detailed answer without need of component reference but using document.body reference.
First somewhere in your app, there is a component method that will create a new custom event and send it.
For example, your customer switch lang.
In this case, you can attach to the document body a new event :
setLang(newLang) {
// lang business logic here
// then throw a new custom event attached to the body :
document.body.dispatchEvent(new CustomEvent("my-set-lang", {detail: { newLang }}));
}
Once that done, you have another component that will need to listen to the lang switch event. For example, your customer is on a given product, and you will refresh the product having new lang as argument.
First add/remove event listener for your target component :
componentDidMount() {
document.body.addEventListener('my-set-lang', this.handleLangChange.bind(this));
}
componentWillUnmount() {
document.body.removeEventListener('my-set-lang', this.handleLangChange.bind(this));
}
then define your component my-set-langw handler
handleLangChange(event) {
console.log("lang has changed to", event.detail.newLang);
// your business logic here .. this.setState({...});
}

How to get the state of a React app?

So, I've built up this React app with some state. I want to know what that state is, so I can save it to localStorage and let state carry from one session to another. Basically, the app is pretty complex and I don't want people to lose their "place" just because the closed it and opened it again later.
Reading through the React docs though, I don't see anything that references accessing a component's state from outside of React.
Is this possible?
You should never ever try to get a state from a component as a component should always be representing and not generate state on its own. Instead of asking for the component's state, ask for the state itself.
That being said, I definitely see where you're coming from. When talking about React, the term "state" seems to be pretty ambiguous indeed.
I don't see anything that references accessing a component's state
from outside of React.
Once and for all, here's the difference:
Local state, shouldn't persist: this.state, this.setState et al. Local state lives only within the component and will die once the component dies.
Global state, can be persisted: this.props.passedState. Global state is only passed to the component, it can not directly modify it. The view layer will adjust to whatever global state it got passed.
Or in simple:
this.state means local state, won't get persisted.
this.props.state means passed state, could be persisted, we just don't know and we don't care.
Example
The following example uses stuff of Babel's stage-1 Preset
React is all about giving a display representation of a data structure. Let's assume we have the following object to visibly represent:
let fixtures = {
people: [
{
name: "Lukas"
},
{
name: "fnsjdnfksjdb"
}
]
}
We have an App component in place, that renders Person components for every entry in the people array.
import React, { Component, PropTypes } from 'react';
import ReactDOM from 'react-dom';
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired
}
render() {
return (
<li>
<input type="text" value={this.props.name} />
</li>
);
}
}
class App extends Component {
static propTypes = {
people: PropTypes.array.isRequired
}
render() {
let people = this.people.map(person => {
<Person name={person.name} />
});
return (
<ul>
{people}
</ul>
);
}
}
ReactDOM.render(
<App people={fixtures} />,
document.getElementById('yourid')
);
Now, we will implement focus functionality. There are 2 options:
We don't care about which person was focused the last time the user used the app, so we use local state.
We do care about which person was focused the last time the user used the app, so we use global state.
Option 1
The task is simple. Just adjust the People component so that it knows (and rerenders) once the focus changed. The original data structure won't be changed and the information whether a component is focused or not is
merely client side information that will be lost once the client is closed/reset/whatever.
State is local: We use component.setState to dispatch changes to local state
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired
}
constructor(...args) {
super(...args);
this.state = {
isFocused: false
}
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
static propTypes = {
name: PropTypes.string.isRequired
}
onFocus() {
this.setState({
isFocused: true;
});
}
onBlur() {
this.setState({
isFocused: false;
});
}
render() {
let borderColor = this.state.isFocused ? '#ff0' : '#000';
style = {
border: `1px solid ${borderColor}`
}
return (
<li>
<input
style={style}
type="text"
value={this.props.name}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</li>
);
}
}
Option 2
We actually want to persist the focused element to whatever store (eg. backend) we have, because we care about the last state. State is global: React components receive only props as "state", even granular information as whether an element is focused. Persist and feed global state to the app and it will behave accordingly.
function setFocus(index) {
fixtures.people[index].isFocused = true;
render();
}
function clearFocus(index) {
fixtures.people[index].isFocused = false;
render();
}
function render() {
ReactDOM.render(
<App people={fixtures} />,
document.getElementById('yourid')
);
}
class Person extends Component {
static propTypes = {
name: PropTypes.string.isRequired,
isFocused: PropTypes.bool,
index: PropTypes.number.isRequired
}
static defaultProps = {
isFocused: false
}
constructor(...args) {
super(...args);
this.onFocus = this.onFocus.bind(this);
this.onBlur = this.onBlur.bind(this);
}
static propTypes = {
name: PropTypes.string.isRequired
}
onFocus() {
setFocus(this.props.index);
}
onBlur() {
clearFocus(this.props.index);
}
render() {
let borderColor = this.props.isFocused ? '#ff0' : '#000';
style = {
border: `1px solid ${borderColor}`
}
return (
<li>
<input
style={style}
type="text"
value={this.props.name}
onFocus={this.onFocus}
onBlur={this.onBlur}
/>
</li>
);
}
}
class App extends Component {
static propTypes = {
people: PropTypes.array.isRequired
}
render() {
let people = this.people.map((person, index) => {
<Person name={person.name} index={index} isFocused={person.isFocused} />
});
return (
<ul>
{people}
</ul>
);
}
}
render();
I think the solution to your problem really lies in how your application is modelled.
Ideally what you would need (depending on complexity) would be a single (flux/redux) store upon which you could subscribe to changes, if it diffs then save it to localStorage.
You would then need to determine a way to bootstrap this data into your single store.
Their is no API per se (that I know of) to do specifically what you want.
Don't try to get the state from outside of React -- pass the state from React to wherever it needs to be.
In your case, I would do this componentDidUpdate.
var SampleRootComponent = React.createClass({
componentDidUpdate: function() {
localStorage.setItem(JSON.stringify(this.state))
}
})
You're right in that there is no way to get the state from outside; you have to set it from React. But there is no reason to view this as a bad thing -- just call an external function from inside of React if you need to pass the info to something else, as shown. No functionality is lost.

Resources