Querying and Mutating a Form with Apollo Client GraphQL - reactjs

The whole GraphQL paradigm is new to me, usually working with React Redux. My requirements are:
User clicks a Row Item w/ UID
We open a form to edit this data (with previously saved information pre-populated)
We then edit this data and save
I would think (2) & (3) would be handled by a <Query><Mutation/><Query> type of structure but it doesn't work, mainly because setting state in the Query will make the textinputs controlled inputs... controlled by <Query>, and I can no longer edit it.
Besides this, I've read that GraphQL removes the need for Redux, etc? So I'm reluctant to go around sticking this query in a middleware and propogating to Redux.
Any thoughts? This must be a common requirement. What have others came up with?

Your data should be passed down as a prop to whatever component will actually render the form. Inside that component's constructor, you then set the initial state based on the props. A rough example:
class FormComponent extends React.Component {
constructor () {
this.state = this.props.initialState
}
render () {
// Render form using this.state and this.props.update
}
}
<Mutation mutation={SOME_MUTATION}>
{(mutate) => (
<Query query={SOME_QUERY}/>
{({ data, loading, error }) => {
if (loading) return <LoadingIndicator/>
if (error) return <ErrorComponent/>
if (data) return <FormComponent initialValues={data.someQuery} update={mutate}/>
}}
</Query>
)}
</Mutation>
The mutation could go inside the Query, or inside the FormComponent itself -- that bit doesn't really matter. We're not rendering the FormComponent until we have data, so the initial values will always reflect the results of the query.

Related

Different ways to query with react-apollo

