React + Redux : How to manage transitions when replacing components - reactjs

I use redux in my react app to dynamically swap components in and out of the DOM. What I'm trying to do is have css transitions happen that fade in and fade out these components.
I thought I could use the CSSTransitionGroup component for that, but as both the entering and leaving components are in the DOM at the same time (new component is mounted while the previous component is still not unmounted), it messes up the layout during these transitions.
I can get either the fade in or fade out to work by not displaying the entering or leaving component, and I played around with css absolute positioning to put one in front of the other during the transition, but both have unwanted side effects.
How does one properly replace one component with another and use transitions on both entering and leaving?
Thanks!
See my component code below just for understanding how I dynamically replace the components using redux.
Component code:
import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import Services from '../components/services';
import About from '../components/about';
import Contact from '../components/contact';
import { connect } from 'react-redux';
import { selectDetail } from '../actions/index';
import { CSSTransitionGroup } from 'react-transition-group'
class Category extends Component {
constructor(props){
super(props);
this.handleDetailSelected = this.handleDetailSelected.bind(this);
}
getSelectedCategory(){
switch (this.props.selectedCategory) {
case 'about':
return <About key={ this.props.selectedCategory } handleDetailSelected={this.handleDetailSelected}/>;
case 'contact':
return <Contact key={ this.props.selectedCategory } handleDetailSelected={this.handleDetailSelected}/>;
default:
return <Services key={ this.props.selectedCategory } handleDetailSelected={this.handleDetailSelected}/>;
}
}
handleDetailSelected(event, detail){
if(detail){
this.props.selectDetail(detail);
}
}
render() {
const category = this.getSelectedCategory();
return (
<CSSTransitionGroup
className="col-xs-12 col-sm-6 content left-content"
component="div"
transitionAppear={true}
transitionAppearTimeout={ 1000 }
transitionName = "category"
transitionEnterTimeout={ 1000 }
transitionLeaveTimeout={ 500 }>
{ category }
</CSSTransitionGroup>
);
}
}
//selectedCategory is set through a navigation component.
function mapStateToProps({ selectedCategory }) {
return { selectedCategory };
}
function mapDispatchToProps(dispatch) {
return bindActionCreators({
selectDetail
}, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(Category);

Related

Reactjs redux store changes but in component's props doesn't change

I'm tryin to show navigation depends on changes of categoryURL from
redux store and changing the state in other components. Redux changes the store and it works fine. But in my
component "this.props.categoryUrl" doesn't reflect on value. Can't
find out why?
import React, {Component} from 'react';
import NavigationItems from './NavigationItems/NavigationItems';
import classes from './Navigation.module.css';
import {connect} from 'react-redux';
const mapStateToProps = state => {
console.log(state)
return {
categoryURL: state.categoryUrl
};
};
class navigation extends Component {
componentDidMount() {
console.log(this.props.categoryUrl);
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('NAVIGATION!', this.props.categoryUrl);
}
render() {
let nav = null;
if (this.props.categoryUrl) {
nav = (
<div className={classes.Navigation}>
<NavigationItems/>
</div>
)
}
return (
<>
{nav}
</>
)
}
}
export default connect(mapStateToProps, null)(navigation);
In "normal" React it is needed to use <Navigation/> (Capital letter at the beginning) instead of <navigation/>. Also, If <Navigation/> is being used by another React component then it might be needed to add code that will be executed inside <Navigation/> to refresh the component where you are using <Navigation/> (some kind of callback passed to <Navigation/>). It is this the way or move all the <Navigation/>'s code to the component where you are using <Navigation/>. This is how I solved this kind of problem.

GraphQL query works in Gatsby page but not inside class component

There have been a couple of similar questions, but none helped me really understand using a GraphQL inside a (class) component other than the ones in the pages folder.
My project structure looks like that:
-src
--components
---aboutBody
----index.js
--pages
---about.js
I have a page component called about (Prismic single page type) and set up some components to "fill" this page (cleaned up for better readability).
class AboutPage extends Component {
render() {
return (
<LayoutDefault>
<AboutBody
introHeadline={this.props.data.prismicAbout.data.intro_headline.text}
introParagraph={this.props.data.prismicAbout.data.intro_paragraph.text}
/>
</LayoutDefault>
)
}
}
export default AboutPage
This is what my query looks like (had it like this in both files):
export const aboutQuery = graphql`
query About {
prismicAbout {
data {
# Intro Block
intro_headline {
text
}
intro_paragraph {
text
}
}
}
}
`
(In case I am missing a bracket at the bottom, it's due to cleaning up the query example for SO — as mentioned earlier, it's working in my page component).
My graphql query is at the bottom of the AboutPage page component. It works like a charm and as intended.
But to clean this page up a bit I wanted to create appropriate components and put my query inside each component (e.g. aboutBody, aboutCarousel), again cleaned up a bit:
class AboutBody extends Component {
render() {
return (
<StyledIntro>
<h3>About</h3>
<h1>{this.props.data.prismicAbout.data.intro_headline.text}</h1>
</StyledIntro>
)
}
}
export default AboutBody
And I deleted the query from my about page component and put it inside my AboutBody component (exactly the way as shown above).
But with this it always returns the error Cannot read property 'prismicAbout' of undefined (I can't even console log the data, it always returns the same error).
I used import { graphql } from "gatsby" in both files.
Long story short, how can I achieve putting a query inside my class component and render only the component without clarifying the props in my page component like this:
class AboutPage extends Component {
render() {
return (
<LayoutDefault>
<AboutBody />
</LayoutDefault>
)
}
}
Some blogs posts mention GraphQL Query Fragments, but not sure if this is the correct use case or if it's simply a stupid beginner mistake...
That's because you can't use graphql like this in your component.
To use graphql in a component, you've got two options : useStaticQuery function or StaticQuery component, both from graphql
for useStaticQuery :
import React from "react"
import { useStaticQuery, graphql } from "gatsby"
const MyElement = () => {
const data = useStaticQuery(graphql`
query About {
prismicAbout {
data {
intro_headline {
text
}
intro_paragraph {
text
}
}
}
}
`)
return (
<StyledIntro>
<h3>About</h3>
<h1>{this.props.data.prismicAbout.data.intro_headline.text}</h1>
</StyledIntro>
)
}
export default MyElement
with staticQuery
import React from 'react'
import { StaticQuery, graphql } from 'gatsby';
const MyElement = () => {
return(
<StaticQuery
query About {
prismicAbout {
data {
intro_headline {
text
}
intro_paragraph {
text
}
}
}
}
`}
render={data => (
<StyledIntro>
<h3>About</h3>
<h1>{this.props.data.prismicAbout.data.intro_headline.text}</h1>
</StyledIntro>
)}
/>
)
}
export default MyElement
Hope that helps you!
You can only use a query like that in a page component. One option would be to just query it in the page and then pass the data in to your component as a prop. Another is to use a static query in the component.
If your query has variables in it then you can't use a static query. In that case you should either query it all in the page and then pass it in, or you can put the part of the query related to that component in a fragment within that component's file and then use that fragment in the page query.
Example of using fragments in a component and then passing the data into the component:
// MyComponent.js
import React from "react"
import { graphql } from 'gatsby'
const MyComponent = (props) => {
const { myProp: { someData } } = props
return (
<div>
my awesome component
</div>
)
}
export default MyComponent
export const query = graphql`
fragment MyAwesomeFragment on Site {
someData {
item
}
}
`
// MyPage.js
import React from "react"
import { graphql } from "gatsby"
import MyComponent from "../components/MyComponent"
export default ({ data }) => {
return (
<div>
{/*
You can pass all the data from the fragment
back to the component that defined it
*/}
<MyComponent myProp={data.site.someData} />
</div>
)
}
export const query = graphql`
query {
site {
...MyAwesomeFragment
}
}
`
Read more about using fragments in Gatsby docs.
If you need to render the query in a class based component. This worked for me:
import React, { Component } from 'react';
import { StaticQuery, graphql } from 'gatsby';
class Layout extends Component {
render() {
return (
<StaticQuery
query={graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`}
render={data => {
return (
<main>
{!data && <p>Loading...</p>}
{data && data.site.siteMetadata.title}
</main>
)
}}
/>
);
}
}

React router 4 nested routes not working

I am trying to create a nested route - When user logs in it opens dashboard and when dashboard open i want to create a nested route by making a side menu and change the content at the right but not able to do. When I am trying to access the post page in dashboard it is not opening.
import React from 'react';
import { Switch, Route, Link } from 'react-router-dom';
import { connect } from 'react-redux';
import { userActions } from '../_actions';
import { PostPage } from './PostPage';
import { HomePage } from '../HomePage';
class DashboardPage extends React.Component {
render() {
const { url } = this.props;
return (
<div>
<h1>BasicRouting</h1>
<p>With the help of "Match" Component we can specify the Component we want to render for a particular pattern of the App location/window.pathname.</p>
<p>Select a level from Left Navigation to view the content, also notice the change in URL.</p>
<div className="rightContent">
<p>Second Level Content will appear here:</p>
<Route path={`${this.props.match.url}/post`} component={PostPage} />
</div>
</div>
);
}
}
function mapStateToProps(state) {
console.log(state)
return {
isLoggedIn: state
};
}
const connectedDashboardPage = connect(mapStateToProps)(DashboardPage);
export { connectedDashboardPage as DashboardPage };
There are several problems in your code.
You import { Switch ... } from 'react-router-dom', but never used it afterward.
If you want to call the route in upper/parent components, you need to import { withRouter } to wrap the redux connected class, something like,
const connectedDashboardPage = connect(mapStateToProps)(DashboardPage);
const withRouterConnectedDashboardPage =
withRouter(connectedDashboardPage);
export default withRouterConnectedDashboardPage;
Final suggestion, read through the tutorial here:
https://medium.com/#pshrmn/a-simple-react-router-v4-tutorial-7f23ff27adf
&
always refer to: https://reacttraining.com/react-router/

Will choosing whether to display a component in render() using props/state result in a redraw every time?

Are the following two equivalent performance wise? Will the first example cause Component1 and Component2 to be re-rendered every time MyComponent's props are updated?
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectShowComponent1, selectShowComponent2 } from './selectors';
#connect(createStructuredSelector({
showComponent1: selectShowComponent1,
showComponent2: selectShowComponent2,
}))
class MyComponent extends React.PureComponent {
static propTypes = {
showComponent1: React.PropTypes.bool,
showComponent2: React.PropTypes.bool,
};
render() {
const { showComponent1, showComponent2 } = this.props;
return (
<div>
{showComponent1 ? (<Component1>shown 1</Component1>) : ''}
{showComponent2 ? (<Component2>shown 2</Component2>) : ''}
</div>
);
}
}
export default MyComponent;
import React from 'react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectShowComponent1, selectShowComponent2 } from './selectors';
#connect(createStructuredSelector({
showComponent1: selectShowComponent1,
showComponent2: selectShowComponent2,
}))
class MyComponent extends React.PureComponent {
static propTypes = {
showComponent1: React.PropTypes.bool,
showComponent2: React.PropTypes.bool,
};
render() {
const { showComponent1, showComponent2 } = this.props;
return (
<div>
<Component1 renderMe={showComponent1}>shown 1</Component1>
<Component2 renderMe={showComponent2}>shown 2</Component2>
</div>
);
}
}
export default MyComponent;
The first one will re-render every time, whereas the second one won't. The reason is dynamically generated components (in this case, dynamic cos they're the result of a ternary operator) get given a new key each time they're returned from the dynamic context. And this key is what React uses internally to uniquely identify components.
So I believe if you gave them explicit keys:
{showComponent1 ? (<Component1 key="1">shown 1</Component1>) : ''}
{showComponent2 ? (<Component2 key="2">shown 2</Component2>) : ''}
Then in theory that should stop them being re-rendered, but that's not ideal as manually assigning keys in some scenarios may mean the component may not re-render when you actually want it to.

