I want to be able to scroll between table components in my React app. I have created a component for a table called FormattedTable which takes in props and displays all the information that I want.
A lot of the tables refer to other tables with clickable text. If you click on a reference to another table and it is not being displayed, I add the table to the display and the app automatically scrolls down to the bottom of the screen where the table has been added. However, if the table is already being displayed, I want the app to scroll to where it is being displayed already.
The clicking on the reference and adding another table all occurs in the FormattedTable.js file.
In my Home.js I have an array of objects called selected and this array contains all the objects that I want to be displayed in tables. I display the tables by mapping through the selected array creating a FormattedTable component on each iteration.
Home.js
<div className="rightColumn" style={{flex: 4}}>
{selected.length > 0 ? selected.map((obj, index) => {
return (
<div style={{width: '60%'}}>
<FormattedTable data={data} selected={selected} obj={obj} index={index} onSelectedChange={setSelected}/>
</div>
)
})
: null}
</div>
Because the FormattedTables are being created dynamically in the Home.js file, I'm not sure how to scroll from one table to another in FormattedTable.js (since there is only 1 file but multiple instances).
Does anyone know how this would be possible to do in the FormattedTable.js file?
What I've tried so far is added a ref to the div that's being dynamically created in Home.js and also passed in a triggerScroll method to the FormattedTable component so that I can trigger the scroll when a reference is clicked on a table. The issue with this though is that it still scrolls to the last element as the value of the ref is (naturally) the last element of the array when the mapping stops.
<div className="rightColumn" style={{flex: 4}}>
{selected.length > 0 ? selected.map((obj, index) => {
return (
<div ref = {scrollRef} style={{width: '60%'}}>
<FormattedTable data={data} selected={selected} obj={obj} index={index} onSelectedChange={setSelected} triggerScroll={scrollToTable}/>
</div>
)
})
: null}
</div>
Fixed myself:
Added an attribute to each object in the selected array called inFocus. The most recently selected object in the array has a value of true for this attribute.
I then added a ternary to setting the ref of the FormattedTable based on the inFocus attribute so only one object will be set as the ref at a time.
FormattedTable.js
//For selecting results
const select = (name) => {
//Deep clone object so that results doesn't change when selected changes
const obj = cloneDeep(data.find(element => element.name === name));
const refObj = data.find(element => element.name === name);
//If object is in the original JSON array
if(typeof refObj !== 'undefined') {
//If object is not in the selected array, add it to selected array
if(selected.find(element => element.name === name) === undefined) {
//Make the new object in focus and remove the focus for all the other objects
obj.inFocus = true;
copy.map((el) => {
if(el.name !== obj.name) {
el.inFocus = false
}
})
handleSelectedChange([...copy, obj]);
}
//Otherwise, set focus to selected object
else {
//Make the selected object in focus and remove the focus for all the other objects
copy.map((el) => {
if(el.name !== obj.name) {
el.inFocus = false
} else {
el.inFocus = true
}
})
handleSelectedChange([...copy]);
}
}
}
return (
<div ref={obj.inFocus ? messagesEndRef : null} style={{display: 'flex', flexDirection: 'row'}}>
...
)
Related
Having a hard time seeing how I could accomplish this. I created some custom number buttons from 0-9 that users can click on instead of using the keyboard. The problem I'm having is I have multiple dynamically created input fields depending on JSON Data so let's say there are 10 dynamically created input fields and a user starts with question one and the user then uses the custom number buttons I created and clicks numbers "145" to answer question one, but what happens is then all 10 inputs have the same number "145" not the problem the user was trying to solve. I'm using the context API to then save the values typed in on a function called getButtonValue that I then call to the parent component and save the values in a state array, so I know that my problem is that all the inputs share the same state array but how could I make sure the correct input the user clicks on is only receiving those values.
Thanks in advance.
My Custom Number Button Component:
import { FormContext } from "../../lib/FormContext";
function ActivityBar() {
const { getButtonValue } = useContext(FormContext);
return (
<div className={`${activity.activity__workSheet__numberButton}`}>
<button value={0} onFocus={(e) => getButtonValue(e)}>
<img
className={`${activity.activity__workSheet__img0}`}
src={"/assets/activityNumber-btn.png"}
alt="activity number button"
/>
.... more code
Parent Component:
const [numberButtonClicked, setNumberButtonClicked] = useState([]);
const getButtonValue = (e) => {
setNumberButtonClicked((prevButtonClicked) => [
...prevButtonClicked,
e?.target?.attributes[0].value
]);
};
return (
<Carousel>
<div ref={imageRef} style={{ height: "100%" }}>
{Object.entries(elements).map((element, i) => {
const { fields } = element[1];
if (fields) {
return (
<Element
key={i}
field={fields[0]}
id={i}
useReff={`answer${i}`}
currentValue={
numberButtonClicked === "" ? null : numberButtonClicked.join("")
}
/>
);
} else {
return;
}
})}
</div>
</Carousel>
Got a good working version figured out for this scenario, what I did was.
I have a onFocus method on my input tags that then takes in the event and calls a handleChange(e) function. Within that function I then save the currentInputId in a variable by using e?.target?.attributes[0]?.value and the previous InputId in a state variable and just check if the previous InputId is equal to the currentId user just focused on. If so then we'll add the next number user clicks into the same field, else if previousInputId !== currentInputId then make my user value state array empty, setNumberButtonClicked([]).
This is how I am currently doing it - it is very uneloquent, and it doesn't work. I am getting
TypeError: Cannot read property '0' of undefined
at grid.push(<Card key={index+x} name={list[index+x][0]} img={list[index+x][1]}/>);
this is my code
const list = // my list of data
var grid = [];
list.map((element, index) => {
if (index%4 == 0) {
if (index<list.length-2) {
const el = (<div className="row">
//card being a component I built
<Card key={index} name={element[0]} img={element[1]}/>
<Card key={index+1} name={list[index+1][0]} img={list[index+1][1]}/>
<Card key={index+2} name={list[index+2][0]} img={list[index+2][1]}/>
<Card key={index+3} name={list[index+3][0]} img={list[index+3][1]}/>
</div>)
grid.push(el);
} else {
let remainder = (list.length-index);
for (var x=0;x+=1;x<remainder) {
grid.push(<Card key={index+x} name={list[index+x][0]} img={list[index+x][1]}/>);
}
}
}
})\
return (
<div>
{grid}
</div>
)
The current way I am doing it is making grid then iterating through the list and adding jsx to it. The problem is that I need to make a row for bootstrap every 4 columns to make the grid, any better ideas, solutions greatly appreciated!
You are using index+1, index+2, index+3 which will never exist in the array, when you reach last element.
Instead calculate it in some other variable to find elements which exist.
I have a redux store where is rooms array stored (fetched from server) and in my component i fetch it from the store, map it and then display elements with value of room['foo'] but my problem is this: when the components are mapped and displayed the value of one that user clicks is going to be sent to server so i store clicked elements value in components local state like this:
...
handleRoomSelection(roomNumber,index,e){
this.setState({
room: roomNumber
})
}
...
{this.props.rooms.map((val,i)=>{
return (
val.reserved === false ? <p className="" key={i} onClick={e => this.handleRoomSelection(val.roomNumber,i,e)}>{val.roomNumber}</p> : null
)
})}
and this works fine, but my problem is that i want to add className "active" to active element (there can only be one active element) it would be easy if there could be many active elements (i would just add e.target.className = "active" after setState) so how can i achieve my aim?
Basically just do what you stated: set the class based on the room number in your state. setState triggers render(), so enrich even though there's nothing active on first render, once you click, render triggers and we can just set that class inside your map:
render() {
return this.props.rooms.map(val => {
if (val.reserved) return null;
return <p
className={ this.state.room === val.roomNumber && `active`}
key={val.roomNubber}
onClick={() => this.handleRoomSelection(val.roomNumber)}
>{val.roomNumber}</p>;
};
}
Note that we've changed the key to something that uniquely identifies the element we're building a <p> for: never use array index as key, because array position does not identify any specific element, it only identifies a position in a list.
Also note that there's no reason to pass more than just the room number in your click handler if the handler itself only cares about the room number.
Compare the room in state to each room's roomNumber for defining the className
{
this.props.rooms.map((val, i) => {
const className = this.state.room === val.roomNumber ? 'active' : '';
return val.reserved === false ? (
<p
className={className}
key={i}
onClick={e => this.handleRoomSelection(val.roomNumber, i, e)}
>
{val.roomNumber}
</p>
) : null;
});
}
Have you tried to use your state to populate className ?
{this.props.rooms.map((val,i)=>{
return (
val.reserved === false
? <p
className={val.roomNumber === this.state.room ? 'active' : ''}
key={i}
onClick={e => this.handleRoomSelection(val.roomNumber,i,e)}>
{val.roomNumber}
</p>
: null
)
})}
Hey Stack OverFlow community I am working on a React project where I am mapping over a set of table rows. Within every table row I have an additional row with more info about each individuals rows data. My issue is that when I click on the button to render additional information for that table it renders all of the additional informations for all of the rows.
I understand that my logic is implemented in a way where every single additional row will show upon a click. What can I do to fix this?
https://codesandbox.io/s/rj8o4r493n
showDrawyer = () => {
let {showDrawyer} = this.state
this.setState({
showDrawyer: !showDrawyer
})
}
renderTableCellData = () => {
let { tableData } = this.props;
return tableData.map((data, index) => {
return (
<Table.Body>
<Table.Row style={{ height: 75 }}>
<Table.Cell onClick={this.showDrawyer}>{data.name}</Table.Cell>
<Table.Cell>{data.number}</Table.Cell>
<Table.Cell>{data.date}</Table.Cell>
<Table.Cell>{data.uid}</Table.Cell>
</Table.Row>
<Table.Row style={{display: this.state.showDrawyer ? '' : 'none' }}>
<Table.Cell>Hidden Row data</Table.Cell>
</Table.Row>
</Table.Body>
)
})
}
state={
shownDrawerIndex:null
}
showDrawyer = (index) => {
this.setState({
shownDrawerIndex:index
})
}
renderTableCellData = () => {
let { tableData } = this.props;
return tableData.map((data, index) => {
return (
<Table.Body>
<Table.Row style={{ height: 75 }}>
<Table.Cell onClick={()=>this.showDrawyer(index)}>{data.name}</Table.Cell>
<Table.Cell>{data.number}</Table.Cell>
<Table.Cell>{data.date}</Table.Cell>
<Table.Cell>{data.uid}</Table.Cell>
</Table.Row>
<Table.Row style={{display: this.state.shownDrawerIndex == index ? '' : 'none' }}>
<Table.Cell>Hidden Row data</Table.Cell>
</Table.Row>
</Table.Body>
)
})
}
You will have to pass the index of the row on click.This will set the state to that index.
React will re-render the component on set state. While doing this it will check for the drawer index value in state.
According to that state value, it will display and hide the drawer
The solution depends on whether you want (1) additional details to be displayed for multiple rows, or whether (2) once you click on one row, only this row's additional details will be shown, and hidden for the one clicked before.
For (1) Add to the tableData array a showDrawyer field which will be tested in order to know whether to display or not to display the additional info for this element.
OnClick should get as parameter the clicked array element and should toggle this element's showDrawyer value.
For (2) - the state variable that decides which row's additional details are displayed will be an index, rather than a toggle. This index will be checked for the additional details display.
I am basically wanting to do individual selected states on divs that I am rendering in a loop. I can only see a way to change the color of all of the rendered divs, but rather I wish to change the color of which ever one was clicked. Below is the code for the loop.
renderSports() {
const {sports} = this.props
return sports.valueSeq().map(sport => this.renderActualSports(sport))
},
renderActualSports(sport) {
const {sportCount} = this.props
return (
<div className="sportSeparator">
{sport} {this.renderCount(sportCount.get(sport))}
</div>
)
},
This will basically just render a list of some sports. I want to change the color of a selected sport on click.
You will need to store the items that were clicked in your component state.
Assuming you would store this highlighted items in this.state.highlighted and that your sport variable is a string or number:
renderActualSports(sport) {
const {sportCount} = this.props
return (
<div
className="sportSeparator"
onClick={this.highlight(sport)}
style={{color: this.state.highlighted.indexOf(sport) > -1 && 'red' : ''}}
>
{sport} {this.renderCount(sportCount.get(sport))}
</div>
)
},
highlight(sport) {
return () => {
this.setState({highlighted: [...this.state.highlighted, sport]});
}
}
So what you are doing is onClick on the div you add that sport to the this.state.highlighted array and when displaying the list. you check if that sport is in the array and if yes you change the color using an inline style