Links to external resource are empty in React - reactjs

In a React app I have a Table tbody of which looks the following way:
<tbody>
{
this.state.datasets.map((datasetName, idx) => {
return (<tr>
<td>
<div className={"pretty p-default p-curve"}>
<input id = {"datasetCheckBox" + idx}
checked = {this.state.checkBoxSelected === datasetName}
type="radio" name="datasetName"
onChange={(e) => this.checkBoxSelected(datasetName, e)}/>
<div className={"state p-primary"}>
<label>{idx}</label>
</div>
</div>
</td>
<td>{datasetName}</td>
<td>{datasetsJSON["datasets"][datasetName]["region"]}</td>
<td>{datasetsJSON["datasets"][datasetName]["authors"]}</td>
<td>
<a href={datasetsJSON["datasets"][datasetName]["link"]}>{datasetName}</a>
</td>
</tr>)
}, this)
}
</tbody>
The link is shown with an empty href even though other parameters of the table from datasetsJSON["datasets"][datasetName] are set correctly. I tried using React Router and the only piece of code that redirected to links is in the answer here:
React-Router External link
But in the answer to the question above if I set path="/", when the table loads, it is just redirecting me straight away to the first link. I would expect then that for each link the path should be different, but supplying datasetsJSON["datasets"][datasetName]["link"] as a path does not work either. Why is the simple link a empty? How could I fix it?

I solved it by using onClick method instead of href link attribute. In the table I have:
<td onClick = {() => this.articleClicked(datasetName)}>
<span role="button"><a>{datasetName}</a></span>
</td>
Then, onClick method:
articleClicked = (datasetName) => {
let url = datasetsJSON["datasets"][datasetName]["link"];
window.open(url);
}
<span role="button"> is needed for the cursor to show hand when the user is hovering over the link. It is bootstrap thing.

Related

Getting an object from a table row in React

I'm a beginner working on a simple CRUD, and all I want to do is get the user from the selected row. If you look below:
{/* ANCHOR - TABLE ROWS */}
{admins.map((user, index) => (
<tbody key={index}>
<tr>
<td style={{fontWeight: 'bold'}}>{user._id}</td>
<td>{shortenStr(user.username)}</td>
<td>{shortenStr(user.firstName)}</td>
<td>{shortenStr(user.lastName)}</td>
<td>{shortenStr(user.dob)}</td>
<td>
<div className="button-div">
<button id="updateUser"
className="button-update-admin"
onClick={handleClick && console.log(user.firstName)}>
</button>
</div>
</td>
</tr>
</tbody>
))}
Console.log gives me the first names of every user in the table, such as:
John
Steve
Frank
Ideally I would like to pass the user Data into my handle click function so that I can create a context, perhaps. Any help is appreciated, thanks!
Please change -
onClick={handleClick && console.log(user.firstName)}>
To
onClick={() => {
console.log(user.firstName)
}}
Or if you want to have a separate onClick function with the event data -
function handleClick(event, user) {
console.log(user.firstName);
}
...
onClick={(event) => handleClick(event, user)}

How to add and remove dynamic rows with "Select-Drop down", "Text Field", "Text-Area auto size" in React JS- Functional component

