As you can see in the code below my ajax (i'm using fetch) is located within a handleSearch function, in App component. How can I delegate and make the ajax call in its own component? I come from angular 1, in angular there's thing like services/factories but I'm not sure how to do it in react.
const App = React.createClass({
getInitialState() {
return {
username: '',
profiles: [],
noUser : false
}
},
handleInputChange(e) {
this.setState({
username: e.target.value
})
},
handleSearch() {
if (!this.state.username) {
alert('username cannot be empty');
return false;
}
fetch('https://api.github.com/search/users?q=' + this.state.username)
.then(response => {
return response.json()
}).then(json => {
this.setState({
profiles: json.items
})
if(json.items.length === 0){
this.setState({noUsers : true});
}else{
this.setState({noUsers : false});
}
})
},
render() {
return (
<div>
<input type="text" placeholder="Github username" onChange={this.handleInputChange} value={this.state.username} />
<button onClick={this.handleSearch}>Search</button>
{this.state.profiles.length > 0 &&
<Users profiles={this.state.profiles} />
}
{this.state.noUsers &&
<p>No users found.</p>
}
</div>
)
}
});
http://codepen.io/eldyvoon/pen/ENMXQz this code need much refactoring.
Not everything in React has to be a component. It is still essentially plain old Javascript underneath. Extract the API logic out into it's own module/file, e.g.
GithubApi.js
module.exports.getUsers = function(username) {
return fetch('https://api.github.com/search/users?q=' + username)
.then(response => {
return response.json()
})
}
And then use it where ever it is required:
var GithubApi = require('/path/to/GithubApi.js');
const App = React.createClass({
..
..
handleSearch() {
if (!this.state.username) {
alert('username cannot be empty');
return false;
}
GithubApi.getUsers(this.state.username)
.then(json => {
this.setState({
profiles: json.items
})
if(json.items.length === 0){
this.setState({noUsers : true});
}else{
this.setState({noUsers : false});
}
})
},
..
..
This is just an example - how you want to design your module is up to you, depending on how specific or flexible, etc. you want it to be.
You don't need to write it in it's own React component, just write a plain old Javascript CommonJS module and either load it via require() or pass it in as a prop.
Related
I have a function called permCheck() which will resolve to true or false based on some logic. I need to take the value from this and utilize it in a ternary expression to change the content the user sees. However, it seems to always be returning the true part of the expression, even when permCheck() is false.
Here is the permCheck() function which does properly return true or false:
function permCheck(res) {
let isMember;
return axios.get(window.location.origin + "/username").then((res) => {
axios
.get(
"https://api.endpoint.com/ismember/" +
res.data
)
.then((res) => {
return res.data; // This will be true or false
});
isMember = res.data;
return isMember;
});
}
Then in the return for my react app I am trying to change the content based on the permCheck function. This is where it seems to always just default to the first part of the ternary expression. As you can see I even have a console.log in the ternary expression which is properly returning true or false.
return (
<div className="ui">
<AppLayout
content={
permCheck().then((a) => {
console.log("IN TERNARY: " + a);
Promise.resolve(a);
})
? appContent
: accessDenied
} // Here we expect true or false from permCheck()
navigation={<ServiceNavigation />}
notifications={<FlashMessage />}
breadcrumbs={<Breadcrumbs />}
contentType="table"
tools={Tools}
headerSelector="#navbar"
stickyNotifications={true}
/>
</div>
);
You can't check for a value, that you will only receive in the future, so you can't just unwrap your promise.
Instead, you should use a state variable to store the result, and rerender your component, once it's available. Until then, you can render some loading effect/spinner/text, to inform the user.
Assuming you're using a functional component, it'd look like this:
function component(){
const [permission, setPermission] = useState(undefined)
//Use `useEffect` to prevent recurring checking
useEffect(() => {
permCheck().then((a) => {
//Update (rerender) the component with the permission info
setPermission(a)
})
}, [])
return (
<div className="UI">
<AppLayout
content={
//When this runs for the first time, `permission` will be `undefined` (meaning 'not available'), so handle this case as well:
permission === undefined
? 'Checking permission, hang on...'
: permission
? appContent
: accessDenied
}
navigation={<ServiceNavigation />}
notifications={<FlashMessage />}
breadcrumbs={<Breadcrumbs />}
contentType="table"
tools={Tools}
headerSelector="#navbar"
stickyNotifications={true}
/>
</div>
)
}
I write an answer because my comment is illegible.
I think you function permCheck is equivalent to
function permCheck(res) {
let isMember;
return axios.get(window.location.origin + "/username").then((res) => {
isMember = res.data;
return isMember;
});
}
the part
axios.get(
"https://api.endpoint.com/ismember/" +
res.data
)
.then((res) => {
return res.data; // This will be true or false
});
has no effects in the return value.
Here an example. As you can see, the resource logged is the first one not the second
function permCheck(res) {
let isMember;
return axios.get("https://jsonplaceholder.typicode.com/todos/1").then((res) => {
axios
.get("https://jsonplaceholder.typicode.com/todos/2")
.then((res) => {
return res.data; // This will be true or false
});
isMember = res.data;
return isMember;
});
}
permCheck().then(data=>console.log(data))
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.21.1/axios.min.js"></script>
I'm building an application with Vue on the frontend and Laravel PHP on the backend. Its a single page app (SPA).
When changing pages, sometimes - not always - axios makes two requests for the same page. I'm having trouble figure it out what is happening.
When the link changes I have two watchers, one for the top category and another for the sub-category. They trigger the created () hook that calls the loadData method.
If I change the main category and sub category ( Example: from 1/5 to 2/31 ), the loadData method is called two time. How can I fix this ?
Google Network Tab (The .json request does not represent the same type of page that I'm referring above, only the numbers ) :
<script>
import axios from 'axios'
import { mapGetters } from 'vuex'
import Form from 'vform'
export default {
data() {
return {
products: {
cat : {} ,
products : []
},
productsShow: '',
quickSearchQuery: '',
loadeddata : false,
}
},
methods: {
loadMore () {
this.productsShow += 21
},
loadData () {
if ( this.$route.params.sub_id ) {
axios.get('/api/cat/' + this.$route.params.cat_id + '/' + this.$route.params.sub_id).then(response => {
this.products = response.data
this.productsShow = 21
this.loadeddata = true
}).catch(error => {
if (error.response.status === 404) {
this.$router.push('/404');
}
});
} else {
axios.get('/api/cat/' + this.$route.params.cat_id ).then(response => {
this.products = response.data
this.productsShow = 21
this.loadeddata = true
}).catch(error => {
if (error.response.status === 404) {
this.$router.push('/404');
}
});
}
},
computed: {
...mapGetters({locale: 'lang/locale', locales: 'lang/locales' , user: 'auth/user'}),
filteredRecords () {
let data = this.products.products
data = data.filter( ( row ) => {
return Object.keys( row ).some( ( key ) => {
return String( row[key] ).toLowerCase().indexOf(this.quickSearchQuery.toLowerCase()) > -1
})
})
return data
}
},
created() {
this.loadData()
},
watch: {
'$route.params.cat_id': function (cat_id) {
this.quickSearchQuery = ''
this.loadData()
},
'$route.params.sub_id': function (sub_id) {
this.quickSearchQuery = ''
this.loadData()
}
}
}
</script>
So I see that you figured the issue by yourself.
To fix that, you can delay the loadData function (kind of debounce on the tail) so if it's being called multiple times in less than X (300ms?) then only the last execution will run.
try:
loadData () {
clearTimeout(this.loadDataTimeout);
this.loadDataTimeout = setTimeout(()=> {
//The rest of the loadData function should be placed here
},300);
}
Resolved :
watch: {
'$route.params': function (cat_id, sub_id) {
this.quickSearchQuery = ''
this.loadData()
},
I am learning react and is in very early stages. I was trying to add a object to an array from another component to a different component. I was able to achieve this using props but now when I try to set it in this.state setState calls the render function again thus triggering this function again. I was able to solve this problem using a button but I don't want to do it this way. Is there some better way to do this?
getData() {
if (this.props.data.employeeData !== undefined) {
if (this.props.isNewEmployee === "true") {
this.setState({
isNewEmployee: "true",
});
setTimeout(() => {
if (this.state.isNewEmployee === "true") {
console.log(this.props.data.employeeData.firstName);
var joined = this.state.EmployeesList.concat(this.props.data.employeeData);
this.setState({
EmployeesList: joined,
isNewEmployee: "false",
})
}
else {
console.log("Don't know what's happening anymore");
}
}, 100);
}
else {
console.log("No new employee added");
}
}
else {
console.log("Something went wrong");
}
}
render() {
return (
<div>
{this.getData()}
{this.renderTable()}
{this.renderFloatingActionButton()}
</div>
)
}
If the intent of the props is to add the new data to the table I would be doing something in these lines.
componentWillReceiveProps(nextProps) {
if (this.props.data.employeeData && nextProps.isNewEmployee !== this.props.isNewEmployee) {
console.log(this.props.data.employeeData.firstName);
var joined = this.state.EmployeesList.concat(this.props.data.employeeData);
this.setState({
EmployeesList: joined,
isNewEmployee: "false",
});
} else {
console.log("No new employee added");
}
}
render() {
return (
<div>
{this.renderTable()}
{this.renderFloatingActionButton()}
</div>
)
}
When I click a profile (of an author) component, I can't figure out how it should render a scoped sub-component, listing the main entities of the app, so-called fabmoments (containers for 3D print information).
My current solution looks like this:
export default {
name: 'Multipe',
props: [
'author'
],
data () {
return {
// search: '',
localAuthor: '',
fabmoments: []
}
},
created () {
this.localAuthor = this.author
if (typeof localAuthor !== 'undefined') {
this.$http.get(`/users/${this.$route.params.id}/fabmoments`)
.then(request => this.buildFabmomentList(request.data))
.catch(() => { alert('Couldn\'t fetch faboments!') })
} else {
this.$http.get('/fabmoments')
.then(request => this.buildFabmomentList(request.data))
.catch(() => { alert('Couldn\'t fetch faboments!') })
}
},
methods: {
buildFabmomentList (data) {
this.fabmoments = data
}
},
components: {
// Box
}
}
This renders all in the profile, where it should render a list scoped to the current profile's author.
And it renders nothing in the home (without receiving the prop), where it should render all.
I am not much of star in JavaScript. What am I doing wrong?
UPDATE
This works as a solution, though not very elegant.
export default {
name: 'Multipe',
props: [
'author'
],
data () {
return {
fabmoments: []
}
},
created () {
if (this.author.id >= 0) {
this.$http.get(`/users/${this.$route.params.id}/fabmoments`)
.then(request => this.buildFabmomentList(request.data))
.catch(() => { alert('Couldn\'t fetch faboments!') })
} else {
this.$http.get('/fabmoments')
.then(request => this.buildFabmomentList(request.data))
.catch(() => { alert('Couldn\'t fetch faboments!') })
}
},
methods: {
buildFabmomentList (data) {
this.fabmoments = data
}
},
components: {
// Box
}
}
Not sure which part is wrong, but you may definitely debug your code to find out why fabmoments is empty array assuming there is no error occurred yet.
There are three parts to debug:
http response -- to check if data is properly returned
this -- to check if this pointer still points at the component
template -- to check if fabmoments are correctly bind to the element
At last, it would be better to separate your http request logics from your components.
Good luck!
I have been working on the feature of comment deleting and came across a question regarding a mutation for an action.
Here is my client:
delete_post_comment({post_id, comment_id} = {}) {
// DELETE /api/posts/:post_id/comments/:id
return this._delete_request({
path: document.apiBasicUrl + '/posts/' + post_id + '/comments/' + comment_id,
});
}
Here is my store:
import Client from '../client/client';
import ClientAlert from '../client/client_alert';
import S_Helper from '../helpers/store_helper';
const state = {
comment: {
id: 0,
body: '',
deleted: false,
},
comments: [],
};
const actions = {
deletePostComment({ params }) {
// DELETE /api/posts/:post_id/comments/:id
document.client
.delete_post_comment({ params })
.then(ca => {
S_Helper.cmt_data(ca, 'delete_comment', this);
})
.catch(error => {
ClientAlert.std_fail_with_err(error);
});
},
};
delete_comment(context, id) {
context.comment = comment.map(comment => {
if (!!comment.id && comment.id === id) {
comment.deleted = true;
comment.body = '';
}
});
},
};
export default {
state,
actions,
mutations,
getters,
};
I am not quite sure if I wrote my mutation correctly. So far, when I am calling the action via on-click inside the component, nothing is happening.
Guessing you are using vuex the flow should be:
according to this flow, on the component template
#click="buttonAction(someParams)"
vm instance, methods object:
buttonAction(someParams) {
this.$store.dispatch('triggerActionMethod', { 'something_else': someParams })
}
vuex actions - Use actions for the logic, ajax call ecc.
triggerActionMethod: ({commit}, params) => {
commit('SOME_TRANSATION_NAME', params)
}
vuex mutations - Use mutation to make the changes into your state
'SOME_TRANSATION_NAME' (state, data) { state.SOME_ARG = data }