React: Apollo Client: Cannot update during an existing state transition - reactjs

I am trying to make a component that wraps Apollo Client's Query component. I am using apollo-link-state for local state management and I want to have an error notification system that notifies the user of all the things.
my component looks like this...
export class Viewer extends React.Component {
static propTypes = {
children: PropTypes.func
};
render() {
const { children } = this.props;
return (
<Query query={GET_VIEWER}>
{({ data, client, error }) => {
if (error) {
client.mutate({
mutation: ADD_NOTIFICATION,
variables: { message: unpackApolloErr(error), type: 'Error' }
});
}
return children(data.viewer ? data.viewer : user);
}}
</Query>
);
}
}
but when it tries to add the error with the mutation, I get the react error..
Warning: forceUpdate(...): Cannot update during an existing state transition (such as within `render` or another component's constructor). Render methods should be a pure function of props and state; constructor side-effects are an anti-pattern, but can be moved to `componentWillMount`.
I don't see an obvious way around this and I don't see why it is even happening if the client is provided as a render prop and cannot be used...I must be missing something simple but I cant see what it is

The answer turned out to be that the client is available in the onError function as this when using apollo-link-error https://www.apollographql.com/docs/link/links/error.html
I had tried to use onError before to access the client, but it was not obvious that the scope which it is called in does indeed contain the client even if the client is declared after the onError handler
// client is available here even though it is defined first
const err = onError({ graphQLErrors }) => {
this.client.mutate({ mutation: ADD_ERROR, variables: { graphQLErrors }})
}
const client = new ApolloClient({
cache,
link: ApolloLink.from([err, httpLink])
})

The reason why there was a Warning: forceUpdate was probably because Apollo internally will cause a forceUpdate when there is a mutation. So mutations should be in the render method.
The best way to handle mutations after an Apollo error is to add an onError link as described in https://www.apollographql.com/docs/react/features/error-handling.html#network
// Add errorLink into Apollo Client
const errorLink = onError(({ graphQLErrors, networkError, operation }) => {
this.client.mutate({ mutation: ..., variables: {}});
});
const client = new ApolloClient({
cache,
link: ApolloLink.from([err, httpLink])
})

Related

Prevent Apollo from re-fetching if state changes in the Provider

In our React application we store the currentUser in a state object that can change whenever the token is refreshed and is then passed in the header for API requests.
However because state changes in React cause the component tree to re-render it seems this also causes Apollo to re-fetch queries (even though these don't run on render but explicitly called via events (the events themselves are not re-called)).
So our Provider looks like this:
// ...rest of code removed for brevity...
// currentUser is a state object in a parent Context
const { currentUser } = useContext(AuthContext)
// ...rest of code removed for brevity...
const authLink = setContext((_, { headers }) => {
return {
headers: {
...headers,
authorization: currentUser
? `Bearer ${currentUser.authenticationToken}`
: ''
}
}
})
const client = useMemo(
() =>
new ApolloClient({
link: from([authLink, errorLink, httpLink]),
cache: new InMemoryCache({
dataIdFromObject: () => uuid.v4()
})
}),
[authLink, errorLink, httpLink]
)
return <ApolloProvider client={client}>{children}</ApolloProvider>
}
export default CustomApolloProvider
And then in some lower level component:
// This query will run after every currentUser change
// AFTER the first time this query is run via the click event
const [
loadData,
{ loading: loadingData, error: errorData }
] = useLazyQuery(DATA_QUERY, {
onCompleted: (data) => {
console.log('data', data)
}
})
<Button onClick={() => loadData()}>Load Data</Button>
So after clicking that Button to load some data, and then making the currentUser update, that query will then re-fetch... even though the query should ONLY be firing on event click...
Our current solution is to drop any use of 'states' inside this component and instead pull values directly from either a service or from localStorage for that header, so there's no chance of a re-fetch on the state change which works...
But this seems really flaky... is there a way to stop Apollo re-fetching these queries if a state changes inside its Provider?

Redux toolkit RTK query mutation not getting returning data

Hi I recently learned the new react toolkit with the rtk query tool, and I am trying to put in a login system together using the createApi from the rtk package.
After giving it a test on the login button pressed, I see the network request going through without any issue(status code 200), and I get a response object providing user, token, however, when I try to get the returning data using useLoginMutation I get an undefined value.
below is the code for my endpoint which is injected in a base api:
export const apiLogin = theiaBaseApi.injectEndpoints({
endpoints: (build) => ({
loginUser: build.mutation<UserReadonly, loginValuesType | string>({
query: (values: loginValuesType, redirect?: string) => {
const { username, password } = values;
const header = gettingSomeHeaderHere
return {
url: "login",
method: "GET",
headers,
crossDomain: true,
responseType: "json",
};
},
}),
}),
});
export const { useLoginUserMutation } = apiLogin
then inside my React component I destructure the mutation result such like below:
const [login, {data, isLoading}] = useLoginUserMutation();
const submitLogin = () => {
// pass in username password from the form
login({username, password});
}
Suppose if I console log out data and isLoading I assume that I will see data: {user: "abc", token: "xyz"}, because under network tab of my inspect window I can see the response of this network request, but instead I am seeing data: undefined
Does any have experience on solving this?
Oh I found the reason, it was a very careless mistake. I had to wrap the reducer to my store, which was what I was missing
In my case the issue was that I was trying to access the UseMutationResult object inside onClick callback. And the object was not updating inside the callback, even though in the component the values were accurate.
If I put the log outside it's working just fine.
here is an example for better understanding (inside handleAddPost the mutationResult is not updating)
Here is a code sample (in case link is not working):
const Component = () => {
const [addPost, mutationResult] = useAddPostMutation();
...
const handleAddPost = async () => {
...
console.log("INSIDE CALLBACK isLoading and other data is not updating:");
console.log(JSON.parse(JSON.stringify(mutationResult)))
...
};
// in the example this is wrapped in an useEffect to limit the number of logs
console.log(mutationResult.data,"OUTSIDE CALLBACK isLoading and other data is working:")
console.log(JSON.parse(JSON.stringify(mutationResult)))
return (
...
<Button
...
onClick={handleAddPost}
>
Add Post
</Button>
...

How can I maintain state and "this" with react component functions

Currently, when socket.io emits a 'gmessage' from my server, and the socket in my component catches it, my entire state is replaced.
So the current flow is like this:
I emit a message from the component, it goes to my server, then to the watson API. The API sends a response to my server, and the server sends that to the component.
So that first message creates the connection to socket.io, then the second socket.io event is caught on the same connection.
Is there a better way to set up the connection to socket.io and handle both the emit and the "on gmessage" parts of this? Thank you for any tips in advance. I'm still new to react, so anything you see that I should do differently is helpful!!
...
import {Launcher} from 'react-chat-window'
import io from 'socket.io-client';
import { getUser } from '../actions/userActions';
class Workshop extends Component {
constructor() {
super();
this.state = {
messageList: []
};
}
_onMessageWasSent(message) {
this.setState({
messageList: [message, ...this.state.messageList]
})
var socket = io.connect('http://localhost:5000');
socket.emit('message', { message });
var myThis = this
var myState = this.state
socket.on('gmesssage', function (data) {
console.log(data);
myThis.setState({
messageList: [{
author: 'them',
type: 'text',
data: data
}, ...myState.messageList]
})
})
}
render() {
return (
<Grid container className="DashboardPage" justify="center">
<Grid item xs={12}>
<div>Welcome to your Workshop</div>
<TeamsTaskLists />
</Grid>
<Launcher
agentProfile={{
teamName: 'Geppetto',
imageUrl: 'https://geppetto.ai/wp-content/uploads/2019/03/geppetto-chat-avi.png'
}}
onMessageWasSent={this._onMessageWasSent.bind(this)}
messageList={this.state.messageList}
showEmoji
/>
</Grid>
);
}
}
const mapStatetoProps = state => ({
user: state.user
});
export default connect(
mapStatetoProps,
{ getUser }
)(Workshop);
Fareed has provided a working Redux solution and suggested improvements for a non-Redux approach, so I'd like to address the issues in your current code:
You're reinitializing the socket variable and creating its listener every time a new message is received instead of configuring and initializing the socket object just once inside a separate file and then consuming it by your Workshop component and possibly other components across your project.
You're appending the newly received message by calling setState({ messages: [...] }) twice in _onMessageWasSent.
To solve the first issue, you can create a separate file and move your socket related imports and initialization to it like:
// socket-helper.js
import io from 'socket.io-client';
export const socket = io.connect('http://localhost:5000');
Bear in mind, this is a minimal configuration, you can tweak your socket object before exporting it as much as the socket.io API allows you to.
Then, inside the Workshop component's componentDidMount subscribe to this socket object like:
import { socket } from 'path/to/socket-helper.js';
class Workshop extends Component {
constructor(props) {
super(props);
...
}
componentDidMount() {
// we subscribe to the imported socket object just once
// by using arrow functions, no need to assign this to myThis
socket.on('gmesssage', (data) => {
this._onMessageWasSent(data);
})
}
...
}
React docs say
If you need to load data from a remote endpoint, this is a good place to instantiate the network request.
componentDidMount will run only once, so your listener will not get redefined multiple times.
To solve the second issue, _onMessageWasSent handler function should only receive the new message and append it to previous messages using setState, like this:
_onMessageWasSent(data) {
this.setState(previousState => ({
messageList: [
{
author: 'them',
type: 'text',
data: data,
},
...previousState.messageList,
]
}))
}
Since you are already using redux it's better to make socket.io dispatch redux actions for you, you can use redux saga same as this article describes or you can use this implementation without saga but you need to have redux-thunk middleware :
1- Create a file lets say lib/socket.js
then inside this file create socket.io client lets call it socket and create initSocketIO function where you can fire any redux actions in response to socket.io:
import socket_io from "socket.io-client";
// our socket.io client
export const socket = socket_io("http://localhost:5000");
/**
* init socket.io redux action
* #return {Function}
*/
export const initSocketIO = () => (dispatch, getState) => {
// connect
socket.on('connect', () => dispatch(appActions.socketConnected()));
// disconnect
socket.on('disconnect', () => dispatch(appActions.socketDisconnected()));
// message
socket.on('message', message => {
dispatch(conversationActions.addOrUpdateMessage(message.conversationId, message.id, message));
socket.emit("message-read", {conversationId: message.conversationId});
});
// message
socket.on('notifications', ({totalUnread, notification}) => {
dispatch(recentActions.addOrUpdateNotification(notification));
dispatch(recentActions.setTotalUnreadNotifications(totalUnread));
});
};
2- then inside your App component call this action once your App is mounted:
import React, {Component} from "react";
import {initSocketIO} from "./lib/socket.js";
export class App extends Component {
constructor(props) {
super(props);
this.store = configureStore();
}
componentDidMount() {
// init socket io
this.store.dispatch(initSocketIO());
}
// ... whatever
}
3- now you can also fire any socket.io action from any component using this:
import React, {Component} from "react";
import {socket} from "./lib/socket.js"
MyComponent extends Components {
myMethod = () => socket.emit("message",{text: "message"});
}
or from any redux action using thunk for example:
export const sendMessageAction = (text) => dispatch => {
socket.emit("my message",{text});
dispatch({type: "NEW_MESSAGE_SENT",payload:{text}});
}
Notes:
1-This will perfectly work, but still, I prefer the redux-saga method for managing any side effects like API and socket.io.
2-In your components, you don't need to bind your component methods to this just use arrow functions for example:
myMethod = () => {
// you can use "this" here
}
render = (){
return <Launcher onMessageWasSent={this.myMethod} />
}
Depending on your application, I think Redux would be overkill for this.
I would change to a lambda expression here though to avoid having to save this references:
socket.on('gmesssage', data => {
console.log(data);
this.setState({
messageList: [{
author: 'them',
type: 'text',
data: data
}, ...this.messageList]
})
})
You might consider using the new React Hooks also e.g. turn it into a stateless component and use useState:
const Workshop = () => {
const [messageList, setMessageList] = useState([]);
...

How to properly hook up Apollo Client to Redux in React Native

I've been researching this for the past couple hours, and I am now extremely close to solving this. Everything seems to be working now, but I do not have this.props.mutate in my component that has the Apollo HOC wrapping it.
I wired Apollo up as a reducer, so in my component, I would expect to see this.props.apollo.mutate available, but it's not there.
This is all that seems to be provided currently:
console.log(this.props.apollo)
{"data":{},"optimistic":[],"reducerError":null}
Here is how it is hooked up:
./src/apolloClient.js
import { AsyncStorage } from 'react-native'
import { ApolloClient, createNetworkInterface } from 'react-apollo'
const networkInterface = createNetworkInterface({
uri: 'http://localhost:8000/graphql',
opts: {
credentials: 'same-origin',
mode: 'cors'
}
})
// networkInterface is half baked as I haven't got this far yet
networkInterface.use([{
async applyMiddleware(req, next) {
if (!req.options.headers) req.options.headers = {}
const token = await AsyncStorage.getItem('token')
req.options.headers.authorization = token ? token : null
next()
}
}])
const client = new ApolloClient({
networkInterface,
dataIdFromObject: (o) => o.id
})
export default client
./src/reducers.js
import client from './apolloClient'
export default combineReducers({
apollo: client.reducer(),
nav: navReducer,
signup: signupReducer,
auth: loginReducer,
})
./src/App.js
import store from './store'
import client from './apolloClient'
const Root = () => {
return (
<ApolloProvider store={store} client={client}>
<RootNavigationStack />
</ApolloProvider>
)
}
export default Root
Oh, and here's the bottom of my Login component (also fairly half-baked):
export default compose(
connect(mapStateToProps, {
initiateLogin,
toggleRememberMe
}),
withFormik({
validate,
validateOnBlur: true,
validateOnChange: true,
handleSubmit: ({ tel, password }, { props }) => {
props.apollo.mutate({
variables: { tel, password }
})
.then((res) => {
alert(JSON.stringify(res))
//const token = res.data.login_mutation.token
//this.props.signinUser(token)
//client.resetStore()
})
.catch((err) => {
alert(JSON.stringify(err))
//this.props.authError(err)
})
//props.initiateLogin({ tel, password })
}
}),
graphql(LOGIN_MUTATION, { options: { fetchPolicy: 'network-only' } })
)(LoginForm)
It feels like I need an action creator and to manually map it to my component. What do I need to do to run this loosely shown mutation LOGIN_MUTATION onSubmit?
I'm currently confused by the fact this.props.apollo has Apollo's data in it, but there is no mutate.
I don't see the solution here: http://dev.apollodata.com/react/mutations.html or maybe I do -- is this what I need to be looking at?
const NewEntryWithData = graphql(submitRepository, {
props: ({ mutate }) => ({
submit: (repoFullName) => mutate({ variables: { repoFullName } }),
}),
})(NewEntry)
I'd like to get it to the point where the component can call the mutation when it needs to. I'd also like it to be available on this.props.something so I can call it from Formik's handleSubmit function, but I am open to suggestions that enable the best declarative scalability.
[edit] Here is the code that I am considering solved:
./src/apolloClient.js
This file was scrapped.
./src/reducers.js
I removed the Apollo reducer and client reference.
./src/App.js
I put the Apollo Client inside the root component. I got this technique from Nader Dabit's Medium post. He illustrates this in a GitHub repo:
https://github.com/react-native-training/apollo-graphql-mongodb-react-native
https://medium.com/react-native-training/react-native-with-apollo-part-2-apollo-client-8b4ad4915cf5
Here is how it looks implemented:
const Root = () => {
const networkInterface = createNetworkInterface({
uri: 'http://localhost:8000/graphql',
opts: {
credentials: 'same-origin',
mode: 'cors'
}
})
networkInterface.use([{
async applyMiddleware(req, next) {
try {
if (!req.options.headers) req.options.headers = {}
const token = await AsyncStorage.getItem('token')
req.options.headers.authorization = token || null
next()
} catch (error) {
next()
}
}
}])
const client = new ApolloClient({
networkInterface,
dataIdFromObject: (o) => o.id
})
return (
<ApolloProvider store={store} client={client}>
<RootNavigationStack />
</ApolloProvider>
)
}
export default Root
When you use compose, the order of your HOCs matters. In your code, the props added by your first HOC (connect) are available to all the HOCs after it (withFormik and graphql). The props added by withFormik are only available to graphql. The props added by graphql are available to neither of the other two HOCs (just the component itself).
If you rearrange the order to be compose -> graphql -> withFormik then you should have access to props.mutate inside withFormik.
Additionally, while you can integrate Redux and Apollo, all this does is prevent you from having two separate stores. Passing an existing store to Apollo is not going to change the API for the graphql HOC. That means, regardless of what store you're using, when you correctly use the HOC, you will still get a data prop (or a mutate prop for mutations).
While integrating Apollo with Redux does expose Apollo's store to your application, you should still use Apollo like normal. In most cases, that means using the graphql HOC and utilizing data.props and data.mutate (or whatever you call those props if you pass in a name through options).
If you need to call the Apollo client directly, then use withApollo instead -- this exposes a client prop that you can then use. The apollo prop that connect exposes in your code is just the store used by Apollo -- it's not the actual client, so it will not have methods like mutate available to it. In most cases, though, there's no reason to go with withApollo over graphql.

Need individual entry with Redux and Router

I'm using ReactJS, Redux (with server-side rendering) and react-router-redux as set up here and am getting a little thrown by how routes work with the rest of the redux state and actions.
For example, I have a members component with the route /members:
class Members extends Component {
static need = [
fetchMembers
]
render() {
...
the static need array specifies an action that populates an array on the state that is then mapped to the component props. That much works.
But then I have an individual member component with the route members/:memberId. How do I load that individual member in a way that works both client- and server-side.
What I'm doing now is the same:
class Member extends Component {
static need = [
fetchMembers
]
render() {
...
but then map just the single member
function mapStateToProps(state, ownProps) {
return {
member: state.member.members.find(member => member.id == ownProps.params.memberId),
};
}
This works but is obviously wrong. So the question is two-fold:
When the user clicks the router Link that has a query param (:memberId), how do I use that router param to query a specific document (assume a mongo database). Do I somehow trigger a separate action that populates an active member field on the redux state? Where does this happen, in the route component's componentDidMount?
How does this work with server-side rendering?
I’ve had the same question and seemed to find a way that works pretty well with my setup. I use Node, Express, React, React Router, Redux and Redux Thunk.
1) It really depends on where your data is. If the data needed for /member/:memberId is already in state (e.g. from an earlier call) you could theoretically filter through what you already have when componentDidMount is fired.
However, I'd prefer to keep things separate simply to avoid headaches. Starting to use one data source for multiple destinations/purposes throughout your app might give you long days down the road (e.g. when Component A needs more/less properties about the member than Component B or when Component A needs properties in a different format than Component B etc.).
This decision should of course be based on your use-case but due to the cost of API calls nowadays I wouldn't be afraid (at all) to make one when someone navigates to /member/:memberId.
2) I’ll answer with a simplified version of my typical setup:
Whenever a request comes through, I have this fella handle it.
// Imports and other jazz up here
app.use((req, res) => {
const store = configureStore({});
const routes = createRoutes(store);
match({ routes, location: req.url }, (error, redirectLocation, renderProps) => {
if (error) {
res.status(500).send(error.message);
} else if (redirectLocation) {
res.redirect(302, redirectLocation.pathname + redirectLocation.search);
} else if (renderProps) {
const fetchedData = renderProps.components
.filter(component => component.fetchData)
.map(component => component.fetchData(store, renderProps.params));
Promise.all(fetchedData).then(() => {
const body = renderToString(
<Provider store={store}>
<RouterContext {...renderProps} />
</Provider>
);
res.status(200).send(`<!doctype html>${renderToStaticMarkup(
<Html
body={body}
state={store.getState()}
/>)
}`);
});
} else {
res.status(404).send('Not found');
}
});
});
It’ll look for fetchData on the components that are about to be rendered, and make sure we have the data before we send anything to the client.
On each and every route, I have a Container. The Container’s sole purpose is to gather the data needed for that route. As you’ve touched upon this can happen server-side (fetchData in my case) or client-side (componentDidMount in my case). A typical Container of mine looks like this:
// Imports up here
class Container extends Component {
static fetchData(store, params) {
const categories = store.dispatch(getCategories());
return Promise.all([categories]);
}
componentDidMount() {
this.props.dispatch(getCategoriesIfNeeded());
}
render() {
return this.props.categories.length ? (
// Render categories
) : null;
}
}
Container.propTypes = {
categories: PropTypes.array.isRequired,
dispatch: PropTypes.func.isRequired,
params: PropTypes.object.isRequired,
};
function mapStateToProps(state) {
return {
categories: state.categories,
};
}
export default connect(mapStateToProps)(Container);
In the Container above I’m using getCategories and getCategoriesIfNeeded to make sure that I have the data needed for the route. getCategories is only called server-side, and getCategoriesIfNeeded is only called client-side.
Note that I have params available for both fetchData and componentDidMount (passed from connect()), which I could potentially use to extract something like :memberId.
The two functions used to fetch data above are listed below:
// Using this for structure of reducers etc.:
// https://github.com/erikras/ducks-modular-redux
//
// actionTypes object and reducer up here
export function getCategories() {
return (dispatch, getState) => {
dispatch({
type: actionTypes.GET_REQUEST,
});
return fetch('/api/categories').then(res => {
return !res.error ? dispatch({
error: null,
payload: res.body,
type: actionTypes.GET_COMPLETE,
}) : dispatch({
error: res.error,
payload: null,
type: actionTypes.GET_ERROR,
});
});
};
}
export function getCategoriesIfNeeded() {
return (dispatch, getState) => {
return getState().categories.length ? dispatch(getCategories()) : Promise.resolve();
};
}
As displayed above I have both dispatch and getState available thanks to Redux Thunk - that handles my promises too - which gives me freedom use the data I already have, request new data and do multiple updates of my reducer.
I hope this was enough to get you moving. If not don't hesitate to ask for further explanation :)
The answer, it turns out, was pretty simple. The implementation taken from Isomorphic Redux App ties the need static property on a component back to the router by passing the routes query params into the action creator.
So for the route:
items/:id
you'd use a component like
class Item extends Component {
static need = [
fetchItem
]
render() {
specifying that it needs the fetchItem action. That action is passed the route's query params, which you can use like
export function fetchItem({id}) {
let req = ...
return {
type: types.GET_ITEM,
promise: req
};
}
For a more detailed explanation about why this work, read marcfalk's answers, which describes a very similar approach.

Resources