Redux mapStateToProps: dispatch action to close popup after async update - javascript

I pass state to my component as such:
const mapStateToProps = (state, ownProps) => {
return {
pageState: state.pageState
}
}
The pageState contains information on the 'page', i.e. page meta data (such as page-name), and it also receives the method that was used when an async CRUD operation has updated the page data ("PUT").
The React component is a modal window that the user calls when (s)he wants to update and save the page name.
The async PUT operation works fine and the pageState gets properly updated (no problem here), but I don't understand from where I should dispatch the "hideModal" action that is executed after the async call.
Options are:
mapStateToProps: check here if the pageState contains "PUT", then dispatch the close action;
From the render method: but this is discouraged;
From shouldComponentUpdate. However, I bumped into a strange bug when testing this; the mapStateToProps doesn't appear to update the this.props.pageState properly on the first request. It only works on the second (?). Also, I don't feel like I should put it here anyhow; shouldComponentUpdate should not have side-effects like this.
My code to connect the component from the container:
const renameModal = connect(
mapStateToProps,
mapDispatchToProps
)(RenameModal);
Any help?
For info: I use Redux-saga for the async call. This shouldn't matter though. Updates of page info can happen from different places in my app, and this specific action (closing the modal) should stay decoupled from the implementation that takes care of the server requests.
Redux-saga, listening for async update requests:
function* updatePage(action){
try{
const page = yield call(Api.updatePage, action.payload.siteSlug, action.payload.pageId, action.payload.page);
yield put({type: 'PUT_PAGE_SUCCEEDED', page});
}catch(error){
yield put({type: 'PUT_PAGE_FAILED', error});
}
}
export function* watchUpdatePage(){
yield* takeLatest('PUT_PAGE_REQ', updatePage);
}
The reducer:
const asyncReducer = (state = pageState, action) => {
switch(action.type){
case 'GET_PAGE_SUCCEEDED':
console.log("get page succeeded");
return Immutable.fromJS({
type: "GET_PAGE_SUCCEEDED",
method: "GET",
pageState: action.page
});
case 'PUT_PAGE_SUCCEEDED':
console.log("put page succeeded");
return Immutable.fromJS({
type: "PUT_PAGE_SUCCEEDED",
method: "PUT",
pageState: action.page
});
default:
return state;
}
}

Actions in redux can be handled by multiple reducers. Since your async call already dispatches the PUT_PAGE_SUCCEEDED action type when the call ends (and PUT_PAGE_REQ before it), you don't have to dispatch another action. Create a modal reducer like this:
const modalReducer = (state = false, action) => {
case 'SHOW_MODAL':
return Immutable.fromJS({
showModal: true
});
case 'PUT_PAGE_SUCCEEDED':
return Immutable.fromJS({
showModal: false
});
default:
return state;
}
}
And change mapStateToProps accordingly:
const mapStateToProps = (state, ownProps) => {
return {
pageState: state.pageState,
modalState: state.modalState
}
}
btw - I'm not really familiar with redux-saga, but I believe that it's not that different from the simple API middleware in the way in dispatches actions during the API call lifecycle. If it is....

Related

Async/Await inside reducer in Redux

