I'm using nextjs and react-apollo (with hooks). I am trying to update the user object in the apollo cache after a mutation (I don't want to refetch). What is happening is that the user seems to be getting updated in the cache just fine but the user object that the component uses is not getting updated. Here is the relevant code:
The page:
// pages/index.js
...
const Page = ({ user }) => {
return <MyPage user={user} />;
};
Page.getInitialProps = async (context) => {
const { apolloClient } = context;
const user = await apolloClient.query({ query: GetUser }).then(({ data: { user } }) => user);
return { user };
};
export default Page;
And the component:
// components/MyPage.jsx
...
export default ({ user }) => {
const [toggleActive] = useMutation(ToggleActive, {
variables: { id: user.id },
update: proxy => {
const currentData = proxy.readQuery({ query: GetUser });
if (!currentData || !currentData.user) {
return;
}
console.log('user active in update:', currentData.user.isActive);
proxy.writeQuery({
query: GetUser,
data: {
...currentData,
user: {
...currentData.user,
isActive: !currentData.user.isActive
}
}
});
}
});
console.log('user active status:', user.isActive);
return <button onClick={toggleActive}>Toggle active</button>;
};
When I continuously press the button, the console log in the update function shows the user active status as flipping back and forth, so it seems that the apollo cache is getting updated properly. However, the console log in the component always shows the same status value.
I don't see this problem happening with any other apollo cache updates that I'm doing where the data object that the component uses is acquired in the component using the useQuery hook (i.e. not from a query in getInitialProps).
Note that my ssr setup for apollo is very similar to the official nextjs example: https://github.com/zeit/next.js/tree/canary/examples/with-apollo
The issue is that you're calling the client's query method. This method simply makes a request to the server and returns a Promise that resolves to the response. So getInitialProps is called before the page is rendered, query is called, the Promise resolves and you pass the resulting user object down to your page component as a prop. An update to your cache will not trigger getInitialProps to be ran again (although I believe navigating away and navigating back should), so the user prop will never change after the initial render.
If you want to subscribe to changes in your cache, instead of using the query method and getInitialProps, you should use the useQuery hook. You could also use the Query component or the graphql HOC to the same effect, although both of these are now deprecated in favor of the new hooks API.
export default () => {
const { data: { user } = {} } = useQuery(GetUser)
const [toggleActive] = useMutation(ToggleActive, { ... })
...
})
The getDataFromTree method (combined with setting the initial cache state) used in the boilerplate code ensures that any queries fetched for your page with the useQuery hook are ran before the page render, added to your cache and used for the actual server-side rendering.
useQuery utilizes the client's watchQuery method to create an observable which updates on changes to the cache. As a result, after the component is initially rendered server-side, any changes to the cache on the client-side will trigger a rerender of the component.
My problem is simple to understand.
I have an userlist in my state. Filled by a DataBase call.
And I also have a selecteduser filled when a element in the userlist is clicked.
When this element from userlist is clicked, a modal is opening with input with a default value filled by selecteduser.
I assigned onChange function to those input, then each change is saved in selecteduser.
But the problem is, each change is also saved in userlist and I really don't understand why.
This userlist doesn't have any setState in the code except for the data call from the database.
class Users extends Component {
constructor(props) {
super(props);
this.state = {
userlist: [],
selecteduser: [],
IsModalUserVisible: false
};
}
Then call and store data in userlist.
componentWillMount() {
db.collection("user")
.get()
.then(collection => {
this.setState({
userlist: collection.docs.map(doc => doc.data())
});
});
}
Here is the onChange from the input
handleChangeEmail(event) {
event.preventDefault();
const selectedUserUpdate = this.state.selecteduser;
selectedUserUpdate.email = event.target.value;
this.setState({
selecteduser: selectedUserUpdate
});
}
And here the function called onClick on a userlist element.
UserSelected(user) {
this.setState({
selecteduser: user,
IsModalUserVisible: true
});
}
The userlist.map to show the user list.
{this.state.userlist.map(user => {
return (
<UserItem
callback={() => this.UserSelected(user)}
key={user.email}
email={user.email}
/>
);
})}
And for finish, the user manage modal, opened on click to an user list element.
<ManageUserItem
isOpen={this.state.IsModalUserVisible}
email={this.state.selecteduser.email}
changeEmail={this.handleChangeEmail.bind(this)}
/>
So, when I change the email inside the input, I can see on background that the list also change. And I checked with a console.log(this.state.userlist) in the handleChangeEmail, I can see the userlist is updated too.
I wanted to be clear, but i hope it's not to long to read.
Thanks in advance :)
userlist: collection.docs.map(doc => doc.data()) creates a Javascript Array that at each index holds a memory reference point to the data returned by doc.data(). When you initialize the <UserItem> component, you are providing this component with the same memory reference in the userlist state Array. When a user is clicked, that same reference is passed through the callback prop method UserSelected and in that method the same memory reference is assigned to the state variable selecteduser. Later on when the email change is handled for that selected user, the method handleChangeEmail is operating on a memory reference stored in two places, the userlist Array and the selecteduser Object. When you update an attribute of an Object reference, any other place that Object is referenced will show such a mutation because they are pointing to the same underlying data. One minor alteration that I would suggest for this code is in the constructor method, initialize selecteduser to be an Object ({}) not an Array ([]) since selecteduser is eventually assigned an Object anyway and not an array. Finally, if you'd prefer that selecteduser not be the same Object reference as what is referenced in the userlist Array, you may create a new Object (and thus a new Object reference) from each Object referenced in the userlist Array. Like so:
{this.state.userlist.map(user => {
// this is one way to do it, you can find others elsewhere on SO for more complex cases.
// Look into, Javascript as a pass by reference language versus pass by value languages
// and their nuances.
const newObject = JSON.parse(JSON.stringify(user))
return (
<UserItem
callback={() => this.UserSelected(newObject)}
key={newObject.email}
email={newObject.email}
/>
);
})}
I have this code: https://codesandbox.io/s/507w9qxrrl
I don't understand:
1) How to re-render() Menu component after:
this.props.client.query({
query: CURRENT_USER_QUERY,
fetchPolicy: "network-only"
});
If I login() I expect my Menu component to re-render() itself. But nothing.
Only if I click on the Home link it re-render() itself. I suspect because I'm using this to render it:
<Route component={Menu} />
for embrace it in react-router props. Is it wrong?
Plus, if inspect this.props of Menu after login() I see loading: true forever. Why?
2) How to prevent Menu component to query if not authenticated (eg: there isn't a token in localStorage); I'm using in Menu component this code:
export default graphql(CURRENT_USER_QUERY)(Menu);
3) Is this the right way to go?
First, let me answer your second question: You can skip an operation using the skip query option.
export default graphql(CURRENT_USER_QUERY, {
skip: () => !localStorage.get("auth_token"),
})(Menu);
The problem now is how to re-render this component when the local storage changes. Usually react does not listen on the local storage to trigger a re-render, instead a re-render is done using one of this three methods:
The component's state changes
The parent of the component re-renders (usually with new props for the child)
forceUpdate is called on the component
(Note that also a change of a subscribed context will trigger a re-render but we don't want to mess around with context ;-))
You might want to go with a combination of 2. and 3. or 1. and 2.
In your case it can be enough to change the route (as you do in the login function). If this does not work you can call this.forceUpdate() on the App component after Login using a callback property on <Login onLogin={() => this.forceUpdate() } />.
EDITED
1) I just created new link https://codesandbox.io/s/0y3jl8znw
You want to fetch the user data in the componentWillReceiveProps method:
componentWillReceiveProps() {
this.props.client.query({query: CURRENT_USER_QUERY})
.then((response) => {
this.setState({ currentUser: response.data.currentUser})
})
.catch((e) => {
console.log('there was an error ', e)
})
}
This will make the component re-render.
2) Now when we moved the query call in the component's lifecycle method we have full control over it. If you want to call the query only if you have something in localstorage you just need to wrap the query in a simple condition:
componentWillReceiveProps() {
if(localstora.getItem('auth_token')) {
this.props.client.query({query: CURRENT_USER_QUERY})
.then((response) => {
this.setState({ currentUser: response.data.currentUser})
})
.catch((e) => {
console.log('there was an error ', e)
})
}
}
3) You want to store the global application state in redux store. Otherwise you will have to fetch the user info every time you need to work with it. I would recommend to define a state in you App component and store all the global values there.
I have a simple react app, I get a list of contacts from a web api and i want to display them. Each contact has a name, last name, phone etc
My app class gets the contacts then renders as
render(){
var contact= this.state.contacts[0];
return( <div><Contact label='First Contact' person={contact}/></div>
)
}
Then in my Contact class
render(){
return(
<div> {this.props.label} : {this.props.person.Name}</div>)
}
When I debug on chrome, I see that person is passed as prop, object has all the parameters, however when I run the code, on the {this.props.person.Name} I get error
Cannot read property Name of undefined
If I remove that, {this.props.label} is displayed without issue. So I can pass text as prop but not the object.
Any idea?
Edit: I can also pass the Name property as
Contact personName= {contact.Name}/>
This works but not passing the contact as object and then reading properties in the render of
my guess is (since you're using flux) that upon loading the page (initial load) the contacts on your state represents an empty array.
Try
var contact= this.state.contacts[0] || {};
and for some good tips => don't use vars, use const :-)
let your component listen to your flux store:
Make sure your flux store holds an addChangeListener and removeChangeListener function that you can call in your component so your component gets updated automatically
componentDidMount(){
myStore.addChangeListener(this._onChange);
}
componentWillUnmount(){
myStore.removeChangeListener(this._onChange);
}
_onChange = () => {
this.setState({contacts: myStore.getContacts()});
}
I have recently moved from Angular to ReactJs. I am using jQuery for API calls. I have an API which returns a random user list that is to be printed in a list.
I am not sure how to write my API calls. What is best practice for this?
I tried the following but I am not getting any output. I am open to implementing alternative API libraries if necessary.
Below is my code:
import React from 'react';
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {
person: []
};
}
UserList(){
return $.getJSON('https://randomuser.me/api/')
.then(function(data) {
return data.results;
});
}
render() {
this.UserList().then(function(res){
this.state = {person: res};
});
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">
{this.state.person.map((item, i) =>{
return(
<h1>{item.name.first}</h1>
<span>{item.cell}, {item.email}</span>
)
})}
<div>
</div>
)
}
}
In this case, you can do ajax call inside componentDidMount, and then update state
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
this.UserList();
}
UserList() {
$.getJSON('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
render() {
const persons = this.state.person.map((item, i) => (
<div>
<h1>{ item.name.first }</h1>
<span>{ item.cell }, { item.email }</span>
</div>
));
return (
<div id="layout-content" className="layout-content-wrapper">
<div className="panel-list">{ persons }</div>
</div>
);
}
}
You may want to check out the Flux Architecture. I also recommend checking out React-Redux Implementation. Put your api calls in your actions. It is much more cleaner than putting it all in the component.
Actions are sort of helper methods that you can call to change your application state or do api calls.
Use fetch method inside componentDidMount to update state:
componentDidMount(){
fetch('https://randomuser.me/api/')
.then(({ results }) => this.setState({ person: results }));
}
This discussion has been for a while and #Alexander T.'s answer provided a good guide to follow for newer of React like me. And I'm going to share some additional know-how about calling the same API multiple times to refresh the component, I think it's probably a common question for beginners.
componentWillReceiveProps(nextProps), from official documentation :
If you need to update the state in response to prop changes (for
example, to reset it), you may compare this.props and nextProps and
perform state transitions using this.setState() in this method.
We could conclude that here is the place we handle props from the parent component, have API calls, and update the state.
Base on #Alexander T.'s example:
export default class UserList extends React.Component {
constructor(props) {
super(props);
this.state = {person: []};
}
componentDidMount() {
//For our first load.
this.UserList(this.props.group); //maybe something like "groupOne"
}
componentWillReceiveProps(nextProps) {
// Assuming parameter comes from url.
// let group = window.location.toString().split("/")[*indexParameterLocated*];
// this.UserList(group);
// Assuming parameter comes from props that from parent component.
let group = nextProps.group; // Maybe something like "groupTwo"
this.UserList(group);
}
UserList(group) {
$.getJSON('https://randomuser.me/api/' + group)
.then(({ results }) => this.setState({ person: results }));
}
render() {
return (...)
}
}
Update
componentWillReceiveProps() will be deprecated.
Here are only some methods (all of them in Doc) in the life cycle I think that they are related to deploying API in the general cases:
By referring to the diagram above:
Deploy API in componentDidMount()
The proper scenario to have API call here is that the content (from the response of API) of this component will be static, componentDidMount() only fire once while the component is mounting, even new props are passed from the parent component or have actions to lead re-rendering.
The component do check difference to re-render but not re-mount.
Quote from doc:
If you need to load data from a remote endpoint, this is a good place to
instantiate the network request.
Deploy API in static getDerivedStateFromProps(nextProps, prevState)
We should notice that there are two kinds of component updating, setState() in current component would not trigger this method but re-rendering or new props from parent component would.
We could find out this method also fires while mounting.
This is a proper place to deploy API if we want to use the current component as a template, and the new parameters to make API calls are props coming from parent component.
We receive a different response from API and return a new state here to change the content of this component.
For example:
We have a dropdown list for different Cars in the parent component, this component needs to show the details of the selected one.
Deploy API in componentDidUpdate(prevProps, prevState)
Different from static getDerivedStateFromProps(), this method is invoked immediately after every rendering except the initial rendering. We could have API calling and render difference in one component.
Extend the previous example:
The component to show Car's details may contain a list of series of this car, if we want to check the 2013 production one, we may click or select or ... the list item to lead a first setState() to reflect this behavior (such as highlighting the list item) in this component, and in the following componentDidUpdate() we send our request with new parameters (state). After getting the response, we setState() again for rendering the different content of the Car details. To prevent the following componentDidUpdate() from causing the infinity loop, we need to compare the state by utilizing prevState at the beginning of this method to decide if we send the API and render the new content.
This method really could be utilized just like static getDerivedStateFromProps() with props, but need to handle the changes of props by utilizing prevProps. And we need to cooperate with componentDidMount() to handle the initial API call.
Quote from doc:
... This is also a good place to do network requests as long as you
compare the current props to previous props ...
I would like you to have a look at redux
http://redux.js.org/index.html
They have very well defined way of handling async calls ie API calls, and instead of using jQuery for API calls, I would like to recommend using fetch or request npm packages, fetch is currently supported by modern browsers, but a shim is also available for server side.
There is also this another amazing package superagent, which has alot many options when making an API request and its very easy to use.
You can also fetch data with hooks in your function components
full example with api call: https://codesandbox.io/s/jvvkoo8pq3
second example: https://jsfiddle.net/bradcypert/jhrt40yv/6/
const Repos = ({user}) => {
const [repos, setRepos] = React.useState([]);
React.useEffect(() => {
const fetchData = async () => {
const response = await axios.get(`https://api.github.com/users/${user}/repos`);
setRepos(response.data);
}
fetchData();
}, []);
return (
<div>
{repos.map(repo =>
<div key={repo.id}>{repo.name}</div>
)}
</div>
);
}
ReactDOM.render(<Repos user="bradcypert" />, document.querySelector("#app"))
1) You can use Fetch API to fetch data from Endd Points:
Example fetching all Github repose for a user
/* Fetch GitHub Repos */
fetchData = () => {
//show progress bar
this.setState({ isLoading: true });
//fetch repos
fetch(`https://api.github.com/users/hiteshsahu/repos`)
.then(response => response.json())
.then(data => {
if (Array.isArray(data)) {
console.log(JSON.stringify(data));
this.setState({ repos: data ,
isLoading: false});
} else {
this.setState({ repos: [],
isLoading: false
});
}
});
};
2) Other Alternative is Axios
Using axios you can cut out the middle step of passing the results of
the http request to the .json() method. Axios just returns the data
object you would expect.
import axios from "axios";
/* Fetch GitHub Repos */
fetchDataWithAxios = () => {
//show progress bar
this.setState({ isLoading: true });
// fetch repos with axios
axios
.get(`https://api.github.com/users/hiteshsahu/repos`)
.then(result => {
console.log(result);
this.setState({
repos: result.data,
isLoading: false
});
})
.catch(error =>
this.setState({
error,
isLoading: false
})
);
}
Now you can choose to fetch data using any of this strategies in componentDidMount
class App extends React.Component {
state = {
repos: [],
isLoading: false
};
componentDidMount() {
this.fetchData ();
}
Meanwhile you can show progress bar while data is loading
{this.state.isLoading && <LinearProgress />}
Render function should be pure, it's mean that it only uses state and props to render, never try to modify the state in render, this usually causes ugly bugs and decreases performance significantly. It's also a good point if you separate data-fetching and render concerns in your React App. I recommend you read this article which explains this idea very well. https://medium.com/#learnreact/container-components-c0e67432e005#.sfydn87nm
This part from React v16 documentation will answer your question, read on about componentDidMount():
componentDidMount()
componentDidMount() is invoked immediately after a component is
mounted. Initialization that requires DOM nodes should go here. If you
need to load data from a remote endpoint, this is a good place to
instantiate the network request. This method is a good place to set up
any subscriptions. If you do that, don’t forget to unsubscribe in
componentWillUnmount().
As you see, componentDidMount is considered the best place and cycle to do the api call, also access the node, means by this time it's safe to do the call, update the view or whatever you could do when document is ready, if you are using jQuery, it should somehow remind you document.ready() function, where you could make sure everything is ready for whatever you want to do in your code...
As an addition/update to Oleksandr T.'s excellent answer:
If you use class components, backend calls should happen in componentDidMount.
If you use hooks instead, you should use the effect hook
For example:
import React, { useState, useEffect } from 'react';
useEffect(() => {
fetchDataFromBackend();
}, []);
// define fetchDataFromBackend() as usual, using Fetch API or similar;
// the result will typically be stored as component state
Further reading:
Using the Effect Hook in the official docs.
How to fetch data with React Hooks? by Robin Wieruch
A clean way is to make an asynchronous API call inside componentDidMount with try/catch function.
When we called an API, we receive a response. Then we apply JSON method on it, to convert the response into a JavaScript object. Then we take from that response object only his child object named "results" (data.results).
In the beginning we defined "userList" in state as an empty array. As soon as we make the API call and receive data from that API, we assign the "results" to userList using setState method.
Inside the render function we tell that userList will be coming from state. Since the userList is an array of objects we map through it, to display a picture, a name and a phone number of each object "user". To retrieve this information we use dot notation (e.g. user.phone).
NOTE: depending on your API, your response may look different. Console.log the whole "response" to see which variables you need from it, and then assign them in setState.
UserList.js
import React, { Component } from "react";
export default class UserList extends Component {
state = {
userList: [], // list is empty in the beginning
error: false
};
componentDidMount() {
this.getUserList(); // function call
}
getUserList = async () => {
try { //try to get data
const response = await fetch("https://randomuser.me/api/");
if (response.ok) { // ckeck if status code is 200
const data = await response.json();
this.setState({ userList: data.results});
} else { this.setState({ error: true }) }
} catch (e) { //code will jump here if there is a network problem
this.setState({ error: true });
}
};
render() {
const { userList, error } = this.state
return (
<div>
{userList.length > 0 && userList.map(user => (
<div key={user}>
<img src={user.picture.medium} alt="user"/>
<div>
<div>{user.name.first}{user.name.last}</div>
<div>{user.phone}</div>
<div>{user.email}</div>
</div>
</div>
))}
{error && <div>Sorry, can not display the data</div>}
</div>
)
}}
As best place and practice for external API calls is React Lifecycle method componentDidMount(), where after the execution of the API call you should update the local state to be triggered new render() method call, then the changes in the updated local state will be applied on the component view.
As other option for initial external data source call in React is pointed the constructor() method of the class. The constructor is the first method executed on initialization of the component object instance. You could see this approach in the documentation examples for Higher-Order Components.
The method componentWillMount() and UNSAFE_componentWillMount() should not be used for external API calls, because they are intended to be deprecated. Here you could see common reasons, why this method will be deprecated.
Anyway you must never use render() method or method directly called from render() as a point for external API call. If you do this your application will be blocked.
You must try "axios" library for API call.
Instead of direct using jQuery.
Thanks.
It would be great to use axios for the api request which supports cancellation, interceptors etc. Along with axios, l use react-redux for state management and redux-saga/redux-thunk for the side effects.