React toggle class + CSS transitions, not working... why? - javascript

I've been using classes to control open/close behaviors w/ a CSS transition for effect. I've used this on other components, no problem, but for some reason the same method is failing me in this scenario...
The open/close behaviors attach (I see the end difference w/ background color and translateY) but the CSS transition itself is lost... any ideas why I lose my CSS transition but everything else is working as expected?
Note, when I manually toggle the open/closed classes using Developer Tools, it works just fine! The CSS transition picks up!
So what's up with the React on click to toggle a class applying, but losing the CSS transition?
class Projects extends React.Component {
/* constructor, etc... */
render() {
return (
<div className="projects-nav-container">
<div className="center title monospace" onClick={this.props._toggleProjectNav} id="Menu">Menu</div>
<ul className={`projects-nav ${this.props._isProjectNavOpen ? 'open' : 'closed'}`}>
{ PROJECTS.map((project, index) =>
<li key={index} >
<p>project here</p>
</li>
) }
</ul>
</div>
);
}
}
App.js looks as such:
class App extends React.Component {
constructor() {
super();
this.state = {
_isProjectNavOpen: true
}
this._toggleProjectNav = this._toggleProjectNav.bind(this);
}
_toggleProjectNav() {
this.setState(prevState => ({
_isProjectNavOpen: !prevState._isProjectNavOpen,
}));
}
render() {
<div>
<Router>
<Route path="/projects" component={(props, state, params) =>
<Projects
_toggleProjectNav={this._toggleProjectNav}
_isProjectNavOpen={this.state._isProjectNavOpen}
{...props} />} />
</Router>
</div>
}
}
SCSS:
.projects-nav {
#include transition(all $transition_speed ease);
&.open {
#include transform(translateY(0));
background: red
}
&.closed {
#include transform(translateY(-100vh));
background: green;
}
}

It is because of react-router think of each route as a case in the switch statement, and the path in the <Route /> component being a key for that case. When the path gets changed the component is unmounted completely. Hence you don't see the CSS transitions because the DOM for it doesn't exist anymore.
If you want to animate with react-router. You need to use a react utility library called react-transition-group. Here is a detailed example by the author of react-router which you can follow. React Router Animation Example
I hope this helps.
Also there is this great talk on youtube for about 30 minutes that talks about how to do really nice animations in react with routing https://www.youtube.com/watch?v=S3u-ccn4PEM Cheers :)

Indeed, the problem is that react-router is unmounting your component and mounting it again with the new classes, losing the CSS transition in the process. To solve this issue, simply use render instead of component on the <Route> component.
As to why this works, from react-router documentation:
Instead of having a new React element created for you using the component prop, you can pass in a function to be called when the location matches. The render prop receives all the same route props as the component render prop.
For a more detailed explanation, you could read the question react router difference between component and render.
In summary, App.js should look like this:
class App extends React.Component {
constructor() {
super();
this.state = {
_isProjectNavOpen: true
}
this._toggleProjectNav = this._toggleProjectNav.bind(this);
}
_toggleProjectNav() {
this.setState(prevState => ({
_isProjectNavOpen: !prevState._isProjectNavOpen,
}));
}
render() {
<div>
<Router>
<Route path="/projects" render={(props, state, params) =>
<Projects
_toggleProjectNav={this._toggleProjectNav}
_isProjectNavOpen={this.state._isProjectNavOpen}
{...props} />} />
</Router>
</div>
}
}
I created a CodeSandbox using render and it seems to work properly!
Cheers!

Change key have to update element.
Try this code:
class App extends React.Component {
constructor() {
super();
this.state = {
_isProjectNavOpen: true,
_ProjectsKey: 1,
_RouteKey: 1
}
this._toggleProjectNav = this._toggleProjectNav.bind(this);
}
_toggleProjectNav() {
this.setState(prevState => ({
_isProjectNavOpen: !prevState._isProjectNavOpen,
_ProjectsKey: prevState._ProjectsKey + 1,
_RouteKey: prevState._RouteKey + 1
}));
}
render() {
<div>
<Router>
<Route key={this.state._RouteKey} path="/projects" component={(props, state, params) =>
<Projects
_toggleProjectNav={this._toggleProjectNav}
_isProjectNavOpen={this.state._isProjectNavOpen}
key={this.state._ProjectsKey}
{...props} />} />
</Router>
</div>
}
}