I am new to React JS. I need to develop the add or remove the control dynamically (rows).
I have two "Text field" control, one "Select (Dropdown)" control, "Text Area " control, and one button (Add button) conrol in the first row.
When I click the Add button in the second row is automatically added with first row controls. (Text field, select, text area, button). The first-row value has not changed and the second-row field is empty. How can I do this in react js functional component with React material UI with yup validation (controller-based component)?
I got some sample code in the code sandbox. But, Code is written in-class components without yup validation and without use state. I attached the link below:
https://codesandbox.io/s/3vk7jxv69p
import React from "react";
import { render } from "react-dom";
class App extends React.Component {
state = {
rows: [{}]
};
handleChange = idx => e => {
const { name, value } = e.target;
const rows = [...this.state.rows];
rows[idx] = {
[name]: value
};
this.setState({
rows
});
};
handleAddRow = () => {
const item = {
name: "",
mobile: ""
};
this.setState({
rows: [...this.state.rows, item]
});
};
handleRemoveRow = () => {
this.setState({
rows: this.state.rows.slice(0, -1)
});
};
handleRemoveSpecificRow = (idx) => () => {
const rows = [...this.state.rows]
rows.splice(idx, 1)
this.setState({ rows })
}
render() {
return (
<div>
<div className="container">
<div className="row clearfix">
<div className="col-md-12 column">
<table
className="table table-bordered table-hover"
id="tab_logic"
>
<thead>
<tr>
<th className="text-center"> # </th>
<th className="text-center"> Name </th>
<th className="text-center"> Mobile </th>
<th />
</tr>
</thead>
<tbody>
{this.state.rows.map((item, idx) => (
<tr id="addr0" key={idx}>
<td>{idx}</td>
<td>
<input
type="text"
name="name"
value={this.state.rows[idx].name}
onChange={this.handleChange(idx)}
className="form-control"
/>
</td>
<td>
<input
type="text"
name="mobile"
value={this.state.rows[idx].mobile}
onChange={this.handleChange(idx)}
className="form-control"
/>
</td>
<td>
<button
className="btn btn-outline-danger btn-sm"
onClick={this.handleRemoveSpecificRow(idx)}
>
Remove
</button>
</td>
</tr>
))}
</tbody>
</table>
<button onClick={this.handleAddRow} className="btn btn-primary">
Add Row
</button>
<button
onClick={this.handleRemoveRow}
className="btn btn-danger float-right"
>
Delete Last Row
</button>
</div>
</div>
</div>
</div>
);
}
}
render(<App />, document.getElementById("root"));
I expected the same output but, I want to use a functional component with yup validation (React Material UI and Controller-based component). who knows this answer let me know.
How can I do this in react js functional component with React material UI with yup validation
It sounds like there are a number of things you could benefit from learning more about before copy-pasting some code you find here, but I hope this helps.
"in react js functional component"
For this, I'd recommend this quick read from Digital Ocean. Essentially, you need to understand that instead of state and lifecycle events being managed within the class, you will need to implement React Hooks. With the component you have here, this should be very easy once you've read the two links.
"with React material UI"
This just requires changing out the HTML/CSS table with Material UI components. A simple replacement would be keeping the table structure, but using MUI form elements like their button and text field. If you are looking for a fuller and more robust update, I'd also recommend MUI Data Grid.
"with yup validation"
This really should just require installing it in your project, and implementing it exactly as the docs say. You will just have to define the schema and check that all rows are valid via something like const tableIsValid = Promise.all(state.map(row => schema.isValid)).every(x => x), though you will want to split the results up by row rather than executing them all at once if you'd like to inform the user which row is invalid.

There are any ways of suppressing single validateDOMNesting warning in React?

In my case I have something like this:
<div className="projects">
{getProjectList().map(p => (
<Link key={p.id}
className='project'
to={`/project/${p.id}`}
style={{border: '2px solid red'}}
>
#{p.id} - {p.name}
<div className="helpers">
<Button
icon="trash"
size="mini"
color="red"
onClick={e => {
e.preventDefault();
e.stopPropagation();
setDeleting(p);
}}
/>
<Button
size="mini"
color="red"
as={Link}
to={`/edit/${p.id}`}
>Edit</Button>
</div>
</Link>
))}
</div>
which visually is represented like this:
And I would prefer to keep it like this because it works as it is intended.
Additional explanation why I want it this way: I want to provide to user ability to click on both links with right mouse button and choose "Open Link in New Tab". To navigate to details of the projects and also navigate to edit form to change properties of the project (These are two different pages).
But in this case I have two times tag embedded in each other and React generate:
Warning: validateDOMNesting(...): <a> cannot appear as a descendant of <a>.
any ways to suppress it?
I'm working on rendering emails with React and ran into the same issue because it's recommended to exclude <tbody> for some Email Service Providers (link)
I wasn't able to suppress the error directly, instead, I added a __isTest prop to my component and then only added <tbody> when __isTest === true:
return __isTest ? (
<table>
<tbody>
<tr>
<td>{children}</td>
</tr>
</tbody>
</table>
) : (
<table>
<tr>
<td>{children}</td>
</tr>
</table>
)
I would definitely prefer to be able to suppress the warning, but this is an alternative that may work in certain cases.
Just add /* eslint-disable */ in the file you want the warnings to be suppressed as explained in the official github repository how to hide unwanted warning.
Remember that's not best practice.

react use Link on entire tr failed

<tbody>
{map(items, item =>
<Link to={routeUrl(ROUTES.SOMEWHERE, item.id)}>
<tr key={item.id}>
<td>
{item.name}
</td>
<td>
{item.country}
</td>
</tr>
</Link>
)}
</tbody>
This won't work, any clue why? I can put the Link component within the td but I want the entire row to be clickable.
According to JSX rules, a Link component should contain only a string and not another component. Instead of using Link you can add onClick method on tr component and then you can define the route where you want to go in the body of onClick.

react ref with focus() doesn't work without setTimeout (my example)

