React.js shouldComponentUpdate() and react-router Link - reactjs

I currently have a doubt about the correct combined implementation of react-router Link navigation and shouldComponentUpdate() on the root application level.
That is, I have a root component called App.jsx which contains a global component with a header, footer, sidebar etc and this same component has an ajax long-poll which retrieves new registrations in the system and updates the state when new users register.
Since I don't want to push a re-render to the component (and therefore all it's children) on ajax responses that don't have updates I decided to make use of the lovely shouldComponentUpdate() method.
So, I came up with something like this - noting that I'm making use of lo-dash:
shouldComponentUpdate (/*prevProps*/, prevState) {
return !_.isEqual(this.state,prevState);
}
With this the component correctly ignores irrelevant responses about the latest registrations.
Now, the problem appears when I have to make the routing. To clarify before, this is the kind of structure of the render():
Note: the _routerTransitionKey is just a helper I have to not make transitions when I'm navigating internal views state and it's working correctly.
<Grid key='app' id="wrapper" className="no-padding">
<Header user={this.state.user} allRegistrations={this.state.allRegistrations}/>
<section id="page-wrapper">
<NotificationArea key='internalNotification' />
<RouteHandler key={_routerTransitionKey} user={this.state.user} allRegistrations={this.state.allRegistrations}/>
</section>
</Grid>
Because I have the RouteHandler inside this global component, I have the issue that a change in the route is completely ignored by it, since the application state itself didn't change. That causes the component to never trigger the render() on navigation and therefore never update the RouteHandler.
What I needed would be something like:
shouldComponentUpdate (/*prevProps*/, prevState) {
return !_.isEqual(this.state,prevState) || ROUTE_CHANGED ;
}
My question is: does anybody out there knows of a clever approach to this issue? I'm trying to avoid having to create yet another wrapping component to handle the Routes before they reach this App component I currently have...

So, after the tip from #WayneC, even though the react-router doesn't inject the props directly into the react component props, there's a possible way inspired by that approach.
I achieved what I wanted by doing a slight change using not the this.props, but instead the this.context.router.getCurrentPath()
So now the solution looks like this:
shouldComponentUpdate (/*nextProps*/, nextState) {
return !_.isEqual(this.state,nextState) || this.context.router.getCurrentPath() !== _routerTransitionKey;
}
Just to make it clearer, my _routerTransitionKey gets its value from an imported Util that looks mostly like this:
var Utils = {
Router: {
TransitionKey: {
get: function(){
return Router.HistoryLocation.getCurrentPath();
}
}
}
}
_routerTransitionKey = Utils.Router.TransitionKey.get();
This _routerTransitionKey is scoped in an upper level, and I modify it on every render(), so that I keep track of it for later comparison.
And... that's it.

Related

React Context always returns EMPTY

I have a Search parent component and a SideBar child component, I am trying to get context in SideBar, but everytime it returns empty.
I followed the tutorial exactly like: https://itnext.io/manage-react-state-without-redux-a1d03403d360
but it never worked, anyone know what I did wrong?
Here is the codesandbox link to the project: https://codesandbox.io/s/vigilant-elion-3li7v
I wrote that article.
To solve your specific problem:
When using the HOC withStore you're injecting the prop store into the wrapped component: <WrappedComponent store={context}.
The value of the prop store is an object that contains 3 functions: get, set, and remove.
So, instead of printing it, you should use it. For example this.props.store.get("currentAlbums") or this.props.store.set("currentAlbums", [album1, album2]).
This example is forked by your code: https://codesandbox.io/s/nameless-wood-ycps6
However
Don't rewrite the article code, but use the library: https://www.npmjs.com/package/#spyna/react-store which is already packed, tested, and has more features.
An event better solution is to use this library: https://www.npmjs.com/package/react-context-hook. That is the new version of the one in that article.
This is an example of a sidebar that updates another component content: https://codesandbox.io/s/react-context-hook-sidebar-xxwkm
Be careful when using react context API
Using the React Context API to manage the global state of an application has some performance issues, because each time the context changes, every child component is updated.
So, I don't recommend using it for large projects.
The library https://www.npmjs.com/package/#spyna/react-store has this issue.
The library https://www.npmjs.com/package/react-context-hook does not.
You pass the store as a prop, so to access it, you need this.props.store in your SideBar.
Not this.state.store
Create a wrapping App component around Search and Sidebar:
const App = props => (
<div>
<Search />
<SideBar />
</div>
);
export default createStore(App);
Now you can manipulate state with set and get that you have available in child components Search and Sidebar.
In Search component you can have something like:
componentDidMount() {
this.props.store.set("showModal", this.state.showModal);
}
also wrapped with withStore(Search) ofc.
and in SideBar you can now call:
render() {
return (
<div>
{"Sidebar: this.state.store: ---> " +
JSON.stringify(this.props.store.get("showModal"))}
}
</div>
);
}
and you will get the output.

How to globally disable/hide/replace a component by name in React?

