Is there way to get context in usual Javascript function (in function which is not being rendered)?
I have function which doesn't render anything (it downloads .csv) which uses hook (using hook is not correct solution)
const archiveExportCSV=async(tasks)=> {
let context = useContext(StaticsContext);
//... some code which uses context
Helpers.download(csv,'test.csv','data:text/csv;charset=utf-8');
};
and I am calling it in component:
<button type="button"
className="btn-reset"
title="export to .csv"
onClick={()=>archiveExportCSV(tasks)}>
It is wrong because function is neither a React function component or a custom React Hook function. Is there any way how get context in callable function or I have to pass arguments from context manually as arguments?
You can't call hooks in a handler but you can pass the context to your function when it gets called. Hooks need to be called unconditionally and on every render of your component in the same order as react references them by that. Having them in a handler violates that:
const MyComponent = ({tasks}) => {
const context = useContext(StaticsContext);
const handleClick = useCallback(() => archiveExportCSV(tasks, context), [tasks, context]);
return (
<button
type="button"
className="btn-reset"
title="export to .csv"
onClick={handleClick}
>
Click me
</button>
);
}
Since you are looking for alternatives to explicit arguments - if a function can be created INSIDE a component, it can access all variable in scope (a.k.a. a closure):
const MyComponent = () => {
const context = useContext(StaticsContext);
const archiveExportCSV = async(tasks) => {
//... some code which uses context
};
...
}
Related
How to pass extra parameters to an onClick event using the ES6 syntax?
For instance:
handleRemove = (e) => {
}
render() {
<button onClick={this.handleRemove}></button>
}
I want to pass an id to the handleRemove function like this:
<button onClick={this.handleRemove(id)}></button>
Remember that in onClick={ ... }, the ... is a JavaScript expression. So
... onClick={this.handleRemove(id)}
is the same as
var val = this.handleRemove(id);
... onClick={val}
In other words, you call this.handleRemove(id) immediately, and pass that value to onClick, which isn't what you want.
Instead, you want to create a new function with one of the arguments already prefilled; essentially, you want the following:
var newFn = function() {
var args = Array.prototype.slice.call(arguments);
// args[0] contains the event object
this.handleRemove.apply(this, [id].concat(args));
}
... onClick={newFn}
There is a way to express this in ES5 JavaScript: Function.prototype.bind.
... onClick={this.handleRemove.bind(this, id)}
If you use React.createClass, React automatically binds this for you on instance methods, and it may complain unless you change it to this.handleRemove.bind(null, id).
You can also simply define the function inline; this is made shorter with arrow functions if your environment or transpiler supports them:
... onClick={() => this.handleRemove(id)}
If you need access to the event, you can just pass it along:
... onClick={(evt) => this.handleRemove(id, evt)}
Use the value attribute of the button element to pass the id, as
<button onClick={this.handleRemove} value={id}>Remove</button>
and then in handleRemove, read the value from event as:
handleRemove(event) {
...
remove(event.target.value);
...
}
This way you avoid creating a new function (when compared to using an arrow function) every time this component is re-rendered.
Use Arrow function like this:
<button onClick={()=>{this.handleRemove(id)}}></button>
onClick={this.handleRemove.bind(this, id)}
Using with arrow function
onClick={()=>{this.handleRemove(id)}}
Something nobody has mentioned so far is to make handleRemove return a function.
You can do something like:
handleRemove = id => event => {
// Do stuff with id and event
}
// render...
return <button onClick={this.handleRemove(id)} />
However all of these solutions have the downside of creating a new function on each render. Better to create a new component for Button which gets passed the id and the handleRemove separately.
TL;DR:
Don't bind function (nor use arrow functions) inside render method. See official recommendations.
https://reactjs.org/docs/faq-functions.html
So, there's an accepted answer and a couple more that points the same. And also there are some comments preventing people from using bind within the render method, and also avoiding arrow functions there for the same reason (those functions will be created once again and again on each render). But there's no example, so I'm writing one.
Basically, you have to bind your functions in the constructor.
class Actions extends Component {
static propTypes = {
entity_id: PropTypes.number,
contact_id: PropTypes.number,
onReplace: PropTypes.func.isRequired,
onTransfer: PropTypes.func.isRequired
}
constructor() {
super();
this.onReplace = this.onReplace.bind(this);
this.onTransfer = this.onTransfer.bind(this);
}
onReplace() {
this.props.onReplace(this.props.entity_id, this.props.contact_id);
}
onTransfer() {
this.props.onTransfer(this.props.entity_id, this.props.contact_id);
}
render() {
return (
<div className="actions">
<button className="btn btn-circle btn-icon-only btn-default"
onClick={this.onReplace}
title="Replace">
<i className="fa fa-refresh"></i>
</button>
<button className="btn btn-circle btn-icon-only btn-default"
onClick={this.onTransfer}
title="Transfer">
<i className="fa fa-share"></i>
</button>
</div>
)
}
}
export default Actions
Key lines are:
constructor
this.onReplace = this.onReplace.bind(this);
method
onReplace() {
this.props.onReplace(this.props.entity_id, this.props.contact_id);
}
render
onClick={this.onReplace}
in function component, this works great - a new React user since 2020 :)
handleRemove = (e, id) => {
//removeById(id);
}
return(<button onClick={(e)=> handleRemove(e, id)}></button> )
I use the following code:
<Button onClick={this.onSubmit} id={item.key} value={shop.ethereum}>
Approve
</Button>
Then inside the method:
onSubmit = async event => {
event.preventDefault();
event.persist();
console.log("Param passed => Eth addrs: ", event.target.value)
console.log("Param passed => id: ", event.target.id)
...
}
As a result:
Param passed in event => Eth addrs: 0x4D86c35fdC080Ce449E89C6BC058E6cc4a4D49A6
Param passed in event => id: Mlz4OTBSwcgPLBzVZ7BQbwVjGip1
I am using React-Bootstrap. The onSelect trigger for dropdowns were not allowing me to pass data. Just the event. So remember you can just set any values as attributes and pick them up from the function using javascript. Picking up those attributes you set in that event target.
let currentTarget = event.target;
let currentId = currentTarget.getAttribute('data-id');
let currentValue = currentTarget.getAttribute('data-value');
I am running into an issue trying to integrate a third party product tour (Intercom) with a react application. There is no way to programmatically end a tour that I have found.
Basically, I need a prop that can change inside the react app whenever a certain non-react DOM element exists or not. I need to be able to tell in a hook or in componentDidUpdate whether or not a certain non-React element exists in the DOM.
I am not sure what to do because obviously when this tour opens and closes there is no change to state or props as far as react is concerned.
Is there a way I can wrap a component with the result of something like document.getElementById("Id-of-the-product-tour-overlay") as a prop? Is there a way I can watch for it with a hook?
Ideally something like
componentDidUpdate(){
if(elementExists){
//Do stuff that needs to happen while tour is on
}
if(!elementExists){
//do app stuff to end the tour
}
}
//OR
useEffect(()=>{
//do stuff conditional on element's existence
},[elementExists])
The easy way of doing so is to prepare a funcion that receives an HTML element and returns a function that receives a callback as an argument (function that returns other function - currying for purity). The result of the returned function is a new MutationObserver with the callback set.
const observeTarget = target => callback => {
const mutationObserver = new MutationObserver(callback);
mutationObserver.observe(target, { childList: true });
}
In non-react file you can feed this function with an HTML element that is a container of 3rd party element which you want to investigate.
Then export the function and you can use it in a react component.
export const observeProductTourOverlay = observeTarget(containerOfProductTourOverlay);
Then in a React component, you can use useEffect hook and use the function
const checkIFMyCompExists = () => !!document.querySelector("#my-component");
export const FromOutside = () => {
const [elementExists, setElementExist] = useState(checkIFMyCompExists());
const [indicator, setIndicator] = useState(3);
useEffect(() => {
observeProductTourOverlay((mutationRecord, observer) => {
const doesExist = checkIFMyCompExists();
setElementExist(doesExist);
// this will fire every time something inside container changes
// (i.e. a child is added or removed)
});
// garbage collector should take care of mutationObserver in a way there are no memory leaks, so no need to disconnect it on compoment unmouting.
}, []);
useEffect(() => {
setIndicator(elementExists);
//do stuff when elementExistance changes
}, [elementExists]);
return (
<div>
<div>{"my component has been added: " + indicator}</div>
</div>
);
};
Find the working demo here: https://codesandbox.io/s/intelligent-morning-v1ndx
Could you use a while loop?
useEffect(()=>{
while (document.getElementById('theTour') !== null) {
// do stuff
}
// do cleanup
})
import { useState } from 'react';
function Example() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
In the above example whenever setCount(count + 1) is invoked a re-render happens. I am curious to learn the flow.
I tried looking into the source code. I could not find any reference of useState or other hooks at github.com/facebook/react.
I installed react#next via npm i react#next and found the following at node_modules/react/cjs/react.development.js
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
On tracing back for dispatcher.useState(), I could only find the following ...
function resolveDispatcher() {
var dispatcher = ReactCurrentOwner.currentDispatcher;
!(dispatcher !== null) ? invariant(false, 'Hooks can only be called inside the body of a function component.') : void 0;
return dispatcher;
}
var ReactCurrentOwner = {
/**
* #internal
* #type {ReactComponent}
*/
current: null,
currentDispatcher: null
};
I wonder where can I find dispatcher.useState() implementation and learn how it triggers re-render when setState setCount is invoked.
Any pointer would be helpful.
Thanks!
The key in understanding this is the following paragraph from the Hooks FAQ
How does React associate Hook calls with components?
React keeps track of the currently rendering component. Thanks to the Rules of Hooks, we know that Hooks are only called from React components (or custom Hooks — which are also only called from React components).
There is an internal list of “memory cells” associated with each component. They’re just JavaScript objects where we can put some data. When you call a Hook like useState(), it reads the current cell (or initializes it during the first render), and then moves the pointer to the next one. This is how multiple useState() calls each get independent local state.
(This also explains the Rules of Hooks. Hooks need to be called unconditionally in the same order, otherwise the association of memory cell and hook is messed up.)
Let's walk through your counter example, and see what happens. For simplicity I will refer to the compiled development React source code and React DOM source code, both version 16.13.1.
The example starts when the component mounts and useState() (defined on line 1581) is called for the first time.
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
As you have noticed, this calls resolveDispatcher() (defined on line 1546). The dispatcher refers internally to the component that's currently being rendered. Within a component you can (if you dare to get fired), have a look at the dispatcher, e.g. via
console.log(React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentDispatcher.current)
If you apply this in case of the counter example, you will notice that the dispatcher.useState() refers to the react-dom code. When the component is first mounted, useState refers to the one defined on line 15986 which calls mountState(). Upon re-rendering, the dispatcher has changed and the function useState() on line 16077 is triggered, which calls updateState(). Both methods, mountState() on line 15352 and updateState() on line 15371, return the count, setCount pair.
Tracing ReactCurrentDispatcher gets quite messy. However, the fact of its existence is already enough to understand how the re-rendering happens. The magic happens behind the scene. As the FAQ states, React keeps track of the currently rendered component. This means, useState() knows which component it is attached to, how to find the state information and how to trigger the re-rendering.
setState is a method on the Component/PureComponent class, so it will do whatever is implemented in the Component class (including calling the render method).
setState offloads the state update to enqueueSetState so the fact that it's bound to this is really only a consequence of using classes and extending from Component. Once, you realize that the state update isn't actually being handled by the component itself and the this is just a convenient way to access the state update functionality, then useState not being explicitly bound to your component makes much more sense.
I also tried to understand the logic behind useState in a very simplified and basic manner, if we just look into its basic functionalities, excluding optimizations and async behavior, then we found that it is basically doing 4 things in common,
maintaining of State, primary work to do
re-rendering of the component through which it get called so that caller component can get the latest value for state
as it caused the re-rendering of the caller component it means it must maintain the instance or context of that component too, which also allows us to use useState for multiple component at once.
as we are free to use as many useState as we want inside our component that means it must maintain some identity for each useState inside the same component.
keeping these things in mind I come up with the below snippet
const Demo = (function React() {
let workInProgress = false;
let context = null;
const internalRendering = (callingContext) => {
context = callingContext;
context();
};
const intialRender = (component) => {
context = component;
workInProgress = true;
context.state = [];
context.TotalcallerId = -1; // to store the count of total number of useState within a component
context.count = -1; // counter to keep track of useStates within component
internalRendering(context);
workInProgress = false;
context.TotalcallerId = context.count;
context = null;
};
const useState = (initState) => {
if (!context) throw new Error("Can only be called inside function");
// resetting the count so that it can maintain the order of useState being called
context.count =
context.count === context.TotalcallerId ? -1 : context.count;
let callId = ++context.count;
// will only initialize the value of setState on initial render
const setState =
!workInProgress ||
(() => {
const instanceCallerId = callId;
const memoizedContext = context;
return (updatedState) => {
memoizedContext.state[instanceCallerId].value = updatedState;
internalRendering(memoizedContext);
};
})();
context.state[callId] = context.state[callId] || {
value: initState,
setValue: setState,
};
return [context.state[callId].value, context.state[callId].setValue];
};
return { useState, intialRender };
})();
const { useState, intialRender } = Demo;
const Component = () => {
const [count, setCount] = useState(1);
const [greeting, setGreeting] = useState("hello");
const changeCount = () => setCount(100);
const changeGreeting = () => setGreeting("hi");
setTimeout(() => {
changeCount();
changeGreeting();
}, 5000);
return console.log(`count ${count} name ${greeting}`);
};
const anotherComponent = () => {
const [count, setCount] = useState(50);
const [value, setValue] = useState("World");
const changeCount = () => setCount(500);
const changeValue = () => setValue("React");
setTimeout(() => {
changeCount();
changeValue();
}, 10000);
return console.log(`count ${count} name ${value}`);
};
intialRender(Component);
intialRender(anotherComponent);
here useState and initialRender are taken from Demo. intialRender is use to call the components initially, it will initialize the context first and then on that context set the state as an empty array (there are multiple useState on each component so we need array to maintain it) and also we need counter to make count for each useState, and TotalCounter to store total number of useState being called for each component.
FunctionComponent is different. In the past, they are pure, simple. But now they have their own state.
It's easy to forget that react use createElement wrap all the JSX node, also includes FunctionComponent.
function FunctionComponent(){
return <div>123</div>;
}
const a=<FunctionComponent/>
//after babel transform
function FunctionComponent() {
return React.createElement("div", null, "123");
}
var a = React.createElement(FunctionComponent, null);
The FunctionComponent was passed to react. When setState is called, it's easy to re-render;
My question is to do with the issue React has for binding functions in the render function.
The following is not good practice:
render() {
<div onClick={this.callFunction.bind(this)}/>
}
as each re render would add a new function to the page, eventually causing the browser to run out of memory.
The solution is to do this:
constructor() {
this.callFunction = this.callFunction.bind(this);
}
render() {
<div onClick={this.callFunction}/>
}
The problem with this is when I want to pass a value into the function.
I know I can make the div a child component, and pass the parameter in through the callBack, but this does not seem sensible if the div is only being used once in the whole application. I accept I could make this work, but this is not the scope of this question.
I also know that this solution:
render() {
<div onClick={() => this.callFunction.call(this, param)}/>
}
Is no better, as it is still creating a new function.
So my question is, how can I create a function that I can pass a parameter into without making a new component, and without binding a new funciton on each render?
You can't avoid creating a second component as you need to pass a function reference as an event handler, this will be executed by the browser when the event triggers.
So the problem is not the binding but the fact that you need to pass a reference, and references can't receive parameters.
EDIT
By the way, if you don't like the syntax and noise of binding or anonymous arrow functions you can use currying.
I posted an example in a different question if you find it interesting. this won't solve the problem though, it's just another approach to pass a new reference (which i find it to be the most terse)
You can change the declaration of callFunction to be an arrow function, which implictly binds the scope, like so:
callFunction = () => {
console.log('hi');
};
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
Then your original render function would work as expected!
Use Side effect
Side effect is something that a function use that comes from outside but not as argument. Now this mechanism is majorly used in Redux/Flux where the entire state is stored in a Store and every component fetches their state from it.
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
handlerProps: {
onClick: { count: 0},
onChange: { count: 0}
}
}
}
onClickHandler = () => {
const state = this.state.handlerProps.onClick;
console.log('onClick', state.count);
}
onChangeHandler = (value) => {
const state = this.state.handlerProps.onChange;
console.log('onClick', state.count);
this.setState({value: value})
}
buttonClick = () => {
const random = Math.ceil(Math.random()* 10) % 2;
const handler = ['onClick', 'onChange'][random];
const state = this.state.handlerProps;
state[handler].count++;
console.log('Changing for event: ', handler);
this.setState({handlerProps: state});
}
render () {
return (
<div>
<input onClick={this.onClickHandler} onChange={this.onChangeHandler} />
<button onClick={ this.buttonClick }>Update Props</button>
</div>
)
}
}
ReactDOM.render(<MyComponent/>, document.querySelector('.content'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.0.0/react-dom.min.js"></script>
<div class='content' />
The only way I know of is to create a new React Component which takes the value and the event handler as props.
This way, the handler as a function remains static, and since the value is passed down separately (in its own prop) you don't have any functions being re-instanciated. Because you don't bind anything nor create a new function each time.
Here's an example:
We have two buttons. The first one prints the current state variable value and the other increments it by one.
Normally, if we had done this with onClick={() => this.print(this.state.value)} we would get a new instance of this function, each time the MyApp component would re-render. In this case, it would re-render each time we increment the value with the setState() inside this.increment.
However, in this example, no new instance of this.print happens because we are only passing its reference to the button. In other words, no fat arrow and no binding.
In the <Button /> component, we have a <button> to which event handler we pass a reference to a function - just like we did in <MyApp />. However, here we know exactly what to pass to the function. As such, we have myHandler trigger this.props.handler(this.props.value).
class MyApp extends React.Component {
constructor() {
super();
this.state = {
value: 0
};
}
print = (value) => {
console.log(value);
}
increment = () => {
// This will trigger a re-render, but none of the functions will be reinstanciated!
this.setState((prevState) => ({value: prevState.value + 1}));
}
render() {
// Note that both handlers below are passed just the references to functions. No "bind" and no fat arrow.
return(
<div>
<Button handler={this.print} value={this.state.value}>Print</Button>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
class Button extends React.Component {
// Clicking the button will trigger this function, which in turn triggers the function with the known argument - both of which were passed down via props.
myHandler = () => this.props.handler(this.props.value);
render() {
// Note again that the handler below is just given the reference to a function. Again, not "bind" nor fat arrow.
return(
<button onClick={this.myHandler}>{this.props.children}</button>
);
}
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Though quite tedious, it is an effective solution. That being said, even if you do create a new function each time you render, the performance implications are minimal. From the official docs:
The problem with this syntax is that a different callback is created each time the LoggingButton renders. In most cases, this is fine.
As you might know we can create arrow functions as event handlers without binding this (with Babel Stage 2 support). In React with Redux we have Smart and Dumb Components. Hence I would like to create state modifying event handlers in the Smart Components with default params (for reusability) and pass them on to Dumb Components as props as detailed below : -
// SmartComponent.js
class SmartComponent extends Component {
state = {count: 0};
handleClick = (inc = 1) => this.setState({count: this.state.count + inc});
render = () => <DumbComponent handleClick={this.handleClick} />
}
// DumbComponent.js
function DumbComponent(props) {
return <button onClick={props.handleClick}>Submit</button>;
}
The problem is I get a console log stating that the passed arrow function is an object and not a function. It would work as expected if we create an arrow function as below, or if the initial arrow function doesn't have a default param in the DumbComponent.
// DumbComponent.js
function DumbComponent(props) {
return <button onClick={() => this.props.handleClick()}>Submit</button>;
}
Any ideas/suggestions to make it work without a second arrow function would be much appreciated.