createRef().current is coming as null in React - javascript

so basically my usecase is that when i click the download button present in each row of the table, my data returned should be downloaded via CSV.
I'm creating the ref like this
class ViewStorageGroups extends Component {
csvLink = React.createRef();
This is my custom component which renders a table
<TableWithHeaders
tableHeaders = {tableData.tableHeaders}
rowData = {tableData.rowData}
formatter = {tableData.formatter}
handleClick = {this.handleClick}
clickable = {tableData.clickable}
page = {rowsPerPage}
rowsPerPage = {rowsPerPage}
handlePageChange = {this.handlePageChange}
handleChangeRowsPerPage = {this.handleChangeRowsPerPage}
/>
this is my handleClick function which gets called when i press the download button
handleClick = (rowData, clickComponent,e) => {
const storageGroupDto = StorageGroupHelpers.convertStorageGroupDataToDto(rowData)
this.setState({groupDataDownloadResp: storageGroupDto}, () => this.csvLink.current.link.click())
}
and i'm using React's CSVLink component to download the data as csv.
renderDownloadedData = () => {
const fileName = createFileNameForCSVDownload('STORAGE_GROUP_DOWNLOAD', getWarehouseIdFromRedux(this.props))
return (
<div>
<CSVLink
ref={this.csvLink}
data={this.state.groupDataDownloadResp}
filename={fileName}
target='_blank'
/>
</div>
)
}
Now the issue is when handleClick is triggered, my this.csvLink.current is coming as null. the same implementation is present at other places in my codebase and working fine correct. i'm not able to figure out where i'm going wrong
I have also tried initialising csvLink = React.createRef(); at componenentDidMount and the constructor but still the same error is occuring.

finally got the issue but not sure the Reason.
if i use
<CSVLink
ref={this.csvLink}
data={this.state.groupDataDownloadResp}
filename={fileName}
target='_blank'
/>
inside a arrow function like this
renderDownloadedData = () => {
const fileName = createFileNameForCSVDownload('STORAGE_GROUP_DOWNLOAD', getWarehouseIdFromRedux(this.props))
return (
<div>
<CSVLink
ref={this.csvLink}
data={this.state.groupDataDownloadResp}
filename={fileName}
target='_blank'
/>
</div>
)
}
and then call this inside render like this
render () {
return (
{this.renderDownloadedData()}
)
}
It returns reactRef()'s current value as null. however if I dont use arrow function and use the function like this renderDownloadedData (), it works perfectly fine. if someone can explain me the reason i would be very grateful

Related

JS sync and async onclick functions

I've been struggling with this issue lately.
I'm not sure if it has any connection to "sync/async" functions in JS. If it does, I would be more then thankful to understand the connection.
I've been making a simple component function:
There's a button "next","back" and "reset". Once pressing the matching button, it allows moving between linkes, according to button's type.
The links are an array:
const links = ["/", "/home", "/game"];
Here is the component:
function doSomething() {
const [activeLink, setActiveLink] = React.useState(0);
const links = ["/", "/home", "/game"];
const handleNext = () => {
setActiveLink((prevActiveLink) => prevActiveLink+ 1);
};
const handleBack = () => {
setActiveLink((prevActiveLink) => prevActiveLink- 1);
};
const handleReset = () => {
setActiveLink(0);
};
return (
<div>
<button onClick={handleReset}>
<Link className = 'text-link' to = {links[activeLink]}> Reset</Link>
</button>
<button onClick={handleBack}>
<Link className = 'text-link' to = {links[activeLink]}>Back</Link>
</button>
<button onClick={handleNext}>
<Link className = 'text-link' to = {links[activeLink]}>Next</Link>
</button>
</div>
When I'm trying to put the activeLink in the "to" attribute of Link, it puts the old value of it. I mean, handleNext/ handleReset/handleBack happens after the link is already set; The first press on "next" needs to bring me to the first index of the links array, but it stayes on "0".
Is it has to do something with the fact that setActiveLink from useState is sync function? or something to do with the Link?
I would like to know what is the problem, and how to solve it.
Thank you.
Your Links seem to be navigating to a new page?
If so, the React.useState(0) gets called each time, leaving you with the default value of 0.
Also your functions handleNext and handleBack aren't called from what I can see.

How to ensure state is not stale in React hook

The buttons i create using below seems to lag in the selectedButtonIdx value.
Is the toggleSelected not complete by the time getClass is called ?
function ButtonGroup(props) {
const [selectedButtonIdx,setIdx]=useState(props.loadCurrentAsIndex);
const toggleSelected = (e) => {
setIdx(parseInt(e.target.dataset.index));
props.onclick(e);
};
const getClass = (index) => {
return (selectedButtonIdx === index) ? classnames('current', props.btnClass)
: classnames(props.btnClass)
};
let buttons = props.buttons.map((b, idx) => <Button key={idx} value={b.value} index={idx} text={b.text}
onclick={e => toggleSelected(e)}
btnClass={getClass(idx)}/>);
return (
<div>
{buttons}
</div>
);
}
Every onclick is expected to show the user which button in the group was clicked by changing its class.
By looking at this,
<Button
key={idx}
value={b.value}
index={idx}
text={b.text}
onclick={e => toggleSelected(e)}
btnClass={getClass(idx)}
/>
Button is your custom component,
Two things to notice here,
You have provided onclick (c is small) props, in you actual component it should be onClick={props.onclick}
You have used e.target.dataset.index, to work with dataset we should have attribute with data- prefix. So your index should be data-index in your actual component.
So finally your Button component should be,
const Button = (props) => {
return <button text={props.text} data-index={props.index} onClick={props.onclick} className={props.btnClass}>{props.value}</button>
}
Demo
The function setIdx, returned from useState is asynchronous, this means that it may be not be finished by the time you run your next function (as you guessed).
Take a look at useEffect it allows you to specify a function to run once an item in your state changes, this method will ensure your functions are called in the right order.
By now I don't see anything wrong here.
How it works:
initial render happens, onClick event listener is bound
user clicks a button, event handler calls setIdx triggering new render
new render is initiated, brand new selectedButtonIdx is used for rendering(and for getClass call as well)
See, there is no reason to worry about if setIdx is sync function or async.

React Hooks: useState with onClick only updating the SECOND time button is clicked?

Edit: forgot an important part - this is noticeable if you click the button next to Jeff A. Menges and check the console log.
The important part of the code is the "setFullResults(cardResults.data.concat(cardResultsPageTwo.data))" line in the onClick of the button code. I think it SHOULD set fullResults to whatever I tell it to... except it doesn't work the first time you click it. Every time after, it works, but not the first time. That's going to be trouble for the next set, because I can't map over an undefined array, and I don't want to tell users to just click on the button twice for the actual search results to come up.
I'm guessing useEffect would work, but I don't know how to write it or where to put it. It's clearly not working at the top of the App functional component, but anywhere else I try to put it gives me an error.
I've tried "this.forceUpdate()" which a lot of places recommend as a quick fix (but recommend against using - but I've been trying to figure this out for hours), but "this.forceUpdate()" isn't a function no matter where I put it.
Please help me get this button working the first time it's clicked on.
import React, { useState, useEffect } from "react";
const App = () => {
let artistData = require("./mass-artists.json");
const [showTheCards, setShowTheCards] = useState();
const [fullResults, setFullResults] = useState([]);
useEffect(() => {
setFullResults();
}, []);
let artistDataMap = artistData.map(artistName => {
//console.log(artistName);
return (
<aside className="artist-section">
<span>{artistName}</span>
<button
className="astbutton"
onClick={ function GetCardList() {
fetch(
`https://api.scryfall.com/cards/search?unique=prints&q=a:"${artistName}"`
)
.then(response => {
return response.json();
})
.then((cardResults) => {
console.log(cardResults.has_more)
if (cardResults.has_more === true) {
fetch (`https://api.scryfall.com/cards/search?unique=prints&q=a:"${artistName}"&page=2`)
.then((responsepagetwo) => {
return responsepagetwo.json();
})
.then(cardResultsPageTwo => {
console.log(`First Results Page: ${cardResults}`)
console.log(`Second Results Page: ${cardResultsPageTwo}`)
setFullResults(cardResults.data.concat(cardResultsPageTwo.data))
console.log(`Full Results: ${fullResults}`)
})
}
setShowTheCards(
cardResults.data
.filter(({ digital }) => digital === false)
.map(cardData => {
if (cardData.layout === "transform") {
return (
//TODO : Transform card code
<span>Transform Card (Needs special return)</span>
)
}
else if (cardData.layout === "double_faced_token") {
return (
//TODO: Double Faced Token card code
<span>Double Faced Token (Needs special return)</span>
)
}
else {
return (
<div className="card-object">
<span className="card-object-name">
{cardData.name}
</span>
<span className="card-object-set">
{cardData.set_name}
</span>
<img
className="card-object-img-sm"
alt={cardData.name}
src={cardData.image_uris.small}
/>
</div>
)
}
})
)
});
}}
>
Show Cards
</button>
</aside>
);
});
return (
<aside>
<aside className="artist-group">
{artistDataMap}
</aside>
<aside className="card-wrapper">
{showTheCards}
</aside>
</aside>
);
};
export default App;
CodesAndBox: https://codesandbox.io/embed/compassionate-satoshi-iq3nc?fontsize=14
You can try refactoring the code like for onClick handler have a synthetic event. Add this event Listener as part of a class. Use arrow function so that you need not bind this function handler inside the constructor. After fetching the data try to set the state to the result and use the state to render the HTML mark up inside render method. And when I run this code, I have also seen one error in console that child elements require key attribute. I have seen you are using Array.prototype.map inside render method, but when you return the span element inside that try to add a key attribute so that when React diffing algorithm encounters a new element it reduces the time complexity to check certain nodes with this key attribute.
useEffect(() => {
// call the functions which depend on fullResults here
setFullResults();
}, [fullResults])
// now it will check whether fullResults changed, if changed than call functions inside useEffect which are depending on fullResults

document.execCommand ('copy') don't work in React

I have the function below that is called on click of a button . Everything works well, but the document.execCommand ('copy') simply does not work.
If I create another button and call only the contents of if in a separate function, it works well.
I have already tried calling a second function inside the first one, but it also does not work. the copy is only working if it is alone in the function.
Does anyone know what's going on?
copyNshort = () => {
const bitly = new BitlyClient('...') // Generic Access Token bit.ly
let txt = document.getElementById('link-result')
bitly.shorten(txt.value)
.then((res) => {
this.setState({ shortedLink: res.url })
if (this.state.shortedLink !== undefined) {
document.getElementById('link-result-shorted').select() // get textarea value and select
document.execCommand('copy') // copy selected
console.log('The link has been shortened and copied to clipboard!')
ReactDOM.render(<i className="fas fa-clipboard-check"></i>, document.getElementById('copied'))
}
console.log('Shortened link 👉🏼', res.url) // Shorted url
})
}
The problem is that the copy-to-clipboard functionality will only work as a direct result of a user's click event listener... This event cannot be virtualised and the execCommand will not work anywhere else than the immediate callback assigned to the event listener...
Because react virtualises and abstracts 'events' then that's very possibly where the problem lies and as suggested you should be using React's react-copy-to-clipboard.
You can use lib react-copy-to-clipboard to copy text.
import {CopyToClipboard} from 'react-copy-to-clipboard';`
function(props) {
return (
<CopyToClipboard text={'Text will be copied'}>
<button>Copy button</button>
</CopyToClipboard>
);
}
if you click button Copy button, it will copy the text Text will be copied
The lib react-copy-to-clipboard based on copy-to-clipboard does work for me, but if you want to copy the source into your own file, Some places need attention.
The code below works fine.
import React, { Component } from 'react'
class App extends Component {
render() {
return (
<div className="App">
<h1
onClick={e => {
const range = document.createRange()
const selection = document.getSelection()
const mark = document.createElement('span')
mark.textContent = 'text to copy'
// reset user styles for span element
mark.style.all = 'unset'
// prevents scrolling to the end of the page
mark.style.position = 'fixed'
mark.style.top = 0
mark.style.clip = 'rect(0, 0, 0, 0)'
// used to preserve spaces and line breaks
mark.style.whiteSpace = 'pre'
// do not inherit user-select (it may be `none`)
mark.style.webkitUserSelect = 'text'
mark.style.MozUserSelect = 'text'
mark.style.msUserSelect = 'text'
mark.style.userSelect = 'text'
mark.addEventListener('copy', function(e) {
e.stopPropagation()
})
document.body.appendChild(mark)
// The following line is very important
if (selection.rangeCount > 0) {
selection.removeAllRanges()
}
range.selectNodeContents(mark)
selection.addRange(range)
document.execCommand('copy')
document.body.removeChild(mark)
}}
>
Click to Copy Text
</h1>
</div>
)
}
}
export default App
import React, { Component } from 'react'
class App extends Component {
render() {
return (
<div className="App">
<h1
onClick={e => {
const mark = document.createElement('textarea')
mark.setAttribute('readonly', 'readonly')
mark.value = 'copy me'
mark.style.position = 'fixed'
mark.style.top = 0
mark.style.clip = 'rect(0, 0, 0, 0)'
document.body.appendChild(mark)
mark.select()
document.execCommand('copy')
document.body.removeChild(mark)
}}
>
Click to Copy Text
</h1>
</div>
)
}
}
export default App

React: Unexpected Behaviour

I want my code to toggle a person handler, Before it was working but since I split into components, It seem to have broken.
Toggle happens on button click (see inside return statement <
button className={btnClass}
onClick={props.toggler}>Button</button>
Here is my entire cockpit.js file (inside src/components/cockpit/cockpit.js).
import React from 'react';
import classes from './cockpit.css';
const Ccockpit = (props) => {
const assignedClasses = [];
let btnClass = ''
if (props.cocPersonState) {
btnClass = classes.red;
console.log(".......")
}
if (props.cocperson <= 2) {
assignedClasses.push(classes.red)
}
if (props.cocperson <= 1) {
assignedClasses.push(classes.bold)
}
return(
<div className={classes.cockpit}>
<h1> Hi I am react App</h1>
<p className={assignedClasses.join(' ')}>hey </p>
<button className={btnClass}
onClick={props.toggler}>Button</button>
</div>
);
}
export default Ccockpit;
and inside App.js
return (
<div className={classes.App}>
<Ccockpit>
cocPersonState = {this.state.showPerson}
cocperson = {this.state.person.length}
toggler = {this.togglerPersonHandler}
</Ccockpit>
{person}
</div>
)
}
}
and this is my togglerpersonHandler code.
togglerPersonHandler = () => {
const doesShow = this.state.showPerson;
this.setState({
showPerson: !doesShow
});
}
I can't see to figure out that why it won't toggle and console.log/change color to red (It isn't changing the state). Can someone please review and figure out the mistake?
Your JSX still isn't right. Please review the JSX syntax with regards to giving it props/children.
You have this:
<Ccockpit>
cocPersonState = {this.state.showPerson}
cocperson = {this.state.person.length}
toggler = {this.togglerPersonHandler}
</Ccockpit>
But those values aren't children, they're properties. So they need to be in the opening tag, like this:
<Ccockpit
cocPersonState = {this.state.showPerson}
cocperson = {this.state.person.length}
toggler = {this.togglerPersonHandler}/>
Revisit some React tutorials to see how JSX should be structured and how it works.

Categories

Resources