My use case:
On page load I need to show only filters and empty list.
RA should only make first request to API when user enter anything in one of the filters.
Didn’t found anything related in documentation.
If someone can just pinpoint me correct topic in docs, which I should dig better to achieve what I need, it already will help a lot.
Thank you!
You can't use the <List> component for that, as it makes requests on mount. You'll have to write your own List component and call the API using the useGetList hook, as explained in the documentation:
import { useGetList } from 'react-admin';
const LatestNews = () => {
const { data, ids, loading, error } = useGetList(
'posts',
{ page: 1, perPage: 10 },
{ field: 'published_at', order: 'DESC' }
);
if (loading) { return <Loading />; }
if (error) { return <p>ERROR</p>; }
return (
<ul>
{ids.map(id =>
<li key={id}>{data[id].title}</li>
)}
</ul>
);
};
Related
So I'm using vue-query to get data from my API. The current way I'm doing that looks a little like this. I have a folder in src called hooks, and it may contain a file such as usePosts.ts. That file contains code like this:
import { useQuery, useMutation, useQueryClient } from "vue-query"
import axios, { AxiosError } from "axios"
import {
performOptimisticAdd,
handleMutateSuccess,
handleMutateError,
} from "./handlers"
export interface Post {
id: number
title: string
body: string
user: number // user_id
}
export function usePostsListQuery() {
return useQuery<Post[], AxiosError>(
"posts",
() => axios.get("/v1/posts").then(resp => resp.data),
{ placeholderData: [] }
)
}
export function useAddPostMutation() {
const client = useQueryClient()
return useMutation<Post, AxiosError>(
post => axios.post("/v1/posts", post).then(resp => resp.data),
{
onMutate: performOptimisticAdd(client, "posts"),
onSuccess: handleMutateSuccess(),
onError: handleMutateError()
}
)
}
Of course I'm not showing all the code, for brevity.
Now in my Vue components, I'm often doing something like this:
<script setup>
import { usePostsListQuery, useAddPostMutation } from "#/hooks/usePosts";
import { useUsersListQuery } from "#/hooks/useUsers";
const { data: posts } = $(usePostsListQuery())
const { data: users } = $(useUsersListQuery())
const { mutate: addPost } = $(useAddPostMutation())
const postsWithUsers = $computed(() => posts.map(
post => ({ ...post, user: users.find(user => user.id === post.user) })
))
const addPostWithUserId = (newPost: Post) => addPost({ ...newPost, user: newPost.user.id })
</script>
Because I want to be able to directly access the user associated with a post. And of course, the way I'm doing it works. But it doesn't seem right to do that transformation inside a Vue-component. Because that means I need to repeat that same code inside every new Vue-component.
So I'm wondering what would be the best place to do this transformation. One obstacle is that useQuery() may only be called during / inside the setup() function. So I'm a bit limited in terms of where I'm allowed to call these queries.
Maybe I could just put it inside usePosts.ts? But is that really the best place? I can imagine that it might make all my hooks very messy, because then every hook suddenly has TWO responsibilities (talking to my API, and transforming the output and input). I feel like that breaks the single responsibility principle?
Anyhow, this is why I'd love to hear some of your opinions.
React-admin 3.8.4
I'm rendering some form fields conditionally, and these fields have some validations. Because of this, I'm receiving this error below:
Warning: Cannot update a component (nameOfTheComponent) while rendering a different component
(SelectInput). To locate the bad setState() call inside SelectInput, follow the stack trace
as described in...
I already have read some explanation about the problem and I've discovered that react-final-form
calls a setState() when registering those fields and this seems to be the issue.
I also saw that there is a fix into FileConfig called silent that solves this problem React final form silent
But I don't know if I'm using wrong, because the warning remains showing up.
I'm trying to do something like this:
const OfferVariation = ({ formData, ...rest }) => {
const form = useForm();
useEffect(() => {
return () => {
const initialState = {}
let inConstructor = true
const fieldName = "internalOffer.type"
form.registerField(fieldName, fieldState => {
if (inConstructor) {
initialState[fieldName] = fieldState
} else {
this.setState({ [fieldName]: fieldState })
}
}, { silent: true })
}
}, [])
if (flowType === "Interna") {
return (
<SelectInput
source="internalOffer.type"
label="Tipo da Oferta"
choices={offerTypes}
validate={validateArrayNotEmpty}
{...rest}
/>
)
} else if (flowType === "Externa") {
return (
<TextInput
label="Url Externa"
source="externalOffer.externalURL"
{...rest}
/>
)
}
}
};
export default OfferVariation;
Does anyone know how to fix it and could help me?
I am going to query the database to return the result after receiving a parameter in the dynamic route, and find that the console reports an error.
TypeError: Cannot read property 'title' of null
When I went to see the request, I found that the first request returned the data, and then sent the same request but the spliced parameter was null and reported the error.
This is the second request 304
This is my page code.
`
<templat>
<div class="wrapper qa-content">
<div class="qa-title">
<div class="fl title">
<h2>{{problem.title}}</h2>
<p>
<span
>{{labes(index)}}</span>
<span>{{timeago(problem.createtime)}}</span>
</p>
</div>
import "~/assets/css/page-sj-qa-detail.css";
import axios from "axios";
import problemApi from "#/api/problem";
import replyApi from "#/api/reply";
import labelApi from "#/api/label";
export default {
asyncData({ params }) {
return axios
.all([
problemApi.findById(params.id),
replyApi.findByProId(params.id),
problemApi.findPL(params.id)
])
.then(
axios.spread(function(pojo, replyList, labelList) {
return {
problemId: params.id,
replyList: replyList.data.data,
problem: pojo.data.data,
labelList: labelList.data.data
};
})
);
},
data() {
return {
CurrentreplyId: "",
commentList: [],
labelName: [],
textarea: "",
dialogVisible: false,
content: "",
editorOption: {
// some quill options
modules: {
toolbar: [
[{ size: ["small", false, "large"] }],
["bold", "italic"],
[{ list: "ordered" }, { list: "bullet" }],
["link", "image"],
["blockquote", "code-block"]
]
}
}
};
},
mounted() {
console.log("app init, my quill insrance object is:", this.myQuillEditor);
},
methods: {
labes(index) {
console.log(this.labelList);
labelApi.findOne(this.labelList[index].labelid).then(res => {
this.labelName.push(res.data.data.labelname);
console.log(this.labelName);
});
},
check(id) {
console.log(id);
replyApi.findByParentid(id).then(res => {
this.commentList = res.data.data;
});
},
shows(item) {
console.log(item.id);
if (item.content === null || item.content === "" || item.content === "") {
return false;
} else {
return true;
}
}
`
This page is dynamically routed from the previous page.
<nuxt-link :to="'/qa/items/'+item.id" target="_blank">{{item.title}}</nuxt-link>
Ok. I think that helps. If I understand you correctly you do the following:
You are on a previous page
You click on the nuxt-link with the item id in the route
Your new route loads and you get an error: Because your page fetches the data from the API twice (but you did not reload the page) is that correct?
If so, I am not sure why your asyncData is executed twice, but you could probably solve it like this:
asyncData({ params }) {
if (params.id) {
return axios
.all([...
}
This would make sure that your request is only made ifan ID is present and it would not send null.
Honestly I don't know why the request is resent...
I also don't really understand how your API looks like. I see that it somehow returns promises, and that you can call methods on it... Something I haven't seen in such a context, but OK :).
Besides that you seem to execute further API calls in your methods.
Maybe that is the problem:
<span>{{labes(index)}}</span>
I don't see what would be passed into this method. This index is not defined...
When then calling the
labes() (did you mean labels?) method, you execute
labelApi.findOne(this.labelList[index].labelid)
but as index is undefined, I think this.labelList[index] will not return something useful and you make a request there?
(Depending on what your api.findOne() method does. Could it be that itself sends a request to an actual remote API?)
Cheers
In this article, it says:
While it’s generally poor practice, you can use Axios directly in your components to fetch data from a method, lifecycle hook, or whenever.
I am wondering why? I usually use lifecycle hooks a lot to fetch data (especially from created()). Where should we write the request calls?
Writing API methods directly in components increases code lines and make difficult to read.
As far as I believe the author is suggesting to separate API methods into a Service.
Let's take a case where you have to fetch top posts and operate on data. If you do that in component it is not re-usable, you have to duplicate it in other components where ever you want to use it.
export default {
data: () => ({
top: [],
errors: []
}),
// Fetches posts when the component is created.
created() {
axios.get(`http://jsonplaceholder.typicode.com/posts/top`)
.then(response => {
// flattening the response
this.top = response.data.map(item => {
title: item.title,
timestamp: item.timestamp,
author: item.author
})
})
.catch(e => {
this.errors.push(e)
})
}
}
So when you need to fetch top post in another component you have to duplicate the code.
Now let's put API methods in a Service.
api.js file
const fetchTopPosts = function() {
return axios.get(`http://jsonplaceholder.typicode.com/posts/top`)
.then(response => {
// flattening the response
this.top = response.data.map(item => {
title: item.title,
timestamp: item.timestamp,
author: item.author
})
}) // you can also make a chain.
}
export default {
fetchTopPosts: fetchTopPosts
}
So you use the above API methods in any components you wish.
After this:
import API from 'path_to_api.js_file'
export default {
data: () => ({
top: [],
errors: []
}),
// Fetches posts when the component is created.
created() {
API.fetchTopPosts().then(top => {
this.top = top
})
.catch(e => {
this.errors.push(e)
})
}
}
It's fine for small apps or widgets, but in a real SPA, it's better to abstract away your API into its own module, and if you use vuex, to use actions to call that api module.
Your component should not be concerned with how and from where its data is coming. The component is responsible for UI, not AJAX.
import api from './api.js'
created() {
api.getUsers().then( users => {
this.users = users
})
}
// vs.
created() {
axios.get('/users').then({ data }=> {
this.users = data
})
}
In the above example, your "axios-free" code is not really much shorter, but imagine what you could potentially keep out of the component:
handling HTTP errors, e.g. retrying
pre-formatting data from the server so it fits your component
header configuration (content-type, access token ...)
creating FormData for POSTing e.g. image files
the list can get long. all of that doesn't belong into the component because it has nothing to do with the view. The view only needs the resulting data or error message.
It also means that you can test your components and api independently.
I'm trying to find out how/if it is possible to trigger a refresh in a Relay Modern RefreshContainer without passing (new) variables?
I’m looking for the best way to implement the good ol’ pull-to-refresh on a React Native list, that should simply refetch the original query - no variables needed?
According to docs (https://facebook.github.io/relay/docs/api-cheatsheet.html) this should be possible using
this.props.relay.refetch({}, callback, {force: true})
but I get an error saying "undefined is not an object ('evaluating taggedNode.modern')"
The query works just fine if I use a plain old FragmentContainer instead, but I'd just like a simple pull-to-refresh functionality :-)
EDIT
Adding more code for clarity. Also updated call to reflect change to API that includes render variables, passing null
class HistoryList extends React.PureComponent<void, Props, State> {
state = { refreshing: false };
_renderRow = ({ item }) => {
return <HistoryListItem item={item.node} />;
};
_renderHeader = ({ section }) => {
return (
<Text style={[cs.breadText, _styles.sectionHeader]}>
{section.title}
</Text>
);
};
_onRefresh = () => {
this.setState({ refreshing: true });
this.props.relay.refetch({}, null, this._onRefreshDone(), { force: true });
};
_onRefreshDone = () => {
this.setState({ refreshing: false });
};
_sortDataIntoSections = (edges: Array<Node>) => {
return _.chain(edges)
.groupBy(element => {
return moment(element.node.huntDate).format('MMMM YYYY');
})
.map((data, key) => {
return { title: key, data: data };
})
.value();
};
render() {
return (
<View style={_styles.container}>
<SectionList
renderSectionHeader={this._renderHeader}
sections={this._sortDataIntoSections(
this.props.entries.allJournalEntries.edges
)}
renderItem={this._renderRow}
keyExtractor={(item, index) => item.node.__id}
onRefresh={this._onRefresh}
refreshing={this.state.refreshing}
/>
</View>
);
}
}
export default createRefetchContainer(
HistoryList,
graphql`
fragment HistoryList_entries on Viewer {
allJournalEntries(orderBy: huntDate_DESC) {
count
edges {
node {
huntDate
...HistoryListItem_item
}
}
}
}
`
);
It seems that the arguments of this.props.relay.refetch has been change to refetch(refetchVariables, renderVariables, callback, options) (in https://facebook.github.io/relay/docs/refetch-container.html) and the cheetsheet has been out-of-date.
I'm not sure that in which version that this has change, but you can give it a try and change your code to:
this.props.relay.refetch({}, null, callback, {force: true})
A solution has been found by robrichard at github.
I was missing the third argument for the RefetchContainer, which is the query to execute on refetch. This, combined with the suggestion from #zetavg was what was needed.
The exported module now looks like this:
export default createRefetchContainer(
HistoryList,
{
entries: graphql`
fragment HistoryList_entries on Viewer {
allJournalEntries(orderBy: huntDate_DESC) {
count
edges {
node {
huntDate
...HistoryListItem_item
}
}
}
}
`
},
graphql`
query HistoryListRefetchQuery {
viewer {
...HistoryList_entries
}
}
`
);
I have both solution applied (query for refetch and relay refetch call).
Refetch query
(do not pay attention at fact, that I didn't specify a component for container, there is special decorator in our code base for it):
{
viewer: graphql`
fragment RatingSchoolsTableContainer_viewer on Viewer {
rating {
schools {
uid
masterUrl
paidOrderSum
paidOrderCount
averageReceipt
}
}
}
`,
},
graphql`
query RatingSchoolsTableContainer_RefetchQuery {
viewer {
...RatingSchoolsTableContainer_viewer
}
}
`,
And relay call:
this.props.relay?.refetch({}, null, () => {}, {force: true})
There is no re-render anyway, but I have new response from server in network.