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

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

Related

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.

In react bootstrap how do I show the contents of an array in an html table

I have an array that I add to, each time a button is pressed. That part appears to work. When I output it to a console, I can see that each one is getting added.
const addToPool = () => {
diePool[poolCount] = (
<die
type={diceType}
number={diceNumber}
adjuster={diceAdjuster}
/>
);
poolCount++;
};
How do I loop through and display that in my html table after my form?
return (
<div className="App">
<header className="App-header">
<Form>
<Button onClick={addToPool} aria-controls="diceRoll" aria-expanded={open} variant="secondary" size="sm">
Add to Pool
</Button>
</ButtonToolbar>
</Form>
<Fade in={open}>
<div id="diceRoll">
The result is: {randNum}
</div>
</Fade>
</header>
<Table responsive>
<caption>Dice Pool</caption>
<thead>
<tr>
<th>Type</th>
<th>Number</th>
<th>Adjuster</th>
</tr>
</thead>
<tbody>
</tbody>
</Table>
</div>
);
}
export default App;
In order for React to update the user interface to reflect changes, it expects you to notify it that a change has been made. You do this my modifying the state of your component. In your addToPool method, you are modifying a local variable called diePool. That's just a member variable of the control object, not the state, and React won't know you've made a change.
Instead, modify the state with something like this:
const addToPool(diceType, diceNumber, diceAdjuster) {
this.setState(prevstate => {
return {
diePool: prevstate.diePool.push(
{ type: diceType, number: diceNumber, adjuster: diceAdjuster }
)
}
});
}
For this to work, your component will have to have an initial state to work with. You create this in the constructor:
constructor(props) {
this.state = { diePool: [] };
}
Now, whenever your code calls addToPool, the state will be updated and through setState, React will know about it and call the render method as needed.
In your render method, you will need to consult the state to place the dice rolls in your table. You can do so by using map over your array of dice rolls:
<Table responsive>
// ...snip...
<tbody>
{this.state.diePool.map((roll, index) => {
<tr key={index}>
<td>{roll.type}</td>
<td>{roll.number}</td>
<td>{roll.adjuster}</td>
</tr>
})}
</tbody>
</Table>
(Please note the use of index here to make sure that each table row has a unique index - this is a requirement for React to be able to update table rows individually when it can.)
What's important here is that the component state is a crucial concept in React. If you modify something and you expect React to react (ha!) to it, you'll have to modify the state through setState (not by modifying this.state directly). That's the only way React will know about it. React will then work its magic and call the render methods of your component and its children, as necessary.
inside your body tag, you can iterate through an array using the map method (https://developer.mozilla.org/fr/docs/Web/JavaScript/Reference/Objets_globaux/Array/map)
<tbody>
{dice.map((item,index) => {
return(
<tr key={index}>
<td>{item.key1}</td>
etc...
</tr>
)})}
</tbody>
I figured it out and posting it here in case others have trouble:
declared a counter:
const [poolCount, setPoolCount] = useState(0);
Created my addToPool:
const addToPool = () => {
setDiePool([
...diePool,
{
dp1 : poolCount,
dp2 : diceType,
dp3 : diceNumber,
dp4 : diceAdjuster
}
]);
setPoolCount(poolCount+1);
};
Call my addToPool when I click the button:
<Button onClick={addToPool} aria-controls="diceRoll" aria-expanded={open} variant="secondary" size="sm">
Add to Pool
</Button>
Then create my table and loop:
<Table responsive>
<caption>Dice Pool</caption>
<thead>
<tr>
<th>#</th>
<th>Type</th>
<th>Number</th>
<th>Adjuster</th>
</tr>
</thead>
<tbody>
{diePool.map(die => (<tr key={die.dp1}><td>{die.dp1}</td><td>{die.dp2}</td><td>{die.dp3}</td><td>{die.dp4}</td></tr>))}
</tbody>
</Table>

onClick inside of a <img

Not able to perform a test for a click event that is inside of an image tag.
Here is the code that I am trying to test it. Not able to test both click events due to setState
<td className="centertext">
<input type='radio' name='req' onClick={() => {this.handleRequestClick(titles[i][j].RequestId, titles[i][j].Name, titles[i][j].Content)}} />
</td>
<td className="modal-add-window-td">{i}</td>
<td className="modal-add-window-td">
<div>{titles[i][j].Name}</div>
</td>
<td className="modal-add-window-td">
<img className='request-info'src={infoSVG} alt='Info' onClick={() => {this.showDescription(titles[i][j].Name, titles[i][j].Description)}} />
</td>
Here is what I have tried to so far but getting the following msg:
Method “props” is meant to be run on 1 node. 0 found instead.
Using Jest and enzyme - memory router and mount the components
it("Test click event on show Description", () => {
wrapper.find('AddViewModal').setState({
titles:[[{
Requests:{
Name:""
}}
]]
}),
wrapper.update();
expect(wrapper.find('AddViewModal').find('img').props().src).toEqual('../../Images/chartImages/info.svg')
//wrapper.find('AddViewModal').find('img[alt="Info"]').simulate('click')
});
did you try findWhere?
element.findWhere(node => node.hasClass("request-info"))
If it doesn't, can you verify if findWhere traverses the node you're targeting?
Make sure you get the right state otherwise node wont be found
it("Test click event on show Description", () => {
baseProps.onClick.mockClear();
wrapper.find('Component').setState({
Requests: [{
Name: 'testing'
}]
})
wrapper.update();
wrapper.find('Componentl').find('img').simulate('click')

Links to external resource are empty in React

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.

Shallow rendering unexpected behaviour

I have a Component called 'Grid' having a render method like this -
render(){
this.createTable();
return (
<div
style={{display:'flex',justifyContent:'center',flexDirection:'column'}}
>
<input
id='gridFilterText'
placeholder="filter text here....."
style={{width:'50%',height:'25px',borderRadius:'4px'}}
onChange={this.onType}
value={this.state.value}
></input>
<table style={{flex:'0.5',borderRadius:'4px',boxShadow:'-1px 3px 4px 0px black'}}>
<thead style={{textDecoration:'underline'}}>
<tr>
{this.headerArr}
</tr>
</thead>
<tbody>
{this.tableBody}
</tbody>
</table>
</div>
);
}
createTable() {
this.headerArr = this.props.headerList.map((item,index)=>(
<th key={index}>
{item}
</th>
));
this.tableBody = this.filteredProdList.map((item,index)=>(
<tr key={index} onMouseOver={this.onFocus} onMouseOut={this.onFocusRemove}>
{Object.keys(item).map((key,index)=>(
<td key={index}>{item[key]}</td>
))}
</tr>
))
}
I was trying to test this with this test (Jest and Enzyme) script -
test('Grid filters properly',()=>{
const gridWrapper=mount(<Grid headerList={['Id','Name','Stock']} prodList={items}/>);
const textBoxWrapper=gridWrapper.find('#gridFilterText');
textBoxWrapper.simulate('change',{target:{value:'p'}});
gridWrapper.update();
console.log(textBoxWrapper.debug());
expect(
gridWrapper
.find('table')
.children()
.find('tbody')
.children()
).toHaveLength(2);
})
What I am trying to acheive here is -
Render the Grid component
The grid component has a control in it (its not a child component)
Simulate an OnChange on the control by setting value (value ='p')
This should cause the grid to show filtered output (2 rows)
Why it works only if I use mount and it does not work if I use shallow.
As per my understanding shallow works on the component and does go down to the child components.
In this case, is part of the component and not its child component, right?
Please let me know if I can clarify further.

Resources