React Hook not update conditional component - reactjs

thank for your time and sorry if this question was already asked, but honestly i didn't find any solution.
i'm new on React and i'm trying to create a website-portfolio, i finish my project but now i'm trying to implement a Spinner loader while my video background is loading.
i'm going to simplify the code only for explain better the problem.
Here's my classes
function App() {
const [isLoading, setIsLoading] = useState(true);
return isLoading ? (
<LoaderContainer>
<Loader type="Puff" color="rgb(241, 113, 27)" height={100} width={100} />
</LoaderContainer>
) : (
<Router>
<Home isLoading={isLoading} setIsLoading={setIsLoading} />
</Router>
);
}
export default App;
//HOME functional component-------------------
const Home = ({ isLoading, setIsLoading }) => {
return (
<>
//...other components
<HeroSection isLoading={isLoading} setIsLoading={setIsLoading} />
//...other components
</>
);
};
export default Home;
Now.. in HeroSection there is a video with this option:
" onLoadStart={() => setIsLoading(false)}"
this working fine but not for update the LoaderContainer with the Router one.
i really don't now why. if i try to put an useEffect inside App with a timer and try to update isLoading it's working fine!! also isLoading is used inside of HomeContainer for render a String and it's updating correctly! it seems that isLoading in the conditional statement not re-render the component but only if setIsLoading is called from the child (Home).
Thank all for the support!

Because in this case there's an endless loop: isLoading === true -> Home not render -> onLoadStart not trigger -> no chance to set isLoading false -> ...
You can't avoid rendering <HeroSection/> even in loading because HeroSection is responsible to terminate the loading. So the solution will be controlling the opacity or stacking context to make <Home/> invisible at the start instead of not rendering at all

Related

How can I prevent re-render after state changed in React Hooks?

I am trying to build an app but the problem is when I change a state, all the components re-render.
const App=()=>{
const [showMenu,setshowMenu]=useState(false)
return(
<>
<Header showMenu={showMenu} setShowMenu={setShowMenu}/>
<MainArea/>
{showMenu ? <Menu/> : null}
<Footer/>
</>
)
}
When I set showMenu to true by button, a menu appears but the problem is all my components (Header,MainArea,Footer) do re-render. I don't want that. How can I solve this problem?
You can use useMemo hook.
It prevents specific jsx contents from rerendering, even when the state that those jsx contents/components use get updated.
const App=()=>{
// you can only memoize parts that do not require use of the updated variable in this case. It means that Header cannot be memoized.
const mainArea = React.useMemo( () => <MainArea/>, [] );
const footer = React.useMemo( () => <Footer/>, [] );
const [showMenu,setShowMenu]=useState(false)
return(
<>
<Header showMenu={showMenu} setShowMenu={setShowMenu}/>
{mainArea}
{showMenu ? <Menu/> : null}
{footer}
</>
)
}
EDIT:
Does the really need the state of the showMenu? Or we can only pass the setShowMenu to it?
If so, then you can also memoize the Header component into memo chunk like:
const header = React.useMemo( () => , [] );
You can use React memo (or PureComponent if you use classes) on the components that you don't want to re-render (MainArea,Footer). This way when an update is forced by their parent they will first make a check if any of their props changed and if not (which is your case), re-render will be skipped.
However it's advisable to perform memoization on expensive components only instead of wrapping everything with React.memo, because memoization also introduces some overhead.
In this case, the state is only for header component. U can bring the state inside the Header component. U can read here for further explaination https://overreacted.io/before-you-memo/
Also here another good explanation How to prevent re-rendering of components that have not changed?
To prevent re-rendering of reusable component while changing other states, We can use React.memo()
const MainArea = React.useMemo(() => {
return <div />;
});
const Footer = React.useMemo(() => {
return <div />;
});
const App = () => {
const [showMenu,setshowMenu]=useState(false)
return(
<>
<Header showMenu={showMenu} setShowMenu={setShowMenu}/>
<MainArea/>
{showMenu ? <Menu/> : null}
<Footer/>
</>
)
}

Cannot set a Loader while big component renders

