How to call child's method from parent without using Refs? - javascript

Let's say I've a parent component A and a child B:
A:
class A {
constructor() {
this.state = {data: []};
}
handleClick = () => {
// api call
// set data state to the returned value from api
// call B's createTable method
}
render() {
return(
<div>
<button onClick={()=> this.handleClick()}>Fetch data</button>
<B data={this.state.data} />
</div>
}
}
B:
class B {
constructor() {
this.state = {...};
}
createTable = () => {
const { data } = this.props;
// do smth
}
render() {
return(...);
}
}
I want to call createTable method from A without using Refs.
What I've done so far is using componentDidUpdate life cycle method in B to check if data prop has changed or not, If it changed call createTable method but I want to know is this right? or there's a better way of doing it because I feel it is kinda hacky or maybe bad design.
class B {
constructor() {
this.state = {...};
}
componentDidUpdate(prevProps) {
const { data } = this.props;
if (data !== prevProps.data) {
this.createTable();
}
}
createTable = () => {
const { data } = this.props;
// do smth
}
render() {
return(...);
}
}
NOTE I don't want to use hooks either just class based component.

The following example might be useful
class Parent extends Component {
render() {
return (
<div>
<Child setClick={click => this.clickChild = click}/>
<button onClick={() => this.clickChild()}>Click</button>
</div>
);
}
}
class Child extends Component {
constructor(props) {
super(props);
this.getAlert = this.getAlert.bind(this);
}
componentDidMount() {
this.props.setClick(this.getAlert);
}
getAlert() {
alert('clicked');
}
render() {
return (
<h1 ref="hello">Hello</h1>
);
}
}

Related

HandleClick not changing the state

I have the below where it should display images of beers retrieved from an API. Each image has a handleClick event which will direct them to a details page about this beer. My code below doesn't render the beers at all and goes straight to the details page of a random beer. Can anyone help me figure out why?
Thanks
export default class GetBeers extends React.Component {
constructor() {
super();
this.state = {
beers: [],
showMethod: false,
beerDetails: []
};
this.getBeerInfo = this.getBeerInfo.bind(this);
this.handleClick = this.handleClick.bind(this);
}
handleClick(details) {
this.setState({
showMethod: !this.state.showMethod,
beerDetails: details
});
}
render() {
if(this.state.showMethod) {
return (
<Beer details = {this.state.beerDetails}/>
);
}
else {
return (
<div>{this.state.beers.map(each=> {
return <img className = "img-beer" onClick = {this.handleClick(each)} src={each.image_url}/>
})}</div>
);
}
}
componentDidMount() {
this.getBeerInfo()
}
getBeerInfo() {
...gets info
}
}
When you use onClick like that you run the function at the render.
So you have to use arrow function:
Not Working:
<img className = "img-beer" onClick = {this.handleClick(each)} src={each.image_url}/>
Working:
<img className = "img-beer" onClick = {() => this.handleClick(each)} src={each.image_url}/>
The main issue is not calling the handle properly.
Also, I noticed that you are binding the functions in the constructor. It might be simpler to use ES6 function creation, so the scope of the class is bound to your handle method.
export default class GetBeers extends React.Component {
constructor() {
super();
this.state = {
beers: [],
showMethod: false,
beerDetails: []
};
}
handleClick = (details) => {
this.setState({
showMethod: !this.state.showMethod,
beerDetails: details
});
}
render() {
if(this.state.showMethod) {
return (
<Beer details = {this.state.beerDetails}/>
);
}
else {
return (
<div>{this.state.beers.map(each=> {
return <img className = "img-beer" onClick = {() => this.handleClick(each)} src={each.image_url}/>
})}</div>
);
}
}
componentDidMount() {
this.getBeerInfo()
}
getBeerInfo = () => {
...gets info
}
}

React - How to render components inside a function outside of render plus execute same function outside of render?

I have a function outside of render. That function returns (conditionally) a component, that function is beeing triggered not inside render, but inside componentWillReceiveProps (which was necessary due to other facts).
My problem is that the function does not end up returning the component and I dont know why. When I call that function inside render, then of it works, but I cant do that as I must call it inside componentWillReceiveProps. Any ideas? Thanks!!
class App extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.user != this.props.user) {
this.getData(nextProps.user)
}
}
getData() {
if (...) {
return <Child />
}
}
render() {
return (
<div>{this.getData}</div>
);
}
}
const Child = () => {
return <h1>Hello</h1>
}
Create a state called data in the constructor as follows:
constructor(props){
super(props);
this.state={data:""};
}
Now, {this.getdata} inside render() with {this.state.data}
Also replace componentWillReceiveProps as follows:
componentWillReceiveProps(nextProps) {
if (nextProps.user != this.props.user) {
var newdata = this.getData(nextProps.user)
this.setState({data:newdata});
}
}
Because you can't return children from other hooks than render you will need to keep them in a state:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
someChildren: null
};
}
componentWillReceiveProps(nextProps) {
if (nextProps.user != this.props.user) {
this.setState({ someChildren: this.getData(nextProps.user) });
}
}
getData() {
if (...) {
return <Child />;
}
return null
}
render() {
return <div>{this.state.someChildren}</div>;
}
}
When you component will receive new props, it will re-render automatically, doing like following you should have you component to re-render and being updated:
class App extends React.Component {
getData: () => {
if (...) {
return <Child />
}
return null;
};
render() {
return (
<div>{this.getData()}</div>
);
}
}
componentWillReceiveProps is React lifecycle method which is invoked as soon as your React Component receive a prop by the parent. Actions that could be performed in there are for example update the state what you are doing instead is calling a getDate method which is returning a React Component .
A possible implementation could be:
class App extends React.Component {
getData() {
const { user } = this.props;
return user ? <Child /> : <div />
}
render() {
return (
<div>
{this.getData()}
</div>
)
}
}
const Child = () => {
return <h1>Hello</h1>
}
You can only return JSX data in render and not in the other lifecycle function to render. Also render method is pure so for the same input it returns the same output and hence react is able to rightly optimise on performance for the same by maintaining a virtual dom, so you would just write
class App extends React.Component {
getData() {
if (...) {
return <Child />
}
}
render() {
return (
<div>
{this.getData()}
</div>
)
}
}
const Child = () => {
return <h1>Hello</h1>
}
and it would have a the same effect, also if you further optimise by using React.PureComponent, so that render is called on when there is a prop change. React.PureComponent implements shouldComponentUpdate with a shallow prop and state comparison.
class App extends React.PureComponent {
componentWillReceiveProps(nextProps) {
if (nextProps.user != this.props.user) {
this.getData(nextProps.user)
}
}
getData() {
if (...) {
return <Child />
}
}
render() {
return (
<div>
{this.getData()}
</div>
)
}
}
However to do what you want, you would actually store the date in state of component and then render the data based on state in render method
class App extends React.Component {
constructor(props) {
super(props);
this.state {
data: this.getData(props)
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.user != this.props.user) {
this.getData(nextProps.user)
}
}
getData(props) {
if (...) {
const newData;
// update newData based on receivedProps here
// store the data in state
this.setState({data: newData});
}
return [];
}
render() {
return (
<div>
{this.state.data.map((obj) => return <Child data={obj}/>)}
</div>
)
}
}