We started using the react-apollo#2.1 in one of our react-redux application and we are trying to replace most of the redux stuff with graphql. So we started using the <Query> component and converted some pages to use the <Query> component. But in some pages, we have some existing logic which uses react lifecycle events like componentWillReceiveProps and if we used the <Query> component then those events won't get fired and if we use HOC type querying like below then the lifecycle events get fired and we will get the data in the props
export default graphql(GET_APP_INFO, {
options: ownProps => ({
variables: { appName: ownProps.params.app }
}) })
My understanding is that the component is the latest way to query and HOC is the old way which may get deprecated, also I saw some other way to query with withApollo() like below.
this.props.client.query({
query: gql...,
variables: { ... },
});
So I am looking for suggestions on when to use these different types of querying patterns to get the data
The Query component and the graphql HOC have the same basic functionality, so there shouldn't be a distinction from that perspective. Your example of the Query component not running lifecycle methods, you could move the Query component up one level out of the component, and it would work exactly the same. So something like this:
const CompQueryWrapper = () => {
return (
<Query ...>
{({ data, loading, error }) => <CompWithLifecycle data={data} />}
</Query>
}
You also might be able to move the lifecycle logic down a level. reactions/component can sometimes be handy in this case, when you just want something to trigger on a props change but don't want to go through the ceremony of making a new class component.
So yeah, for your specific situation, my recommendation would be to move the Query up so that it triggers the lifecycle or move the lifecycle logic down so it can be triggered by the Query result.
In general, the two components that I use for making queries are
Query Component
ApolloConsumer (it's basically just a render-prop version of withApollo)
If I can use the Query component, I use the Query component. It works for most situations. The use-case for ApolloConsumer for querying is when you don't want to immediately trigger a query. For example, say you have a form that requires some info from the user and then gets some data based on that input. It's not a mutation, since we're not changing any data, but we don't want it to fire immediately either like the Query component does. In this case, use ApolloConsumer to get the client instance, and fire the query using client.query.
if you want use React-Apollo and run Query Dynamic , for example inside ReactLifeCycleMethod or in any other method ( for some case , when any Event listener trigger ) , you cane use Client for access Query or Mutation
React-Apollo have Consumer , with React Context API
in Top Level of your Application you implement ApolloProvider like this
<ApolloProvider client={client} >
< App />
</ApolloProvider>
and now you have access client
you can create Query with Client
client.query()
or
client.mutate()
and if you want use client in other component you must use ( like react new Context API)
class App extends React.Component {
render() {
return(
<ApolloConsumer >
{ client => {
<MyComponent accessClient={client} />
} }
<ApolloConsumer>
)
}
}
now in you can access client as props
class App extends React.Component {
componentDidMount() {
this.props.accessClient.query( ... )
}
render() {
return(
....
)
}}
https://www.apollographql.com/docs/react/essentials/queries.html#manual-query

Apollo Client and Update Forms

I was wondering if anyone has a good example of an update form using Apollo Client and the new query and mutation components. By update I mean:
Populate form values with query results
Update the component state (or apollo-link-state?) when the input is edited.
On submission update the state (or apollo-link-state?)
I'm struggling with it right now and I'm wondering what the best way to build it would be. Should I be using Apollo-link-state to store the form state? If not, I can't find a way to map the props from the query component into component state object without using the query HOC. Any examples would be great!
Thanks!
If the state doesn't need to be accessed by other parts of your app, apollo-link-state is probably overkill -- regular component state will do just fine. Just create a component like you would when using the HOC:
class MyComponent extends React.Component {
constructor (props) {
super(props)
this.state = {
fieldA = props.myQuery.fieldA
fieldB = props.myQuery.fieldB
}
render () {
// your form fields here
}
}
}
You can then just do:
<Query>
{({data, loading})=>(
if (loading || error) return null
<MyComponent myQuery={data.myQuery}>
)}
</Query>

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/

Make sure a new instance of certain component is used

I have a feeling this is a "wrong" question to ask, but here goes anyway:
I'm making some sort of quiz app (using redux for state management). (showing the important bits here)
quiz.js
<Slider {...sliderSettings} slideIndex={currentQuestionIndex}>
<Start onStart={() => onNextQuestion()} topicId={topicId} />
{
questions.map((question, ndx) => {
return (
<Question {...question} done={onDone} key={`question-${question.id}`} />
);
})
}
<Result score={score} onRestart={() => onRestart()}/>
</Slider>
question.js
<div className="question">
<h2 className="question__text">{ question }</h2>
<MultipleChoice options={answers} onChange={done} />
</div>
multiple-choice.js
const
initialState = {
selectedValue: null
};
class MultipleChoice extends Component {
constructor(props) {
super(props);
this.state = initialState;
}
handleChange(value, correct) {
this.setState({
selectedValue: value
});
this.props.onChange(correct);
}
render() {
const
{ options } = this.props,
getStateClass = (option, ndx) => {
let sc = '';
if (this.state.selectedValue !== null) {
if (this.state.selectedValue === ndx) {
sc = option.correct ? 'is-correct' : 'is-incorrect';
} else if (option.correct) {
sc = 'is-correct';
}
}
return sc;
};
return (
<ul className="multiple-choice">
{ options.map((option, ndx) => {
return (
<li key={`option-${ndx}`} className={cx('multiple-choice__option', getStateClass(option, ndx))}>
<button className="multiple-choice__button" onClick={() => this.handleChange(ndx, option.correct)}>{option.answer}</button>
</li>
);
}) }
</ul>
);
};
}
export default MultipleChoice;
The problem lies within the rendering of MultipleChoice. It uses internal state to show which answer is wrong and right.
in quiz.js, onRestart dispatches a redux action which updates the store to fetch some new questions and reset the currentQuestionIndex to 0. This all works.
But somehow, sometimes the MultipleChoice element is "reused" and is still showing the state it had in the previous round of questions. In other words, most of the time a new MultipleChoice gets mounted, but sometimes it isn't. This is react reconciliation, if I understand correctly?
But how do I solve this problem? In my view, MultipleChoice needs its internal state. So should I reset this state somehow? Or make sure a new MultipleChoice gets mounted everytime? Or am I asking the wrong questions here?
I looked at your repository, and the issue, as correctly noted in another answer is that your <Question> (and thus inner <MultipleChoice>) components never unmount, so they keep their state.
Normally this doesn’t come up often in React because people usually want the state to be preserved while the component is in the tree. When the state is no longer needed, people usually stop including components in the render() method, and React unmounts them. Next time they are rendered, their state gets reset.
The state does not get reset in your example because you always keep the <Question>s visible, even between the quiz runs. You can see that <Question>s are already mounted before we begin the quiz, and stay mounted after it ends:
So how can we force React to reset their state? There are three options:
You may cause them to unmount. Next time they get mounted, they’ll have a new state. This is usually the simplest solution, because you don’t actually display the questions on the initial “start quiz” page. For example, you can do this by adding currentQuestionIndex > 0 && guard before rendering questions.map(...) in the render() method of <Questions>.
You may pass new keys to them that don’t match previous keys. You are currently using question-${question.id} as the key right now but that will produce the same key for the same question even if you retry the quiz. To solve this, you could introduce a new state variable (either in Redux or in top-level component state), for example, quizAttemptIndex, and increment it on any new attempt. Then you could change the key to be question-${quizAttemptIndex}-${question.id}. This way attempting a quiz another time would reset the internal state of the question (as well as cause it to remount).
Finally, if you’d rather not destroy the DOM completely by passing a different key, you could pass quizAttemptIndex (explained in the previous section) as a prop to <MultipleChoice>. Then, inside it, you could this.setState({ selectedValue: null }) inside componentWillReceiveProps(nextProps) if nextProps.quizAttemptIndex !== this.props.quizAttemptIndex.
You can choose either solution depending on how important it is for you to keep the questions mounted all the time.
As far as I understood, you want your MultipleChoice component to refresh its state when you want it to do so. But as you are using react state in your component, as long as your component doesn't unmount, your react state in your MultipleChoice keeps its latest state.
This behaviour is expected from react state, because mostly you want to use it for internal behaviour. Maybe you want to toggle some ui data in your component, when some button or something triggers your component to do so. Or you want to control your input forms etc.
But what you expect should be accomplished in redux state. Your component should be reusable which doesn't care where it mounted. You pass your mounted-place-related data to your MultipleChoice with props, which is taken from redux. So now you have a reusable component. Don't spend much time about mounting etc. You might check react-redux repositories on github to get familiar with how to shape your project's data flow. When to use redux state to make desicions or handle state in your component by react state.

Prevent react component from rendering twice when using redux with componentWillMount

I have a React component that dispatches a redux state change in its componentWillMount function. The reason is that when the component is loaded, it needs to get the id from the url (powered by react-router), and trigger an action that sets up the state with that id's data.
Here is the component:
class Editor extends React.Component {
componentWillMount() {
const { dispatch, params } = this.props
dispatch(editItem(params.id))
}
render() {
const item = this.props.item
console.log("Editing", item)
}
}
export default connect(state => ({item: state.item}))(Editor)
Here's the catch: render is getting called twice. item is undefined on the first call, and valid on the second. Ideally, it should only be called once this.props.item actually exists (after the editItem action has been dispatched and run).
According to the React docs: "If you call setState within this method, render() will see the updated state and will be executed only once despite the state change."
In redux, dispatch is the equivalent of calling setState, as it results in a state change. However, I'm guessing something in the way connect works is still causing render to be called twice.
Is there a way around this besides adding a line like if (!item) return; ?
One thing you might do is create a higher order component that handles the basic pattern of loading a different component (or no component) before the required props are loaded.
export const LoaderWrapper = function(hasLoaded, Component, LoaderComponent, onLoad) {
return props => {
if (hasLoaded(props)) {
return <Component {...props} />
}
else {
if (onLoad) onLoad(props)
return { LoaderComponent ? <LoaderComponent /> : null }
}
}
}
Then you can wrap your component before connecting it to get the desired behaviour.
export default connect(state => ({item: state.item}))(LoaderWrapper(
((props) => !!props.item),
Editor,
null,
(props) => props.dispatch(editItem(props.params.id))
))
You might want to add some currying magic to make sure you can compose these kinds of wrapper functions more nicely. Take a look at recompose for more info.
It looks like there's already an issue in the react-redux library.
https://github.com/rackt/react-redux/issues/210
What does editItem do? Does it add item to the redux state or is it there already?
If it is adding I imagine what is happening is that a render cycle happens with the current props, ie item being blank.
Then it gets rendered again when the props have changed, via setting the item.
One approach to fixing this sort of thing is to create a higher order component that wraps Editor and calls the dispatch action the rendering though is set either to a loading screen or and empty div until item is set. That way you can be assured that Editor will have an item.
But without knowing what editItem does it's sort of hard to know. Maybe you could paste the code for that?

Resources