How to solve useEffect running twice? [duplicate] - node.js

Consider the snippets below. With React 18, count gets printed twice to the console on every render but with React 17 it gets printed only once.
React 18 Example:
function App() {
const [count, setCount] = React.useState(0);
console.log(count);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
React 17 Example
function App() {
const [count, setCount] = React.useState(0);
console.log(count);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
I know this has something to do with StrictMode but I'm not sure what. And also I've always been unclear about how strict mode works and what's its purpose, so I'd appreciate if anyone could highlight that as well.

TL;DR
When components are wrapped in StrictMode, React runs certain functions twice in order to help developers catch mistakes in their code.
And this happens both in React 18 and React 17 but the reason you aren't experiencing this with the latter is because in React 17, React automatically silences logs in the second call.
If you extract out console.log and use the extracted alias to log, then you would get similar behavior with both versions.
const log = console.log;
function App() {
const [count, setCount] = React.useState(0);
log(count);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
Note:
In React 17, React automatically modifies the console methods like console.log() to silence the logs in the second call to lifecycle functions. However, it may cause undesired behavior in certain cases where a workaround can be used.
Starting from React 18, React does not suppress any logs. However, if you have React DevTools installed, the logs from the second call will appear slightly dimmed. React DevTools also offers a setting (off by default) to suppress them completely.
Source
Now let's dive deep to understand what actually happens in strict mode and how it can helpful.
Strict Mode
Strict Mode is a tool that helps identify coding patterns that may cause problems when working with React, like impure renders.
In Strict Mode in development, React runs the following functions twice:
Functional Components
Initializers
Updaters
And this is because your components, initializers & updaters need to be pure functions but if they aren’t then double-invoking them might help surface this mistake. And if they are pure, then the logic in your code is not affected in any manner.
Note: React uses the result of only one of the calls, and ignores the result of the other.
In the example below observe that components, initializers & updaters all run twice during development when wrapped in StrictMode (snippet uses the development build of React).
// Extracting console.log in a variable because we're using React 17
const log = console.log;
function App() {
const [count, setCount] = React.useState(() => {
log("Initializers run twice");
return 0;
});
log("Components run twice");
const handleClick = () => {
log("Event handlers don’t need to be pure, so they run only once");
setCount((count) => {
log("Updaters run twice");
return count + 1;
});
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</div>
);
}
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"></div>
Few notes from the above example:
You might have noticed that when you click the button for the first time the Updaters run twice log prints only once but on subsequent clicks it prints twice. But you can ignore this behavior and assume that it always prints twice but if you want more details about the same you can follow this github issue.
We had to extract console.log into a separate variable to get logs for both the invocations printed and this is because React 17 automatically silences logs for the second call (as mentioned in the TL;DR). If you update the CDN link to React 18, then this extraction wouldn't be required.
Calling the setCount updater function twice doesn’t mean that it would now increment the count twice on every click, no, because it calls the updater with the same state both the times. So, as long as your updaters are pure functions, your application wouldn’t get affected by the no. of times it’s called.
"Updaters" & "Initializers" are generic terms in React. State updaters & state initializers are just one amongst many. Other updaters are "callbacks" passed to useMemo and "reducers". Another initializers is useReducer initializer etc. And all of these should be pure functions so strict mode double invokes all of them. Checkout this example:
const logger = console.log;
const countReducer = (count, incrementor) => {
logger("Updaters [reducers] run twice");
return count + incrementor;
};
function App() {
const [count, incrementCount] = React.useReducer(
countReducer,
0,
(initCount) => {
logger("Initializers run twice");
return initCount;
}
);
const doubleCount = React.useMemo(() => {
logger("Updaters [useMemo callbacks] run twice");
return count * 2;
}, [count]);
return (
<div>
<p>Double count: {doubleCount}</p>
<button onClick={() => incrementCount(1)}>Increment</button>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
How is Strict Mode helpful?
Let's look at an example where Strict Mode would help us find a serious mistake.
// This example is in React 18 to highlight the fact that
// the double invocation behavior is similar in both React 17 & 18.
function App() {
const [todos, setTodos] = React.useState([
{ id: 1, text: "Learn JavaScript", isComplete: true },
{ id: 2, text: "Learn React", isComplete: false }
]);
const handleTodoCompletion = (todoId) => {
setTodos((todos) => {
console.log(JSON.stringify(todos));
return todos.map((todo) => {
if (todo.id === todoId) {
todo.isComplete = !todo.isComplete; // Mutation here
}
return todo;
});
});
};
return (
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span
style={{
textDecoration: todo.isComplete ? "line-through" : "none"
}}
>
{todo.text}
</span>
<button onClick={() => handleTodoCompletion(todo.id)}>
Mark {todo.isComplete ? "Incomplete" : "Complete"}
</button>
</li>
))}
</ul>
);
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
What's the problem with the above example?
You would've noticed that the buttons don't work as expected, they don't toggle the isComplete boolean and the problem is that the updater function passed to setTodos is not a pure function as it mutates an object in the todos state. And since the updater is called twice, and it is not a pure function, the second call reverses the isComplete boolean back to it’s original value.
Note: It's only because of strict mode's double invocation that we were able to catch this mistake. If we opt out of strict mode, then the component would luckily work as expected but that doesn't mean the code is authored correctly, it only works because of how isolated the component is and in real world scenarios mutations like these can cause serious issues. And even if you luckily get away with such mutations, you might still encounter problems because currently the updater relies on the fact that it's only called once for every click but this is not something that React guarantees (with concurrency features in mind).
If you make the updater a pure function, it would solve the issue:
setTodos((todos) => {
logger(JSON.stringify(todos, null, 2));
return todos.map((todo) =>
todo.id === todoId ? { ...todo, isComplete: !todo.isComplete } : todo
);
});
What's new with Strict Mode in React 18
In React 18, StrictMode gets an additional behavior to ensure it's compatible with reusable state. When Strict Mode is enabled, React intentionally double-invokes effects (mount -> unmount -> mount) for newly mounted components. This is to ensure that a component is resilient to being "mounted" and "unmounted" more than once. Like other strict mode behaviors, React only does this for development builds.
Consider the example below (Source):
function App(props) {
React.useEffect(() => {
console.log("Effect setup code runs");
return () => {
console.log("Effect cleanup code runs");
};
}, []);
React.useLayoutEffect(() => {
console.log("Layout effect setup code runs");
return () => {
console.log("Layout effect cleanup code runs");
};
}, []);
console.log("React renders the component")
return <h1>Strict Effects In React 18</h1>;
}
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
<script crossorigin src="https://unpkg.com/react#18/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#18/umd/react-dom.development.js"></script>
<div id="root"></div>
The App component above declares some effects to be run on mount and unmount. Prior to React 18, the setup functions would only run once (after the component is initially mounted) and the cleanup functions would also run only once (after the component is unmounted). But in React 18 in StrictMode, the following would happen:
React renders the component (twice, nothing new)
React mounts the component
Layout effect setup code runs
Effect setup code runs
React simulates the component being hidden or unmounted
Layout effect cleanup code runs
Effect cleanup code runs
React simulates the component being shown again or remounted
Layout effect setup code runs
Effect setup code runs
Suggested Readings
ReactWG: How to support Reusable State in Effects
ReactWG: Adding Reusable State to StrictMode
React Docs: Strict Mode
Beta React Docs: My initializer or updater function runs twice
Beta React Docs: My reducer or initializer function runs twice

React 17 Strict Mode was also double rendering but it was suppressing the logs. That is why we were not seeing double logs. From react 17 docs:
Starting with React 17, React automatically modifies the console
methods like console.log() to silence the logs in the second call to
lifecycle functions. However, it may cause undesired behavior in
certain cases where a workaround can be used.
The difference of React 18 is it is now showing the double renderings. If you are using "React Developer Tools" chrome extension with, you can see that which logs are coming from strict mode:
One of the major additions to React 18 is Concurrency so Strict Mode will also help us see concurrency-related bugs during development.

Related

How to use React.lazy in Gatsby

As using React.lazy in Gatsby you'll get error when making production( gatsby build), what is the best way to use React.lazy and suspense in Gatsby Project
React.lazy and Suspense are still not ready for server-side rendering, but they can still be used by checking that the code is executed only on the client. While this solution is inferior to loadable-components, that works both on server side and client, it still provides an alternative for dealing with client-side only packages, without an added dependency. Remember that the following code could break if executed without the isSSR guard.
import React from "react"
const ClientSideOnlyLazy = React.lazy(() =>
import("../components/ClientSideOnly")
)
const MyPage = () => {
const isSSR = typeof window === "undefined"
return (
<>
{!isSSR && (
<React.Suspense fallback={<div />}>
<ClientSideOnlyLazy />
</React.Suspense>
)}
</>
)
}
Try following this guide on gatsbyjs.com, if the above doesn't work for you

How to include dom-manipulating scripts into SSR Next.js App

I am experiencing following error:
Warning: Text content did not match. Server: "But I want to be altered by the client" Client: "Test"
in div (at pages/index.tsx:17)
in div (at pages/index.tsx:6)
in HomePage (at _app.tsx:5)
in MyApp
in Container (created by AppContainer)
in AppContainer
... with the following setup:
A NextJS App Component:
function HomePage() {
return (
<>
<div id="test-div">I am rendered on the server.</div>
<script src="http://localhost:8080/bundle.js"></script>
</>
);
}
export default HomePage;
(Note: The URL http://localhost:8080/bundle.js assumes webpack-dev-server is running and serving that resource)
The included "example" script:
const element = document.getElementById('test-div') as any;
element.innerHTML = 'But I want to be altered by the client';
In a simple setup I would just have a static html file, declaring a div element and including the "example" script.
But I would like to use NextJS, because I want to render dynamic (SSR) content into the page (eg. Text contents from a cms).
I noticed, that sometimes (if script execution takes some more ms of time), there is no error. Just do something time consuming in the example script.
Another hacky approach is to use setTimeout in the example script.
I don't want to do that until I know why this is happening:
setTimeout(function() {
const element = document.getElementById('test-div') as any;
element.innerHTML = 'But I want to be altered by the client';
}, 20);
Next.js 11.0.0 and above
You can use Next.js Script component to load third-party scripts.
// pages/index.js
import Script from 'next/script'
function Home() {
return (
<>
<Script src="https://www.google-analytics.com/analytics.js" />
</>
)
}
With next/script, you can define the strategy property and Next.js will optimize loading for the script.
Before Next.js 11.0.0
Browser and document, window objects are not available during server-side rendering.
You can initialize scripts that manipulate DOM after React component did mount.
useEffect(() => init(), [])
To add an external script you can do the following:
useEffect(() => require('../babylon.js'), [])
To include a script from another server you can add a script tag:
useEffect(() => {
const script = document.createElement("script");
script.src = "http://localhost:8080/bundle.js";
script.async = true;
document.body.appendChild(script);
},[])
If you're adding DOM listeners you would also need to do cleanup.
Using the Effect Hook
Effects with Cleanup

how to get a Network Panel show up when debugging Electron/Node applications

I am building an electron application and I am using libraries (request/axios) for making requests. One thing I didn't expect is that making these requests on Node won't display a Network Panel when running in chrome debug mode. Is there a simple way to tell debug mode to turn on a network panel for tuning into https requests(I assume these libraries all use https)?
currently on my server side electron app i only see the following
Solution 1 - create your own
you can wrap your axios functions and send an event to your renderer process
main electron process
const electron = require('electron');
const {
app,
BrowserWindow,
ipcMain
} = electron;
const _axios = require('request-promise');
const axios = {
get: (url, params) => _axios.get(url, params).then(sendData),
post: (url, params) => _axios.post(url, params).then(sendData),
delete: (url, params) => _axios.delete(url, params).then(sendData),
put: (url, params) => _axios.put(url, params).then(sendData)
// ...
};
function sendData() {
return (data) => {
mainWindow.webContents.send('network', data);
return data;
};
}
renderer process (index.html):
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
<link href="https://cdnjs.cloudflare.com/ajax/libs/bulma/0.7.1/css/bulma.min.css"
rel="stylesheet">
<style>
.kb-debug-widget {
position: fixed;
bottom: 0;
height: 200px;
overflow-x: hidden;
overflow-y: auto;
background: grey;
left: 0;
right: 0;
font-size: 10px;
}
</style>
</head>
<body>
<div class="kb-debug-widget">
<table class="table is-bordered is-striped is-narrow is-hoverable is-fullwidth"
id="network">
<tr>
<th>Name</th>
<th>Method</th>
<th>Status</th>
<th>Type</th>
<th>Body</th>
</tr>
</table>
</div>
<script>
require('./renderer.js');
var {
ipcRenderer,
remote
} = require('electron');
ipcRenderer.on('network', (event, response) => {
const networkElement = document.getElementById('network');
// print whatever you want here!
networkElement.innerHTML +=
`
<tr>
<td>${response.request.href}</td>
<td>${response.request.method}</td>
<td>${response.statusCode}</td>
<td>${response.headers['content-type']}</td>
<td>${response. data}</td>
</tr>
`;
// you can also print the network requests to the console with a decent UI by using console.table:
console.table({
name: response.request.href,
method: response.request.method,
status: response.statusCode,
type: response.headers['content-type'],
body: response. data,
});
});
</script>
</body>
</html>
This will create a widget on the bottom of your view.
it's even easier with request:
const _request = require('request-promise');
const _axios = require('request-promise');
// this should cover all sub-methods
const request = (params, callback) => {
return _request(params, callback)
.on('response', (response) => {
mainWindow.webContents.send('network', response);
return response;
});
};
Since both axios & request return similar objects, you can use the same function on the renderer side.
code in action
Here's a GitHub repository with the code implemented
Solution 1: Alt - write network requests to renderer's console
I also added an option to print the requests to the dev tools console, with console.table. Here's how it looks:
You can leave only this method if you don't want a widget inside your HTML.
Solution 2 - Run electron with the --inspect flag
You can also just run electron with the inspect flag, which allows you to debug your server code and has its own network tab with the "server-side" HTTP requests.
In order to see it, run your electron application like so:
electron --inspect=<port> your/app
if you want to immediatly break on the first line, run the same command but replace --inspect with --inspect-brk.
After running the command, open any web-browser and go to chrome://inspect and selecting to inspect the launched Electron app present there.
Expanding on #Thatkookooguy 's answer:
Run electron with the --inspect and --remote-debugging-port flags
You can also just run electron with the inspect flag, which allows you to debug your server code and has its own network tab with the "server-side" HTTP requests.
In order to see it, run your electron application like so:
electron --inspect=<portA> --remote-debugging-port=<portB> your/app
or
/Applications/My.app/Contents/MacOS/Foo --inspect=<portA> --remote-debugging-port=<portB>
if you want to immediatly break on the first line, run the same command but replace --inspect with --inspect-brk.
After running the command, open any web-browser and go to chrome://inspect and selecting to inspect the launched Electron app present there.
hope this helped. If you have any questions, you can ask me in the comments
P.S if you want even more details you can run it like
/Applications/Mattermost.app/Contents/MacOS/Mattermost --no-sandbox --enable-logging --inspect --remote-debugging-port=9222

react 16 and react router 4, how to test if defined for route component was rendered?

I have an application based on react-boilerplate. I updated it lately to the newest version, which includes react-router 4.2 and also bumped react to version 16.
Now I work on aligning routes in application to the newest version and also want to write tests for routing.
I have main file App.js
export default () => (<Switch>
<Route path="/offer" component={Offer} />
<Route path="" component={NotFoundPage} />
</Switch>);
And I wrote test:
describe('<App />', () => {
it('should render proper component, based on routing', () => {
const wrapper = shallow(
<MemoryRouter initialEntries={['/offer']} initialIndex={0}>
<App />
</MemoryRouter>
);
expect(wrapper.find(Offer)).toBePresent();
});
});
I installed enzyme-matchers to make toBePresent work, but I'm getting an error: TypeError: expect(...).toBePresent is not a function.
So probably you know any other way to test with jest if on concrete route defined component was rendered?

RequireJS does not run data-main script before loading required modules

My project includes the following files:
./index.html
./js/main.js
./js/vendor/require.js
./js/viewmodel/vm.js
The index.html has the following relevant snippet:
<script data-main="js/main.js" src="js/vendor/require.js"></script>
<script type="text/javascript">
require(['viewmodel/vm', 'ko'],
function(viewmodel, ko) {
ko.applyBindings(viewmodel);
}
);
</script>
The js/main.js file is as follows:
var root = this;
define('jquery', ['http://ajax.aspnetcdn.com/ajax/jQuery/jquery-1.8.3.js'], function () { return root.$; });
define('ko', ['http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.1.0.js'], function (ko) { return ko; });
The js/viewmodel/vm.js file...
define(['jquery', 'ko'],
function($, ko) {
return {
subject: 'world',
greeting: 'hello'
}
}
);
When you open a browser to index.html, then the browser tries to load a file called js/ko.js instead of using the module defined in main.js. It seems like the js file pointed to by the data-main attribute is not guaranteed to run before dependency resolution. This does not seem correct to me since one purpose of the data-main js file is to define require configuration (i.e. path, shim, etc). I am using require v2.1.2.
This works perfectly fine if I copy the contents of my main.js file into the script block in index.html. By "perfectly fine" I mean that it resolved ko to be a module and finds the appropriate CDN link to resolve ko instead of trying to download ./js/ko.js.
to use the data-main attribute for configuring your whole application, it is necessary that it is the single entry point for all your code.
your 2nd script block breaks this requirement by providing a 2nd entry point. since these entry points will resolve independently of each other (and asynchronously), you cannot rely on one to affect the other.
to resolve it, refactor your code in a way that provides a single entry point to your application and do your configuration via this entry point.
That's because requirejs sets the async. Attribute on the script.
The boolean async attribute on script elements allows the external
JavaScript file to run when it's available, without delaying page load
first.
This means that both scripts are loaded and evaluated parallel, so none of the two scripts can access methods or functions from the other one.
If you want to define requirejs variables in one script you mustn't load that script with require js.
For me there are three possibilities how you can solve that problem:
Add the content of main.js to your page (as you mention)
Load the main.js file without requirejs as normal script
Define the require config before loading the scripts (link to requirejs docu )
I had the same problem. The architecture of the site that i was working was components that was loading asynchronous at each part of the page.
Each component has its own html, css, and js code.
So, my solution is to keep a guard function for all the required dependency code, to protect them from running before the main javascript file:
index.html
<head>
<script type="text/javascript">
window.BeforeMainGuard = {
beforeMainLoadedFunctions: [],
hasMainLoaded: false,
guard: function( func ) {
console.assert( typeof func === 'function' );
if( this.hasMainLoaded ) {
func();
}else {
this.beforeMainLoadedFunctions.push( func );
}
},
onMainLoaded: function() {
for( var i = 0; i<this.beforeMainLoadedFunctions.length; ++i ) {
var beforeMainLoadedFunction = this.beforeMainLoadedFunctions[i];
beforeMainLoadedFunction();
}
this.beforeMainLoadedFunctions = null;
this.hasMainLoaded = true;
}
};
</script>
<script data-main="js/main.js" src="js/vendor/require.js"></script>
<script type="text/javascript">
window.BeforeMainGuard.guard( function() {
require(['viewmodel/vm', 'ko'],
function(viewmodel, ko) {
ko.applyBindings(viewmodel);
}
);
});
</script>
</head>
js/main.js
require.config({
// your config
});
require( [ 'AppLogic' ], function( AppLogic ){
AppLogic.Init();
window.BeforeMainGuard.onMainLoaded();
} );

Resources