How to call dispatch from onChange in react-router Route? - javascript

I am developing a web application where I want to use the URL query for running search queries.
When executing a search, a new request should be sent to the server to get new data, according to the url query params.
Using react-router and Redux, I want to call the dispatch function to fetch the new data from the server according to the query (i.e url change).
I was thinking to use the onChange event and call the dispatch function, like this:
<IndexRoute component={Catalog} onChange={(prevState, nextState, replace) => {dispatch(fetchProducts(nextState.location.search))}}/>
But since my routes is not a React component, I cant access the dispatch function.
I can import my store and use store.dispatch, but I read it is not recommended.
What would you suggest me to do?

To solve this kind of issues, we always declare or routes in a function similar to this:
function createRoutes(store) {
return {
component: App,
childRoutes: [
// Here are defined the other routes.
// They can be defined using plain objects:
{path: "profile", component: Profile},
// Or generated through a function call:
...require("./some/module")(store),
]
}
}
When you do this it is fairly simple to use the store to dispatch an action or to do calculations in React Router lifecycle hooks.
function createRoutes(store) {
return {
component: Settings,
onEnter: (nextState, replace, callback) => {
const state = store.getState()
if (!state.user.logged) {
dispatch(createNotification("You must be logged to perform this"))
replace("/login")
}
callback()
}
}
}

Related

Passing an Object through params in react (not passing the properties one by one with { }) [duplicate]

In v4 you would do a history.push('/whatever', { data })
Then the component tied to that route could read the data object by referencing the history.location.state
Now with v6 that's changed to navigate('whatever')
How do you pass data like before?
It's similar to how it's done in v4, two arguments, the second being an object with a state property.
navigate(
'thepath',
{
state: {
//...values
}
}
})
From the migration guide: Use navigate instead of history
If you need to replace the current location instead of push a new one
onto the history stack, use navigate(to, { replace: true }). If you
need state, use navigate(to, { state }). You can think of the first
arg to navigate as your and the other arg as the replace
and state props.
To access the route state in the consuming component use the useLocation React hook:
const { state } = useLocation();

redirect inside a redux action with react-router-dom

I am new to react. I have a component called Register.js there I call a redux action's method register. So inside that I want to redirect user to path /dashboard after the dispatch.
This is my action inside redux
const { REGISTER_USER } = require('./types')
export const register = (values) => async dispatch => {
try {
// register part goes here ...
dispatch({
type: REGISTER_USER,
payload: values
})
} catch (error) {
console.log(error)
}
}
How do I achive this in react?
I personally would use react router and the history functionality to push to a new page after the action has dispatched and resolved (e.g. the user has registered)
React router redirect after action redux
Be aware newer versions of react router behave differently
How to push to History in React Router v4?
Another option would be having "register" as a modal, and once submitted you just hide the modal from view and refresh the dashboard component with the new props (so it'd go and fetch data for the newly authenticated user)
You wouldn't want to redirect in the action or redux reducer/state files, it'd be in your react component logic.

Next/React-Apollo: React props not hooked up to apollo cache when query comes from getInitialProps

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.

How to call multiple graphql mutation from a single react component?

Currently, I have a react component which needs to call 2 mutations from the same component on different buttons clicks.
I am using react-apollo to interact with graphql server from react component.
If I use this.props.mutate to call the mutation on click event, there is no option/argument to specify which mutation I want to call.
https://www.apollographql.com/docs/react/essentials/mutations.html
If I directly use this.props.mutate({variables: {...}}) it always calls the first mutation imported to the file.
Inspiration: https://github.com/sysgears/apollo-universal-starter-kit/blob/master/packages/client/src/modules/post/containers/PostComments.jsx
https://github.com/sysgears/apollo-universal-starter-kit/blob/master/modules/post/client-react/containers/PostComments.web.jsx
By props property you can pass mutation/closure as named (addComment, editComment) prop. Compose query, many mutations, redux (if needed) with one component.
UPDATE:
Other solution from the same project: https://github.com/sysgears/apollo-universal-starter-kit/blob/master/modules/user/client-react/containers/UserOperations.js
You can use "compose" function from react-apollo library to make multiple mutations available to be called inside your component.
import { compose, graphql } from "react-apollo";
const XComponent = {
...
}
const XComponentWithMutations = compose(
graphql(mutation1, {
name : 'mutation1'
}),
graphql(mutation2, {
name: 'mutation2'
})
)(XComponent);
Then inside your XComponent you can call both
props.mutation1({variables: ...})
or
props.mutation2({variables: ...})

React, Apollo 2, GraphQL, Authentication. How to re-render component after login

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.

Categories

Resources