Can I call a variable created with javascript in React? - javascript

Can I call a variable whose path is in /public/assets/js in my React project within useEffect() in React views?
For example, can I use jsVal like below?
useEffect(() => {
const existingScript = jsVal.getSum();
...
}
The reason why I am curious is because, when the screen made with React is turned on with chrome, jsVal is not 'jsVal is not defined' in the Console Tab of Developer Tools.
However, in useEffect, jsVal comes out as 'can not find Name', so I leave a question.
Add The location where jsVal is defined is /public/assets/js/test.js.

If your value is exposed in some way through global or window then yes, you can access it in React!
i.e.
// public/assets/file.js
window.jsVal = {
getSum(a, b) { return a + b; }
}
// In case of typescript, add an ambient declaration too!
declare global {
interface Window {
jsVal: {
getSum(a: number, b: number): number;
}
}
}
// React app (make sure file.js is included before React in index.html)
const MyComponent = () => {
const [val, setVal] = useState(null);
useEffect(
() => {
setVal(window.jsVal.getSum(10, 32)); // 42
},
[window.jsVal, setVal]
);
return null;
}

I assume jsVal is a global service/variable ? If so.
Everything of React is javascript. You can think React is a global variable.
React.render(<App />, el)
So in general, it depends on if jsVal is defined early or later than render. However useEffect is also a bit special. It actually fires in another Javascript task/tick, similar to setTimeout or an API, so its execution is deferred in async way.

Related

React useEffect called twice even with strict mode disabled

I'm new to React and am working on a simple receipt scanning web app based on AWS (Amplify, AppSync, GraphQL, DynamoDB, S3). I'm using the useEffect hook to fetch data for the user currently logged in, via a GraphQL call, and am noticing duplicate runs of it. At first, there were three calls, after which I read about and disabled Strict Mode. But now, even with Strict Mode disabled, I am seeing two calls.
Debugging reveals that useEffect is called only once if I comment out setWeekly(getTotalFromItems(response)), but even as little as setWeekly() ends up creating duplicate calls.
I've perused this post as well as numerous others, but they all point to Strict Mode being the primary culprit, which is not the case here.
Network log attached below for reference.
Could someone help me understand what might be causing this double-call, and how to fix it?
import WebFont from 'webfontloader';
import React, {
useEffect,
useState
} from 'react'
import {
withAuthenticator,
Text,
View
} from '#aws-amplify/ui-react';
import {
MainLayout
} from './ui-components';
import awsExports from "./aws-exports";
Amplify.configure(awsExports);
function App({signOut, user}) {
const [weekly, setWeekly] = useState([]);
useEffect(() => {
WebFont.load({
google: {
families: ['DM Sans', 'Inter']
}
});
async function fetchWeekly(queryTemplate, queryVars) {
try {
const weeklyData = await API.graphql(graphqlOperation(queryTemplate, queryVars))
console.log(weeklyData)
const response = weeklyData.data.getUser.receiptsByPurchaseDateU.items
const total = getTotalFromItems(response) // sums 'total' in [{'date': '...', 'total': 9}, ...]
setWeekly(total)
} catch (err) {
console.log('Error fetching data.');
console.log(err)
}
}
const queryVars = {
username: user.attributes.email,
}
let d = new Date();
d.setDate(d.getDate() - 7);
d = d.toLocaleDateString('en-CA');
let tmpl = generateSummaryTemplate(d) // returns a template string based on d
fetchWeekly(tmpl, queryVars);
console.log('Complete.')
});
return ( <View >
<MainLayout/>
</View >
)
}
export default withAuthenticator(App);
The issue here is that the useEffect hook is missing a dependency array. The useEffect callback enqueues a weekly state update which triggers a component rerender and the useEffect hook is called again. This second time it again computes a value and enqueues a weekly state update. It's this second time that the state is enqueued with the same value as the current state value and React decides to bail on further rerenders. See Bailing out of State Updates.
If you update a State Hook to the same value as the current state,
React will bail out without rendering the children or firing effects. (React uses the Object.is comparison algorithm.)
The solution is to add a dependency array with appropriate dependencies. Use an empty array if you want the effect to run once after the initial render when the component mounts. In this case it seems the passed user prop is the only external dependency I see at the moment. Add user to the dependency array. This is to indicate when the effect should run, i.e. after the initial mount/render and anytime user value changes. See Conditionally firing an effect.
Example:
useEffect(() => {
...
async function fetchWeekly(queryTemplate, queryVars) {
try {
const weeklyData = await API.graphql(graphqlOperation(queryTemplate, queryVars));
const response = weeklyData.data.getUser.receiptsByPurchaseDateU.items
const total = getTotalFromItems(response) // sums 'total' in [{'date': '...', 'total': 9}, ...]
setWeekly(total);
} catch (err) {
console.log('Error fetching data.');
console.log(err);
}
}
const queryVars = {
username: user.attributes.email,
};
let d = new Date();
d.setDate(d.getDate() - 7);
d = d.toLocaleDateString('en-CA');
let tmpl = generateSummaryTemplate(d); // returns a template string based on d
fetchWeekly(tmpl, queryVars);
console.log('Complete.');
}, [user]); // <-- user is external dependency
First of add a dependency array as the second argument to the useEffect, after the callback function. Moreover, I may advice that you have service functions outside the useEffect body, don't overload it like that, is not appropirate.
It’s because setWeekly() is called within your useEffect() and triggers another render. You may remove this useState() entirely and instead return the data you need in fetchWeekly().

Alternative to global variable when using clearTimeout & setTimeout

EDIT I used the wrong term in the title and question. I did not mean a global variable, but to instead declare timeoutID inside of the showNotification function.
I'm on my first week of testing Redux. I'm wondering if there is a more elegant / less hacky solution to using a glodal variable for the timeoutID? clearTimeout is used to guarantee that the last added notification is always shown for the full desired time, even if it would be added before the previous notification was set to "empty".
actionCreator.js
import { addQuote } from "./addQuote"
import { showNotification } from "./showNotification"
export const actionCreator = (quote, time) => {
return dispatch => {
dispatch(addQuote(quote))
dispatch(showNotification(quote, time))
}
}
showNotification.js
let timeoutID = null
export const showNotification = (newQuote, time) => {
const message = `A new quote by ${newQuote.author} was added.`
return dispatch => {
dispatch({ type: 'SHOW_NOTIFICATION', data: message })
clearTimeout(timeoutID)
timeoutID = setTimeout(() => {
dispatch({ type: 'SHOW_NOTIFICATION', data: '' })
}, time * 1000)
}
}
notificationReducer.js
const initState = {
notification: ''
}
const notificationReducer = (state=initState, action) => {
switch (action.type) {
case 'SHOW_NOTIFICATION':
const message = action.data
return {
...state,
notification: message
}
default:
return {
...state
}
}
}
export default notificationReducer
It's not global, it's a closure, and that's one of the core principles in a module world (which was invented to circumvent having to make use of the global namespace!).
If it were global you could use the variable in any other JS file without ever explicitly importing it.
In actionCreator.js, which does import { showNotification } from "./showNotification", try to console.log(timeoutID) and you'll get undefined.
Closures are really nothing complicated; it just means that any function when declared will "remember" any local variables that were known ("in scope" is the more technically correct term for "known") at the point of the function's declaration, no matter when the function is called, or who calls it. Those variables known to a function via this mechanism are called closures.
There is not only nothing wrong with programming this way; it moreso is state of the art and in contrast to other far more verbose and lengthy solutions like passing parameters around, the most elegant way to solve the problem.

why is my gloval setInterval ID variable unavailable to a function?

I am writing a simple timer app and using setInterval for the first time. The project is in react typescript and I use the useReducer hook to manage state.
The project requires two separate timers, session and break, which may explain some of the code. The project also requires one button to start and stop the timer, hence I am using a single function.
I have redacted my reducer and types as the problem, as i understand, does not involve them, but some simple use of setInterval that I just don't get yet.
When the startStopTimer function is called a second time, the intervalID is undefined, even though it is declared globally.
const App = () => {
const [timerState, dispatch] = useReducer(timerStateReducer, initialTimerState
);
let intervalID: NodeJS.Timer;
const startStopTimer = () => {
let name: string = timerState.session.count !== 0 ? 'session' : 'break';
if (timerState[name].running) {
console.log('stopping timer' + intervalID);
//reducer sets running boolean variable to false
dispatch({type: ActionKind.Stop, name: name});
clearInterval(intervalID);
} else {
//reducer sets running boolean variable to true
dispatch({type: ActionKind.Start, name: name});
intervalID = setInterval(() => {
dispatch({type: ActionKind.Decrease, name: name});
}, 1000);
}
};
return (
//redacted JSX code
<button onClick={startStopTimer}>Start/Stop</button>
)
I have tried passing onClick as an arrow function (rather than a reference, i think?) and it behaves the same. I tried simplifying this with useState, but came across a whole 'nother set of issues with useState and setInterval so I went back to the useReducer hook.
Thanks!

How to handle (worker) jobs in React?

I have a React component with a state variable jobs. When the state variable ready is true, it should start executing jobs by a Web Worker (one at a time).
import React, { useEffect, useRef } from 'react'
// create webworker
const job_worker = new Worker("worker.bundle.js", { type: "module" });
function App() {
const [jobs, set_jobs] = React.useState([
{ ... },
{ ... },
])
const [ready, set_ready] = React.useState(false)
// start worker loop
useEffect(() => {
const worker_loop = async () => {
setTimeout(async () => {
// check if ready to execute a job
if (ready) { // <== suffers from 'stale closure'
// grab a job
const job = jobsRef.current.find(j => !j.done)
// listen for webworker results
job_worker.onmessage = (e) => {
console.log("received response from webworker: '", e.data, "'")
// SET RESULT IN JOB
// job is handled by worker; now start the worker_loop again
return worker_loop()
}
// post job to worker
job_worker.postMessage({job: job})
return // do not continue; 'onmessage' will continue the loop
}
return worker_loop()
}, 1000)
}
// start worker_loop
worker_loop()
}, [])
return (
<div>
{/* code to add jobs and set ready state */}
</div>
);
}
I tried to do this by using an (infinite) worker_loop, which is started when the React component mounts (using useEffect). The loop kinda works, but the ready variable inside the worker_loop stays at the initial state value (known as the 'stale closure' problem). Probably the same for the jobs state variable.
I've already tried to use 'createRef' as suggested here. But the problem persists. Also I feel like there is a much simpler solution.
Is there a better way to handle 'jobs' in a React-state variable? Some sort of 'job-runner process/function' with access to the React component. By the way, I am not obliged to use WebWorker.
Thanks for the comments!
It indeed makes more sense to control the jobs outside React. I solved it by creating a global state using #hookstate/core. This makes it possible to access and control the state outside of React. Much cleaner code!

Mocking a function jest but jest calling original function

I have a function that returns true or false, lets call it myFunc
myFunc (){
if(something){return true}
else{return false}
}
that's what it does for sake of arg
I then call it somewhere else
if(myFunc()){
}else{
}
when I log it out, it continually comes out as false. however, when i have mocked it in my test like so:
const myMock = (myModule.myFunc = jest.fn())
myMock.mockReturnValue(true)
so why is it still coming back as false when I log it from the index file? or is that not quite how mocking works?
I'm guessing that myModule is the object you imported, and then you set the mock function on that object. But in the myModule file you are referencing that function directly, not through a module reference, right?
The proper way would probably be to move myFunc out of myModule. But if you want to keep it there, then you are going to have to partially mock myModule:
jest.mock('./myModule', () => {
return {
...jest.requireActual('./myModule'),
myFunc: jest.fn()
}
})
But seriously consider moving myFunc out of myModule, because partial mocking is difficult and confusing.
One way I found to solve my issue was to use a class instead.
Here is a sudo example:
Implementation
export class Location {
getLocation() {
const environment = this.getEnvironmentVariable();
return environment === "1" ? "USA" : "GLOBAL";
}
getEnvironmentVariable() {
return process.env.REACT_APP_LOCATION;
}
}
Test
import { Location } from "./config";
test('location', () => {
const config = new Location();
jest.spyOn(config, "getEnvironmentVariable").mockReturnValue("1")
const location = config.getLocation();
expect(location).toBe("USA");
});

Categories

Resources