How To Access Callback Like in setState from useState Hook - reactjs

Has anyone managed to create a sync callback for the update piece of the useState hook in react 16.8? I have been looking for one so that I can deal with synchronous actions with a 3rd party library and I can't seem to make one work for my needs.
If anyone has any references to people that have successfully completed this please add them here.
Cheers,

With hooks, you no longer need a callback from the setState function. Now, you can set state with the useState hook, and listen for its value to update with the useEffect hook. The optional second parameter of the useEffect hook takes an array of values to listen to for changes. In the example below, we are just monitoring one value for changes:
const Example = () => {
const [value, setValue] = useState(null);
useEffect(() => {
// do something when value changes
}, [value]);
return (
<button onClick={() => setValue('Clicked!')}>
Click Me!
</button>
);
};
Read more about the useEffect hook here.

You can use useEffect/useLayoutEffect to achieve this:
const SomeComponent = () => {
const [count, setCount] = React.useState(0)
React.useEffect(() => {
if (count > 1) {
document.title = 'Threshold of over 1 reached.';
} else {
document.title = 'No threshold reached.';
}
}, [count]);
return (
<div>
<p>{count}</p>
<button type="button" onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
If you are looking for an out of the box solution, check out this custom hook that works like useState but accepts as second parameter a callback function:
import useStateWithCallback from 'use-state-with-callback';
const SomeOtherComponent = () => {
const [count, setCount] = useStateWithCallback(0, count => {
if (count > 1) {
document.title = 'Threshold of over 1 reached.';
} else {
document.title = 'No threshold reached.';
}
});
return (
<div>
<p>{count}</p>
<button type="button" onClick={() => setCount(count + 1)}>
Increase
</button>
</div>
);
};
It can be installed via npm install use-state-with-callback
If you want to make synchrone layout updates, use import { useStateWithCallbackInstant } from 'use-state-with-callback'; instead.

Related

Memoized functions to custom hook not picking up updated value from hook

Given a small hook defined as
const useCount = ({ trigger }) => {
const [count, setCount] = useState(1)
const increment = () => setCount(count => count + 1);
return {
data: count,
increment,
trigger,
}
}
and being consumed as
function App() {
let data;
const log = useCallback(() => {
console.log(data);
}, [data]);
const hooked = useCount({
trigger: log
});
({ data } = hooked);
const { trigger, increment } = hooked;
return (
<div className="App">
<div>{data}</div>
<button onClick={increment}>Increment</button>
<button onClick={trigger}>Log</button>
</div>
);
}
If we click on increment, the data is updated. If we click on Log, the memoized value of 1 is logged.
Q1. Is it an anti-pattern to consume data returned by the hook in the memoized callback that is itself passed to the hook?
Q2. Why does it log 1 rather than undefined. If the log fn has picked up 1, why doesn't it pick up subsequent updates?
Q3. Removing the memoization for log fn would make it work. Is there any other apporach to work around this that doesn't involve removing memoization?
A small reproduction is available in this codesandbox.

How to move react event handlers to separate file ,export and then import for reuse in multiple different functional components?

I have a React functional component which prints a counter number being incremented or decremented on button clicks..Below is my function
I am using react version : ^16.13.1
export default function Counter(){
const [count, setCount] = useState(0);
const increment = () => {
//more logic here ..
setCount(count + 1);
}
const decrement = () => {
//more logic here ..
setCount(count !== 0 ? count - 1 : 0);
}
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => increment()}>Increment SO</button>
<button onClick={() => decrement()}>Decrement SO</button>
</div>
);
}
I want to separate event handlers logic that includes setting state in to a separate file and export it.. like below
import React, { useState } from 'react';
const Increment = () => {
//more logic here ..
setCount(count + 1);
}
const Decrement = () => {
//more logic here ..
setCount(count !== 0 ? count - 1 : 0);
}
export default { Increment, Decrement };
I wanted to use these exported functions in the Counter function like below
import CounterHelper from './CounterHelper';
<button onClick={() => CounterHelper.Increment()}>Increment SO</button>
I have run in to several error's while running this ,so i am sure i am missing some thing fundamental here. Can somebody please tell me if this is possible at all ? are there any alternate options to achieve the above said with functional component and react hooks only.
Thanks in advance.
Just pass the variable references.
CounterHelper.js
export const Increment = (count, setCount) => {
//more logic here ..
setCount(count + 1);
};
export const Decrement = (count, setCount) => {
//more logic here ..
setCount(count !== 0 ? count - 1 : 0);
};
Counter.js
import React, { useState } from 'react';
import { Increment, Decrement } from './CounterHelper';
export default function Counter(){
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => Increment(count, setCount )}>Increment SO</button>
<button onClick={() => Decrement(count, setCount)}>Decrement SO</button>
</div>
);
}

How to have callbacks in useState