Related

In React JavaScript, How to call a class inside another class?

I began to learn JavaScript and React these days, I tried to draw some grids in a website and met a problem like this:
Everything works fine when I code like this:
export default class PathfindingVisualizer extends Component {
constructor(props) {
super(props);
this.state = {
nodes: [],
};
}
componentDidMount() {
const nodes = getInitialGrid();
this.setState({ nodes });
}
render() {
const { nodes } = this.state;
console.log(nodes);
return (
<>
<p style={{ fontSize: 40 }}>Visualize Algorithms</p>
<br />
<br />
<div className="node-container">{nodes}</div> // HERE WORKS FINE
</>
);
}
}
And the website turned out to be like this which is fine:
But when I changed the code like this:
render() {
const { nodes } = this.state;
console.log(nodes);
return (
<>
<p style={{ fontSize: 40 }}>Visualize Algorithms</p>
<br />
<br />
<NodeContainer>{nodes}</NodeContainer> // HERE
</>
);
}
}
The grids just disappear, and nothing in <body>:
Could anybody help me? I can't figure out why this is happening.
Class NodeContainer and Node are like this:
export default class NodeContainer extends Component {
render() {
return <div className="node-container"></div>;
}
}
export default class Node extends Component {
render() {
return <div className="node-item"></div>;
}
}
Hey, thank you guys for the answers:) this is my first time to ask a question here. I solved the problem by adding {this.props.xxxxx} as you said and it works.
Corrected codes as following:
...
<br />
<br />
<NodeContainer nodes={nodes}></NodeContainer> // HERE
</>
...
the NodeContainer class:
export default class NodeContainer extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return <div className="node-container">{this.props.nodes}</div>; //HERE
}
}
I didn't use 'this.props.children', but will check out later. I skipped the basic tutorial so I didn't understand how to pass params to class, I checked this video to help myself quickly understand this:
https://www.youtube.com/watch?v=ICmMVfKjEuo&list=PLN3n1USn4xlntqksY83W3997mmQPrUmqM&index=5&t=0s
For this you need to call children inprops
export default class NodeContainer extends Component {
render() {
return <div className="node-container">{this.props.children}</div>;
}
}
I don't see where the Node class is being referenced so I'm not sure that's relevant.
Your issue is that the your passing the nodes component to the NodeContainer component, but not rendering it in NodeContainer. You should look into how props are passed to components - they appear as this.props.children on the component. Your code should look like this.
export default class NodeContainer extends Component {
render() {
return <div className="node-container">{this.props.children}</div>;
}
}
If you're wondering how nodes appears as this.props.children, it's because of how React treats components. You can achieve the same thing by passing it into children explicitly as a prop.
Dude, in reactJS, there's should be data to be pass from your Parent element to your Children element.
In your case to be able to show the data you want,
you need to pass your state from the <PathFindingVisualizer /> to your <NodeContainer />, which you have done it by using node as a children between <NodeContainer /> tag. And you forget the second step,
You need to access the data you have passed inside <NodeContainer /> class you made. How? just access it by using this.props.children.
here's the example.
export default class NodeContainer extends Component {
render() {
return <div className="node-container">{this.props.children}</div>
}
}
Problem solved.
as a reference see this. https://learn.co/lessons/react-this-props-children

Passing callback to children, to update a different child