I have a large React app and I have a few components that I would like to completely disable from a config or global level. Is there any kind of global hook that I can use that is called before any component is rendered? If so, I imagine I can check the name of the component and return null if the name is on the disabled list. How would you do this?
There are a lot of ways to do this:
React's Context API allows you pass props through every level of the component tree so you can use them as flags to enable/disable components. Should be used sparingly however.
Higher Order Components are basically just functions that return a component. You could wrap your components in logic to render them as needed.
Or of course you could use a global state manager like redux to set global states.
There are many ways to do this, so, I'll just describe one simple way: using references and updating the states accordingly.
Full working feature hide/showing sandbox online: codesandbox.io ReactJS Feature Hide/Show Demo
Defined are two classes, class Feature extends React.Component and class App extends React.Component. The render() for <Feature/> is...
render() {
if (!this.state.enabled) {
return <div />;
}
return (
<div className="Feature">
<h1>My Feature!</h1>
</div>
);
}
And the option for enabling/disabling a feature in <App /> would handle display/hiding like so...
handleOnClick(e) {
if (e.target.checked) {
this.feature.setState({ enabled: true });
} else {
this.feature.setState({ enabled: false });
}
}
Of course, you need to make sure that <Feature /> has the reference set...
<Feature
ref={instance => {
this.feature = instance;
}}
/>
If you need simplest solution just use browser global vars and check it in render.
render() {
if( window.globalFlag ) return null
return (
<div> feature content...
Drawbacks:
modifying component,
using global scope,
some unnecessary code can be run earlier (f.e. constructor) and later (f.e. componentDidMount).
Use HOCs - wrap your component - connecting with global store using redux or context API.
<FlagsProvider store={flagStore}>
<SomeComponent_1>
<SomeComponent_2>
<FlagsConsumer flag="someFeatureFlag">
<SomeFeatureComponent />
<FlagsConsumer/> connects to store (redux connect would be an inner wrapper - composing HOCs) and conditionally renders <SomeFeatureComponent /> (or null).
Of course HOC can pass received props to wrapped component - it can be functionally transparent.
Don't reinvent the wheel - use some ready module, read tutorials, google for sth suitable.
HOC can also play a role of A/B testing.

Bind method to react state changes

Given the code below, I would like the transform() method to run anytime this.props.code changes.
class Editor extends Component {
render() {
return (
<div id="pseudo-editor" />
);
}
transform() {
var editor = ace.edit("pseudo-editor");
editor.setValue(this.props.code,1);
}
}
I am using react-redux and the state to props binding works as intended.
But Im not quite sure how to approach method binding. I guess its not an alternative to fit my JS code editors API calls inside the render method. Problably a simple solution to this one but could not find an example of which pattern to use here. Thankful for any help.
Use componentWillReceiveProps lifecycle method, it will get called whenever any change happens to props values, check the previous and nextProps values if they are not same call the transform method.
Like this:
componentWillReceiveProps(nextProps){
if(this.props.code != nextProps.code)
this.transform();
}
As per DOC:
componentWillReceiveProps() is invoked before a mounted component
receives new props. If you need to update the state in response to
prop changes (for example, to reset it), you may compare this.props
and nextProps and perform state transitions using this.setState() in
this method.

Will ReactDOM.hydrate() trigger lifecycle methods on the client?

From the React 16 docs about ReactDOM.hydrate(),
Same as render(), but is used to hydrate a container whose HTML contents were rendered by ReactDOMServer. React will attempt to attach event listeners to the existing markup.
Will ReactDOM.hydrate() also trigger lifecycle methods on the client such as componentWillMount(), componentDidMount() during initial render?
Will render() method be called on the client during hydration? I suppose not, because that's the difference between ReactDOM.render() and ReactDOM.hydrate()?
If render method won't be called on the client, we wouldn't expect componentDidMount() lifecycle method to be triggered.
If none of the lifecycle methods are called on the client, how would we know when has React finished rendering. I suppose the callback in the following syntax:
ReactDOM.hydrate(element, container[, callback])
I want to understand if there are lifecycle methods / hooks (which give more control over the application) available when React is "attempting to attach event listeners to existing markup".
Since ReactDOM.hydrate is (and should be) called on the client then YES it is supposed to run componentDidMount. componentWillMount is already called when rendered on the server.
componentDidMount does not run on the server, therefore when you call hydrate, the app runs the event.
Think about hydrate as a different render method. It does render but not in the same way. It looks for mismatches between your server rendered React and your client React. It does not render everything again.
React expects that the rendered content is identical between the server and the client. It can patch up differences in text content (such as timestamps), but you should treat mismatches as bugs and fix them
However you might want to do some crazy stuff like rendering something completely different on the client side (than what was rendered on the server). For this pay attention to this paragraph
If you intentionally need to render something different on the server and the client, you can do a two-pass rendering. Components that render something different on the client can read a state variable like this.state.isClient, which you can set to true in componentDidMount(). This way the initial render pass will render the same content as the server, avoiding mismatches, but an additional pass will happen synchronously right after hydration. Note that this approach will make your components slower because they have to render twice, so use it with caution.
So as you can see it does a render pass. If there are no mismatches React is optimized for that.
I hope it was clarifying. I speak from experience with React SSR and basic understanding of reading the docs.
The rendered elements probably aren't same between server and client, because initially the elements are rendered into texts at the server in memory, therefore they are not mounted. When the content is moved to client, it can be re-attached to react via hydrate which is fake "render" to wire with the rest of react functionalities, such as events.
In order to tell when it's hydated, here's a piece from internet which I found clearly stated the above rational. https://dev.to/merri/understanding-react-ssr-spa-hydration-1hcf?signin=true
const HydrateContext = createContext('hydrated')
export function useIsHydrated() {
return useContext(HydrateContext)
}
export function IsHydratedProvider({ children }) {
const [isHydrated, setIsHydrated] = useState(false)
useEffect(() => {
setIsHydrated(true)
}, [])
return (
<HydrateContext.Provider value={isHydrated}>
{children}
</HydrateContext.Provider>
)
}
To use it,
function MyComponent() {
const isHydrated = useIsHydrated()
return !isHydrated ? 'Initial render' : 'SPA mode'
}
function App() {
return (
<IsHydratedProvider>
<MyComponent />
</IsHydratedProvider>
)
}
It feels to me, any rendered component teleports from the server to the client.
p.s Here's another article which talks about the second render after the mount, https://medium.com/swlh/how-to-use-useeffect-on-server-side-654932c51b13
I read the type of ReactDOM.hydrate in TypeScript system:
(
element: SFCElement<any> | Array<SFCElement<any>>,
container: Container| null,
callback?: () => void
): void;
And example to the above declaration:
ReactDOM.hydrate(
<App />, // element
document.getElementById('root'), // container
() => { // callback
/* do what you want after hydration */
}
);

Immutability is an implementation detail in React?

I recently watched a talk by David Nolen where he says that 'immutability is an implementation detail in React'?
What does this mean and if this wasn't the case, how would React be different?
What does "implementation detail" mean:
I would summarize as:
Immutability is a detail of react that you have to implement yourself.
BTW: "Detail" is this case can still mean a lot of work.
React depends on props and state to be immutable.
React does not make props or state immutable for you. You have to ensure that in your code yourself.
So the following code is a recipe for disaster:
// DO NOT TRY THIS AT HOME
var customerObject = { name: "Bill" };
this.setState( customer: customerObject }; // valid react code, triggering re-render
...
customerObject.name = "Karl";
// state still has the same customerObject,
// but the contents of the object have changed. This is where things break down.
React has to ensure that its internal virtual DOM, and all props and states, are always in sync with the actual DOM.
So every time something changes anywhere in a prop or state, react needs to run its render cycle.
How would react be different without immutability:
Without immutability your react implementation may not work properly.
If react were not designed for immutability, then it would not be react (i.e. a state machine) but a different beast altogether.
Immutable Data Structure with ReactJS
The first of all, react team strongly recommend applying immutable data structure like Immutability Helpers or immutable.js. Why? Because we can use "shallow comparison" to increase component re-render performance. like
MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
return !shallowEqual(this.props, nextProps) ||
!shallowEqual(this.state, nextState);
}
render() {
...
}
}
According to immutability, the data alway return a new reference if it has been changed. We can easy use shallowEqual(only check reference whether is same or not) to determine component will re-render. If we dont use immutable data, we have to check props or state object deeply not reference to make sure re-rendering.
As for my understanding, each component in React has its own standalone scope and they don't share the variables.
That means when you pass an mutable variable(such as Object or Array) through props to a specific react component. It will clone each variable so that this component will have a totally new environment.
For example, assuming you have component A, and it works like this,
var ComponentA = React.createClass({
render: function() {
var user = { name: 'Tyler', role: 'Developer' };
return (
<SubComponent user={user} />
);
}
});
What ComponentA wants is simply render the user. So it require another module, let's say SubComponent to do that.
var SubComponent = React.createClass({
render: function() {
return (
<div>
<span>Name: {this.props.user.name}</span>
<span>Role: {this.props.user.role}</span>
</div>
);
}
});
For now, we should notice the variable user in ComponentA is different with the variable this.props.user in SubComponent. The this.props.user is not a reference. It's cloned from the ComponentA.
So that means, when you try to change the value of this.props.user in SubComponent, it won't destroy the user in ComponentA. Which is what David Nolen said in his tech talk. ("Change something in data without destroy the old one.")
Of course this would sacrifice some extra spaces, but you can get lots of benefits. Such as each of your component would be totally separated. Then all the nightmares cause by Shared Mutable Variables are gone. Shared Mutable Data is the root of evil, it's unpredictable and unreliable.
Imagine the SubComponent and the ComponentA are share the same user and you want to render another module by passing props user. Then you will update your code into this way,
var ComponentA = React.createClass({
render: function() {
var user = { name: 'Tyler', role: 'Developer' };
return (
<div>
<AnotherComponent user={user} />
<SubComponent user={user} />
</div>
);
}
});
Once we change the name of user in SubComponent(maybe by accident), we will have a cascading effect, and we don't know which one change the variable. That's painful coz then we have to check each line of the code in SubComponent and AnotherComponent. You really don't want to do that, right?
So I think that's what he mean. Hope this can solve your problem. : )

Resources