Im working on a React Material Ui component currently.
currently the goal is to be able to change JSS styling according to some custom function or values in another part of the application, without needing to add / remove classes
This is my current setup:
stepControl.js
const stepControl = () => {
const [activeStep, setActiveStep] = useState(0)
const [completed, setCompleted] = useState(new Set())
const [stepCount, setStepCount] = useState(0)
const completedSteps = () => {
return completed.size
}
const totalSteps = () => {
return stepCount
}
const allStepsCompleted = () => {
return completedSteps() === totalSteps()
}
return {
state: {
activeStep,
completed
},
actions: {
totalSteps,
completedSteps,
allStepsCompleted,
setActiveStep,
setCompleted,
setStepCount
}
}
}
export default stepControl
index.styles.js
import { makeStyles } from '#tim/functional'
import stepControl from './stepControl.js'
const useStyles = () => {
const { state, actions } = stepControl()
return makeStyles(theme => ({
root: {
width: '70%',
margin: 'auto',
'& .MuiPaper-root': {
backgroundColor: 'transparent'
},
'& .MuiStepConnector-lineHorizontal': {
borderTopWidth: 6,
borderRadius: 1,
marginTop: -2,
borderColor: actions.allStepsCompleted()
? theme.palette.primary
: rgba(0, 0, 0, 0.24)
}
},
button: {
marginRight: theme.spacing(1)
},
backButton: {
marginRight: theme.spacing(1)
},
completed: {
display: 'inline-block',
'& .MuiStepConnector-lineHorizontal': {
borderTopWidth: 6,
borderRadius: 1,
borderColor: theme.palette.primary
}
},
instructions: {
marginTop: theme.spacing(1),
marginBottom: theme.spacing(1)
}
}))
}
export default useStyles
Currently im getting too many rerenders error even if i just return a fixed value in the function in question allStepsCompleted() . how would i go about doing this in Material Ui?
Im running:
React 16.9
Mui 4.9.5
on a webpack 4.4 server on macOS 10.15
Thanks in advance for any suggestions
Related
I have a <TextInput> which, when the user enters anything into, will have a button appear from behind it.
This works fine using the Animated library from react, but each time I input text into the area, the animation "rubber bands" up and down.
My question is: How can I keep the button in place after the initial animation happens?
I still need to monitor whether the text box is empty in order to decide whether to show or hide the button.
My code:
import { View, TextInput, StyleSheet, Animated, TouchableOpacity } from "react-native";
import { useState } from "react";
import CurrencyInput from 'react-native-currency-input';
import { Ionicons } from "#expo/vector-icons";
type expenseItemData = {
item:string;
costAndSign:string;
cost:number | null;
}
type Props = {
sendItem: ({ item, cost, costAndSign }: expenseItemData) => void;
}
const AnimatedTouchable = Animated.createAnimatedComponent(TouchableOpacity);
const AddAndConfirmExpense: React.FC<Props> = ({sendItem}) => {
const [animation] = useState(new Animated.Value(-58));
const [expenseValue, setExpenseValue] = useState<number>(0.00);
const [expenseItem, setExpenseItem] = useState<string>("");
const [expenseValueAndSign, SetExpenseValueAndSign] = useState<string>("");
const [buttonLayoutPersistence, setButtonLayoutPersistence] = useState({
bottom:0
});
const [validExpenseItem, setValidExpenseItem] = useState<boolean>(false);
const onChangeExpenseItem = (text: string) => {
setExpenseItem(text);
if (text.trim().length === 0) {
setValidExpenseItem(false);
hideButton();
setButtonLayoutPersistence({bottom:0})
return;
}
if (validExpenseItem) {
setButtonLayoutPersistence({bottom:-48})
return
};
showButton();
setValidExpenseItem(true);
};
const onButtonPress = () => {
const newData:expenseItemData = {
item:expenseItem,
costAndSign:expenseValueAndSign,
cost:expenseValue
}
sendItem(newData);
};
const setAreaDynamicStyling = () => {
if (validExpenseItem) {
return {
borderTopRightRadius:5,
borderTopLeftRadius:5,
backgroundColor:"#f5f5f5"
}
}
return {borderRadius:5};
};
const setButtonDynamicStyling = () => {
if (!validExpenseItem) return {borderRadius:5}
return {borderBottomLeftRadius: 5,borderBottomRightRadius:5}
};
const animatedStyle = {transform: [{translateY:animation}],};
const showButton = () => {
Animated.timing(animation, {
toValue: -10,
duration: 1000,
useNativeDriver: true,
}).start();
}
const hideButton = () => {
Animated.timing(animation, {
toValue: -58,
duration: 500,
useNativeDriver: true,
}).start();
}
return (
<View>
<View style={validExpenseItem ? [styles.inputsContainer, setAreaDynamicStyling()] : [styles.inputsContainer,styles.shadowProp,setAreaDynamicStyling()]}>
<TextInput
style={styles.textInputArea}
placeholder='Item'
placeholderTextColor="#aaaaaa"
onChangeText={(text) => {onChangeExpenseItem(text)}}
value={expenseItem}
underlineColorAndroid="transparent"
autoCapitalize="none"
/>
<View style={styles.verticalLine}/>
<CurrencyInput
style={styles.currencyInputArea}
value={expenseValue}
onChangeValue={setExpenseValue}
prefix="£"
delimiter=","
separator="."
precision={2}
minValue={0}
onChangeText={(formattedValue) => {
SetExpenseValueAndSign(formattedValue)
}}
/>
</View>
<AnimatedTouchable onPress={()=>{onButtonPress()}} style={[{flex:1, zIndex:-1},animatedStyle]}>
<View style={[styles.confirmInputContainer, setButtonDynamicStyling(), buttonLayoutPersistence]}>
<Ionicons name="checkmark-circle-outline" size={24} color="white" />
</View>
</AnimatedTouchable>
</View>
)
}
const styles = StyleSheet.create({
confirmInputContainer:{
backgroundColor:"#7f96ff",
height: 48,
flexDirection:"row",
paddingVertical:10,
justifyContent:"center",
},
inputsContainer:{
backgroundColor:"white",
height: 48,
flexDirection:"row",
paddingVertical:10,
marginVertical:10,
justifyContent:"space-between",
},
shadowProp: {
shadowColor: '#353935',
shadowOffset: {width: -2, height: 4},
shadowOpacity: 0.2,
shadowRadius: 4,
},
textInputArea:{
width:"60%",
maxWidth:"60%",
marginLeft:20,
},
verticalLine:{
height: "100%",
width: 1,
backgroundColor: "#909090",
marginHorizontal:5,
},
currencyInputArea:{
maxWidth:"20%",
width:"20%",
marginRight:20
},
})
export default AddAndConfirmExpense;
EDIT:
I have added:
const [animationActive, setAnimationActive] = useState(false);
useEffect(() => {
if (!animationActive && validExpenseItem) {
setButtonLayoutPersistence({bottom:-48})
};
if (!animationActive && !validExpenseItem) {
setButtonLayoutPersistence({bottom:0})
}
},[animationActive])
And changed my show and hide functions to the following:
const showButton = () => {
if (animationActive) return;
setAnimationActive(true)
Animated.timing(animation, {
toValue: -10,
duration: 500,
useNativeDriver: true,
}).start(
() => {
setAnimationActive(false)
}
);
}
const hideButton = () => {
if (animationActive) return;
setAnimationActive(true)
Animated.timing(animation, {
toValue: -58,
duration: 500,
useNativeDriver: true,
}).start(
() => {
setAnimationActive(false)
}
);
}
I have also changed onChangeExpenseItem to :
const onChangeExpenseItem = (text: string) => {
setExpenseItem(text);
if (text.trim().length === 0) {
setValidExpenseItem(false);
hideButton();
setButtonLayoutPersistence({bottom:0})
return;
}
if (!validExpenseItem && !animationActive) {
setValidExpenseItem(true);
showButton();
return
};
};
It is slightly better now, but a better solution is still needed.
Trying to make a stopwatch in react native I have the working code with class components but when trying with functional components the clearInterval() function doesn't work
source code from ReactNativeAcademy/Stopwatch before modifications here https://github.com/ReactNativeAcademy/Stopwatch/blob/master/App.js
I need just a basic timer without laps with only start/resume/stop/reset buttons
snack url for my code: https://snack.expo.io/#mansouriala/nervous-mixed-nuts
in order to test it in class based components you can find each class based function commented under the functional one.
I don't know but maybe one solution for that could be to wrap setInterval in a useEffect then make a new state variable toggle it to true when start and the useEffect listens to that variable.
without further ado here's the code:
import React, { Component, useEffect, useState } from 'react';
import { StyleSheet, Text, View, TouchableOpacity } from 'react-native';
import moment from 'moment';
function Timer({ interval, style }) {
const pad = (n) => (n < 10 ? `0${n}` : n);
const duration = moment.duration(interval);
const centiseconds = Math.floor(duration.milliseconds() / 10);
return (
<View style={styles.timerContainer}>
<Text style={style}>{pad(duration.minutes())}:</Text>
<Text style={style}>{pad(duration.seconds())},</Text>
<Text style={style}>{pad(centiseconds)}</Text>
</View>
);
}
function RoundButton({ title, color, background, onPress }) {
return (
<TouchableOpacity onPress={onPress} style={[styles.button, { backgroundColor: background }]}>
<View style={styles.buttonBorder}>
<Text style={[styles.buttonTitle, { color }]}>{title}</Text>
</View>
</TouchableOpacity>
);
}
function ButtonsRow({ children }) {
return <View style={styles.buttonsRow}>{children}</View>;
}
// export default class App extends Component {
export default function App() {
const [timer, setTimer] = useState(0);
const [state, setState] = useState({
start: 0,
now: 0,
currentTime: 0,
});
// constructor(props) {
// super(props);
// this.state = {
// start: 0,
// now: 0,
// currentTime: 0,
// };
// }
useEffect(() => {
return () => {
clearInterval(timer);
};
}, []);
// componentWillUnmount() {
// clearInterval(this.timer);
// }
const startHandler = () => {
const now = new Date().getTime();
setState({
start: now,
now,
currentTime: 0,
});
setInterval(
setInterval(() => {
setState((prev) => ({ ...prev, now: new Date().getTime() }));
}, 100)
);
};
// startHandler = () => {
// const now = new Date().getTime();
// this.setState({
// start: now,
// now,
// currentTime: 0,
// });
// this.timer = setInterval(() => {
// this.setState({ now: new Date().getTime() });
// }, 100);
// };
const stopHandler = () => {
clearInterval(timer);
const { currentTime, now, start } = state;
setState((prev) => ({
// ...prev,
currentTime: currentTime + (now - start),
start: 0,
now: 0,
}));
};
// stopHandler = () => {
// clearInterval(this.timer);
// const { currentTime, now, start } = this.state;
// this.setState({
// currentTime: currentTime + now - start,
// start: 0,
// now: 0,
// });
// };
const resetHandler = () => {
setState({
currentTime: 0,
start: 0,
now: 0,
});
};
// resetHandler = () => {
// this.setState({
// currentTime: 0,
// start: 0,
// now: 0,
// });
// };
const resumeHandler = () => {
const now = new Date().getTime();
setState({
start: now,
now,
});
setTimer(
setInterval(() => {
setState((prev) => ({ ...prev, now: new Date().getTime() }));
}, 100)
);
};
// resumeHandler = () => {
// const now = new Date().getTime();
// this.setState({
// start: now,
// now,
// });
// this.timer = setInterval(() => {
// this.setState({ now: new Date().getTime() });
// }, 100);
// };
// render() {
const { now, start, currentTime } = state;
// const { now, start, currentTime } = this.state;
return (
<View style={styles.container}>
<Timer interval={currentTime + (now - start)} style={styles.timer} />
<ButtonsRow>
<RoundButton title={'Start'} color={'#50D167'} background={'#1B361F'} onPress={startHandler} />
<RoundButton title={'Stop'} color={'#E33935'} background={'#3C1715'} onPress={stopHandler} />
<RoundButton title={'Reset'} color={'#FFFFFF'} background={'#3D3D3D'} onPress={resetHandler} />
<RoundButton title={'Resume'} color={'#50D167'} background={'#1B361F'} onPress={resumeHandler} />
</ButtonsRow>
</View>
);
}
// }
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#0D0D0D',
alignItems: 'center',
paddingTop: 130,
paddingHorizontal: 20,
},
timer: {
color: '#FFFFFF',
fontSize: 76,
fontWeight: '200',
width: 110,
},
button: {
width: 80,
height: 80,
borderRadius: 40,
justifyContent: 'center',
alignItems: 'center',
},
buttonTitle: {
fontSize: 18,
},
buttonBorder: {
width: 76,
height: 76,
borderRadius: 38,
borderWidth: 1,
justifyContent: 'center',
alignItems: 'center',
},
buttonsRow: {
flexDirection: 'row',
alignSelf: 'stretch',
justifyContent: 'space-between',
marginTop: 80,
marginBottom: 30,
},
timerContainer: {
flexDirection: 'row',
},
});
Try it:
useEffect(() => {
clearInterval(timer);
}, []);
When using return inside useEffect, the code is only triggered when component unmount.
I am new to using socket.io with react native.I am able to connect the socket.io instance with my node.js backend but the socket.io client is emmitting events multiple times.The app has grown quite complex so,I am also trying to explain what I did.
Main.js (User is redirected here from App.js)
I am using a library called react-native-tab-view and I used useContext to pass the socket instance.I have checked that the hook is working properly.
export const SocketObj = createContext()
const Main = ({ route }) => {
let [socket, setSocket] = useState(undefined)
const routesLength = useNavigationState(state => state.routes.length);
const connect = async () => {
if (routesLength == 1) {
setSocket(io("http://192.168.43.115:8000", {
transports: ['websocket'],
query: {
token: await AsyncStorage.getItem("token")
}
}))
} else {
const { socketInstanse } = route.params
setSocket(socketInstanse)
}
}
connect()
const layout = useWindowDimensions();
const [index, setIndex] = useState(0);
const [routes] = useState([
{ key: 'first', title: 'CHAT' },
{ key: 'second', title: 'PEOPLE' },
]);
const renderScene = SceneMap({
first: SplashScreen,
second: PeopleScreen,
});
return (
<SocketObj.Provider value={socket} >
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={(number) => {
setIndex(number)
}}
initialLayout={{ width: layout.width }}
/>
</SocketObj.Provider>
);
}
export default Main;
PeopleScreen.js
The user is directed here from Main.js. I have used the context api here to get the socket instance.
As you can see , i am using an event to log "Connected" to the console when connected but it is emitting multiple times.I don't want to emit it multiple times.Help me
import { SocketObj } from "./Main"
const PeopleScreen = ({ route, navigation }) => {
let socket = useContext(SocketObj)
socket.on("connect", () => console.log("Connect"))
const [peopleList, setPeople] = useState([])
useEffect(
() => {
const fetchData = async () => {
try {
const response = await API.get('get/users', {
headers: {
'Content-Type': 'application/json',
"auth-token": await AsyncStorage.getItem("token")
}
})
setPeople(response.data)
} catch (err) {
console.log(err.response)
}
}
fetchData()
}, []
)
return (
<View style={{
flex: 1,
backgroundColor: '#fff',
width: '100%',
height: '100%'
}} >
<View>
{
peopleList.map(
(i, key) => {
return (
<View style={{
display: 'flex',
flexDirection: 'row',
top: 40,
marginVertical: 5,
marginBottom: 10,
backgroundColor: 'grey',
height: 50
}}
key={key} >
<Text style={{
maxWidth: "50%"
}} >{i.firstName + ' ' + i.email}</Text>
<TouchableOpacity
onPress={
async () => {
console.log(socket)
API.post('post/add-friend', {
friend_email: i.email
}, {
headers: {
"auth-token": await AsyncStorage.getItem("token")
}
}).then(
data => console.log(data.data)
)
}
}
style={{
backgroundColor: 'rgb(255, 105, 105)',
width: 130,
justifyContent: 'center',
alignItems: 'center',
left: 150
}}
activeOpacity={0.6} >
<Text style={{
color: 'white',
}} >Add Friend</Text>
</TouchableOpacity>
</View>
)
}
)
}
</View>
</View >
)
}
export default PeopleScreen
I have ignored unnecessary imports.
Try to put your connect() inside an useEffect with an empty array as second argument of useEffect, then your connect function will be called only at the first render of Main.js.
...
const connect = async () => {
if (routesLength == 1) {
setSocket(io("http://192.168.43.115:8000", {
transports: ['websocket'],
query: {
token: await AsyncStorage.getItem("token")
}
}))
} else {
const { socketInstanse } = route.params
setSocket(socketInstanse)
}
}
useEffect(() => {
connect();
}, []);
...
if (!socket) return null; // or you can return a loading page
return (
<SocketObj.Provider value={socket} >
<TabView
navigationState={{ index, routes }}
renderScene={renderScene}
onIndexChange={(number) => {
setIndex(number)
}}
initialLayout={{ width: layout.width }}
/>
</SocketObj.Provider>
);
It happens due to initialisation of socket multiple time.Put instance in Main.js .
If socket instance is empty then initialise socket.
{ useEffect(() => { if(!isConnected){ connect() } });
I have an array of 3 images and I want to use it as a background image.
import React from 'react'
import Bin1 from './images/bin1.png'
import Bin2 from './images/bin2.png'
import Bin3 from './images/bin3.png'
const array = ['Bin1', 'Bin2', 'Bin3'];
const style = {
height: '20rem',
width: '15rem',
marginRight: '1.5rem',
marginBottom: '1.5rem',
color: 'white',
padding: '1rem',
textAlign: 'center',
fontSize: '1rem',
lineHeight: 'normal',
float: 'left',
backgroundImage : `url(${Bin1})`
}
So instead of one URL of image, I want to map all three of them. I am new in React and really wanted to know how to solve this one.
So for Dustbin.jsx you will need to create an object of images with key-value as name of the image and the source. Then, appending backgroundImage while passing the style object to the div. Now, when you create a Dustbin component, just pass the name of the image you want to render as a prop (I called it bgImageName). Like this:
Dustbin.jsx
import React from "react";
import { DropTarget } from "react-dnd";
// THIS IS THE IMAGES LIST
const backgroundsList = {
tree:
"https://cdn.pixabay.com/photo/2020/02/17/19/33/tree-4857597_960_720.png",
avocado:
"https://cdn.pixabay.com/photo/2020/05/04/18/55/avocado-5130214_960_720.png",
snowman:
"https://cdn.pixabay.com/photo/2019/12/22/01/14/snowman-4711637_960_720.png"
};
const style = {
height: "12rem",
width: "12rem",
marginRight: "1.5rem",
marginBottom: "1.5rem",
color: "white",
padding: "1rem",
textAlign: "center",
fontSize: "1rem",
lineHeight: "normal",
float: "left",
backgroundSize: "contain" // TO FIT DIV
};
export const Dustbin = ({
accepts,
isOver,
canDrop,
connectDropTarget,
lastDroppedItem,
bgImageName
}) => {
const isActive = isOver && canDrop;
let backgroundColor = "#222";
if (isActive) {
backgroundColor = "darkgreen";
} else if (canDrop) {
backgroundColor = "darkkhaki";
}
let backgroundImage = `url(${backgroundsList[bgImageName]})`; // PASS A PROPERTY CALLED bgImageName WITH THE NAME OF THE IMAGE WE WANT.
return connectDropTarget(
<div style={{ ...style, backgroundColor, backgroundImage }}> // APPEND HERE
{isActive
? "Release to drop"
: `This dustbin accepts: ${accepts.join(", ")}`}
{lastDroppedItem && (
<p>Last dropped: {JSON.stringify(lastDroppedItem)}</p>
)}
</div>
);
};
export default DropTarget(
(props) => props.accepts,
{
drop(props, monitor) {
props.onDrop(monitor.getItem());
}
},
(connect, monitor) => ({
connectDropTarget: connect.dropTarget(),
isOver: monitor.isOver(),
canDrop: monitor.canDrop()
})
)(Dustbin);
And in Container.jsx add a property of background image to each dustbin object, and pass it to the component. like this:
Container.jsx
import React, { useState, useCallback } from "react";
import { NativeTypes } from "react-dnd-html5-backend";
import Dustbin from "./Dustbin";
import Box from "./Box";
import { ItemTypes } from "./ItemTypes";
import update from "immutability-helper";
export const Container = () => {
// ADD bgImageName TO EACH DUSTBIN OBJECT
const [dustbins, setDustbins] = useState([
{ accepts: [ItemTypes.GLASS], lastDroppedItem: null, bgImageName: "tree" },
{
accepts: [ItemTypes.FOOD],
lastDroppedItem: null,
bgImageName: "avocado"
},
{
accepts: [ItemTypes.PAPER, ItemTypes.GLASS, NativeTypes.URL],
lastDroppedItem: null,
bgImageName: "snowman"
},
{
accepts: [ItemTypes.PAPER, NativeTypes.FILE],
lastDroppedItem: null,
bgImageName: "tree"
}
]);
const [boxes] = useState([
{ name: "Bottle", type: ItemTypes.GLASS },
{ name: "Banana", type: ItemTypes.FOOD },
{ name: "Magazine", type: ItemTypes.PAPER }
]);
const [droppedBoxNames, setDroppedBoxNames] = useState([]);
function isDropped(boxName) {
return droppedBoxNames.indexOf(boxName) > -1;
}
const handleDrop = useCallback(
(index, item) => {
const { name } = item;
setDroppedBoxNames(
update(droppedBoxNames, name ? { $push: [name] } : { $push: [] })
);
setDustbins(
update(dustbins, {
[index]: {
lastDroppedItem: {
$set: item
}
}
})
);
},
[droppedBoxNames, dustbins]
);
return (
<div>
<div style={{ overflow: "hidden", clear: "both" }}>
{dustbins.map(({ accepts, lastDroppedItem, bgImageName }, index) => (
<Dustbin
accepts={accepts}
lastDroppedItem={lastDroppedItem}
onDrop={(item) => handleDrop(index, item)}
key={index}
bgImageName={bgImageName} // DONT FORGET TO PASS bgImageName PROPERTY TO Dustbin COMPONENT
/>
))}
</div>
<div style={{ overflow: "hidden", clear: "both" }}>
{boxes.map(({ name, type }, index) => (
<Box
name={name}
type={type}
isDropped={isDropped(name)}
key={index}
/>
))}
</div>
</div>
);
};
Codesandbox here
I've done some research on how to trigger CSS animation once the element comes into view, and I've found the answer that makes use of IntersectionObserver and element.classList.add('.some-class-name')
Above method is demonstrated in pure CSS, but I want to implement it with Material-UI. Here is my code.
import React, { useEffect } from 'react';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
root: {
height: '100vh'
},
box: {
opacity: 0,
width: 100,
height: 100,
backgroundColor: 'red'
},
animated: {
animationName: '$fadein',
animationDuration: '1s'
},
'#keyframes fadein': {
'0%': {
opacity: 0
},
'100%': {
opacity: 1
}
},
}));
function App() {
const classes = useStyles();
useEffect(() => {
const observer = new IntersectionObserver((entries, observer) => {
entries.forEach((entry) => {
if (entry.intersectionRatio > 0) {
// trigger animation
entry.target.classList.add('animated');
// remove observer
observer.unobserve(entry.target);
}
});
});
const element = document.getElementById('item');
observer.observe(element);
}, []);
return (
<div>
<div className={classes.root} />
<div id="item" className={classes.box} />
</div>
);
};
export default App;
Unfortunately, the above code isn't working and I think it's because the className 'animated' does not exist. I know Material-UI has internal logic that generates the unique className, so my question is how do I figure out the real className of 'animated'? Or, is there a better way to go about this? Any help would be appreciated.
This is what I came up with.
import React, { useEffect, useState, useRef } from 'react';
import { makeStyles } from '#material-ui/core/styles';
const useStyles = makeStyles((theme) => ({
root: {
height: '100vh'
},
box: {
opacity: 0,
width: 100,
height: 100,
backgroundColor: 'red'
},
animated: {
animationName: '$fadein',
animationDuration: '1s',
animationFillMode: 'forwards'
},
'#keyframes fadein': {
'0%': {
opacity: 0
},
'100%': {
opacity: 1
}
}
}));
function App() {
const classes = useStyles();
const BoxSection = (props) => {
const [isVisible, setVisible] = useState(false);
const domRef = useRef();
useEffect(() => {
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => setVisible(entry.isIntersecting));
});
observer.observe(domRef.current);
return () => observer.unobserve(domRef.current); // clean up
}, []);
return (
<div className={`${classes.box} ${isVisible ? classes.animated : ''}`} ref={domRef}>
{props.children}
</div>
);
};
return (
<div>
<div className={classes.root} />
<BoxSection />
</div>
);
}
export default App;
Basically, I've decided to use state to trigger animation by adding the class like above. I've got some pointers from this article, if anyone is interested.