Calling a function from another in ReactJs - javascript

I have the following ReactJS component:
articles_list.jsx
import React from 'react';
import marked from 'marked';
import './articles_list.css';
export default class ArticlesList extends React.Component {
constructor(props) {
super(props);
this.state = {
articles: null
}
}
componentWillMount() {
fetch('/articles_all')
.then(res => res.json())
.then(json => {
this.setState({
articles: json.articles
});
});
}
handleClick(e) {
e.preventDefault();
var elem = e.target;
var file = elem.getAttribute('data-file').split('.')[0];
fetch('/article/'+file, {
headers: {
'Accept': 'text/markdown'
}
})
.then(res => res.text())
.then(txt => marked(txt))
.then(html => document.getElementById('article-text').innerHTML = html)
}
render() {
var teste = []
if (this.state.articles === null) {
teste.push(<div id="no-articles" key="1">No articles</div>)
} else {
{this.state.articles.forEach( function(element, index) {
teste.push(<div onClick={this.handleClick} data-file={element.file} className="articles-menu-item" key={index.toString()}>{element.title}</div>);
}.bind(this))}
}
return(
<div className="articles-list">
<div className="articles-list-title">
ARTICLES
</div>
<div id="menu-body" className="menu-body">{teste}</div>
</div>
);
}
}
As you may see, it fetches as list of articles and creates links. When these links are clicked the corresponding article is load in a certain area of the page.
The code is working perfectly, but now I need to load a certain article before any of the links to be clicked. Then I decided to break the code inside handleClick like this:
loadArticle(file) {
fetch('/article/'+file, {
headers: {
'Accept': 'text/markdown'
}
})
.then(res => res.text())
.then(txt => marked(txt))
.then(html => document.getElementById('article-text').innerHTML = html)
}
handleClick(e) {
e.preventDefault();
var elem = e.target;
var file = elem.getAttribute('data-file').split('.')[0];
loadArticle(file);
}
My idea with this is to invoke loadArticle inside render to load an specific article when the component loads, like this:
return(
<div className="articles-list">
<div className="articles-list-title">
ARTICLES
</div>
<div id="menu-body" className="menu-body">{teste}{this.loadArticle('my_specific_article')}</div>
</div>
);
It works and now my_specific_article loads correctly when I navigate to the page. But...
But now when I click the links, that were working fine before, I got an error
Uncaught ReferenceError: loadArticle is not defined
And if I do
handleClick(e) {
e.preventDefault();
var elem = e.target;
var file = elem.getAttribute('data-file').split('.')[0];
this.loadArticle(file);
}
using this, then I get
Uncaught TypeError: Cannot read property 'loadArticle' of null
How should I deal with this? I know it is a matter of context but, being new to ReactJS, I really don't know how to proceed.
EDIT
This is not a duplicate of this question, as it was marked. The reason is simple. In the mentioned question the function to bind is a event handler and in my problem a function being called by another. In fact, my event handler (similar to the other question) was working fine without the bind, then we are not talking about the same thing.

You must bind your custom functions in the constructor.
Modify your constructor like this.
constructor(props) {
super(props);
this.state = {
articles: null
}
this.handleClick = this.handleClick.bind(this);
this.loadArticle = this.loadArticle.bind(this);
}
Now everywhere you should call this function as this.handleClick & this.loadMore.
You can read about other binding patterns here. The one I've mentioned is number 4.
That is the preferred way in facebook docs as well.

Related

Show new data and replace the old data in the same position

