How to trigger GraphQL Query from another React / ReactNative component - reactjs

I have some problem.
I have 2 Component in React Native.
1. (Component A) for showing the data , this component use query to graphql
2. (Component B) is button to trigger query in component A with some filtering variable.
When I click the button component ,
it mutate
save filter into graphql link state
trigger the component A to rerender new data.
Problem is , component A not rerender..
Here is code that triggered when I click the button
this.props.mutate({
variables: {
Category
},
refetchQueries: [{
query: FETCH_SEARCH,
variables: {
productcategory: Category,
search: '',
},
}]
});
How do I achieve rerendering component A ?
Thanks

Hey your question is basically "what is wrong with my code?" without providing much code. I will try to guess what is wrong here:
Your query component is still connected to the old variables, refetching it with new variables will not update the query component. Only when you refetch with the same variables it will update.
What to do instead:
React is not designed to have a messaging system from one component to another. Instead lift the variable state into a parent component or a data store so that the query component takes the variables from props (if the props have the same name as the expected variable names you don't even have to explicitly pass them in the graphql higher order component function options. Now the query will be automatically refetched when the props - and therefore the variables - change.
<QueryComponent
search={this.state.search}
productcategory={this.state.productcategory}
/>
<SelectorComponent
onSearchChange={search => this.setState({ search })}
onCategoryChange={productcategory => this.setState({ productcategory })}
/>

I faced the same problem, after doing a lot of search, none of answers suited me. I didn't want to lift up the state, I wanted to maintain the query logic inside the same component, also re-fetching from another component was a bad idea, since the variables passed to the query are not the same and Apollo interprets it as another query and the first component don't change.
So my solution is to save the "filtering variable" in the local state with Apollo. This code is an example just for this answer, I didn't test it, just copied from my project and keep out only the necessary for the explanation.
In some place you need to initialise your apollo cache
const data = {
keyword: "",
};
cache.writeData({ data });
return ApolloClient({
cache,
});
In the first component, you will query this "keyword" variable from the cache and pass it to the actual query you want to re-fetch every time
const GET_KEYWORD = gql`
{
keyword #client
}
`;
const FirstComponent = () => {
const {
data: { keyword },
} = useQuery(GET_KEYWORD);
const {
data: importantData
} = useQuery(SOME_OTHER_QUERY_YOU_WANT_TO_REFETCH, {variables: { keyword }});
return <div>{importantData}</div>
}
In the second component you just need to update this "keyword"
const SecondComponent = () => {
const client = useApolloClient();
const handleChange = (keyword: string) => {
client.writeData({ data: { keyword: keyword } });
};
return (
<div>
<input onChange={handleChange} />
</div>
);
};

Related

setInterval with updated data in React+Redux

I have setInterval setup to be working properly inside componentDidMount but the parameters are not updated. For example, the text parameter is the same value as when the component initially mounted, despite being changed in the UI. I've confirmed text's value is correctly updated in Redux store but not being passed to this.retrieveData(text). I suspect the const { text } = this.props set the value in componentDidMount, which forbids it from updating despite it being different. How would I go about this issue?
Code below is an example, but my real use-case is retrieving data based on search criteria. Once the user changes those criteria, it will update with the new result. However, I'm unable to pass those new criteria into componentDidMount so the page would refresh automatically every few seconds.
class App extends React.Component {
componentDidMount() {
const { text } = this.props //Redux store prop
setInterval(() => this.retrieveData(text), 3000)
}
retrieveData = (text) => {
let res = axios.post('/search', { text })
updateResults(res.data) //Redux action
}
render() {
const { text, results } = this.props
return (
<input text onChange={(e) => updateText(e.target.value)} />
<div>
{results.map((item) => <p>{item}</p>}
</div>
)
}
}
Because you are using componentDidMount and setTimeout methods your retrieveData is called only once with initial value of the text. If you would like to do it in your current way please use componentDidUpdate method which will be called each time the props or state has changed. You can find more information about lifecycle here https://projects.wojtekmaj.pl/react-lifecycle-methods-diagram/.
If you would like to use setInterval just like in the question, you just need to access props inside of retrieveData method instead of using an argument.
retrieveData = () => {
let res = post("/search", { text: this.props.text });
updateResults(res); //Redux action
};
You can find working example for both cases here https://codesandbox.io/s/charming-blackburn-khiim?file=/src/index.js
The best solution for async calls would be to use some kind of middleware like https://github.com/reduxjs/redux-thunk or https://redux-saga.js.org/.
You have also small issue with input, it should be:
<input type="text" value={text} onChange={(e) => updateText(e.target.value)} />

How to use React Hooks Context with multiple values for Providers

What is the best way to share some global values and functions in react?
Now i have one ContextProvider with all of them inside:
<AllContext.Provider
value={{
setProfile, // second function that changes profile object using useState to false or updated value
profileReload, // function that triggers fetch profile object from server
deviceTheme, // object
setDeviceTheme, // second function that changes theme object using useState to false or updated value
clickEvent, // click event
usePopup, // second function of useState that trigers some popup
popup, // Just pass this to usePopup component
windowSize, // manyUpdates on resize (like 30 a sec, but maybe can debounce)
windowScroll // manyUpdates on resize (like 30 a sec, but maybe can debounce)
}}
>
But like sad in docs:
Because context uses reference identity to determine when to re-render, there are some gotchas that could trigger unintentional renders in consumers when a provider’s parent re-renders. For example, the code below will re-render all consumers every time the Provider re-renders because a new object is always created for value:
This is bad:
<Provider value={{something: 'something'}}>
This is ok:
this.state = {
value: {something: 'something'},
};
<Provider value={this.state.value}>
I imagine that in future i will have maybe up to 30 context providers and it's not very friendly :/
So how can i pass this global values and functions to components? I can just
Create separate contextProvider for everything.
Group something that used together like profile and it's functions,
theme and it's functions (what about reference identity than?)
Maybe group only functions because thay dont change itself? what
about reference identity than?)
Other simpliest way?
Examples of what i use in Provider:
// Resize
const [windowSize, windowSizeSet] = useState({
innerWidth: window.innerWidth,
innerHeight: window.innerHeight
})
// profileReload
const profileReload = async () => {
let profileData = await fetch('/profile')
profileData = await profileData.json()
if (profileData.error)
return usePopup({ type: 'error', message: profileData.error })
if (localStorage.getItem('deviceTheme')) {
setDeviceTheme(JSON.parse(localStorage.getItem('deviceTheme')))
} else if (profileData.theme) {
setDeviceTheme(JSON.parse(JSON.stringify(profileData.theme)))
} else {
setDeviceTheme(settings.defaultTheme)
}
setProfile(profileData)
}
// Click event for menu close if clicked outside somewhere and other
const [clickEvent, setClickEvent] = useState(false)
const handleClick = event => {
setClickEvent(event)
}
// Or in some component user can change theme just like that
setDeviceTheme({color: red})
The main consideration (from a performance standpoint) for what to group together is less about which ones are used together and more about which ones change together. For things that are mostly set into context once (or at least very infrequently), you can probably keep them all together without any issue. But if there are some things mixed in that change much more frequently, it may be worth separating them out.
For instance, I would expect deviceTheme to be fairly static for a given user and probably used by a large number of components. I would guess that popup might be managing something about whether you currently have a popup window open, so it probably changes with every action related to opening/closing popups. If popup and deviceTheme are bundled in the same context, then every time popup changes it will cause all the components dependent on deviceTheme to also re-render. So I would probably have a separate PopupContext. windowSize and windowScroll would likely have similar issues. What exact approach to use gets deeper into opinion-land, but you could have an AppContext for the infrequently changing pieces and then more specific contexts for things that change more often.
The following CodeSandbox provides a demonstration of the interaction between useState and useContext with context divided a few different ways and some buttons to update the state that is held in context.
You can go to this URL to view the result in a full browser window. I encourage you to first get a handle for how the result works and then look at the code and experiment with it if there are other scenarios you want to understand.
This answer already does a good job at explaining how the context can be structured to be more efficient. But the final goal is to make context consumers be updated only when needed. It depends on specific case whether it's preferable to have single or multiple contexts.
At this point the problem is common for most global state React implementations, e.g. Redux. And a common solution is to make consumer components update only when needed with React.PureComponent, React.memo or shouldComponentUpdate hook:
const SomeComponent = memo(({ theme }) => <div>{theme}</div>);
...
<AllContext>
{({ deviceTheme }) => <SomeComponent theme={deviceTheme}/>
</AllContext>
SomeComponent will be re-rendered only on deviceTheme updates, even if the context or parent component is updated. This may or may not be desirable.
The answer by Ryan is fantastic and you should consider that while designing how to structure the context provider hierarchy.
I've come up with a solution which you can use to update multiple values in provider with having many useStates
Example :
const TestingContext = createContext()
const TestingComponent = () => {
const {data, setData} = useContext(TestingContext)
const {value1} = data
return (
<div>
{value1} is here
<button onClick={() => setData('value1', 'newline value')}>
Change value 1
</button>
</div>
)
}
const App = () => {
const values = {
value1: 'testing1',
value2: 'testing1',
value3: 'testing1',
value4: 'testing1',
value5: 'testing1',
}
const [data, setData] = useState(values)
const changeValues = (property, value) => {
setData({
...data,
[property]: value
})
}
return (
<TestingContext.Provider value={{data, setData: changeValues}}>
<TestingComponent/>
{/* more components here which want to have access to these values and want to change them*/}
</TestingContext.Provider>
)
}
You can still combine them! If you are concerned about performance, you can create the object earlier. I don't know if the values you use change, if they do not it is quite easy:
state = {
allContextValue: {
setProfile,
profileReload,
deviceTheme,
setDeviceTheme,
clickEvent,
usePopup,
popup,
windowSize
}
}
render() {
return <AllContext.Provider value={this.state.allContextValue}>...</AllContext>;
}
Whenever you then want to update any of the values you need to do I like this, though:
this.setState({
allContextValue: {
...this.state.allContextValue,
usePopup: true,
},
});
This will be both performant, and relatively easy as well :)
Splitting those up might speed up a little bit, but I would only do that as soon as you find it is actually slow, and only for parts of your context that would have a lot of consumers.
Still, if your value does not change a lot, there is really nothing to worry about.
Based on Koushik's answer I made my own typescipt version.
import React from "react"
type TestingContextType = {
value1?: string,
value2?: string,
value3?: string,
value4?: string,
value5?: string,
}
const contextDefaultValues = {
data: {
value1: 'testing1',
value2: 'testing1',
value3: 'testing1',
value4: 'testing1',
value5: 'testing1'
} as TestingContextType,
setData: (state: TestingContextType) => {}
};
const TestingContext = React.createContext(contextDefaultValues);
const TestingComponent = () => {
const {data, setData} = React.useContext(TestingContext);
const {value1} = data
return (
<div>
{value1} is here
<button onClick={() => setData({ value1 : 'newline value' })}>
Change value 1
</button>
</div>
)
}
const App = () => {
const [data, setData] = React.useState(contextDefaultValues.data)
const changeValues = (value : TestingContextType) => setData(data && value);
return (
<TestingContext.Provider value={{data, setData: changeValues}}>
<TestingComponent/>
{/* more components here which want to have access to these values and want to change them*/}
</TestingContext.Provider>
)
}

How to replace componentWillRecieveProps

I'm new to redux and followed this tutorial to create a simple blog app with react and redux. I've completed it, however I noticed that componentWillRecieveProps is being deprecated. I'm trying to replace it with more up-to-date code, but have been unable to understand how to do so. I've read this article about replacing ‘componentWillReceiveProps’ with ‘getDerivedStateFromProps’, but I don't think that this is the correct use for getDerivedStateFromProps as React's blog post on replacing one with the other describes.
My current componentWillRecieveProps code:
import axios from "axios";
import React from "react";
import { connect } from "react-redux";
class Form extends React.Component {
constructor(props) {
super(props);
this.state = {
title: "",
body: "",
author: ""
};
this.handleChangeField = this.handleChangeField.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
//This is deprecated
componentWillReceiveProps(nextProps) {
console.log(this);
if (nextProps.articleToEdit) {
this.setState({
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
});
}
}
handleSubmit() {
const { onSubmit, articleToEdit, onEdit } = this.props;
const { title, body, author } = this.state;
if (!articleToEdit) {
return axios
.post("http://localhost:8000/api/articles", {
title,
body,
author
})
.then(res => onSubmit(res.data))
.then(() => this.setState({ title: "", body: "", author: "" }));
} else {
return axios
.patch(
`http://localhost:8000/api/articles/${articleToEdit._id}`,
{
title,
body,
author
}
)
.then(res => onEdit(res.data))
.then(() => this.setState({ title: "", body: "", author: "" }));
}
}
handleChangeField(key, event) {
this.setState({
[key]: event.target.value
});
}
render() {
const { articleToEdit } = this.props;
const { title, body, author } = this.state;
return (
<div className="col-12 col-lg-6 offset-lg-3">
<input
onChange={ev => this.handleChangeField("title", ev)}
value={title}
className="form-control my-3"
placeholder="Article Title"
/>
<textarea
onChange={ev => this.handleChangeField("body", ev)}
className="form-control my-3"
placeholder="Article Body"
value={body}
/>
<input
onChange={ev => this.handleChangeField("author", ev)}
value={author}
className="form-control my-3"
placeholder="Article Author"
/>
<button
onClick={this.handleSubmit}
className="btn btn-primary float-right"
>
{articleToEdit ? "Update" : "Submit"}
</button>
</div>
);
}
}
const mapDispatchToProps = dispatch => ({
onSubmit: data => dispatch({ type: "SUBMIT_ARTICLE", data }),
onEdit: data => dispatch({ type: "EDIT_ARTICLE", data })
});
const mapStateToProps = state => ({
articleToEdit: state.home.articleToEdit
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(Form);
Full Repo
Codesandbox
If componentWillRecieveProps is being deprecated, how should I update my code?
Edit: using getDerivedStateFromProps, nextProps still has the previous values so when returning the object, state is set back to previous state, no updates are actually made.
Trying to use prevState values doesn't work in this case because the initial prevState values are all empty strings which are called and placed into state anytime the component renders, which occurs on initial page load, and when clicking the edit button.
static getDerivedStateFromProps(nextProps, prevState) {
if (
JSON.stringify(nextProps.articleToEdit) !==
JSON.stringify(prevState.articleToEdit)
) {
console.log(nextProps);
console.log(prevState);
return {
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
}; // <- is this actually equivalent to this.setState()?
} else {
return null;
}
}
componentWillReceiveProps() method is deprecated by introducing a new life cycle method called getDerivedStateFromProps().
Keep in mind that you should always compare current props with previous props like below and if they both are not same then do setState otherwise you will get into infinite setState warning
Replace below code in place of componentWillReceiveProps method
static getDerivedStateFromProps(nextProps, prevState) {
//Below I used JSON.stringify to compare current props with previous props of articleToEdit object
if (JSON.stringify(nextProps.articleToEdit) !== JSON.stringify(prevState.artileToEdit)){
return({
title: nextProps.articleToEdit.title,
body: nextProps.articleToEdit.body,
author: nextProps.articleToEdit.author
});// <- this is setState equivalent
}
return null;
}
Edit:
You cannot access this.props inside getDerivedStateFromProps function. If you want to access some previous props (like for comparison) inside the function, then you can mirror such values inside the state. Then you can compare the nextProps with the value stored in state like above
Check this thread for better understanding
https://reactjs.org/blog/2018/03/27/update-on-async-rendering.html#updating-state-based-on-props
Alrighty. Here's a working example for your application. I completely rewrote its structure so that each component handles its own set of state and props. The components are also reusable so they can be placed wherever -- provided that it has required props.
Notes:
Don't mix .jsx and .js (see step 6 for fix)
Separate your container-components from your components. Also make reusable components -- this vastly simplifies your state and prop logic.
For your application, you can completely remove redux and instead allow your server to be the "source of truth". So instead of saving to redux state and manipulating redux state, you would simply make AJAX requests to leverage your API for CRUDing your articles (for example, form submission would send post request to API, which would add an article to the DB, then either redirect the user back to /articles or it could simply return all articles after it has posted and update ShowArticles state with new data). Up to you on how you want to handle that. But, with that said, the only time you really need redux is if you're sharing props from two separate heavily nested components.
If using redux, add a types and actions folder as shown in the example below. It makes your code easier to read and more modular.
Avoid using index.js unless the root folder only houses a single file. Otherwise, when the application breaks (and it will), it'll be a hassle to figure out which "index.js" broke if you have many.
The boilerplate you're using is pretty old and you're making your life harder by using it. I've created a MERN Fullstack Boilerplate that allows things like: fat arrow functions in class methods, scss/css node_module imports, component-level scss module imports, runs server and client simultaneously, has an error overlay, eslinting, and plenty more. I highly recommend porting what you have over to it.
Working example: https://codesandbox.io/s/4x4kxn9qxw (unfortunately, the create-react-app template doesn't allow scss module imports... oh well, you'll get the idea)
My advice would be to heed the warning that React is giving us by replacing this lifecycle method because of its misuse — they are telling us only do this if you really have to, because there is probably a better way (including the newer getDerivedStateFromProps).
From what I gather in this code, you are rendering a form, letting the user interact with it, but in some circumstance you replace the users' inputs with props (overwriting component state). Its easy to see why this is an anti-pattern but tricky to re-architect without knowing more about the app.
I'd suggest moving the inputs' values into the parent state (or creating a new parent), and change to value={props.title} etc. If there is a parent that controls a forms' state in any circumstance, you may as well hold form state there all the time.

How to setState() in React/Apollo with graphQL

I am trying to setState() to a query result I have from graphQL, but I am having difficulty finding out how to do this because it will always be loading, or it's only used from props.
I first set the state
constructor (props) {
super(props);
this.state = { data: [] };
Then I have this query
const AllParams = gql`
query AllParamsQuery {
params {
id,
param,
input
}
}`
And when it comes back I can access it with this.props.AllParamsQuery.params
How and when should I this.setState({ data: this.props.AllParamsQuery.params }) without it returning {data: undefined}?
I haven't found a way to make it wait while it's undefined AKA loading: true then setState. I've tried componentDidMount() and componentWillReceiveProps() including a async function(){...await...} but was unsuccessful, I am likely doing it wrong. Any one know how to do this correctly or have an example?
EDIT + Answer: you should not setstate and just leave it in props. Check out this link: "Why setting props as state in react.js is blasphemy" http://johnnyji.me/react/2015/06/26/why-setting-props-as-state-in-react-is-blasphemy.html
There is more to the problem to update props, but some great examples can be found at this app creation tutorial: https://www.howtographql.com/react-apollo/8-subscriptions/
A simple solution is to separate your Apollo query components and React stateful components. Coming from Redux, it's not unusual to transform incoming props for local component state using mapStateToProps and componentWillReceiveProps.
However, this pattern gets messy with Apollo's <Query />.
So simply create a separate component which fetches data:
...
export class WidgetsContainer extends Component {
render (
<Query query={GET_WIDGETS}>
{({ loading, error, data }) => {
if (loading) return <Loader active inline="centered" />;
const { widgets } = data;
return (
<Widgets widgets={widgets} />
)
}}
</Query>
)
}
And now the Widgets components can now use setState as normal:
...
export class Widgets extends Component {
...
constructor(props) {
super()
const { widgets } = props;
this.state = {
filteredWidgets: widgets
};
}
filterWidget = e => {
// some filtering logic
this.setState({ filteredWidgets });
}
render() {
const { filteredWidgets } = this.state;
return (
<div>
<input type="text" onChange={this.filterWidgets} />
{filteredWidgets.count}
</div>
)
}
}
What is the reason behind setting it to state? Keep in mind, Apollo Client uses an internal redux store to manage queries. If you're trying to trigger a re render based on when something changes in the query, you should be using refetchQueries(). If you absolutely need to store it in local state, I would assume you could probably compare nextProps in componentWillReceiveProps to detect when loading (the value that comes back when you execute a query from apollo client) has changed, then update your state.
I had a similar issue (although it was happening for a totally different reason). My state kept getting set to undefined. I was able to solve it with a React middleware. It made it easy to avoid this issue. I ended up using superagent.
http://www.sohamkamani.com/blog/2016/06/05/redux-apis/

Calling a graphQL query from within react client

I'm trying to return a list of all users transactions, but I can't seem to call the graphQL query correctly. I've defined the query at the bottom and I'm trying to call it in refreshData(). What am I missing?
async refreshData() {
const allTransactions = await graphql(getTransactionsQuery);
console.log(allTransactions);
this.setState({
transactions: allTransactions
});
}
render() {
const transactionsFromState = this.state.transactions;
const transactions = transactionsFromState.map((transaction) =>
<li key={transaction.id}>{transaction}</li>
);
return (
<div className="App">
<button onClick={() => this.refreshData()}> Refresh Data </button>
<ul>{ transactions }</ul>
</div>
);
}
}
const getTransactionsQuery = gql`
query getTransactions($id: ID!) {
transactions(userId: $id){Transaction}
}
`;
//change User ID use context.user.id
export default graphql(getTransactionsQuery)(App);
Using the graphql Higher Order Component
The graphql function you're calling returns a higher order component that can be then used to wrap another React component. It does not return a promise that can be awaited like you're trying to do.
You are already utilizing it (more or less) correctly here:
export default graphql(getTransactionsQuery)(App)
When you wrap your component with the graphql HOC like this, your component receives a data prop that can then be used inside your component's render method. This negates the need to utilize component state to persist your query results. Your render method should look something like this:
render() {
const { transactions } = this.props.data
const transactionItems = transactions
? transactions.map(t => <li key={t.id}>{t}</li>)
: null
return (
<div className="App">
<button onClick={() => this.refreshData()}> Refresh Data </button>
<ul>{ transactionItems }</ul>
</div>
);
}
data.transactions will be initially undefined before the query is fetched, so it's important to be mindful of that inside your render function. You can also check data.loading to determine if the query is still in flight. Please check the Apollo docs (here) for more examples.
Fetching a query with variables
Your query utilizes variables, so those variables need to be sent along with it. You'll need to pass those into the graphql function as options like this:
export default graphql(getTransactionsQuery, { options: { variables: { id: 'youridhere' } } })(App)
Options is normally passed an object, but it can also be passed a function. This function takes props as an argument, so that you can derive your variables from props if needed. For example:
const options = props => ({ variables: { id: props.id } })
export default graphql(getTransactionsQuery, { options })(App)
Check here for more information.
Refetching data
Since Apollo does the heavy lifting for you, there's no need to have a button to fetch the data from your server. But should you need to refetch your data, you can do so through the data prop passed down to your component by calling props.data.refetch(). You can read more about refetch here.

Resources