I've got a page with a table generated by a class component where, depending on some logic, I have only one of its rows "active" in a certain moment. I'm trying to scroll to the "active" row as the user opens the page.
I've already read this discussion but it didn't help
ReactJS how to scroll to an element
Doing like this I get that this.scrollHere.current is null
constructor(props) {
super(props);
this.scrollHere = React.createRef();
}
componentDidMount() {
// not using 'current', scrollIntoView() results not to be a function
this.scrollHere.current.scrollIntoView({behavior: 'smooth', block: 'start'})
}
render() {
return(
...
<tr ref={this.scrollHere}><td>...</td></tr>
...
)
}
Is that because the reference, when componentDidMount() is called, doesn't yet exist? If so, how to make it work?
We can do something like this (use scrollIntoView), this issue with your code is that you are not using callback refs-:
it should be defined like this -:
<tr ref={e => this.scrollHere = e}><td>....</td></tr>
this.scrollHere.scrollIntoView()
Here is a working demo https://stackblitz.com/edit/react-yzszzv
also replace
this.scrollHere.current.scrollIntoView({behavior: 'smooth', block: 'start'})
with this.scrollHere.scrollIntoView({behavior: 'smooth', block: 'start'})
Related
Its been a while since I used ReactJS and I need to create a list of items that I can add to and remove. I've added an onClick event to my li to remove it. I also have a button to add new items, these seem to work but the state is not updating.
var new_items = [...Array(1)].map((val, i) => `No Items`);
<ul className="App-list">
{new_items.map((item, i) => (<li key={`item_${i}`} onItemClick={onItemClick(i)}>{ item }</li>))}
</ul>
the onClick function is here
function onItemClick(num) {
this.setState({
new_items: this.state.new_items.concat('new value')
})
}
I just need to either delete a line from the List or Add depending on status but even though it runs it does not update the state. Can someone give me either a batter way of updating a list of rows dynamically or tell me what I'm doing wrong.
You need to add a constructor as follows:
class MyClassName {
constructor(props) {
super(props);
this.state = {
new_items: [] // or null or any other initial value depending on your use case
}
this.onItemClick = this.onItemClick.bind(this);
}
function onItemClick(num){ ... }
}
Then while calling the onClick function you call it as follows:
onItemClick={this.onItemClick(i)}
Also, if you are using the generic onClick functionality, you would have to change onItemClick to onClick:
<li key={`item_${i}`} onClick={()=>this.onItemClick(i)}>
Since you are not using the event information from the click, you have to add an anonymous function that calls your desired onClick handler. Hence the ()=>this.onItemClick(i)
Since you are using Class Component you have to call the method with the context of class
onItemClick={this.onItemClick(i)}
Background:
I'm developing an itinerary builder which is made up of rows, or component instances named EventContainers, that represent an activity on a user's given day.
The class, shown below, has an array in state.events that accepts EventContainers from the setState in the pushNewEventContainerToState function.
Of note, each EventContainer contains a button that is intended to give the user the ability to onClick an additional row/EventContainer by calling pushNewEventContainerToState.
The same button is also listed as its own component instance, named NewEventButton, and is displayed before any EventContainers.
Any EventContainer that is setStated to state.events is supposed to be placed in the index immediately after the EventContainer that calls setState, not at the beginning or end.
Method
I'm using .slice() in setState with the intention of doing just that => placing the newest EventContainer in the index immediately following the EventContainer that called setstate.
Problem
However, there are three issues I see:
a) Only the very first button, NewEventButton, will actually call setState. The buttons on the new EventContainer's won't do anything.
b) The EventContainers that pass through setState seem to be .pushed() to state.events, not .sliced(), but I don't need them at the end of the array.
c) When I check on Chrome devtools, I see that any EventContainer that's setStated to state.events is undefined.
What I've tried
I've tried placing two different kinds of props directly into the EventContainer that's inside pushNewEventContainerToState:
1. The first prop I tried didn't do anything -> onClick={() => this.pushNewEventContainerToState(index)
2. The second prop I tried was the same as the first except, instead of onClick, I named it pushNewEventContainerToState. This got the button on new EventContainers working but the EventContainers seemed to again be .pushed() instead of .sliced the way I need them to be. Chrome devtools will then show this is defined but I don't see a bound like I do for the first NewEventButton.
Thank you very much for taking a look.
class DayContainer extends React.Component {
constructor(props){
super(props);
this.state = {
events: [],
};
this.pushNewEventContainerToState = this.pushNewEventContainerToState.bind(this);
}
pushNewEventContainerToState(index) {
let newEvent = < EventContainer / > ;
this.setState(prevState => {
const updatedEvents = [...prevState.events.slice(0, index), newEvent, ...prevState.events.slice(index + 1)];
return {
events: updatedEvents
};
})
}
render(){
return (
<>
<div>
<ul>
{
this.state.events === null
? <EventContainer pushNewEventContainerToState={this.pushNewEventContainerToState} />
: <NewEventButton pushNewEventContainerToState={this.pushNewEventContainerToState} />
}
{this.state.events.map((item, index) => (
<li
key={item}
onClick={() => this.pushNewEventContainerToState(index)}
>{item}</li>
))}
</ul>
</div>
</>
)
}
}
Try fixing this part of your function first, looks like it doesn't behave as expected:
[...prevState.events.slice(0, index), newEvent, ...prevState.events.slice(index + 1)]
change to:
[...prevState.events.slice(0, index), newEvent, ...prevState.events.slice(index)]
.slice()'s second argument tells it to stop there, but doesn't include the index of the element. So if you have [1, 2, 3].slice(0,1) in example, you'll get only the first element -> [1].
Hope that fixes the issue.
I'm trying to make scroll to the top, if a certain condition is met, in the component's componentWillReceiveProps event ... but nothing happens:
componentWillReceiveProps(nextProps) {
// some code...
if (newQuery === query && this.scrollViewRef.current) {
console.log('Should scroll to top'); // << logs successfully
this.scrollViewRef.current.scrollTo({
x: 0,
y: 0,
duration: 500,
animated: true,
});
}
}
Code snippet of how I created ref for the scrollView:
class SearchResult extends React.Component {
constructor(props) {
super(props);
this.scrollViewRef = React.createRef();
}
//...
}
render method:
render () {
return (
<ScrollView
ref={this.scrollViewRef}
contentContainerStyle={{ marginVertical: 8 }}
>
...
)
}
I also tried to scroll manually via a button press ... doesn't work as well
Any help ?
I figured this out ...
The scrollView worked perfectly in an isolated env ( a brand new project ) ...
I thought the issue could be in the container of that scrollview ... and I found that the parent component has also a ScrollView ... once i removed it, everything worked perfectly.
For those people who use useRef() method and gets 'xRef.scrollTo' is not a function error, try to use it like xRef.current.scrollTo({[YOUR_PARAMS]}).
I didn't know this current thing and was getting crazy.
React Native docs say:
Note: The weird function signature is due to the fact that, for historical reasons, the function also accepts separate arguments as an alternative to the options object. This is deprecated due to ambiguity (y before x), and SHOULD NOT BE USED.
Maybe try scrollToOffset method, if you are also using FlatList with ScrollView?
The componentWillReceiveProps is becoming deprecated, however, I am unclear as to how to migrate away from it. For example, a simplified version of my current looks something like this:
import Reorder, {reorder, reorderImmutale, reorderFromTo, reorderFromToImmutable} from 'react-reorder'
class ObjectsArea extends React.Component {
constructor(props) {
super(props);
this.state = {
items: this.props.objects ? this.props.objects.items : []
};
}
componentWillReceiveProps(nextProps){
//May have to do a deep compare between nextProps.items and current items?
if (nextProps.objects){
this.setState({items: this.nextProps.objects.items})
}
}
onReorder (event, previousIndex, nextIndex, fromId, toId) {
let new_items = reorder(this.state.items, previousIndex, nextIndex)
this.setState({
items: new_items
});
//call to parent function
}
render(){
orderable_items = <Reorder reorderId="objects" onReorder={this.onReorder.bind(this)}>
{
this.state.items.map(item => (
<div key={item.id}>
{item.text}
</div>
))
}
</Reorder>
return (
<div>{orderable_items}</div>
)
}
My requirements:
Sometimes there will be no objects property (there isn't one on initial load)
When there is an objects property a sortable/draggable list is created using the react-reorder component
When items in the list are dragged to be rearranged the onReorder function is called.
The onReorder function should do two things: update the list on the screen, call a parent function passed in from props.
Currently all of this will work with componentWillReceiveProps, however, what is the proper way to migrate away from componentWillReceiveProps based on the above requirements?
While Tolsee's answer is perfectly correct it is also worth mentioning that the react docs suggest removing derived state (state that is calculated based on props) altogether. There is a great article here that is a great read in my opinion.
Your example fits the Anti-pattern: Unconditionally copying props to state example perfectly.
Without knowing your environment I cannot recommend a solution certainly, but to me it looks like you will be able to use the Fully controlled component example.
In that case, you'd need to lift your state up, simply use objects.items to render your Reorder child, and during the onReorder event simply call a function that you received as a prop.
In your problem you can do.
static getDerivedStateFromProps(nextProps, prevState){
if (nextProps.objects){){
return {items: this.nextProps.objects.items};
}
else return null;
}
Please follow this post for better understanding
I have master-detail table in react. If I select (or click a button) on a row in the master table, I want to set a state variable with the id of the selected row, which will be used to render the details table for that ID.
However, react does not allow me to use setState in the event function.
constructor(props){
super(props);
this.state = {
customer: {},
customerRequests: [],
interactions: [],
order: [],
error: '',
selectedCustomerRequest: ''
}
this.onSelectCustomerRequest = this.onSelectCustomerRequest.bind(this);
}
The event function is
onSelectCustomerRequest(id){
console.log(id)
// this.setState({selectedCustomerRequest: id});
}
The HTML is
<tr key={item._id} onClick={this.onSelectCustomerRequest(item._id)}>
<td><button onClick={this.onSelectCustomerRequest(item._id)} /></td>
<td>{moment(item.orderDate).format('DD/MM/YYYY hh:mm')}</td>
<td>{item.genericName}</td>
<td>{item.quantity}</td>
<td>{item.priority}</td>
<td>{interaction.orderStatus}</td>
<td>{moment(interaction.created).format('DD/MM/YYYY hh:mm')}</td>
<td>{interaction.createdBy}</td>
</tr>
if I enable the setState in onSelectCustomerRequest(id){} I get error. secondly I see that the onSelectCustomerRequest() gets executed for every row as I can see it console.log on my terminal.
I want to setState only when I click the button only.
I am sure I am doing a silly mistake somewhere, can you help? Please.
the problem here is that you call the function immediately by add parentheses () in the end of method.
To make it work, I would recommend create a closure within a method, so your case it would look like
onSelectCustomerRequest(id) {
return () => {
console.log(id)
}
}
So you basically by calling onSelectCustomerRequest in onClick return a function, which you can fire late.