I want to show a loader while my Analysis component (and its children) load but I cannot for the life of me get this to work.
The main idea is:
<Context>
<Admin>
<Analysis (with other child components) />
</Admin>
</Context>
The Admin component has a sidebar and the main view that is based upon a Switch/Router as seen here:
const Admin = () => {
const classes = useStyles();
const { pageLoading } = useContext();
return (
<>
<Box display="flex">
<Sidebar
routes={sidebarRoutes}
logo={{
innerLink: "/",
imgAlt: "...",
}}
/>
<Box position="relative" flex="1" className={classes.mainContent}>
<Switch>
{ pageLoading ?
<Loader />
: (<Route
path="/admin/stats"
component={Analysis}
key={"stats"} />) }
</Switch>
<Container
maxWidth={false}
component={Box}
classes={{ root: classes.containerRoot }}
>
</Container>
</Box>
</Box>
</>
);
};
However, since the Analysis component and its children take a while to load, I want to display a loader like this (no API calls are made):
My current loading screen
See, the problem is that I tried setting the context loading state in my useEffect hook inside the Sidebar component, like this:
const handleTabClick = () => {
setPageLoading(true);
};
And then stopping the loader using the same context after the Analysis component mounts:
React.useEffect(() => {
setPageLoading(false);
}, []);
...And the loader gets stuck forever...
My conclusion is that when the Context component has its state changed and re-renders, the Admin component then has pageLoading field set to "true" and doesn't display the Analysis component.
What can I do to fix this? It's not crucial for my website but I'm trying to find a solution to this clunkiness. Clicking on the "Analysis" tab changes URLs in my browser search bar above, but nothing happens for a few seconds while the Analysis component loads. Looks stupid.
I managed to solve this with following ugly solution:
import React, { useEffect, useState } from "react";
import { Route } from "react-router-dom";
import Loader from "./Loader";
function SubPageLoader({path, component, key}) {
const [updates, setUpdates] = useState(0);
useEffect(() => {
// stupid solution to postpone loading of children, so we can show a loading screen. TODO: find a better method
const interval = setInterval(() => {
setUpdates(updates => updates + 1);
}, 1);
if (updates > 1) {
clearInterval(interval);
}
return () => clearInterval(interval);
}, [updates]);
return (
<>
{updates == 0 ? <Loader />
: <Route
path={path}
component={component}
key={key}
/> }
</>
);
}
export default SubPageLoader;
This basically postpones the children load by 1 millisecond and that way we can instantly enter the new tab and show a loader without having to update parent states.

How do components re-render in redux using hooks

I am trying to conceptualize redux and its working, and after some testing, I have noticed a thing. I would like to quote this example
lets say, I have a single reducer (a boolean variable). based on that variable, the following code happens.
reducer
const initState = { isLoggedIn: false };
const isLoggedInReducer = (state = initState, action) => {
switch (action.type) {
case "LOG_IN":
return { ...state,isLoggedIn: true };
case "LOG_OUT":
return { ...state,isLoggedIn: false };
default:
return state;
}
};
export default isLoggedInReducer;
action
export const logIn = () => {
return {
type:'LOG_IN'
}
}
export const logOut = () => {
return {
type:'LOG_OUT'
}
}
index.js
<Provider store={createStore(isLoggedInReducer)}>
<Router>
<Switch>
<Route path="/" exact>
<AppScreen />
</Route>
<Route path="/auth">
<AuthScreen />
</Route>
<Route path="*">
<NotFoundScreen />
</Route>
</Switch>
</Router>
</Provider>
so, my app firstly directs the user to a component called "mainScreen" , which is as follows
const AppScreen = () => {
let isLoggedIn = useSelector((state) => state.isLoggedIn);
if (isLoggedIn)
return (
<>
<button onClick={() => dispatch(logOut())}>unauthenticate</button>
<NavBar />
<Content />
<BottomBar />
</>
);
else{
return (
<>
<Redirect to="/auth" push />
</>
);
}
};
so if the reducer state has value TRUE , my navbar and stuff is shown, else the user is redirected to the "authScreen" , which is as
const AuthScreen = () => {
let isLoggedIn = useSelector((state) => state.isLoggedIn);
const dispatch = useDispatch();
return isLoggedIn ? (
<>
<Redirect to="/" push />
</>
) : (
<>
<h1> auth is {isLoggedIn?"true":"false"}</h1>
<button onClick={() => dispatch(logIn())}>authenticate</button>
</>
);
};
This creates a setup where "authScreen" can toggle the reducer to TRUE and it re-renders, and finds that reducer is TRUE, so it renders the "mainScreen". Vice versa for "MainScreen"
Now, what components actually re-render ? If I place my authenticate button in the "navbar" instead as a sibling to "navbar" , will it re-render the "navbar" or the "mainScreen" ?
How does redux calculate what component to re-render when a peice of state changes ? How does the useSelector fit in, when I did not even use "connect".
Using hooks with redux made it very confusing. I am sorry if my explanation is hard to understand. The code actually works, I just don't know how.
Any piece of information is appreciated!
Using Redux with a UI always follows the same basic steps:
Render components using initial state
Call store.subscribe() to be notified when actions are dispatched
Call store.getState() to read the latest data
Diff old and new values needed by this component to see if anything actually changed. If not, the component doesn't need to do anything
Update UI with the latest data
React-Redux does that work for you internally.
So, useSelector decides whether a component should re-render based on whatever data you return in your selector functions:
https://redux.js.org/tutorials/fundamentals/part-5-ui-react#reading-state-from-the-store-with-useselector
If the selector return value changes after an action was dispatched, useSelector forces that component to re-render. From there, normal React rendering behavior kicks in, and all child components are re-rendered by default.
Please read my post The History and Implementation of React-Redux and talk A Deep Dive into React-Redux for details on how React-Redux actually implements this behavior.

