Updating React Context Issue - reactjs

I am use Suspense to lazy load my page components in my app.
const Page = lazy(() => import('./Page'));
<Suspense>
<MyAuthRoute
path='/page/:id'
component={Page}
/>
</Suspense>
I am also using Context for state management thorough-out the app. Inside of my MyContext, I have:
const [pageId, setPageId] = useState();
<MyContext.Provider value={{pageId, setPageId}}>
{children}
</MyContext.Provider>
In my page component, I want to set pageId in MyContext to the id that was passed in the URL, but I am receiving an error: Cannot update a component (MyContextProvider) while rendering a different component
const { id } = useParams();
const { pageId, setPageId } = useContext(MyContext)
setPageId(id);
Does setPageId need to be in a useEffect? If so, why?

Yes, you'll need to wrap the set call in a useEffect, React added the warning you are seeing in version 16. As a rule of thumb it is recommended to avoid side effects while rendering, there is a GitHub issue where why this warning was added is discussed at great length, where Dan Abramov explains it as:
... The answer to “why” isn’t any more complex than “if one component triggers updates on other components during rendering, it becomes very hard to trace the data flow of your app because it no longer goes from top down”. So if you do that you’re kind of throwing away the benefits of React.
...
Source: Dan's comment on the React Issue

Related

Reactjs: don't let Modal trigger a re-render

In my Reactjs project I have a component that contains a Modal that has its own states and when 1 (or more) of these states change, they trigger a re-render of that component:
import React from "react";
import CustomModalComponent from "./CustomModalComponent";
const MainComponent = () => {
const [isModalOpen,setIsModalOpen] = React.useState(false);
console.log("main component");
return(
<React.Fragment>
<section>Some UI here that can also turn modal state to true</section>
<CustomModalComponent open={isModalOpen} toggleOpen={() => setIsModalOpen(!isModalOpen)} />
</React.Fragment>
);
}
export default MainComponent;
As I said whenever a state changes inside that custom modal component, it triggers a re-render in my main component which is due to the fact that I have a state that changes, but I was wondering if there is a way to change this "behavior" since if my main component is a big one, re-renders will take away from the performance.
You don't need to worry about rerenders until they are a problem. It is a common beginner's error to optimize for reducing rerenders. Sadly, a lot of false material exists online around this that suggests you need to be thinking about this from day 1. Rerenders are usually extremely cheap and simply don't matter.
Keep in mind a "rerender" doesn't mean it loads all the HTML up again in the DOM. Internally React diffs over the state, meaning it only makes small edits when the state changes.

Should we change class component to functional component when we put state into Redux?

