I am trying to page through data in a react component in Gatsby from graphql via a button that increments state.count.
When I click the button the state.count is incremented but it is not passed to the query.
Any ideas what I am doing wrong?
pageUp=() => {
this.setState({count: this.state.count +2});
let skip=this.state.count
}
render() {
return (
<StaticQuery
query={graphql`
query ListingPageQuery ($skip:Int){
allMarkdownRemark(
limit:2
skip: $skip
)
{
...
}
}
`
}...
I see two issues in this code. First:
pageUp=() => {
let skip=this.state.count
}
The let statement causes the skip variable to be local to this function. It's not obvious from your question what causes it to be passed on to the GraphQL query, but if it's a member variable or something else, this statement shadows it and you're setting a purely local variable whose state will be lost.
Second:
this.setState({count: this.state.count +2});
let skip=this.state.count
State updates may be asynchronous, and the React documentation specifically advises against changing state the way you have it (there is a callback-based pattern that's more appropriate). The other consequence of this is that the state may not have actually updated when you get to the next line, so you're assigning count from the "old" state to the skip variable.
Looking at the Gatsby documentation, there is a specific note that StaticQuery does not support GraphQL variables, though browsing it doesn't suggest another path that does. (Every single example that shows potentially paginated data only shows the first page.)
You can't do that with Gatsby. GraphQL only happens at build-time not on the client. You'd want to grab all the content and then use JS to only show/skip what you want to show.
Looking at the Gatsby documentation, there is a specific note that StaticQuery does not support GraphQL variables, though browsing it doesn't suggest another path that does.
Moreover as David Maze said the StaticQuery doesn't support such variables. This wouldn't work in normal queries either because such variables need to be passed in via context.
https://next.gatsbyjs.org/docs/creating-and-modifying-pages/
Only if you pass it via context it's available in pageContext and as a variable in your queries (you normally use that for templates).
The setState() is asynchronous. The right way to capture the state is in a callback, also use the prev parameter to reference state inside a setState() :
this.setState(prev => ({
count: prev.count + 2
}), () => {
// here this.state.count was updated and safe to use
});
Reference: https://reactjs.org/docs/react-component.html#setstate
Related
I just started using hooks in react and am creating a prototype custom hook for a framework.
The hook should take an object as an argument for initialization and cleanup (setting up/removing callbacks for example).
Here is my simplified Code so far:
export function useManager(InitObj) {
const [manager] = useState(() => new Manager());
useEffect(() => {
manager.addRefs(InitObj)
return () => manager.removeRefs(InitObj)
}, [manager]);
return manager;
}
to be used like this:
useManager({ cb1: setData1, cb2: setData2... })
In future Iterations the Manager might be a shared instance, so I need to be able to be specific about what I remove upon cleanup.
I put console.log all over the place to see If i correctly understand which code will be run during a render call. From what I can tell this code does 100% what I expeted it to do!
Unfortunately (and understandably) I get a warning because I did not include InitObj in the effects dependencies. But since I get an object literal simply putting it in there will cause the effect to be cleaned up/rerun on every render call since {} != {} which would be completely unnecessary.
My research so far only revealed blog posts like this one, but here only primitive data is used that is easily classified as "the same" (1 == 1)
So far I have found 3 possible solutions that I am not completely happy with:
using useMemo to memoize the object literal outside the hook
useManager(useMemo(() => { cb: setData }, []))
This adds more responsibility on the developer using my code => not desirable!
using useState inside the hook
const [iniOBj] = useState(InitObj);
A lot better already, but it adds state that does not feel like state. And it costs (minimal) execution time and memory, I would like to avoid that if possible.
using // eslint-disable-next-line react-hooks/exhaustive-deps
Works for sure, but there might still be other dependencies that might be missed if I simply deactivate the warning.
So my question is:
How can I use an object as initializer for custom hooks without adding complexity/state or inviting future problems?
I half expect that the useState option will be my best choice, but since I am new to hooks there might still be something that eluded my understanding so far.
I have a useEffect which reads the location.hash and based on some other dependencies, will change the hash. It looks something like this:
useEffect(() => {
const hashAlreadyPresent = () => {
const hashArr = history.location.hash.split('#');
return hashArr.includes(hashId);
};
const addToHash = () => {
return history.location.hash.concat(`#${hashId}`);
};
const removeFromHash = () => {
const hashArray = history.location.hash.split('#').filter(hashStr => hashStr);
const indexOfHashId = hashArray.indexOf(hashId);
(indexOfHashId !== -1) && hashArray.splice(indexOfHashId, 1);
return hashArray;
};
// if hashId props is present then attach hash in route
hashId && !hashAlreadyPresent() && history.push({
hash: `${hashAlreadyPresent() ? '' : addToHash()}`,
search: history.location.search,
});
return () => {
// remove hashId only, retain any other hash if present
const hashArray = removeFromHash();
hashId && hashAlreadyPresent() && history.replace({
hash: hashArray.join('#'),
search: history.location.search,
});
};
}, [history, hashId, history.location.hash, history.location.search]);
where history is from React Router.
The logic is that once the component is on screen (mounted), it adds a hash to the URL and once it is getting unmounted it will remove the hash from the url.
Of course, in terms of useEffect it translates to: if any of the dependencies change, the previous effect would be cleaned up and a new instance of the effect would be in place. The effective deps rule helped me with that, as earlier i was missing the fact that this hook should be cleaned up and re-run if hashId changes.
Now, we should have a dependency on history.location.hash for exhaustive deps, but the problem is every time I change hash from within the hook, the hook will run again (the previous instance will cleanup and change the hash again), which would lead to an infinite update kind of scenario.
NOTE: I know this is possible by switching off exhaustive-deps rule and excluding history.location.hash from dependencies, but would like to figure out any possibilities of refactoring/breaking down the useEffect, so that this can be solved without turning it off.
Another thing to note is that if I add history as a dependency (Which I have to because i am using a method from history), then the rule does not ask me to explicitly add the nested dependecies (history.lcoation.search, history.location.hash), but, those should be added, as the history object would remain the same but the nested objects would change on url change. This is same as the use case where you specify complete props object as a dependency instead of only the required specific nested properties.
Should i have a condition inside my useEffect based on when the location changes, which can somehow tell me if the location was changed from inside the hook and so don't do anything ?
Should i destructure and specify the dependencies in a different way, so that the effect does not run when the location.hash is changed from within the effect ?
NOTE:
had a discussion for this on github. got some more insights.
https://github.com/facebook/react/issues/19636
When a non-empty dependency array is specified, any value that you add to the dependency array causes the cleanup function to run first (except on the first render), followed by the effect function (except during unmount). To decide whether a value should go into the dependency array then, try answering this question for that value:
When this value is updated, should the effect run again, such that:
The desired effect is observed
Any potential changes made previously are cleaned up if required
No bugs due to stale references are introduced?
If the answer is yes for any of the points above, then that value makes it to the dependency array.
We can now answer the question above for all the values used within the useEffect function:
hashId: Yes. This is the primary driver for the effect, and each time this value changes, the URL should reflect the change. This becomes the source of truth for the effect. Therefore this is required to ensure that the desired effect is observed. Additionally, this is also required to clean up the previous hashId as the clean up function needs a reference to the previous hashId.
history: Yes. I suppose that as this is provided by react router, the reference should not change throughout the component's life cycle. In that sense, the only purpose of adding it here would be to satisfy the lint rule, with no real impact (other than an extra referential check). However, if it does change, the effect function will have a stale reference to it which could potentially cause bugs. This has to be taken care of.
history.location.search: No. This has nothing to do with the primary effect, as only the hashId is required to ensure that the desired effect is observed. There is also no danger of stale references, as this is always read from the history object. Since the history object is mutable and updated with the latest value every time, and is already a part of the dependency array, history.location.search can be safely omitted. *
history.location.hash: No, for the same arguments as for history.location.search. Additionally, it is always hashId that determines what the history.location.hash should be, so an update to this value should not be used to re-run the effect.
The final dependency array then is just [hashId, history]. **
* be careful to not extract out search from history.location and use search within the cleanup function, as it will be a stale reference
** noticed routeModal being used in the body of the effect, if needed this would also have to be a part of the dependency array
I have a PageContext that is holding the state of User objects as array. Each User object contains a ScheduledPost object that does mutate when user decides to add a new post. I have no idea how to trigger an update on my PageContext when it happens (I want to avoid forceUpdate() call). I need to somehow be notified of that, in order to re-render posts, maintain timer etc.
Please, see the code:
class User {
name: string;
createTime: number;
scheduledPosts: ScheduledPost[] = [];
/*
* Creates a new scheduled post
*/
public createScheduledPost(title : string, content : string, date : number): void {
this.scheduledPosts.push(Object.assign(new ScheduledPost(), {
title,
content,
date
}));
}
}
class ScheduledPost {
title: string;
content: string;
date: number;
public load(): void {
// Create timers etc.
}
public publish(): void {
// Publish post
}
}
// PageContext/index.tsx
export default React.createContext({
users: [],
editingUser: null,
setEditingUser: (user: User) => {}
});
// PageContextProvider.tsx
const PageContextProvider: React.FC = props => {
const [users, setUsers] = useState<User[]>([]);
const [editingUser, setEditingUser] = useState<User>(null);
// Load users
useEffect(() => {
db.getUsers()
.then(result => setUsers(result));
}, []);
return (
<PageContext.Provider value={{
users,
editingUser,
setEditingUser
}}>
{props.children}
</PageContext.Provider>
);
};
What I would like to achieve is, when consuming my provider with useContext hook:
const ctx = useContext(PageContext);
I would like to create a schedule post from any component like so:
// Schedule post (1 hour)
ctx.editingUser.createScheduledPost("My post title", "The post content", (new Date).getTime() + 1 * 60 * 60);
However, this wont work, since React doesn't know that User property has just mutated.
Questions:
How can I make React being notified of the changes within any of the User object instance? What is the way to solve it properly (excluding forceUpdate)?
Am I doing it right? I'm new to React and I feel like the structure I'm using here is cumbersome and just not right.
Where are the users being mutated? If you're storing them in your state as it appears, the changes should be detected. However if you're using the methods built into the User class to let them directly update themselves, then React will not pick up on them. You would need to update the entire users array in your state to make sure React can respond to the changes.
It's tough to give a more specific example without seeing exactly where/how you're updating your users currently, but a generalized mutation might go something like this (you can still use a class method, if desired):
const newUsers = Array.from(users); // Deep copy users array to prevent direct state mutation.
newUsers[someIndex].createScheduledPost(myTitle, myContent, myDate);
setUsers(newUsers); // Calling the setX function tied to your useState call will automatically trigger updates/re-renders for all (unless otherwise specified) components/operations that depend on it
In React re-render is caused by calling setState within component (or by using hooks, but the point is that you need to call specific method) or by changing component props. That means that manual mutation of your state will never cause a re-render - even if you had simple component and called
this.state.something = somethingElse;
re-render would not occur. Same thing works for context.
For your case, this means that you should not mutate editingUser state, but call setEditingUser with changed user state, something like:
const user = { ...ctx.editingUser };
user.createScheduledPost("My post title", "The post content", (new Date).getTime() + 1 * 60 * 60);
ctx.setEditingUser(user);
I'm not sure about your inner structure, but if that same user is also in users array, then you'll need to update that part of state by calling setUsers method where you maintain whole array and only update that single user which changed data - if thats the case then I'd think about restructuring the app because it already gets complicated for such simple state changes. You should also consider using redux, mobx or some other state management library instead of react context (my personal advice).
EDIT
Please take a look at this quite:
In a typical React application, data is passed top-down (parent to
child) via props, but this can be cumbersome for certain types of
props (e.g. locale preference, UI theme) that are required by many
components within an application. Context provides a way to share
values like these between components without having to explicitly pass
a prop through every level of the tree.
As you can see, react team is suggesting using context for some global preferences that are required within many components. The main problem with using context (in my opinion) is that you don't write natural react components - they don't receive dependant data through props but rather from within the context api itself. This means that you won't be able to reuse your components without also integrating context part of application.
While for example redux has similar concept of keeping state at one place, it still propagades that state (and its changes) to components via props, making your components undependent of both redux, context or anything else.
You can stick to react context and make whole app work with it, but I'm just saying it wouldn't be best practice to do so.
I am learning how redux works but its a lot of code to do simple things. For example, I want to load some data from the server before displaying. For editing reasons, I can't simply just use incoming props but I have to copy props data into the local state.
As far as I've learned, I have to send a Fetch_request action. If successful, a fetch_success action will update the store with new item. Then updated item will cause my component's render function to update.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id);
}
...
In actions
export function FETCH_REQUEST(id) {
api.get(...)
.then(d => FETCH_SUCCESS(d))
.catch(e => FETCH_FAILURE(e));
}
...
In reducer
export function FETCH_REDUCER(state = {}, action ={}) {
switch (action.type) {
case 'FETCH_SUCCESS':
return { ...state, [action.payload.id]: ...action.payload }
...
}
Back in component
this.props.FETCH_REDUCER
// extra code for state, getting desired item from...
Instead, can I call a react-thunk function and pass some callback functions? The react-thunk can update the store and callbacks can change the component's local state.
In component
componentWillMount() {
this.props.FETCH_REQUEST(this.props.match.params.id, this.cbSuccess, this.cbFailure);
}
cbSuccess(data) {
// do something
}
cbFailure(error) {
// do something
}
...
In action
export function FETCH_REQUEST(id, cbSuccess, cbFailure) {
api.get(...)
.then(d => {
cbSuccess(d);
FETCH_SUCCESS(d);
}).catch(e => {
cbFailure(d);
FETCH_FAILURE(e);
});
}
...
Is this improper? Can I do the same thing with redux-observable?
UPDATE 1
I moved nearly everything to the redux store, even for edits (ie replaced this.setState with this.props.setState). It eases state management. However, every time any input's onChange fires, a new state is popping up. Can someone confirm whether this is okay? I'm worried about the app's memory management due to redux saving a ref to each state.
First of all, you should call your API in componentDidMount instead of componentWillMount. More on this at : what is right way to do API call in react js?
When you use a redux store, your components subscribe to state changes using the mapStateToProps function and they change state using the actions added a props through the mapDispatchToProps function (assuming you are using these functions in your connect call).
So you already are subscribing to state changes using your props. Using a callback would be similar to having the callback tell you of a change which your component already knows about because of a change in its props. And the change in props would trigger a re-render of the component to show the new state.
UPDATE:
The case you refer to, of an input field firing an onChange event at the change of every character, can cause a lot of updates to the store. As mentioned in my comments, you can use an api like _.debounce to throttle the updates to the store to reduce the number of state changes in such cases. More on handling this at Perform debounce in React.js.
The issue of memory management is a real issue in real world applications when using Redux. The way to reduce the effect of repeated updates to the state is to
Normalize the shape of state : http://redux.js.org/docs/recipes/reducers/NormalizingStateShape.html
Create memoized selectors using Reselect (https://github.com/reactjs/reselect)
Follow the advice provided in the articles regarding performance in Redux github pages (https://github.com/reactjs/redux/blob/master/docs/faq/Performance.md)
Also remember that although the whole state should be copied to prevent mutating, only the slice of state that changes needs to be updated. For example, if your state holds 10 objects and only one of them changes, you need to update the reference of the new object in the state, but the remaining 9 unchanged objects still point to the old references and the total number of objects in your memory is 11 and not 20 (excluding the encompassing state object.)
I may be missing something. I know setState is asynchronous in React, but I still seem to have this question.
Imagine following is a handler when user clicks any of the buttons on my app
1. ButtonHandler()
2. {
3. if(!this.state.flag)
4. {
5. alert("flag is false");
6. }
7. this.setState({flag:true});
8.
9. }
Now imagine user very quickly clicks first one button then second.
Imagine the first time the handler got called this.setState({flag:true}) was executed, but when second time the handler got called, the change to the state from the previous call has not been reflected yet -- and this.state.flag returned false.
Can such situation occur (even theoretically)? What are the ways to ensure I am reading most up to date state?
I know setState(function(prevState, props){..}) gives you access to previous state but what if I want to only read state like on line 3 and not set it?
As you rightly noted, for setting state based on previous state you want to use the function overload.
I know setState(function(prevState, props){..}) gives you access to previous state
So your example would look like this:
handleClick() {
this.setState(prevState => {
return {
flag: !prevState.flag
};
});
}
what if I want to only read state like on line 3 and not set it?
Let's get back to thinking why you want to do this.
If you want to perform a side effect (e.g. log to console or start an AJAX request) then the right place to do it is the componentDidUpdate lifecycle method. And it also gives you access to the previous state:
componentDidUpdate(prevState) {
if (!prevState.flag && this.state.flag) {
alert('flag has changed from false to true!');
}
if (prevState.flag && !this.state.flag) {
alert('flag has changed from true to false!');
}
}
This is the intended way to use React state. You let React manage the state and don't worry about when it gets set. If you want to set state based on previous state, pass a function to setState. If you want to perform side effects based on state changes, compare previous and current state in componentDidUpdate.
Of course, as a last resort, you can keep an instance variable independent of the state.
React's philosophy
The state and props should indicate things the components need for rendering. React's render being called whenever the state and props change.
Side Effects
In your case, you're causing a side effect based on user interaction which requires specific timing. In my opinion, once you step out of rendering - you probably want to reconsider state and props and stick to a regular instance property which is synchronous anyway.
Solving the real issue - Outside of React
Just change this.state.flag to this.flag everywhere, and update it with assignment rather than with setState. That way you
If you still have to use .state
You can get around this, uglily. I wrote code for this, but I'd rather not publish it here so people don't use it :)
First promisify.
Then use a utility for only caring about the last promise resolving in a function call. Here is an example library but the actual code is ~10LoC and simple anyway.
Now, a promisified setState with last called on it gives you the guarantee you're looking for.
Here is how using such code would look like:
explicitlyNotShown({x: 5}).then(() => {
// we are guaranteed that this call and any other setState calls are done here.
});
(Note: with MobX this isn't an issue since state updates are sync).