I have encounter this problem, the .focus() only works with setTimeout if i take it out and it stop working. can anyone explain me what's the reason for that, possible i am doing it incorrectly and how can i fix the problem.
componentDidMount() {
React.findDOMNode(this.refs.titleInput).getElementsByTagName('input')[0].focus();
}
works example with setTimeout
componentDidMount() {
setTimeout(() => {
React.findDOMNode(this.refs.titleInput).getElementsByTagName('input')[0].focus();
}, 1);
}
JXS
<input ref="titleInput" type="text" />
and i have followed this example React set focus on input after render
render function
render() {
const {title, description, tagtext, siteName} = (this.state.selected !== undefined) ? this.state.selected : {};
const hasContentChangedYet = this.hasContentChangedYet(title, description);
return (
<div>
<h2 className={styles.formMainHeader}>Edit Meta-Data Form</h2>
<table className={styles.formBlock}>
<tbody>
<tr>
<td className={styles.tagEditLabel}>
Tag
</td>
<td className={styles.inputFieldDisableContainer}>
{tagtext}
</td>
</tr>
<tr>
<td className={styles.tagEditLabel}>
Site
</td>
<td className={styles.inputFieldDisableContainer}>
{siteName}
</td>
</tr>
<tr>
<td className={styles.tagEditLabel}>
Title
</td>
<td className={styles.inputFieldContainer}>
<ReactInputField
ref="titleInput"
id="title"
defaultValue={(title) ? title : ''}
onChange={this.onInputChange}
placeholder="Title"
clearTool={true} />
</td>
</tr>
<tr>
<td className={styles.tagEditLabel}>
Description
</td>
<td className={styles.inputFieldContainer}>
<ReactInputField
id="description"
defaultValue={(description) ? description : ''}
onChange={this.onInputChange}
placeholder="Description"
clearTool={true} />
</td>
</tr>
</tbody>
</table>
<div className={styles.formFooter}>
<button id="save-button" className={styles.saveButton} disabled={!hasContentChangedYet} onClick={() => this.handleSavePressed()}>
Save
</button>
<button id="form-cancel-button" className={styles.cancelButton} onClick={this.actions.form.cancelUpdateToTagData}>
Cancel
</button>
</div>
</div>
);
}
After seeing the update to the question, I realise that you have deeply nested HTML passed to the render function, and the input element of your interest will indeed not be available at the time of the componentDidMount call on the ancestor element. As stated in the React v0.13 Change Log:
ref resolution order has changed slightly such that a ref to a component is available immediately after its componentDidMount method is called; this change should be observable only if your component calls a parent component's callback within your componentDidMount, which is an anti-pattern and should be avoided regardless
This is your case. So either you have to break down the HTML structure into separately rendered elements, as described here, and then you would access the input element in its own componentDidMount callback; or you just stick with the timer hack you have.
Use of componentDidMount makes sure the code runs only when the component on which it is called is mounted (see quote from docs further down).
Note that calling React.findDOMNode is discouraged:
In most cases, you can attach a ref to the DOM node and avoid using findDOMNode at all.
Note
findDOMNode() is an escape hatch used to access the underlying DOM node. In most cases, use of this escape hatch is discouraged because it pierces the component abstraction.
findDOMNode() only works on mounted components (that is, components that have been placed in the DOM). If you try to call this on a component that has not been mounted yet (like calling findDOMNode() in render() on a component that has yet to be created) an exception will be thrown.
And from the docs on the ref string attribute:
Assign a ref attribute to anything returned from render such as:
<input ref="myInput" />
In some other code (typically event handler code), access the backing instance via this.refs as in:
var input = this.refs.myInput;
var inputValue = input.value;
var inputRect = input.getBoundingClientRect();
Alternatively, you could eliminate the need of the code, and use the JSX autoFocus attribute:
<ReactInputField
ref="titleInput"
autoFocus
... />
Using setTimeout() is a bad idea and using componentDidMount() is irrelevant. You may find the answer to your question in the following example:
In a parent component I render a primereact Dialog with an InputText in it:
<Dialog visible={this.state.visible} ...>
<InputText ref={(nameInp) => {this.nameInp = nameInp}} .../>
...
</Dialog>
Initially, this.state.visible is false and the Dialog is hidden.
To show the Dialog, I re-render the parent component by calling showDlg(), where nameInp is the ref to InputText:
showDlg() {
this.setState({visible:true}, ()=>{
this.nameInp.element.focus();
});
}
The input element gets the focus only after rendering has been accomplished and the setState callback function called.
Instead of using the setState callback, in some cases you may simply use:
componentDidUpdate(){
this.nameInp.element.focus();
}
However, componentDidUpdate() is being called every time you (re)render the component, including in case the InputText is hidden.
See also: https://reactjs.org/docs/react-component.html#setstate

Resources