Goal:
Get the second data by pressing the button and the list should display in the html page. It should replace the old data with a new data.
Problem:
I cannot make the button to be working in order to display the result on the webpage. Show new data and replace the old data.
What part am I missing?
Info:
*I'm newbie in Reactjs
Stackblitz:
https://stackblitz.com/edit/react-fetch-data-from-api-f632fh?
Thank you!
import React, { Component } from 'react';
import { render } from 'react-dom';
import Hello from './Hello';
import './style.css';
class App extends Component {
constructor() {
super();
this.state = {
name: 'React',
data: null
};
}
componentDidMount = () => {
fetch('https://jsonplaceholder.typicode.com/users')
.then(res => res.json())
.then(data => {
console.log(data);
this.setState({ data });
});
};
getSecondData() {
fetch('https://fakerestapi.azurewebsites.net/api/v1/Activities')
.then(res => res.json())
.then(data => {
console.log(data);
this.setState({ data });
});
}
showAlert() {
alert("I'm an alert");
}
render() {
return (
<div>
<button onClick={this.getSecondData}>ASDF</button>
<p>Start editing to see some magic happen :)</p>
<ul>
{this.state.data &&
this.state.data.map(user => <li id={user.id}>{user.id}</li>)}
</ul>
</div>
);
}
}
render(<App />, document.getElementById('root'));
You were missing a couple of things.
First of all, you should never setState like that. Instead, always use prevState. Like this:
this.setState(prevState => ({
...prevState,
data
}));
Second, you either need to bind the function you are creating in the constructor like this: this.getSecondData = this.getSecondData.bind(this)
You read more about that here: https://reactjs.org/docs/handling-events.html
Or better solution is to just use arrow functions.
The correct working solution can be found here:
https://stackblitz.com/edit/react-fetch-data-from-api-znjdf6

React axios request in ComponentDidMount not passing data as props to component

Hi guys I can't see my Error here hope someone can hlep...
This is my fetch Data class:
export default class Auftrag extends Component {
state = {
auftraege: "Test",
};
getAuftraege = () => {
axios.get("Auftraege/auftraege").then(e => {
this.setState({
auftraege: e.data,
});
console.log(e.data);
});
};
componentDidMount() {
this.getAuftraege();
}
render() {
return (
<>
<AuftragDisplay test={this.state.auftraege} ></AuftragDisplay>
</>
);
}
}
And this is my constructor in my Display class:
constructor(props) {
super(props);
console.log(props);
}
The axios Request is getting fired and I get the right data in my console. But It is not getting passed to my Component.
Hope someone knows whats wrong and can help me
SOLVED:
Thx to san I tried it and could solve the problem. I got the data passed but console.log() was called before the update so I got the old data. THX again
Your code looks fine. you can see below same code with different api as an example
class Auftrag extends Component {
state = {
auftraege: "Test",
};
getAuftraege = () => {
axios
.get("https://jsonplaceholder.typicode.com/posts/1")
.then(e => this.setState({auftraege: e.data}))
};
componentDidMount() {
this.getAuftraege();
}
render() {
return (
<>
<AuftragDisplay test={this.state.auftraege} ></AuftragDisplay>
</>
);
}
}
const AuftragDisplay = ({test}) =><h2>Hi--->{test.title}</h2>
Just put the state inside constructor of Auftrag class, I should work.

getDerivedStateFromProps, change of state under the influence of changing props