Changing material-ui theme on the fly --> Doesn't affect children

I'm working on a react/redux-application where I'm using material-ui.
I am setting the theme in my CoreLayout-component (my top layer component) using context (in accordance to the documentation). This works as expected on initial load.
I want to be able to switch themes during runtime. When I select a new theme, my redux store gets updated and therefore triggers my components to update. The problem is that the children of my CoreLayout-component doesn't get affected - the first time! If I repeatedly change my theme (using a select-list that sends out a redux-action onChange) the children are updated. If a child component is located 2 layers down in my project hierarchy, it is updated after 2 action calls - so there is some issue with how the context is passed down.
My CoreLayout.js component
import React, { PropTypes } from 'react';
import { connect } from 'react-redux';
import ThemeManager from 'material-ui/lib/styles/theme-manager';
const mapStateToProps = (state) => ({
uiStatus: state.uiStatus
});
export class CoreLayout extends React.Component {
getChildContext() {
return {
muiTheme: ThemeManager.getMuiTheme(this.props.uiStatus.get("applicationTheme").toJS())
};
}
render () {
return (
<div className='page-container'>
{ this.props.children }
</div>
);
}
}
CoreLayout.propTypes = {
children: PropTypes.element
};
CoreLayout.childContextTypes = {
muiTheme: PropTypes.object
};
export default connect(mapStateToProps)(CoreLayout);
One of my child components (LeftNavigation.js)
import React from "react";
import { connect } from 'react-redux';
import { List, ListItem } from 'material-ui';
const mapStateToProps = (state) => ({
uiStatus: state.uiStatus
});
export class LeftNavigation extends React.Component {
render () {
return (
<div className="left-pane-navigation">
<List subheader="My Subheader" >
<ListItem primaryText="Search" />
<ListItem primaryText="Performance Load" />
</List>
</div>
);
}
}
LeftNavigation.contextTypes = {
muiTheme: React.PropTypes.object
};
export default connect(mapStateToProps)(LeftNavigation);
I can access the theme located in context by this.context.muiTheme.
I can get the component to update the theme by using another instance of getChildContext() inside each child component, but I will have such a large number of components that I would very much like to avoid having to do that.
My CoreLayout component's getChildContext-method is called when I change theme and all my child components gets re-rendered as expected.
Any ideas?
Update: It works as expected on mobile devices (at least iOS)
You can use muiThemeProvider to avoid having to add getChildContext to any child component, the provider does this for you.
...
import getMuiTheme from 'material-ui/styles/getMuiTheme';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import MyAwesomeReactComponent from './MyAwesomeReactComponent';
const App = () => (
<MuiThemeProvider muiTheme={getMuiTheme()}>
<MyAwesomeReactComponent />
</MuiThemeProvider>
)
...
More info in documentation.

Resources