React calling methods in different conditions

I'm beginner on react and i've written the code below:
class Note extends React.Component {
constructor(props) {
super(props);
this.state = {editing: false};
this.edit = this.edit.bind(this);
this.save = this.save.bind(this);
}
edit() {
// alert('edit');
this.setState({editing: !this.state.editing});
}
save() {
this.props.onChange(this.refs.newVal.value, this.props.id);
this.setState({editing: !this.state.editing});
// console.log('save is over');
}
renderForm() {
return (
<div className="note">
<textarea ref="newVal"></textarea>
<button onClick={this.save}>SAVE</button>
</div>
);
}
renderDisplay() {
return (
<div className="note">
<p>{this.props.children}</p>
<span>
<button onClick={this.edit}>EDIT</button>
<button onClick={this.remove}>X</button>
</span>
</div>
);
}
render() {
console.log(this.state.editing);
return (this.state.editing) ? this.renderForm()
: this.renderDisplay()
}
}
class Board extends React.Component {
constructor(props){
super(props);
this.state = {
notes: []
};
this.update = this.update.bind(this);
this.eachNote = this.eachNote.bind(this);
this.add = this.add.bind(this);
}
nextId() {
this.uniqeId = this.uniqeId || 0;
return this.uniqeId++;
}
add(text) {
let notes = [
...this.state.notes,
{
id: this.nextId(),
note: text
}
];
this.setState({notes});
}
update(newText, id) {
let notes = this.state.notes.map(
note => (note.id !== id) ?
note :
{
id: id,
note: newText
}
);
this.setState({notes})
}
eachNote(note) {
return (<Note key={note.id}
id={note.id}
onChange={this.update}>
{note.note}
</Note>)
}
render() {
return (<div className='board'>
{this.state.notes.map(this.eachNote)}
<button onClick={() => this.add()}>+</button>
</div>)
}
}
ReactDOM.render(<Board />,
document.getElementById('root'));
In render(), onClick event has a function, that is, if used in this way: {this.add} the following error is created:
Uncaught Error: Objects are not valid as a React child (found: object with keys {dispatchConfig, _targetInst, nativeEvent, type, target, currentTarget, eventPhase, bubbles, cancelable, timeStamp, defaultPrevented, isTrusted, view, detail, ...})
Why? while in the eachNote() method this command is used:
onChange={this.update}
And there was no error.
Someone can tell me the reason? thanks.
The problem is that in the add function you are taking an argument text and setting it in the state so when you call onClick={() => this.add()}, you are not passing any argument to add function and hence in its definition text is undefned and hence state note is set as undefined.
However if you directly call it like onClick={this.add} , the add function receives the event object as a parameter and hence it sets state note to be an event object which you are using to render
onClick={this.add} will pass the click event to this.add.
So what you need to do is either:
onClick={e => this.add('some text')} or similar.
If you want to onClick={this.add} you have to ensure that your add method is: add(event) { ... } instead.
The <Note /> component does not contain a render() method to return anything. Add a render() method and return something.
class Note extends React.Component {
constructor(props) {
super(props);
this.state = {editing: false};
this.edit = this.edit.bind(this);
}
edit() {
// alert('edit');
this.setState({editing: !this.state.editing});
}
render() {
return (
<div>Render something</div>
)
}
}
class Board extends React.Component {
constructor(props){
super(props);
this.state = {
notes: []
};
this.update = this.update.bind(this);
this.eachNote = this.eachNote.bind(this);
this.add = this.add.bind(this);
}
nextId() {
this.uniqeId = this.uniqeId || 0;
return this.uniqeId++;
}
add(text) {
let notes = [
...this.state.notes,
{
id: this.nextId(),
note: text
}
];
this.setState({notes});
}
update(newText, id) {
let notes = this.state.notes.map(
note => (note.id !== id) ?
note :
{
id: id,
note: newText
}
);
this.setState({notes})
}
eachNote(note) {
return (<Note key={note.id}
id={note.id}
onChange={this.update}>
{note.note}
</Note>)
}
render() {
return (<div className='board'>
{this.state.notes.map(this.eachNote)}
<button onClick={() => this.add()}>+</button>
</div>)
}
}
ReactDOM.render(<Board />,
document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.js"></script>
<div id="root"></div>

ES6 - Warning: setState(…): Cannot update during an existing state transition

I am rewriting some old ReactJS code, and got stuck fixing this error (the error repeats about 1700 times in the console, the DOM does not render at all):
Warning: setState(...): Cannot update during an existing state
transition (such as within render or another component's
constructor). Render methods should be a pure function of props and
state; constructor side-effects are an anti-pattern, but can be moved
to componentWillMount.
I am a Component that passes it's state down to a component that should render some controls. Based on the clicked controls, the state should change, and new controls should render.
So this is my Container component:
class TeaTimer extends Component {
constructor(props) {
super(props);
this.state = {
count: 120,
countdownStatus: 'started'
}
}
componentDidUpdate(prevProps, prevState) {
if (this.state.countdownStatus !== prevState.countdownStatus) {
switch (this.state.countdownStatus) {
case 'started':
this.startTimer();
break;
case 'stopped':
this.setState({count:0});
}
}
}
componentWillUnmount() {
clearInterval(this.timer);
delete this.timer;
}
startTimer() {
this.timer = setInterval(() => {
let newCount = this.state.count -1;
this.setState({
count: newCount >= 0 ? newCount : 0
});
if(newCount === 0) {
this.setState({countdownStatus: 'stopped'});
}
}, 1000)
}
handleStatusChange(newStatus) {
this.setState({ countdownStatus: newStatus });
}
render() {
let {count, countdownStatus} = this.state;
let renderStartStop = () => {
if (countdownStatus !== 'stopped') {
return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
} else {
return <div>This will be the slider form</div>
}
};
return(
<div className={styles.container}>
<p>This is the TeaTimer component</p>
<Clock totalSeconds={count}/>
{renderStartStop()}
</div>
)
}
}
And this is my controls component:
class StartStop extends Component {
constructor(props) {
super(props);
}
onStatusChange(newStatus) {
return() => {
this.props.onStatusChange(newStatus);
}
}
render() {
let {countdownStatus} = this.props;
let renderStartStopButton = () => {
if(countdownStatus === 'started') {
return <button onClick={()=> this.onStatusChange('stopped')}>Reset</button>;
} else {
return <button onClick={()=> this.onStatusChange('started')}>Start</button>
}
};
return(
<div className={styles.tt.Controls}>
{renderStartStopButton()}
</div>
)
}
}
StartStop.propTypes = {
countdownStatus: React.PropTypes.string.isRequired,
onStatusChange: React.PropTypes.func.isRequired
};
I am sorry about the wall of text, but I really can;t figure out where the error is coming from - and therefor don't know which part of the code I can leave out.
I have tried implementing the solution found in a seemingly related question, but can't get it to work either.
I think you have a typo in this line:
return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/>
It should be:
return <StartStop countdownStatus={countdownStatus} onStatusChange={() => this.handleStatusChange}/>
You seem to be calling the method handleStatusChange instead of passing it as a callback.
Your metods call each other so you must define two instance of your metods.
class StartStop extends Component {
constructor(props) {
super(props);
this.onStatusChangeReset=this.onStatusChange.bind(this);
this.onStatusChangeStart=this.onStatusChange.bind(this);
}
onStatusChange(newStatus) {
return() => {
this.props.onStatusChange(newStatus);
}
}
render() {
let {countdownStatus} = this.props;
let renderStartStopButton = () => {
if(countdownStatus === 'started') {
return <button onClick={this.onStatusChangeReset('stopped')}>Reset</button>;
} else {
return <button onClick={this.onStatusChangeStart('started')}>Start</button>
}
};
return(
<div className={styles.tt.Controls}>
{renderStartStopButton()}
</div>
)
}
}
StartStop.propTypes = {
countdownStatus: React.PropTypes.string.isRequired,
onStatusChange: React.PropTypes.func.isRequired
};
In this line in your return <StartStop countdownStatus={countdownStatus} onStatusChange={this.handleStatusChange()}/> gives the warning, the handleStatusChanged function is called on pressing a button which tries to change the state by setState keyword. whenever the state is changed render function is called again but in your case render function was in progress of returning while the render function is called again by setState keyword.

React component method this.state.myState undefined after returning from child

I am having a Parent component (BookApplication) and a child component (SearchBox) in React. The SearchBox has an input field, and should give the input back to the parent for handling the event. That is working fine, but when i am back in the parent component in the method handleSearch the this.state... is undefined.
TypeError: Cannot read property 'books' of undefined
But searchInput has the value it should have.
But i need the books from this.state.books again :/
I understand that in the method handleSearch i am working in it's scope, so this.... is the context of handleSearch... but how do i get the arguments of it's component the BookApplication again?
I am still learning javascript, and i thought this shouldnt be a problem, because a function can always use the variables of it's parent object?
class BookApplication extends React.Component {
constructor() {
super();
this.state = {books: []};
}
componentDidMount() {
$.get(PATH, function (result) {
this.setState({
books: result
});
}.bind(this));
}
handleSearch(searchInput) {
//Sort the books list
var sortedList = [];
this.state.books.map(
function (currentBook) {
currentBook.keys().forEach(
function (key, pos) {
if (key.contains(searchInput)) {
sortedList.push(currentBook)
}
}
)
}
);
}
render() {
return (
<div>
<SearchBox onSearch={this.handleSearch}/>
<div className="book-list">
{this.state.books.map(function (currentBook) {
return <Book book={currentBook} key={currentBook.id}/>;
}) }
</div>
</div>
);
}
Here also my SearchBox:
class SearchBox extends React.Component {
constructor(props) {
super(props);
this.state = {searchFieldInput: ''};
this.handleSearchChange = this.handleSearchChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleSearchChange(event) {
this.setState({searchFieldInput: event.target.value});
}
handleSubmit(e) {
//Prevent the browser's defeault action of submitting the form
e.preventDefault();
var searchFieldInput = this.state.searchFieldInput.trim();
//Call the passed callback function
this.props.onSearch({searchFieldInput: searchFieldInput});
}
render() {
return (
<div className="book-search">
<input
type="text"
value={this.state.searchFieldInput}
onChange={this.handleSearchChange}
placeholder="Search..."
className="search-bar"
/>
<button onClick={this.handleSubmit} className="search-button">Search</button>
</div>
);
}
}
If your question is how to get parent's context from child component, then try
class ParentComponent extends React.Component {
...
...
clickHandler(event){}
render(){
<ChildComponent parent={this}/>
}
}
class ChildComponent extends React.Component {
constructor(props){
super(props);
}
render(){
let parent = this.props.parent;
return <button onClick={parent.clickHandler}></button>
}
}
And you will get an error here
componentDidMount() {
$.get(PATH, function (result) {
this.setState({
books: result
});
}.bind(this));
}
Because this in the callback function not referred to your Component's context. You should keep component's context in variable
componentDidMount() {
let self = this;
$.get(PATH, function (result) {
self.setState({
books: result
});
}.bind(this));
}
Finally decision is
constructor(props) {
super(props);
this.state = {books: []};
//add the following line into your code
this.handleSearch = this.handleSearch.bind(this);
}

Categories

Resources