I was learning React and created two class components having respective states. Then, I learned about Redux and decided to transfer states into redux store. The question is "Is it best practice to change class componenents into functional components since we get state via props from redux store?"
Functional components with react hooks is the new standard of coding on React. For store management(f.e. redux) you may use as classes as functional components, but most of the libs moved to functional components and you may not use all benefits of last versions of them.
Why I prefer functional components and hooks over classes:
Cleaner render tree. No wrapper components
More flexible code. You
can use useEffect on different state changes, in classes you have
only componentDidUpdate for ANY state/props change
You can define your custom hooks to keep your code clean and shiny
IMHO, yes, I suggest that you should switch from class-based component to functional component as soon as possible.You might not want to know how the class-based components have bugged me so hurt before I decided to go with Hooks. The number of components in my large project is now over 400 (including both smart and dumb components) and keep increasing. Hooks keep my life easier to continue developing and maintaining.
Have a look at this useful article: https://blog.bitsrc.io/why-we-switched-to-react-hooks-48798c42c7f
Basically, this is how we manage state with class-based:
It can be simplified to half the lines of code, achieving the same results with functional component and useState, useEffect:
Please also take a look at this super useful site: https://usehooks.com/
There are many useful custom hooks from the community that you can utilize. Below are the ones that I have been using all the time:
useRouter: Make your life easier with react-router. For example:
import { useRouter } from "./myCustomHooks";
const ShowMeTheLocation = () => {
const router = useRouter();
return <div>Show me my param: {router.match.params.myDesiredParam}</div>;
}
useEventListener: simplify your event handler without using componentDidMount and componentWillUnmount to subscribe/unsubscribe. For example, I have a button that needs to bind a keypress event:
import { useEventListener } from "./myCustomHooks";
const FunctionButton = () => {
const keydownHandler = event => { // handle some keydown actions };
const keyupHandler = event => { // handle some keyup actions };
// just simple like this
useEventListener("keydown", keydownHandler);
useEventListener("keyup", keyupHandler);
}
useAuth: authenticate your user.
import { useAuth } from "./use-auth.js";
const Navbar = (props) => {
// Get auth state and re-render anytime it changes
const auth = useAuth();
// if user is authenticated, then show user email, else show Login
return <div>{auth.user? auth.user.email: "Login"}</div>;
}
useRequireAuth: handle redirect your user if they are signed out and trying to view a page that should require them to be authenticated. This is composed by useRouter and useAuth above.
import { useRequireAuth } from "./myCustomHooks";
// Dashboard is a page that need authentication to view
const Dashboard = () => {
const isAuth = useRequireAuth();
// If isAuth is null (still fetching data)
// or false (logged out, above hook will redirect)
// then show loading indicator.
if (isAuth) {
return <div>Fetching data, please wait!</div>
}
// {...{ isAuth }} is similar to:
// isAuth={isAuth}
return <Dashboard {...{ isAuth }} />
}
Hope this helps!
First of All, States can be used only in Class Component. In React's latest version there's a huge update that allows functional components to declare and use state using React-Hooks. So, the best practice I would personally suggest you is to use Class Component when you use the Redux Store. As you're a beginner, Please use a functional component where you don't use any state or props and just render DOM elements (Note: Functional components can accept props). Once you learn the differences properly, go with React-Hooks.
I hope it helps!! Happy Coding!!

Component unmounts when parent state changes

I'm using React 16.8.2, and I'm having a problem with children of my component unmounting whenever state is changed in the app component.
Here's the scenario:
I have App.jsx (a functional component) with a number of state variables (useState)
The setters for some of these state variables are passed down the tree through a Context provider (useContext in the descendent)
I have a menu component (descendent of app), that invokes these setters to (for example) show a modal dialog
I have a modal dialog component (child of App), that uses the state variable as a property to determine whether it is open or not -- standard React stuff I think.
My problem: when any state variables in App are changed (through hooks of course), the children of App are unmounted and remounted- even if they have no connection to the state being changed. They aren't just re-rendered - the children are unmounted and their state is re-initialized. So the fields are cleared on my dialog, when they shouldn't be, for example.
This is already a fairly complex application, so I've spent a lot of time today isolating the problem. I then set up a simple create-react-app to try to replicate this behavior there - but this test app behaves like it should. Changing parent state, whether through a prop callback, or through a context-provided callback from the child - re-renders but does not unmount/remount and child state remains intact.
But in my real app, the components re-mount and child state gets re-initialized.
I've simplified it down to the barest that I can - I'm setting a fake state variable "foo" with "setFoo" through the Context from the child. Even though foo is not used by any component, changing the value of foo causes the children of App to unmount/remount.
In App.jsx:
const App = props => {
const [foo, setFoo] = useState(false);
// ...
const appControl = {
toggleFoo: () => setFoo(!foo);
};
// ...
return (
<AppContext.Provider value={appControl}>
... a bunch of stuff not using foo anywhere
... including, deep down:
<Menu />
</AppContext.Provider>
);
};
In Menu.jsx:
const Menu = props => {
const appControl = useContext(AppContext);
// ...
return (
... super simplified for test
<div onClick={appControl.toggleFoo}>
Toggle Foo
</div>
);
};
If I understand state properly, I do believe that changing state should result in children being re-rendered, but not re-mounted. This is what I'm seeing in my simple create-react-app test, but not in my real app.
I do see that I'm not on the latest React - perhaps upgrading will fix this?
Thanks for any insight on what I may be doing wrong, or misunderstanding here.
Solved. This is an interesting one. Here's what happened.
In my App component, I had a fairly deep tree of HOC's. Due to some dubious decisions on my part, I ended up breaking App into two components. App and AppCore. I had a reason for it, and it seemed to make sense at 3am. But to be both quick and dirty, I stuck AppCore as a const, inside my App function. I remember thinking to myself "I wonder what problems this will cause?" Now I know. Perhaps a React expert can fully explain this one to me though, as I don't see the difference between JSX assigned to a constant, and JSX returned directly. But there clearly is, and this is simple to reproduce.
To reproduce, create-react-app a test app:
create-react-app test
cd test
Then replace the contents of App.js with:
import React, { useState, useEffect } from "react";
const Menu = props => <div onClick={props.click}>Toggle Foo</div>;
const Test = props => {
useEffect(() => {
console.log("mounted");
return () => console.log("unmounted");
}, []);
return null;
};
const App = props => {
const [foo, setFoo] = useState(false);
// this is the root of the problem
// move this line outside of the function body
// and it mounts/unmounts correctly
const AppCore = props => <Test />;
return (
<>
<Menu click={() => setFoo(!foo)} />
<AppCore />
</>
);
};
export default App;
Then npm start, and when you click on "Toggle Foo" you'll see that the Test component is unmounted/remounted.
The solution here, is to simply move AppCore out of the function body. In my real app, this means I have some refactoring to do.
I wonder if this would be considered a React issue?

How to keep React new Context API state when routing between Components?

Summary:
1) Do you know how to keep the state of a Context Provider present when it is mounted/unmounted through routing?
2) Or do you know a well maintained Flux implementation that supports multiple separated stores?
In detail:
Besides React components own state I've been using mostly redux so far. Besides not loving the idea of having every state managed globally, even though it might only be relevant for a subtree, it also becomes an issue for my new project.
We want to dynamically load components and add them via routing to the app. To be able to have components ready for plug and play, we want them to take care of their own state (store it, request it from the server, provide a strategy to modify it).
I read about how to dynamically add reducers to the global store with redux, but I actually find the approach of Reacts Context API much nicer where I can encapsulate some state in a Provider and can consume it wherever I need it.
The only issue I have is, that a Provider and a Consumer are React components, so if they are part of a component, that is mounted and unmounted through routing, the state that might have been created or fetched once, is gone.
It seems to me that there is no way to solve that, besides temporarily storing the state in the localstorage or on the server. If there is, please let me know!!!
If there shouldn't be a better solution:
I also thought about a more original Flux implementation that would allow multiple stores, which could be encapsulated with the relavant component subtree. But I haven't really found any well maintained Flux implementation besides Redux. Mobx being the exception, but I really prefer the reducer solution of Redux over the observable solution of Mobx. So again, if you know a multi store Flux implementation that is well maintained, please let me know!!!
I would be really happy about some feedback and hope you can point me into a direction that is more satisfiying than dynamic reducer Redux or temporarily persisted Context state.
Thanks a lot in advance!
Sorry that it's quite a late answer
Are you using React Router?
The state should be persisted and it shouldn't clear if you are navigating correctly. There should be no page reload as this will cause the state to clear.
Here is an example:
import { Router as RootRouter } from 'react-router-dom';
import Router from './routes/Router';
const App = () => {
return (
<MyContext value={useReducer(myReducer, initialState)}>
<RootRouter history={browserHistory}>
<Router />
</RootRouter>
</AuthContext>
);
}
import About from '../components/About';
const Router = () => {
return (
<Switch>
<Route exact path='/about' component={About}></Route>
</Switch>
}
On your main home component, you have to use a Link or Navlink to "switch" between components. Therefore, you'll have something like...
import { Link } from 'react-router-dom';
<Link to="/about">About</Link>
This will navigate you to the about page in which you can still access the context stage where nothing is cleared.
So I figured out a way to work around the problem with Context (first question): I store the state of the Provider component in a variable. That way, when that component is mounted again, it uses the "persisted" state as the initial value for it's state.
let persistedState = {};
const Context = React.createContext();
export class Provider extends React.PureComponent {
state = { ...persistedState };
updateState = (nextState) => this.setState(nextState, () => {
persistedState = {...this.state};
});
render() {
return (
<Context.Provider value={this.state}>
{this.props.children}
</Context.Provider>
);
}
}

Limit Redux to update only components affected by the change

trying to understand React-Redux, i find it unusual that all my components get new props when ever any slice of the state get changed. so is this by design or i'm doing something wrong ?
example App
class App extends React.Component {
render(){return (
<div>
<Navbar data={this.props.navbar} />
<Content data={this.props.content} />
</div>);
}
}
select (state) => ({ navbar:state.navbar, content:state.content});
export default connect(select)(App);
Components
export const NavbarForm = props => {
console.log('RENDERING with props--->',props);
return (<h1>NAV {props.data.val}</h1>);
};
export const ContentForm = props => {
console.log('RENDERING CONTENT with props--->',props);
return (<h1>CONTENT {props.data.val}</h1>);
};
////////INDEX.js//////
const placeholderReducer = (state={val:0},action)=>{
//will update val to current time if action start with test/;
if(action.type.indexOf('TEST/') === 0)return {val:Date.now();}
return state;
}
export const rootReducer = combineReducers({
navbar:placeholderReducer,
content: (state,action)=>(state || {}), //**this will never do a thing.. so content should never updates right !!**
});
const store = createStore(rootReducer, {}, applyMiddleware(thunk));
render( <Provider store={store}> <App /></Provider>, document.getElementById('app')
);
setInterval(()=>{ store.dispatch(()=>{type:'TEST/BOOM'}) },3000);
okay in this app, what i expect is that Navbar component will get updated every 3000ms while content component will never updates because its reducer will always return the same state.
yet i find it really strange that both components does reRender every time an action is fired.
is this by design ? should i worry about performance if my app has 100+ component ?
This is entirely by design. React assumes that your entire app will be re-rendered from the top down by default, or at least a given subtree will be re-rendered if a certain component does a setState or something similar.
Because you only have the very top component in your app connected, everything from there on down is React's standard behavior. A parent component re-renders, causing all of its children to re-render, causing all of their children to re-render, and so on down.
The core approach to improving UI performance in React is to use the shouldComponentUpdate lifecycle method to check incoming props and return false if the component does not need to re-render. This will cause React to skip re-rendering that component and all of its descendants. Comparisons in shouldComponentUpdate are generally done using shallow reference equality, which is where the "same object references means don't update" thing becomes useful.
When using Redux and connect, you will almost always find yourself using connect on many different components in your UI. This provides a number of benefits. Components can individually extract the pieces of the store state that they need, rather than having to hand them all down from the root component. In addition, connect implements a default shouldComponentUpdate for you, and does a similar check on the values you return from your mapStateToProps function. So, in a sense, using connect on multiple components tends to give you a "free win" in regards to performance.
Further reading on the topic:
Redux FAQ: Connecting multiple components
React/Redux Links: Performance articles
Yes this is by design. Action is dispatched. Reducers run. Store subscribers get notified "the store has changed". Connected components are store subscribers.
Typically you just don't worry about it until you can actually measure a performance problem that you can attribute to this - don't prematurely optimize.
If you find out that it is a problem, then you can do one of the following:
Add a shouldComponentUpdate method to your components so they can see that the props they received aren't different and do not need to render (there are lots of Pure Render mixins & high order components available to make this easy)
Instead of connecting the top-level app, connect the Navbar and Content components directly. The App will never rerender, but the children will if the store changes. And react-redux automatically uses shouldComponentUpdate to only re-render the connected components that actually have new props.

Resources