I got somthing like this on React?
$(document).on("click", function (e) { ... }
Or is mandatory to use onClick on all components?
It is not a good practice and You should try and avoid jQuery in ReactJS, But:
You can have that, but in order to do that you need to add your code in the right life cycle hook
for example, you can do this
import React from 'react';
import $ from 'jquery';
useEffect(() => {
function onClickHanlder (e) { ... }
$(document).on("click", onClickHanlder)
// to prevent leak memory unsubscribe from event on unmount
return () => {
$(document).off("click")
}
},[]);
In a class component you can add your code in componentDidMount
import React from 'react';
import { useEffect } from 'react';
const App = () => {
useEffect(() => {
initCustomScripts();
}, [])
const initCustomScripts = () => {
document.getElementById('custom-button').addEventListener('click', function(){
//code here
alert('custom-button clicked');
})
}
return (
<div>
<button id="custom-button">Custom button</button>
</div>
);
}
export default App;
Related
Newbie to react here.
TLDR: I have a helper function called createNotification which when called inserts a <ToastNotification /> component into a container element using render(). If I use createPortal() nothing is appended. If I use render, the component is only added once despite multiple triggers.
Can anyone help me figure out whats happening please?
Thank you
helpers.js
import { ToastNotification } from "carbon-components-react";
import { render, createPortal } from "react-dom";
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container); //just to check function is running and has found container
return render(<ToastNotification />, container); //works but only once, not on multiple triggers
return createPortal(<ToastNotification />, container); //doesn't render anything in container
};
the function above is called from other components as needed:
login.js
import { createNotification } from "../../helpers";
const Login = () => {
const validateLogin = async (event) => {
createNotification();
// validation logic
performLogin();
};
const performLogin = async () => {
//axios call here
};
// main component content
return (
<>
<!-- validateLogin() called on form submit -->
</>
);
};
export default Login;
app.js
//imports
function App() {
return (
<div>
<div className="App"></div>
</div>
);
}
export default App;
Thank you
Solved this myself by adding the createPortal() within the render().
If anyone can provide an explanation, it would be much appreciated.
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container);
return render(createPortal(<ToastNotification />, container), document.createElement("div"));
};
createNotification aren't mounted in component in app Virtual Dom... when you use render(createPortal) then you just create spearted app.
import { createNotification } from "../../helpers";
export const createNotification = () => {
const container = document.getElementById("notificationContainer");
console.log(container); //just to check function is running and has found container
return createPortal(<ToastNotification />, container); //doesn't render anything in container
};
const Login = () => {
const [validate, setValidate] = useState(false);
const validateLogin = async (event) => {
if('some logic')
return setValidte(true)
setVAlidte(false)
};
useEffect(() => {
if(!valite)
return;
//axios heare
}, [validate])
// main component content
return (
<>
{!validate && <CreateNotfication/>}
<!-- validateLogin() called on form submit -->
</>
);
};
I am trying to implement a search that makes a new query on each character change. After n milliseconds, I need to make a change to the object that stores some properties.
//user typing
const onInputChange = (e) => {
let searchInput = e.target.value;
useDebounce(
handleSearchPropsChange({
filter: {
searchInput,
dateRange: {
start,
end
}
}
}), 1000
);
}
The function I am using for the delayed call
import {debounce} from 'lodash';
import {useRef} from 'react';
export function useDebounce(callback = () => {}, time = 500) {
return useRef(debounce(callback, time)).current;
}
But I am getting the error:
Invalid hook call. Hooks can only be called inside of the body of a function component. This
could happen for one of the following reasons:
1. You might have mismatching versions of React and the renderer (such as React DOM)
2. You might be breaking the Rules of Hooks
3. You might have more than one copy of React in the same app
A example without lodash, just Hooks.
UseDebounce.js
import { useEffect, useCallback } from 'react';
export default function useDebounce(effect, dependencies, delay) {
const callback = useCallback(effect, dependencies);
useEffect(() => {
const timeout = setTimeout(callback, delay);
return () => clearTimeout(timeout);
}, [callback, delay]);
}
App.js
import React, { useState } from 'react';
import useDebounce from './useDebounce';
import data from './data';
export default function App() {
const [search, setSearch] = useState('');
const [filteredTitle, setFilteredTitle] = useState([]);
// DeBounce Function
useDebounce(() => {
setFilteredTitle(
data.filter((d) => d.title.toLowerCase().includes(search.toLowerCase()))
);
}, [data, search], 800
);
const handleSearch = (e) => setSearch(e.target.value);
return (
<>
<input
id="search"
type="text"
spellCheck="false"
placeholder="Search a Title"
value={search || ''}
onChange={handleSearch}
/>
<div>
{filteredTitle.map((f) => (
<p key={f.id}>{f.title}</p>
))}
</div>
</>
);
}
Demo : Stackblitz
Scroll.js
import React from "react";
export const ScrollToTop = ({ children, location }) => {
React.useEffect(() => window.scrollTo(0, 0), [location.pathname]);
return children;
};
Scroll.test.js
import React from "react";
import { ScrollToTop } from "./ScrollToTop";
describe("ScrollToTop", () => {
it("", () => {
expect(
ScrollToTop({
children: "some children",
location: { pathname: "the path" }
})
).toEqual();
});
});
and the result I'm getting is
enter image description here
You should not call ScrollToTop as a function directly, this is what error message is complaining about.
React docs recommend the Testing Library for writing tests.
Here is an example of how you can write Scroll.test.js using the library above:
import React from "react";
import { render } from '#testing-library/react';
import { ScrollToTop } from "./ScrollToTop";
describe("ScrollToTop", () => {
it('calls window.scrollTo()', () => {
window.scrollTo = jest.fn(); // create a moack function and record all calls
render(<ScrollToTop location={{ pathname: 'pathname' }}>Text</ScrollToTop>); // render a component
expect(window.scrollTo).toHaveBeenCalledWith(0, 0); // check that scrollTo mock was called
});
});
Based on the documentation in here to subscribe and unsubscribe Netinfo listener - https://github.com/react-native-netinfo/react-native-netinfo
I define the listener method in a common file and export it
/* util.js */
function listener(){
Netinfo.addEventListener(state => /* something in here */);
}
export { listener };
Then in a React component
import { useEffect } from 'react';
import { listener } from 'util';
const exampleComponent = props => {
// then on useeffect
useEffect(() => {
listener() // to subscribe
return () => listener(); // to unsubscribe
}, []);
return <ExampleComponent />
}
Is this the correct way to setup Netinfo listener if i want to make listener as an exportable method that can be setup anywhere (altho by logic it should only be set up on the main file in the app, but just for the sake of question)?
Im not sure how calling listener the first time will subscribe Netinfo listener and calling it again will unsubscribe it.
You can try this,
import NetInfo from "#react-native-community/netinfo";
const [isConnected, setIsConnected] = useState(true);
NetInfo.fetch().then(state => {
if (state.isConnected && state.isInternetReachable) {
setIsConnected(true);
} else {
setIsConnected(false);
}
});
useEffect(() => {
const unsubscribe = NetInfo.addEventListener(state => {
if (state.isConnected && state.isInternetReachable) {
setIsConnected(true);
} else {
setIsConnected(false);
}
});
if (isConnected) {
} else {
unsubscribe();
}
}, []);
Is this the correct way to setup Netinfo listener if i want to make listener as an exportable method that can be setup anywhere (altho by logic it should only be set up on the main file in the app, but just for the sake of question)?
if you want to setup once and use it many times, no need to do that by yourself, you can use useNetInfo()
import {useNetInfo} from "#react-native-community/netinfo";
const YourComponent = () => {
const netInfo = useNetInfo();
return (
<View>
<Text>Type: {netInfo.type}</Text>
<Text>Is Connected? {netInfo.isConnected.toString()}</Text>
</View>
);
};
How can I clean up function like setTimeout or setInterval in event handler in React? Or is this unnecessary to do so?
import React from 'react'
function App(){
return (
<button onClick={() => {
setTimeout(() => {
console.log('you have clicked me')
//How to clean this up?
}, 500)
}}>Click me</button>
)
}
export default App
Whether it's necessary depends on what the callback does, but certainly if the component is unmounted it almost doesn't matter what it does, you do need to cancel the timer / clear the interval.
To do that in a function component like yours, you use a useEffect cleanup function with an empty dependency array. You probably want to store the timer handle in a ref.
(FWIW, I'd also define the function outside of the onClick attribute, just for clarity.)
import React, {useEffect, useRef} from 'react';
function App() {
const instance = useRef({timer: 0});
useEffect(() => {
// What you return is the cleanup function
return () => {
clearTimeout(instance.current.timer);
};
}, []);
const onClick = () => {
// Clear any previous one (it's fine if it's `0`,
// `clearTimeout` won't do anything)
clearTimeout(instance.current.timer);
// Set the timeout and remember the value on the object
instance.current.timer = setTimeout(() => {
console.log('you have clicked me')
//How to clean this up?
}, 500);
};
return (
<button onClick={onClick}>Click me</button>
)
}
export default App;
An object you store as a ref is usually a useful place to put things you would otherwise have put on this in a class component.
(If you want to avoid re-rendering button when other state in your component changes (right now there's no other state, so no need), you could use useCallback for onClick so button always sees the same function.)
One more solution (Live Demo):
import React, { useState } from "react";
import { useAsyncCallback } from "use-async-effect2";
import { CPromise } from "c-promise2";
export default function TestComponent(props) {
const [text, setText] = useState("");
const click = useAsyncCallback(function* (ms) {
yield CPromise.delay(ms);
setText("done!" + new Date().toLocaleTimeString());
}, []);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>{text}</div>
<button onClick={() => click(2000)}>Click me!</button>
<button onClick={click.cancel}>Cancel scheduled task</button>
</div>
);
}
In case if you want to cancel the previous pending task (Live demo):
import React, { useState } from "react";
import { useAsyncCallback } from "use-async-effect2";
import { CPromise } from "c-promise2";
export default function TestComponent(props) {
const [text, setText] = useState("");
const click = useAsyncCallback(
function* (ms) {
console.log("click");
yield CPromise.delay(ms);
setText("done!" + new Date().toLocaleTimeString());
},
{ deps: [], cancelPrevios: true }
);
return (
<div className="component">
<div className="caption">useAsyncEffect demo:</div>
<div>{text}</div>
<button onClick={() => click(5000)}>Click me!</button>
<button onClick={click.cancel}>Cancel scheduled task</button>
</div>
);
}
Clear timer when unmount component
import React from 'react'
function App(){
const timerRef = React.useRef(null)
React.useEffect(() => {
return () => {
// clean
timerRef.target && clearTimeout(timerRef.target)
}
},[])
return (
<button onClick={() => {
timerRef.target = setTimeout(() => {
console.log('you have clicked me')
}, 500)
}}>Click me</button>
)
}
export default App