Ignore initial Transition for first mount React-Spring - reactjs

I have a react component which needs a transition for for in and out.
But on the first mount it should not use an entry animation.
I use a simple fade in fade out. the initial keyword should deactivate the initial transition for the first mount. But it does not work. The following transitions work as aspected.
I tried to find a solution but most of the topics were outdated or did not work for me.
Maybe I am missunderstanding something since I am pretty new to React & React-Spring.
<Transition
native
items={this.state.showComponent}
initial={null}
from={{opacity:0}}
enter={{opacity:1}}
leave={{opacity:0}}
>
{show => show && (props =>
<animated.div style={props}>
//Component content
</animated.div>
)}
</Transition>

If you do not want to see the initail transition you should introduce a flag for it. And based on the flag you can change the from property of the transition. The flag could be either a class variable or a state variable. For example:
class MyComponent extends React.Component {
initialised = false;
componentDidMount {
initialised = true;
}
...
<Transition
native
items={this.state.showComponent}
initial={null}
from={{opacity: this.initialised ? 0 : 1}}
enter={{opacity:1}}
leave={{opacity:0}}
>
{show => show && (props =>
<animated.div style={props}>
//Component content
</animated.div>
)}
</Transition>

Related

How can I render one component conditionally twice and not lose internal states/unmounts?

I have one component which needs to be rendered conditionally. Renders the same component with different styles. So, I did like this
import ComponentToRender from '../../ComponentToRender'
const Main =()=> {
const [expand,setExpand] =useState(false)
return (
<div>
{!expand && <ComponentToRender {...someProps} />}
{expand && <div>
<ComponentToRender {...otherProps} />
</div>
}
<button onClick={()=>setExpand(pre => !pre)}>Expand</button>
</div>
)
}
For the above code, I get what I want in terms of UI. But, all the internal states are lost. I must render two components like that and keep the internal states. Is that possible to do that in React?
You can achieve this by keeping the component rendered unconditionally and hiding it with CSS.
You get to preserve Component‘s state for free along with the DOM state (scroll, focus, and input position). However, this solution has drawbacks, too:
You mount the component on startup, even if the user never accesses it.
You update the component even when it’s invisible.
import ComponentToRender from "../../ComponentToRender";
const Main = () => {
const [expand, setExpand] = useState(false);
return (
<div>
<div style={{ display: expand ? null : "none" }}>
<ComponentToRender {...someProps} />
</div>
<div style={{ display: !expand ? null : "none" }}>
<div>
<ComponentToRender {...otherProps} />
</div>
</div>{" "}
<button onClick={() => setExpand((pre) => !pre)}>Expand</button>
</div>
);
};
The reconciliation algorithm is such that when on next render you move from one component to component of different type (assuming they have same spot in component hierarchy), instance of old component is destroyed.
Since you have <ComponentToRender/> and another one is <div><ComponentToRender/></div>, they are different components (because one is inside a div).
Read about reconciliation.
What you can do is move the state of ComponentToRender to Main and pass it as props. Now even if the component unmounts the state will not be lost.

React animate leaving div content

I'm using React and React-Spring to animate a questionnaire app.
I want the questionnaire to animate the leaving/enter of a question when the user answer one.
I'm using React for the app and try to use React-Spring to animate the transitions. The issue is that when the user is answering a question, the question component is updated with the new content before it leaving.
To simplify it, the Question component look like this:
export default function Question({question, onAnswer}) {
const [answer, setAnswer] = useState(null);
return (
<animated.div ...>
{question.title}
<select>...{quesiton.options}...</select>
<button onClick={() => onAnswer(question.id, answer)}>Next</button>
</animated.div>
);
}
I create a Code Sandbox that illustrates my issue:
https://codesandbox.io/s/nostalgic-swartz-lhkj0?file=/src/AnimatedComponent.js
How should I handle this? couldn't find any example on the web
Thanks!
You should use the item property in the transition map instead of using the text directly in the animated.div.
{transitions.map(
({ item, key, props }) =>
item && (
<animated.div key={key} style={props}>
{item}
</animated.div>
)
)}

React: Conditional className is not updated in DOM

When conditions changes (location) react doesn't update the classname in dom, although it looks changed in react developer tools. Simplified code is something like this:
<Menu>
{appRoutes.map(route => {
console.log(
pathname,
route.path,
pathname === route.path,
pathname === route.path ? 'ant-menu-item-selected' : '',
);
return (
<Menu.Item
key={route.name}
className={pathname === route.path ? 'ant-menu-item-selected' : ''}
onClick={() => {
setOpenKeys([getKey(route.name, index)]);
if (app.mobile) app.toggleMobileDrawer();
}}
>
<Link to={route.path}>
<span>
<span className="mr-auto">{capitalize(route.name)}</span>
</span>
</Link>
</Menu.Item>
);
})}
</Menu>
pathname variable is from location. I use useLocation hook from react router so when location changes, component re-renders properly. console.log prints all vars as it should be. Further, when I check react developer tool, classname looks as it should be:
But when checking the elements in developer tools, class is not updated for the same element in dom:
When I refresh the page (not changing the location), it renders properly, and class name is removed from dom. So how can I force react to update the classname in dom?
Using react 16.13.1, antd components.
One approach is to use the selectedKeys property from Antd Menu and set this to your current route path value.
selectedKeys: Array with the keys of currently selected menu items
The menu may look like this:
<Menu
activeKey={props.currentPath}
mode="inline"
selectedKeys={props.currentPath}
style={{ height: "100%", borderRight: 0 }}
>
Additional examples can be found in antd's github thread for >how to use sider with react-router links<.

React tabs - switching destroys component, need to maintain the same component

I am trying to make a multi-tabbed SPA with React and Material-UI. I use code from this demo as an example: https://codesandbox.io/s/qlq1j47l2w
It appears that if I follow the aforementioned example, I end up returning new instance of the component with tab contents each time I navigate between the tabs:
<Tabs
value={value}
onChange={this.handleChange}
indicatorColor="primary"
textColor="primary"
scrollable
scrollButtons="auto"
>
<Tab label="Tab 1" />
<Tab label="Tab 2" />
</Tabs>
</AppBar>
{value === 0 && <Tab1Contents/>}
{value === 1 && <Tab2Contents/>}
As Tab1Contents is a form, I would like its internal state to be retained, instead of loading a new instance of the component, which the code above appears to do.
What is the best way to get React to use only one instance of the component and 'memorise field values'?
EDIT
I have added Redux to the example, but the store corresponding to the form within the Tab is destroyed the moment I switch away. Is there any other way to implement tabs in React that would hide the tab contents, instead of destroying them and re-creating them from scratch each time I navigate away?
The solution to my problem was quite simple! If you don't want to destroy the component (remove it from DOM), you can simply hide it!
Instead of:
{value === 0 && <Tab1Contents/>}
Use:
<div style={{ display: value === 0? 'block': 'none'}}>
<Tab1Contents/>
</div>
It was already mentioned that you have to prevent your component from being removed from the DOM. The easiest solution with MUI5 TabPanel is in fact to just replace
{value === index && children}
with
{children}
that means your Tabpanel would look like that:
import * as React from "react";
const TabPanel= ({ children, value, index, ...other }) => {
return (
<div
role="tabpanel"
hidden={value !== index}
id={`tabpanel-${index}`}
aria-labelledby={`tab-${index}`}
{...other}
>
{children}
{/* {value === index && children} // This was removed */}
</div>
);
};
export default TabPanel;
No additional logic necessary as the hidden prop already takes care of the visibility aspect. This way your components should maintain their state!
You would need to persist the state between the tab changes. I prototyped a form using React forms documentation for Tab1Container and as you play around with it, the value will disappear
https://codesandbox.io/s/material-demo-ojwhr
What you ideally need to use something like Redux, which will use a store to keep the information even between the state changes like Tab clicks.
Hope this helps!

MUI Fade component does not show, hide, or fade components

I switched child components from conditional rendering to being wrapped by <Fade /> but Fade doesn't work at all, both components always display. Here is the current code:
// inside top level component
{/*{adminView === bodyViews.addNewCoupon &&*/}
<Fade
in={Boolean(adminView === bodyViews.addNewCoupon)}
timeout={2000}
>
<AddCouponForm />
</Fade>
{/*}*/}
{/*{adminView === bodyViews.viewCurrentCoupons &&*/}
<Fade
in={Boolean(adminView === bodyViews.viewCurrentCoupons)}
timeout={2000}
>
<ViewCoupons />
</Fade>
{/*}*/}
Based on the API here. I believe in set to true should cause the component to fade in. This worked for causing a conditional render in the commented out unary but does not seem to work within the in value. What mistake is being made?
Update
When I comment the custom components and inserting something like <p>Section 1/2</p> then the fade works. Something about the custom compoonents must cause the fade not to work
The issue was traced specifically back to Custom components: they don't seem to work as direct children of <Fade />. Issue is solved by wrapping the custom component children in a div.
<Fade
in={ Boolean(adminView === bodyViews.addNewCoupon) }
timeout={ 4000 }
>
<div>
<AddCouponForm />
</div>
</Fade>
<Fade
in={ Boolean(adminView === bodyViews.viewCurrentCoupons) }
timeout={ 4000 }
>
<div>
<p>Section 2</p>
<ViewCoupons />
</div>
</Fade>
As a side note, Fade also seems to have issues with more than one child. Therefore all children should go inside a single div element.
Fade updates the child component via the styles prop, if you're using a custom component you need to forward the ref to the child and make sure the props from Fade are passed down:
const MyComponent = React.forwardRef((props, ref) => {
return (
<div {...props} ref={ref}>
my component
</div>
);
});
<Fade in={checked}>
<MyComponent />
</Fade>
Live Demo

Resources