I have a component that shows all users tied to a specific type of entity. The component renders with the apollo graphql compose helper. The export of the component looks like this:
export const UsersContainer = compose(
connect(mapStateToProps, mapDispatchToProps),
graphql(gql`
query manager($id: Int!) {
manager(id: $id) {
users {
id
firstName
lastName
email
username
}
}
}`, {
options: (props) => ({
variables: {
id: props.currentOrg.org.id
}
}),
})
)(Users);
This all works fine. The issue I'm facing is that I want to make this component dynamic so it will work with all entity types (ie. manager, client, vendor). So, in the above query: query manager($id: Int!) would change to: query client($id: Int!), and so forth.
How can I access the redux store to pull in data to dynamically build the gql query? The data is all available in the store. I just need a way to access the props in a way that I can dynamically build the gql query.
You effectively need to define three different queries and then conditionally switch between them. The problem is the graphql HOC doesn't provide an easy way to derive which query to use from props. This is probably easiest to do with the new Query component, which uses the render props pattern:
const QueryA = gql`query { # blah }`
const QueryB = gql`query { # blah }`
const WrapperComponent = props => {
const query = props.foo ? QueryA: QueryB
return (
<Query query={query}>
{({loading, error, data}) => (
<YourComponent/>
)}
</Query>
)
}
const mapStateToProps = ({ foo }) => ({ foo })
export default connect(mapStateToProps)(WrapperComponent)
Using the HOC, you could do something like:
const QueryA = gql`query { # blah }`
const QueryB = gql`query { # blah }`
const QueryAComponent = graphql(QueryA)(YourComponent)
const QueryBComponent = graphql(QueryB)(YourComponent)
const WrapperComponent = props => (
props.foo
? QueryAComponent
: QueryBComponent
)
const mapStateToProps = ({ foo }) => ({ foo })
export default connect(mapStateToProps)(WrapperComponent)
If you're going the HOC route, you could also use something like recompose's branch to conditionally render the components.
Related
I'm using Typescript, and I know how to use useQuery hook with variables, but now I have a GraphQL query without variables like below:
const GetTopAlertsQuery = gql`
query getTopAlerts {
getTopAlerts {
ens
walletAddress
}
}
`;
Basically I just need it return all the data in the database without doing any filtering. I have already implemented the back-end and it works successfully, so the query should be good.
I also have set up these two interfaces to hold the data:
interface topAlertValue {
ens: string;
walletAddress: string;
}
interface jsonData {
topalerts: topAlertValue[];
}
And I have tried the below ways, but none of them work:
// attempt #1
const { data } = useQuery<jsonData>(
GetTopAlertsQuery
);
// attempt #2
const data = ({ topalerts }: jsonData ) => {
useQuery(GetTopAlertsQuery);
};
// attempt #3
const data = <Query<Data, Variables> query={GetTopAlertsQuery}>
{({ loading, error, data }) => { ... }}
</Query>
If you know how to use useQuery hook without variables, please help me out! Thanks!
I have problem when trying to send data through the function action in redux,
my code is below
import React from 'react'
import {connect} from 'react-redux'
import {RetrieveCompany} from '../../folder/action/my.actions'
interface Icampaing{
campaing: my_data
}
// campaing IS WORKING WELL, GET ME ALL MY DATA
const Personal: React.FC<Icampaing> = ({campaing}, props: nay) => {
React.useEffect(()=>{
let pf_id: any = campaing.profile ? campaing.profile.id : 0
let pc_id: any = campaing.profile_ca
// THE PROBLEM IS HERE SHOW ME THE ERROR
// TypeError: props.RetrieveCompany is not a function
props.RetrieveCompany(pf_id, pc_id)
},[campaing])
return(<>
{campaing.all_data} // HERE WHEN LOAD DATA campaing WORKING WELL
</>)
}
const mapStateToProps = (state: any) =>({
campaing: state.campaing
})
const mapActionToProps = {
RetrieveCompany
}
export default connect(mapStateToProps, mapActionToProps)(Personal)
please help me, I think forget something.
best words, and happy new year.....!
You should use mapDispatchToProps instead of mapActionToProps
const mapDispatchToProps = (dispatch) => ({
RetrieveCompany: () => dispatch(RetrieveCompany())
// Important: this could be just
// RetrieveCompany depends on how you define your action.
// For naming, I would use camelCase
});
Because what you need to do here is to dispatch an action so that the store will update its state. Then you would read the data returned by mapStateToProps.
I think RetrieveCompany is not among props in deed. Try to spread the rest of the props if you do not want to explicitly name it:
interface Icampaing {
campaing: my_data
[propName: string]: any
}
const Personal: React.FC<Icampaing> = ({ campaing, ...props }) => {
...
or simply add it explicitly since you use it in the component anyways:
interface Icampaing {
campaing: my_data
RetrieveCompany: (pf_id: number, pc_id: number) => void
}
const Personal: React.FC<Icampaing> = ({ campaing, RetrieveCompany }) => {
I am using the Context API to load categories from an API. This data is needed in many components, so it's suitable to use context for this task.
The categories can be expanded in one of the child components, by using a form. I would like to be able to tell useCategoryLoader to reload once a new category gets submitted by one of the child components. What is the best practice in this scenario? I couldn't really find anything on google with the weird setup that I have.
I tried to use a state in CategoryStore, that holds a boolean refresh State which gets passed as Prop to the callback and can be modified by the child components. But this resulted in a ton of requests.
This is my custom hook useCategoryLoader.ts to load the data:
import { useCallback } from 'react'
import useAsyncLoader from '../useAsyncLoader'
import { Category } from '../types'
interface Props {
date: string
}
interface Response {
error?: Error
loading?: boolean
categories?: Array<Category>
}
const useCategoryLoader = (date : Props): Response => {
const { data: categories, error, loading } = useAsyncLoader(
// #ts-ignore
useCallback(() => {
return *APICALL with modified date*.then(data => data)
}, [date])
)
return {
error,
loading,
categories
}
}
export default useCategoryLoader
As you can see I am using useCallback to modify the API call when input changes. useAsyncloaderis basically a useEffect API call.
Now this is categoryContext.tsx:
import React, { createContext, FC } from 'react'
import { useCategoryLoader } from '../api'
import { Category } from '../types'
// ================================================================================================
const defaultCategories: Array<Category> = []
export const CategoryContext = createContext({
loading: false,
categories: defaultCategories
})
// ================================================================================================
const CategoryStore: FC = ({ children }) => {
const { loading, categories } = useCategoryLoader({date})
return (
<CategoryContext.Provider
value={{
loading,
topics
}}
>
{children}
</CategoryContext.Provider>
)
}
export default CategoryStore
I'm not sure where the variable date comes from in CategoryStore. I'm assuming that this is an incomplete attempt to force refreshes based on a timestamp? So let's complete it.
We'll add a reload property to the context.
export const CategoryContext = createContext({
loading: false,
categories: defaultCategories,
reload: () => {},
})
We'll add a state which stores a date timestamp to the CategoryStore and create a reload function which sets the date to the current timestamp, which should cause the loader to refresh its data.
const CategoryStore: FC = ({ children }) => {
const [date, setDate] = useState(Date.now().toString());
const { loading = true, categories = [] } = useCategoryLoader({ date });
const reload = useCallback(() => setDate(Date.now.toString()), []);
return (
<CategoryContext.Provider
value={{
loading,
categories,
reload
}}
>
{children}
</CategoryContext.Provider>
)
}
I think that should work. The part that I am most iffy about is how to properly memoize a function that depends on Date.now().
I have the following situation:
A selector for a list of countries in my app state
export const getCountries = state => state.countries;
And a selector created using reselect to filter for a country with a specific id
export const getCountryById = countryId =>
createSelector(
[getCountries],
(countries) => {
let countriesList = countries.filter(c => c.id === countryId);
if ( countriesList ) {
return countriesList[0];
}
return null;
}
)
Then a have a country details page component ( acessed through http://localhost:3000/country/4 )
import React from 'react';
import { getCountryById } from '../redux/selectors';
import { connect } from 'react-redux';
const CountryPage = ({country}) => {
return <h1>{country.name}</h1>
}
const mapStateToProps = (state, ownProps) => {
console.log(ownProps.match.params.countryId);
const obj = {
country: getCountryById(ownProps.match.params.countryId)
}
return obj;
};
export default connect(mapStateToProps, null)(CountryPage);
The mapStateToProps is correctly receiving the countryId, but the selector is not being called. What am I missing here ?
Or is it the wrong approach to using the selectors ? I am aware that I can use the full country list from state in the CountryPage and filter it there, but what is the best practice here ?
That's because your getCountryById isn't actually a selector. It's a function that returns a selector.
Given that you are trying to use a unique ID per component, and doing filtering, you probably need to use the "factory function" form of mapState to create a unique selector instance per component instance to get proper memoization behavior.
However, having said that, your selector can also be simplified. You don't need a filter() here, you need a find(), and that means it's also not something that really needs to be memoized. This can be simplified down to:
const getCountryById = createSelector(
[getCountries, (state, id) => id],
(countries, id) => countries.find(c => c.id === id) || null
)
and then used as:
const mapStateToProps = (state, ownProps) => {
return {
country: getCountryById(state, ownProps.match.params.countryId)
}
};
So I have to make two queries. First will return an array with multiple objects and I want to get the ID of the first object to use it in my second query.
The problem is that I can't use b_id: props.getBusiness.business[0]._id
Any idea how can I work with this?
const GET_USER_BUSINESS = gql`
query getUserBusiness {
getUserBusiness {
_id
}
}
`;
const GET_BUSINESS_JOBS = gql`
query getBusinessJobs($b_id: ID!) {
getBusinessJobs(b_id: $b_id ) {
_id
name
}
}
`;
export default compose(
withApollo,
graphql(GET_USER_BUSINESS,
{
name: "business"
}),
graphql(GET_BUSINESS_JOBS,
{
name: "jobs",
options: (props) => (
{
variables:
{
b_id: props.getUserBusiness.business[0].b_id
}
}
)
})
)(ProposalContainer);
Couple of things. One, by default, the graphql HOC passes a prop called data down to the wrapped component with the query data. However, because you specify a name in the HOC's config, the prop will actually be called whatever name you pass it. In other words, you can access the _id this way:
props.business.getUserBusiness[0]._id
...if getUserBusiness returns an array. Otherwise:
props.business.getUserBusiness._id
Secondly, props.business will be undefined until the query completes. We probably don't want to send the GET_BUSINESS_JOBS query until we have an _id to work with. So we want to pass a skip function to the HOC's config:
export default compose(
withApollo,
graphql(GET_USER_BUSINESS,
{
name: "business"
}),
graphql(GET_BUSINESS_JOBS,
{
name: "jobs",
skip: (props) => !props.business || !props.business.getUserBusiness[0]
options: (props) => (
{
variables:
{
b_id: props.business.getUserBusiness[0]._id
}
}
)
})
)(ProposalContainer)