const authSlice = createSlice({
name: "auth",
initialState: {
value: {
fields: initialState,
},
},
reducers: {
loginUser: async (state, action) => {
state.value.fields.loading = true;
await http
.post("/login", {
email: state.value.fields.email,
password: state.value.fields.password,
})
.then((response) => {
console.log(response.data);
});
},
},
});
I'm receiving an error saying A non-serializable value was detected in the state, in the path: auth. Value: When I try to make this loginUser async.When I remove async await from the loginUser it's working.Really appreciate it if somebody could help.
You must never do any async work inside of a Redux reducer!
One of the primary rules of Redux is:
Reducers must always be 100% synchronous and "pure", with no side effects ( https://redux.js.org/style-guide/#reducers-must-not-have-side-effects )
Additionally, all state updates must be done by "dispatching an action" (like store.dispatch({type: "todos/todoAdded", payload: "Buy milk"}) ), and then letting the reducer look at that action to decide what the new state should be.
If you need to do async work, the right answer is usually to put the async logic into a "thunk", and dispatch actions based on the async results.
I'd strongly recommend going through the Redux docs tutorials to learn more about this and how to use Redux:
https://redux.js.org/tutorials/index

Trying call useQuery in function with react-apollo-hooks

I want to call useQuery whenever I need it,
but useQuery can not inside the function.
My trying code is:
export const TestComponent = () => {
...
const { data, loading, error } = useQuery(gql(GET_USER_LIST), {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
...
...
const onSaveInformation = async () => {
try {
await updateInformation({...})
// I want to call useQuery once again.
} catch (e) {
return e
}
}
...
How do I call useQuery multiple times?
Can I call it whenever I want?
I have looked for several sites, but I could not find a solutions.
From apollo docs
When React mounts and renders a component that calls the useQuery hook, Apollo Client automatically executes the specified query. But what if you want to execute a query in response to a different event, such as a user clicking a button?
The useLazyQuery hook is perfect for executing queries in response to
events other than component rendering
I suggest useLazyQuery. In simple terms, useQuery will run when your component get's rendered, you can use skip option to skip the initial run. And there are some ways to refetch/fetch more data whenever you want. Or you can stick with useLazyQuery
E.g If you want to fetch data when only user clicks on a button or scrolls to the bottom, then you can use useLazyQuery hook.
useQuery is a declarative React Hook. It is not meant to be called in the sense of a classic function to receive data. First, make sure to understand React Hooks or simply not use them for now (90% of questions on Stackoverflow happen because people try to learn too many things at once). The Apollo documentation is very good for the official react-apollo package, which uses render props. This works just as well and once you have understood Apollo Client and Hooks you can go for a little refactor. So the answers to your questions:
How do I call useQuery multiple times?
You don't call it multiple times. The component will automatically rerender when the query result is available or gets updated.
Can I call it whenever I want?
No, hooks can only be called on the top level. Instead, the data is available in your function from the upper scope (closure).
Your updateInformation should probably be a mutation that updates the application's cache, which again triggers a rerender of the React component because it is "subscribed" to the query. In most cases, the update happens fully automatically because Apollo will identify entities by a combination of __typename and id. Here's some pseudocode that illustrates how mutations work together with mutations:
const GET_USER_LIST = gql`
query GetUserList {
users {
id
name
}
}
`;
const UPDATE_USER = gql`
mutation UpdateUser($id: ID!, $name: String!) {
updateUser(id: $id, update: { name: $name }) {
success
user {
id
name
}
}
}
`;
const UserListComponen = (props) => {
const { data, loading, error } = useQuery(GET_USER_LIST);
const [updateUser] = useMutation(UPDATE_USER);
const onSaveInformation = (id, name) => updateUser({ variables: { id, name });
return (
// ... use data.users and onSaveInformation in your JSX
);
}
Now if the name of a user changes via the mutation Apollo will automatically update the cache und trigger a rerender of the component. Then the component will automatically display the new data. Welcome to the power of GraphQL!
There's answering mentioning how useQuery should be used, and also suggestions to use useLazyQuery. I think the key takeaway is understanding the use cases for useQuery vs useLazyQuery, which you can read in the documentation. I'll try to explain it below from my perspective.
useQuery is "declarative" much like the rest of React, especially component rendering. This means you should expect useQuery to be called every render when state or props change. So in English, it's like, "Hey React, when things change, this is what I want you to query".
for useLazyQuery, this line in the documentation is key: "The useLazyQuery hook is perfect for executing queries in response to events other than component rendering". In more general programming speak, it's "imperative". This gives you the power to call the query however you want, whether it's in response to state/prop changes (i.e. with useEffect) or event handlers like button clicks. In English, it's like, "Hey React, this is how I want to query for the data".
You can use fetchMore() returned from useQuery, which is primarily meant for pagination.
const { loading, client, fetchMore } = useQuery(GET_USER_LIST);
const submit = async () => {
// Perform save operation
const userResp = await fetchMore({
variables: {
// Pass any args here
},
updateQuery(){
}
});
console.log(userResp.data)
};
Read more here: fetchMore
You could also use useLazyQuery, however it'll give you a function that returns void and the data is returned outside your function.
const [getUser, { loading, client, data }] = useLazyQuery(GET_USER_LIST);
const submit = async () => {
const userResp = await getUser({
variables: {
// Pass your args here
},
updateQuery() {},
});
console.log({ userResp }); // undefined
};
Read more here: useLazyQuery
You can create a reusable fetch function as shown below:
// Create query
const query = `
query GetUserList ($data: UserDataType){
getUserList(data: $data){
uid,
first_name
}
}
`;
// Component
export const TestComponent (props) {
const onSaveInformation = async () => {
// I want to call useQuery once again.
const getUsers = await fetchUserList();
}
// This is the reusable fetch function.
const fetchUserList = async () => {
// Update the URL to your Graphql Endpoint.
return await fetch('http://localhost:8080/api/graphql?', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Accept': 'application/json',
},
body: JSON.stringify({
query,
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
})
}).then(
response => { return response.json(); }
).catch(
error => console.log(error) // Handle the error response object
);
}
return (
<h1>Test Component</h1>
);
}
Here's an alternative that worked for me:
const { refetch } = useQuery(GET_USER_LIST, {
variables: {
data: {
page: changePage,
pageSize: 10,
},
},
}
);
const onSaveInformation = async () => {
try {
await updateInformation({...});
const res = await refetch({ variables: { ... }});
console.log(res);
} catch (e) {
return e;
}
}
And here's a similar answer for a similar question.
Please use
const { loading, data, refetch } = useQuery(Query_Data)
and call it when you need it i.e
refetch()

What is the best way to fetch api in redux?

How to write best way to fetch api resource in react app while we use redux in application.
my actions file is actions.js
export const getData = (endpoint) => (dispatch, getState) => {
return fetch('http://localhost:8000/api/getdata').then(
response => response.json()).then(
json =>
dispatch({
type: actionType.SAVE_ORDER,
endpoint,
response:json
}))
}
is it best way to fetch api?
The above code is fine.But there are few points you should look to.
If you want to show a Loader to user for API call then you might need some changes.
You can use async/await the syntax is much cleaner.
Also on API success/failure you might want to show some notification to user. Alternatively, You can check in componentWillReceiveProps to show notification but the drawback will be it will check on every props changes.So I mostly avoid it.
To cover this problems you can do:
import { createAction } from 'redux-actions';
const getDataRequest = createAction('GET_DATA_REQUEST');
const getDataFailed = createAction('GET_DATA_FAILURE');
const getDataSuccess = createAction('GET_DATA_SUCCESS');
export function getData(endpoint) {
return async (dispatch) => {
dispatch(getDataRequest());
const { error, response } = await fetch('http://localhost:8000/api/getdata');
if (response) {
dispatch(getDataSuccess(response.data));
//This is required only if you want to do something at component level
return true;
} else if (error) {
dispatch(getDataFailure(error));
//This is required only if you want to do something at component level
return false;
}
};
}
In your component:
this.props.getData(endpoint)
.then((apiStatus) => {
if (!apiStatus) {
// Show some notification or toast here
}
});
Your reducer will be like:
case 'GET_DATA_REQUEST': {
return {...state, status: 'fetching'}
}
case 'GET_DATA_SUCCESS': {
return {...state, status: 'success'}
}
case 'GET_DATA_FAILURE': {
return {...state, status: 'failure'}
}
Using middleware is the most sustainable way to do API calls in React + Redux applications. If you are using Observables, aka, Rxjs then look no further than redux-observable.
Otherwise, you can use redux-thunk or redux-saga.
If you are doing a quick prototype, then making a simple API call from the component using fetch is good enough. For each API call you will need three actions like:
LOAD_USER - action used set loading state before API call.
LOAD_USER_SUCC - when API call succeeds. Dispatch on from then block.
LOAD_USER_FAIL - when API call fails and you might want to set the value in redux store. Dispatch from catch block.
Example:
function mounted() {
store.dispatch(loadUsers());
getUsers()
.then((users) => store.dispatch(loadUsersSucc(users)))
.catch((err) => store.dispatch(loadUsersFail(err));
}

How should I log every dispatch call to a Redux store?

I want to debug my Redux application by logging every action sent to the dispatcher in my the console.
What is generally regarded as the best approach for doing this?
You can use a simple logging middleware, like the example in the redux middleware docs:
const logger = store => next => action => {
console.group(action.type)
console.info('dispatching', action)
let result = next(action)
console.log('next state', store.getState())
console.groupEnd()
return result
}
Or use the redux-logger module, which basically does the same thing.
With TypeScript, use this annotation (where TStore is the type-name of the root of your state:
import { Middleware } from 'redux';
// ...
const logger: Middleware<{},TState> = store => next => action => {
console.group(action.type)
// etc

Isomorphic Redux app not registering Redux-Thunk?

I seem to have a weird bug. I'm currently using Redux isomorphically and am also including redux-thunk as the middleware for async actions. Here's what my store config looks like:
// Transforms state date from Immutable to JS
const transformToJs = (state) => {
const transformedState = {};
for (const key in state) {
if (state.hasOwnProperty(key)) transformedState[key] = state[key].toJS();
}
return transformedState;
};
// Here we create the final store,
// If we're in production, we want to leave out development middleware/tools
let finalCreateStore;
if (process.env.NODE_ENV === 'production') {
finalCreateStore = applyMiddleware(thunkMiddleware)(createStore);
} else {
finalCreateStore = applyMiddleware(
createLogger({transformer: transformToJs}),
thunkMiddleware
)(createStore);
}
// Exports the function that creates a store
export default function configureStore(initialState) {
const store = finalCreateStore(reducers, initialState);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('.././reducers/index', () => {
const nextRootReducer = require('.././reducers/index');
store.replaceReducer(nextRootReducer);
});
}
return store;
}
The weird part about this is that I don't think there's anything wrong with this file because my createLogger is applied just fine. It logs out all my actions and state, but the minute I return a function instead of an object in an action creator, the execution is lost. I've tried throwing in debugger statements, which never hit and reordering the middleware also doesn't seem to help.
createUser(data) {
// This `debugger` will hit
debugger;
return (dispatch) => {
// This `debugger` will NOT hit, and any code within the function will not execute
debugger;
setTimeout(() => {
dispatch(
AppActionsCreator.createFlashMessage('yellow', 'Works!')
);
}, 1000);
};
},
Has anyone experienced something like this before?
DOH! I wasn't dispatching the action. I was only calling the action creator. Gonna have to get used to that with Redux!
How I thought I was invoking an action:
AppActionCreators.createFlashMessage('some message');
How to actually invoke an action in Redux:
this.context.dispatch(AppActionCreators.createFlashMessage('some message'));
Where dispatch is a method provided by the Redux store, and can be passed down to every child component of the app through React's childContextTypes

Categories

Resources