I click Item -> I get data from url:https: // app / api / v1 / asset / $ {id}. The data is saved in loadItemId. I am moving loadItemId from the component Items to the component Details, then to the component AnotherItem.
Each time I click Item the props loadItemId changes in the getDerivedStateFromProps method. Problem: I'll click Element D -> I see in console.log 'true', then I'll click Element E --> It display in console.log true andfalse simultaneously, and it should display only false.
Trying to create a ternary operator {this.state.itemX ['completed'] ? this.start () : ''}. If {this.state.itemX ['completed'] call the function this.start ()
Code here: stackblitz
Picture: https://imgur.com/a/OBxMKCd
Items
class Items extends Component {
constructor (props) {
super(props);
this.state = {
itemId: null,
loadItemId: ''
}
}
selectItem = (id) => {
this.setState({
itemId: id
})
this.load(id);
}
load = (id) => {
axios.get
axios({
url: `https://app/api/v1/asset/${id}`,
method: "GET",
headers: {
'Authorization': `Bearer ${token}`
}
})
.then(response => {
this.setState({
loadItemId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render () {
return (
<div >
<Item
key={item.id}
item={item}
selectItem={this.selectItem}
>
<Details
loadItemId={this.state.loadTime}
/>
</div>
)
}
Item
class Item extends Component {
render () {
return (
<div onClick={() => this.props.selectItem(item.id}>
</div>
)
}
}
Details
class Details extends Component {
constructor() {
super();
}
render () {
return (
<div>
<AnotherItem
loadItemId = {this.props.loadItemId}
/>
</div>
)
}
}
AnotherItem
class AnotherItem extends Component {
constructor() {
super();
this.state = {
itemX: ''
};
}
static getDerivedStateFromProps(nextProps, prevState) {
if(nextProps.loadItemId !== prevState.loadItemId) {
return { itemX: nextProps.loadItemId }
}
render () {
console.log(this.state.itemX ? this.state.itemX['completed'] : '');
{/*if this.state.loadX['completed'] === true, call function this.start()*/ }
return (
<button /*{this.state.loadX['completed'] ? this.start() : ''}*/ onClick={this.start}>
Start
</button>
);
}
}
here:
selectItem = (id) => {
this.setState({
itemId: id
})
this.load(id);
}
you call setState(), then 'Item' and 'Details' and 'AnotherItem' call their render method. so you see log for previous 'loadItemId'.
when 'load' method work done. here:
this.setState({
loadItemId: response.data
});
you setState() again, then 'Item' and 'Details' and 'AnotherItem' call their render method again. in this time you see log for new 'loadItemId'.
solution
setState both state in one place. after load method done, instead of:
this.setState({
loadItemId: response.data
});
write:
this.setState({
itemId: id,
loadItemId: response.data
});
and remove:
this.setState({
itemId: id
})
from 'selectItem' method.
Need some clarification, but think I can still address this at high level. As suggested in comment above, with the information presented, it does not seem that your component AnotherItem actually needs to maintain state to determine the correct time at which to invoke start() method (although it may need to be stateful for other reasons, as noted below).
It appears the functionality you are trying to achieve (invoke start method at particular time) can be completed solely with a comparison of old/new props by the componentDidUpdate lifecycle method. As provided by the React docs, getDerivedStateFromProps is actually reserved for a few 'rare' cases, none of which I believe are present here. Rather, it seems that you want to call a certain method, perhaps perform some calculation, when new props are received and meet a certain condition (e.g., not equal to old props). That can be achieved by hooking into componentDidUpdate.
class AnotherItem extends Component {
constructor(props) {
super(props);
this.state = {}
}
start = () => { do something, perform a calculation }
// Invoked when new props are passed
componentDidUpdate(prevProps) {
// Test condition to determine whether to call start() method based on new props,
// (can add other conditionals limit number of calls to start, e.g.,
// compare other properties of loadItemId from prevProps and this.props) .
if (this.props.loadItemId && this.props.loadItemId.completed === true) {
//Possibly store result from start() in state if needed
const result = this.start();
}
}
}
render () {
// Render UI, maybe based on updated state/result of start method if
// needed
);
}
}
You are encountering this behaviour because you are changing state of Items component on each click with
this.setState({
itemId: id
})
When changing its state, Items component rerenders causing AnotherItem to rerender (because that is child component) with it's previous state which has completed as true (since you've clicked element D before). Then async request completes and another rerender is caused with
this.setState({
loadItemId: response.data
});
which initiates another AnotherItem rerender and expected result which is false.
Try removing state change in selectItem and you'll get desired result.
I'd suggest you read this article and try to structure your code differently.
EDIT
You can easily fix this with adding loader to your component:
selectItem = (id) => {
this.setState({
itemId: id,
loading: true
})
this.load(id);
}
load = (id) => {
axios.get
axios({
url: `https://jsonplaceholder.typicode.com/todos/${id}`,
method: "GET"
})
.then(response => {
this.setState({
loading: false,
loadItemId: response.data
});
})
.catch(error => {
console.log(error);
})
}
render() {
return (
<div >
<ul>
{this.state.items.map((item, index) =>
<Item
key={item.id}
item={item}
selectItem={this.selectItem}
/>
)
}
</ul>
{this.state.loading ? <span>Loading...</span> : <Details
itemId={this.state.itemId}
loadItemId={this.state.loadItemId}
/>}
</div>
)
}
This way, you'll rerender your Details component only when you have data fetched and no unnecessary rerenders will occur.

React "Cannot read property 'bind' of undefined"

I looked at many other answers but I couldn't figure it out. Here is my code:
// userInputActions.js
...
export function dummy() {
console.log('dummy function called');
}
...
// *userInputPage.js*
import * as userInputActions from '../actions/userInputActions';
class UserInput extends React.Component {
constructor(props) {
super(props);
this.state = {
};
// When un-commented it shows '*f dummy()*' is imported
// console.log('actions: ', userInputActions);
this.dummy = this.dummy.bind(this);
}
render () {
return (
<div className="container-fluid align-items-center">
<FieldLevelValidationForm onSubmit={this.dummy}/>
</div>
);
}
}
const mapStateToProps = (state) => ({
});
const mapDispatchToProps = (dispatch) =>
bindActionCreators(
{
...userInputActions
}, dispatch
);
export default connect(mapStateToProps, mapDispatchToProps)(UserInput);
Note 'FieldLevelValidationForm' is a redux-form and onSubmit is one of the form function arguments.
I tried various things and the bind function does not work. Can someone please let me know where am I going wrong. I think it has something to do with the render() function and the lifetime of the component but I do not know enough yet.
Edit
Thank you - learned a lot from all answers. All of them work. I wish I could give more than one check-mark. However, I think the most appropriate use for my case is to call it as a prop and dispatch an action as so on.
The dummy function is passed as props, so you should access it with this.props.dummy in your render() instead.
There's also no need to bind it to this as it's not using the this instance.
The dummy function you are trying to bind is not in this class. So it would just be:
this.dummy = userInputActions.dummy.bind(this);
Function definition of dummy is missing.
constructor(props) {
super(props);
this.state = { };
this.dummy = this.dummy.bind(this);
}
dummy(e) {
// form submission action
userInputActions.dummy(); // maybe
}
render () {
return (
<div className="container-fluid align-items-center">
<FieldLevelValidationForm onSubmit={this.dummy}/>
</div>
);
}

Test a function that calls an API

I need to test the fetchData() function. I have been trying to follow this (and many other) tutorials and trying to get it to work with my function for the past 3 hours but no luck so far. I'd preferably want to do it another way anyway because I don't think jQuery and React should be used together.
I essentially want to check if 1) the function is called when the search form has been submitted (button within SearchForm clicked) and 2) check if the data comes back.
Does anyone have any suggestions on how to get started please?
Thanks
Home
export default class Home extends Component {
constructor() {
super();
this.state = {
value: '',
loading: false,
dataError: false
}
this.nodes = [];
this.fetchData = this.fetchData.bind(this);
}
fetchData(e) {
e.preventDefault();
this.setState({loading: true});
axios.get(`https://api.github.com/search/repositories?q=${this.state.value}`)
.then(res => {
this.nodes = res.data.items.map((d, k) => <RepoItem {...d} key={k}/>);
this.setState({loading: false});
})
.catch(err => {
this.setState({dataError: true});
});
}
render() {
return (
<div className="home-wrapper">
<SearchForm value={this.state.value}
onSubmit={this.fetchData}
onChange={(e) => this.setState({value: e.target.value})}/>
{this.state.loading ?
<Spinner/>:
!this.state.dataError ? this.nodes :
<h1>Oops! Something went wrong, please try again!</h1>}
</div>
);
}
}
RepoItem
export const RepoItem = props => (
<div className="repo-item">
<h1>{props.full_name}</h1>
</div>);
To check if the function is called upon form submission, you can shallow-render the <SearchForm> component with the spy function passed as a onSubmit prop. Simulate the submit event and check if the spy function is called.
To check if the data comes back, mock the axios service. You can use this library to mock axios calls. Call the fetchData() and see if the this.nodes and state updated correctly.
const wrapper = shallow(<Home />);
wrapper.instance().fetchData().then(() => {
... your assertions go here ...
})
I think it's always the best practice to return a Promise object or any chainable object from a method where asynchronous action takes place.

Categories

Resources