My component looks like this:
constMyComponent = props => {
const [events, setEvents] = useState();
useEffect(() => {
getData(id).then(function(myEvents){
setEvents(myEvents);
});
}, [id]);
When I render the variable "events" it looks good thanks to the question mark operator:
<ul>
{ events && Object.keys(events.events).map( (data, i) => (
<li key = { i }>
<span>
{ events.events[data].mykey}
</span>
</li>
))}
</ul>
However, in "useEffect" I need to apply some logic to the retrieved data. After the row with
setEvents(myEvents);
I cannot access the variable "events". I guess it is not ready yet. I read a little bit about callback. Is that the way to go?
Just adding a ".then" won't work. How do you usually access data when it is accessible in this case?
If i understand ur question correctly u wanna make the new events made logically depending on old events And wana access it,
U can access it like this,
setEvents(prevEvents => {
// some logical computations
return theNewEvents
});
As of this example,
function Counter({initialCount}) {
const [count, setCount] = useState(initialCount);
return (
<>
Count: {count}
<button onClick={() => setCount(initialCount)}>Reset</button>
<button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
<button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
</>
);
}
From React Docs
But, if u wanna make the logical changes to the new events,
U can easily do it in the function before setting the state (as #jonrsharpe pointed out)
constMyComponent = props => {
const [events, setEvents] = useState();
useEffect(() => {
getData(id).then(function(myEvents){
// make ur changes to `myEvents`
...
// then, set it to state
setEvents(myEvents);
});
}, [id]);

Why is my increment count button returning [Object object]

I'm playing around with trying to learn React hooks and I'm trying to write a simple function that increments a count's state.
import React, { useState } from "react";
export const HookCounter = () => {
const [count, setCount] = useState(0);
const incrementCount = (count) => {
setCount(count + 1);
};
return (
<div>
<button onClick={incrementCount}>Press me!</button>
<h1>
You have pressed the button <strong>{count}</strong> times
</h1>
</div>
);
};
However, when I click on the button. Instead of the counter incrementing like I would hope it would. I'm instead seeing:
You have pressed the button [object Object]1 times.
Why is this?
The reason it doesn't work correctly is because you have defined count as an argument which is actually an event from onClick.
Instead of taking count from closure then, the function takes it from argument since that takes precedence. As event is a object when you try to execute count + 1, it stringifies the event object and adds 1 to it giving you [object Object]1
import React, { useState } from "react";
export const HookCounter = () => {
const [count, setCount] = useState(0);
const incrementCount = () => { // no count argument here
setCount(count + 1);
};
return (
<div>
<button onClick={incrementCount}>Press me!</button>
<h1>
You have pressed the button <strong>{count}</strong> times
</h1>
</div>
);
};
#Khatri is right, when you receive count as argument it gets the event object of that button. You can use console.log to print count (I renamed it as event) to check it.
import React, { useState } from "react";
export const HookCounter = () => {
const [count, setCount] = useState(0);
const incrementCount = (event) => {
console.log(event, 'event');
setCount(count+1);
};
return (
<div>
<button onClick={incrementCount}>Press me!</button>
<h1>
You have pressed the button <strong>{count}</strong> times
</h1>
</div>
);
};

Is there any difference between rendering a functional component with and without hooks?

Just tried some react-hooks and got some questions.
Consider this functional component with react-hooks:
const Counter = (props) => {
console.log("Counter component");
const [count, setCount] = useState(0);
const handleIncrease = () => {
setCount(count + 1);
}
const handleDecrease = () => {
setCount(count - 1);
}
return (
<div>
<button onClick={handleIncrease}>+</button>
<button onClick={handleDecrease}>-</button>
<p>{count}</p>
</div>
)
}
It logged everytime I clicked the "+" or "-".
Does it means that every handlers inside this component(or say, function) are redeclared and reassigned to a variable? If it did, won't it cause some performance issues?
To me the functional component with hooks seems like a huge render method of a classical component like this:
class Counter extends React.Component {
state = {
count: 0,
}
render() {
const handleIncrease = () => {
this.setState({ count: this.state.count + 1 });
}
const handleDecrease = () => {
this.setState({ count: this.state.count - 1 });
}
return (
<div>
<button onClick={handleIncrease}>+</button>
<button onClick={handleDecrease}>-</button>
<p>{count}</p>
</div>
)
}
}
which I think nobody will do this.
Did I have misunderstandings of the React's render mechanism or it's just not the best practice when using the functional component with react-hooks?
Although in functional components functions are recreated on every render, the performance cost of it much less compared to the benefits.
You can refer this post for more details: Performance penalty of creating handlers on every render
However you can still optimise so that functions are not recreated on every render using useCallback or useReducer(depending on whether your updates are complex or not)
const Counter = (props) => {
console.log("Counter component");
const [count, setCount] = useState(0);
const handleIncrease = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, [])
const handleDecrease = useCallback(() => {
setCount(prevCount => prevCount + 1);
}, [])
return (
<div>
<button onClick={handleIncrease}>+</button>
<button onClick={handleDecrease}>-</button>
<p>{count}</p>
</div>
)
}
In the above example the functions are re-created only on the initial render and using the state update callback we can update the state and avoid closure issues

Resources