Can two React distinct and mounted components communicate with each other using props?

A simple example for explaining my question:
ButtonComponent is a component mounted on <div id="btn-container"> element
ModalComponent is a component mounted on <div id="modal-container"> element
So two components mounted on different elements in different places in the page, not sharing a common React element.
ModalComponent receive a prop named isOpen to trigger its visibility:
<ModalComponent isOpened={isOpened} />
The ButtonComponent should, in some way, pass the prop isOpen to the ModalComponent without sharing the same "root" component. Is this even possible?
Of course the "normal way" to do this would be a ButtonPlusModalComponent like the following:
export const ButtonPlusModalComponent = () => {
const [isOpened, setIsOpened] = useState(false);
return (
<>
<ButtonComponent onClick={() => setIsOpened(true)} />
<ModalComponent isOpened={isOpened} />
</>
);
};
I'm using React with a regular PHP application, so... not a complete "full" React application, only some parts and portions of the page are React components, no router at all.
So, I have that button somewhere in the page. That button should open the modal component, which is a... React component placed elsewhere in the page.
EDIT: explain why the question.
You must both component to share a common parent, thats how React works as Data Flows Down.
If you can, couple them into the same tree and use common solutions like Context API.
But, you describing a situation where the components are decoupled, so in order to have a common parent and mount a component into another HTML element, you need to use React.Portal.
Therefore you need one component to mount the other using Portals:
<body>
<div id="modal-container"></div>
<div id="btn-container"></div>
</body>
const Modal = ({ open }) => open && <>Modal</>;
const App = () => {
const [open, toggleOpen] = useReducer((p) => !p, true);
return (
<>
<button onClick={toggleOpen}>toggle</button>
{ReactDOM.createPortal(
<Modal open={open} />,
document.getElementById("btn-container")
)}
</>
);
};
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("modal-container")
);

React constructor(), componentDidmount with props variable

Where I can call the constructor() and componentDidmount event with below code:
export const Home = props => (props.isAuthenticated ? (
<DashBoard {...props} />
) : (<Marketing {...props} />));
What is the meaning of the above code and how it's work?
This is a functional component, correctly formatted is probably a little easier to read:
export const Home = props => (
props.isAuthenticated ? (
<DashBoard {...props} /> // if authenticated return and render Dashboard
) : (
<Marketing {...props} /> // else return and render Marketing
)
);
In functional components use the useEffect hook with an empty dependency array to achieve the equivalent of a class-based component's componentDidMount. Hooks are called on mount and whenever a variable in its dependency array are updated.
effect hook
export const Home = props => {
useEffect(() => console.log("I just mounted!", []); // empty so called once when the component is mounted
return (
props.isAuthenticated ? (
<DashBoard {...props} /> // is authenticated return and render Dashboard
) : (
<Marketing {...props} /> // else return and render Marketing
)
);
};
You cannot use react lifecycle hooks in a functional component. Refer to react documentation below for usage of lifecycle hooks, and to convert functional components to class components.
https://reactjs.org/docs/state-and-lifecycle.html
export default class Home extends React.Component {
constructor(props) {
super(props);
}
componentDidMount() {}
render() {
const { isAuthenticated } = this.props;
return (
<>
{isAuthenticated ? <DashBoard {...this.props} /> : <Marketing {...this.props} />}
</>
);
}
}
export const Home = props => (props.isAuthenticated ? (
<DashBoard {...props} />
) : (<Marketing {...props} />));
Details
So the above code is a functional component, currently functional components can handle all the lifecycle methods that we use in class based components
So prev, before 16.8 of reactjs we can have state and life cycle methods in a functional components, It was only used for rendering the elements like as a presentational components. So at a point for complex applications we need to convert the functional components to class based components to handle a single state change
So this made the evolution of hooks, you can read more on the official docs of react js
So comming to your case if you need to call the method in componentDidMount, you can call as shown below
useEffect(() => {
// your logic same as componentDidMount in class based components
}, [])
So the second argument is the dependencies for the useEffect to trigger
if you pass it as like this it will call every time
useEffect(() => {})
If you pass it as like this it will call whenever the passed variable changes from props or state
useEffect(() => {}, [data, userName])
I hope this will give a better understanding of the problem

Resources