I am trying to create a Navbar (component), which changes slightly when a user logs in in a SignIn component. So, my app looks like this:
I have a state in App, with authenticated set to false as default. I then have a function, updatedAuthenticationEvent, which sets the value of the app state.
Ihave a NavBar component, which I pass in the value of Authenticated. The idea is that I change something on the navbar, if authenticated. That would be, dont show "Signin" and rather show "Sign Out". So when I sign in on my sign in component, the navbar changes.
I then have a few routes in my app, one of which is my Signin, which I attempt to pass my function to, as a callback.
export default class App extends React.Component {
constructor(props)
{
super(props);
this.state = {
authenticated: false
}
}
updatedAuthenticationEvent(isAthenticated)
{
console.log('Called!');
this.setState({authenticated: isAthenticated});
}
render() {
return (
<div>
<Router>
<div>
<Navbar authenticated={this.state.authenticated} />
<Route exact path='/' component={Home} />
<Route path="/login" component={Login} changeState={this.updatedAuthenticationEvent} />
</div>
</Router>
<Footer />
</div>
)
}
}
My SignIn component, actually wraps my sign in content.
But for now, I'm just trying to make something work.
So for now, all I am doing is that when the sign in component loads, set the Authenticated to true.
My goal though is to pass that prop to my LoginBox, which has all the logic, and my actual sign in method. But for now, I want this to work.
export default class SignIn extends Component {
constructor(props)
{
super(props);
}
componentWillMount()
{
this.props.changeState(true);
}
render() {
return (
<div className="row justify-content-md-center">
<div className="col-lg-4">
<LoginBox changeState={this.props.changeState} />
</div>
</div>
)
}
}
I get the error:
TypeError: this.props.changeState is not a function
What do I need to change to achieve this? Signin box event, should effect the Navbar.
well, I will not recommend this, but here it is,
use the render prop from Router, so you can pass the callback for changeState directly
<Route path="/login" render={(props) => <Login {...props} changeState={this.updatedAuthenticationEvent} />
then I would recommend change componentWillMount for componentDidMount in your Login Component
and finally
Use an arrow function for your updatedAuthenticationEvent callback like
updatedAuthenticationEvent = (isAthenticated) => {
....
}
So you do not have context problems when executing changeState
working demo

Setup react route to use URL parameters and props' function

I've got a parent component with react-router, setup like this :
constructor(props){
super(props);
this.state = {
diner: false
};
this.updateFromInvite = this.updateFromInvite.bind(this);
}
updateFromInvite(Souper) {
this.setState({diner: Souper});
}
I can't figure out how to setup the route to have both URL parameters and be able to pass a function to update the parent's state from the children component...
<Route path="/Invitation/:NomParam1?/:NomParam2?"
component = {() => (<Invitation updateApp = {this.updateFromInvite} />)} />
I think it's the closest I got...
From children's component :
class Invite extends Component {
constructor(props){
super(props);
this.state = {
diner: this.props.match.params.NomParam1 ,
JSONInfo: this.props.match.params.NomParam2
};
}
componentDidMount() {
const { diner } = this.state;
const { JSONInfo } = this.state;
const { updateApp } = this.props;
updateApp(diner);
}
render() {
return (
<div className="Invite">
<div className="col-centered">
<VidPlay/>
</div>
</div>
);
}
}
export default Invite;
The component property of the route takes a component Class, not an instance of the component. I believe you are looking to use the render property, which takes a rendered component. Your visual component shouldn't be concerned with the routing details, so you can pass that in in the Route configuration like so:
<Route path="/Invitation/:NomParam1?/:NomParam2?"
render={({match}) => (
<Invitation
updateApp={this.updateFromInvite}
diner={match.params.NomParam1}
JSONInfo={match.params.NomParam2}
/>
)}
/>
Then, in the component, don't utilize state, as that's not really what it is for:
class Invite extends Component {
componentDidMount() {
const { diner, JSONInfo, updateApp } = this.props;
// Not exactly sure what is going on here... how you
// will use JSONInfo, etc
updateApp(diner);
}
render() {
return (
<div className="Invite">
<div className="col-centered">
<VidPlay/>
</div>
</div>
);
}
}
Also, I'm not exactly sure what the parent component is doing, and why it is passing both the route params and the function down to the child, only to have the child call it back... but that is probably out of the scope of the question.
Enjoy!
If finally got it (thanks to that answer and the official documentation):
I needed to add props as parameter of my render and
use it with {...props} inside the children element!
<Route path="/Invitation/:NomParam1?/:NomParam2?"
render={ (props) =>
(<Invitation updateApp = {this.updateFromInvite} {...props} />)
}
/>
With that, I have access to BOTH :
my custom props
generic props (match, location and history)

Sharing props/state between two components in react

So I have a global nav bar component that sits at the home screen and app screen and a music playing component. On click of one of the items in the nav bar I want to mute something on the music component.
Currently, to mute the music etc I'm using state.
So the way I've got this setup is to pass through an object as props and set that as state like so:
const obj = {
playing: false,
toggleButtonText: 'Play',
muteActive: false,
};
And I pass this as props into my components:
<Router>
<div>
<Nav stateVal={obj} />
<Route exact path="/" render={() => <Start />} />
<Route path="/app" render={() => <App stateVal={obj} />} />
<Modal />
</div>
</Router>
Then in each of my components, I do:
constructor(props) {
super(props);
this.state = this.props.stateVal;
}
So the props are set as the state of the component.
My problem is that I want one component to update the props and the update the state of the other component but I have no idea how I'm going to do that?
Could anyone give me a bit of help or pointers?
Assigning props to state in constructor is an anti-pattern because if the props change later on then the state isn't going to change.
Have the component update the props of the parent and then pass the props down the other child.
If you can't do this for some reason then you should look into Redux, Flux or MobX to handle the state.
Example
class Parent extends React.Component {
setMusicActive = (muteActive) => {
this.setState({ muteActive });
}
<ChildOne muteActive={this.state.muteActive} setMusicActive={this.setMuteActive} />
<ChildTwo muteActive={this.state.muteActive} setMusicActive={this.setMuteActive} />
}
class ChildOne extends React.Component {
someOtherFunction = () => {
this.props.setMuteActive(!this.props.muteActive);
}
}
Updates the value in one place and you can use it in the children.

How to render a nested component with react-router

This is a noob question. I'm developing a universal react application using react-isomorphic-starterkit boilerplate (https://github.com/RickWong/react-isomorphic-starterkit). I would create a "fixed" sidebar on the left that contains links to the other pages rendered on a child of the main container on the right. Here's my code:
routes.js
module.exports = (
<Router history={browserHistory}>
<Route path="/" component={Main}>
<Route path='/inbox' component={Inbox} />
</Route>
</Router>
);
Main.js
export default class Main extends React.Component {
constructor(props, context){
super(props,context);
console.log('Main props', props);
console.log('Main context', props);
}
/**
* componentWillMount() runs on server and client.
*/
componentWillMount () {
if (__SERVER__) {
console.log("Hello server");
}
if (__CLIENT__) {
console.log("Hello client");
}
}
/**
* Runs on server and client.
*/
render () {
return (
<App/>
)
}
}
App.js
class App extends React.Component{
constructor(props, context) {
super(props, context);
console.log('App props ', props);
}
render(){
return (
<div>
<Sidebar/>
<RightContent />
</div>
)
}
}
RightContent.js
class RightContent extends React.Component{
render(){
return (
<div id="b" style={{backgroundColor:"#EEF0F4", width:"100%"}}>
<NavBar />
{this.props.children}
</div>
)
}
}
Problem is: Inbox component (a simple <div>) is not rendering when I click on the sidebar with <Link to="..."> tag. I don't know if the following thing is correct: as you can see, in App.js class constructor prints props variable...but the output is undefined. However, in Main.js props are printed correctly. react-router version is greater than 2. Could anyone help me?
You're not passing props down to App, so unless you use the location context, it won't have them.
You should do...
render () {
return (
<App {...this.props}/>
)
}
Then, you should have access to this.props.children in App to render the nested route(s). You'll need to specify that in RightContent too...
render(){
return (
<div>
<Sidebar/>
<RightContent>
{this.props.children}
</RightContent>
</div>
)
}
See the tutorial in the docs... https://github.com/reactjs/react-router/blob/master/docs/Introduction.md#with-react-router

Categories