I have a mern app and i'm using redux to maintain state of my posts, I want to fetch all data from my api at first run of the app (when the app component loads initially) but I can't achieve it. It only works when I post something and it fetches the post, but it doesn't fetch all the posts from db initially.
After struggling for a day I decided ask here.
This is my component tree:
In my PostsBody, I want to fetch all the posts from the database whenever the app loads initially (this is not happening) and then whenever there is a change in state like create, delete it should fetch the updated posts (this is happening).
This is my PostsBody component:
import React, { useEffect } from "react";
import { useDispatch, useSelector } from 'react-redux';
import Post from "./Post";
import { getPostsAction } from '../actions/posts';
const PostsBody = () => {
const dispatch = useDispatch();
// fetching posts
useEffect(() => {
dispatch(getPostsAction);
}, [dispatch]);
const posts = useSelector((globalState) => globalState.postsReducer);
console.log(posts); // intially empty when the app reloads/renders.
return (
// simply posts.map to display individual posts
);
}
export default PostsBody;
Action:
export const getPostsAction = () => async (dispatch) => {
try {
const { data } = await getAllPosts();
const action = {
type: 'GET_ALL',
payload: data,
}
dispatch(action);
} catch (error) {
console.log(error.message);
}
}
GET CALL:
import axios from 'axios';
const url = "http://localhost:5000/users";
export const getAllPosts = () => axios.get(url);
Reducer:
const postsReducer = (posts=[], action) => {
switch (action.type) {
case 'GET_ALL':
return action.payload;
case 'CREATE':
return [...posts, action.payload];
default: return posts;
}
}
export default postsReducer;
I repeat, the only problem is, it is not fetching all the posts from db initially when the app renders, after that when I create a new post it does fetch that post (not all from db).
Issues
It doesn't appear as though you are invoking the getPostsAction action creator correctly. Also, with only dispatch in the useEffect's dependency array the hook callback will only be invoked once when the component mounts.
Solution
Invoke the getPostsAction action.
useEffect(() => {
dispatch(getPostsAction()); // <-- invoke
}, [dispatch]);
Now this still only solves for fetching posts from the DB when the component mounts, but not when new posts are POST'd to your backend.
I've looked at your actions and state. Normally you would include another variable in the useEffect dependency array to trigger the effect callback to execute again, but I think a simpler way is possible. Instead of POST'ing the new post and dispatching the CREATE action you should POST the new "post" and immediately GET all posts and dispatch the GET_ALL action instead with that data.
export const createPostAction = (newPostData) => async (dispatch) => {
try {
await createPost(newPostData);
getAllPosts()(dispatch);
} catch (error) {
console.log(error.message);
}
}
I've basic familiarity with Thunks, but if the above doesn't work then you may need to duplicate some behavior, or factor it out into some common utility code used by both action creators.
export const createPostAction = (newPostData) => async (dispatch) => {
try {
await createPost(newPostData);
const { data } = await getAllPosts();
const action = {
type: 'GET_ALL',
payload: data,
}
dispatch(action);
} catch (error) {
console.log(error.message);
}
}
Related
I am having difficulty updating my store after calling an API. I am using reduxjs/toolkit. Here is the structure of the project:
react/
store/
api/
dataconsumer/
dataSlice.js
notifications/
notificationsSlice.js
app.js
Here, api contains non-component API calls to the server. They are bound to thunk functions within dataSlice and a successful query updates data just fine.
The following are relevant parts to my reducers.
notificationSlice.js
const slice = createSlice({
...,
reducers: {
// need to call this from api
setNotifications: (state, action) => state.notification = action.payload
}
})
dataSlice.js
export const fetchInitialData = createAsyncThunk(
'chart/fetchInitialData',
async (data) => {
return API.candles.initialData({
...data
})
}
const slice = createSlice({
...
extraReducers: {
...
[fetchInitialData.success]: (state, action) => state.action = action.payload
}
})
And the api
const fetchInitialData = () => {
return fetch(url, {
...
}).then(data => data.json())
.then(data => {
if(data.status === 200) { return data } // works great!
else {
// doesn't work, but functionally what I'm looking for
store.dispatch(setNotifications(data.status))
}
})
}
The problem is when the response is other than 200, I need to update notifications, but I don't know how to get the data to that reducer.
I can't useDispatch because it is outside a component, and if I import the store to my api files it is outside the context provider and my state is uninitialized.
I'm sure I could use localStorage to solve the problem or some other hack, but I feel I shouldn't have to and I'm wondering if there is a key principle I'm missing when organizing my react-redux project? or if there are standard solutions to this problem.
Thanks - I'm new to redux.
Well, if you are using thunk, then the best solution will be to use it in order to dispatch your action after you get the error.
You do it like this:
export const fetchInitialData = () => {
return dispatch => {
...your logic
else {
// now you can dispatch like this
dispatch(setNotifications(data.status))
}
}
};
I'm beginner with React/Redux.
I want to authenticate a User and display a notification on my app when error occurs.
This is my login function:
const [loading, setLoading] = useState(false);
const dispatch = useDispatch();
const handleSignIn = (values: SignInOpts, setSubmitting: any) => {
setLoading(true);
dispatch(authenticationActions.signInAndFetchUser(values))
.then(res => {
console.log("SignIn Success");
setLoading(false);
})
.catch(err => {
console.log("SignIn Failure");
setLoading(false);
showErrorNotification(
"Error notification"
);
});
};
My action:
export const signInAndFetchUser = (credentials: SignInOpts) => {
return (dispatch, getState) => {
return dispatch(signIn(credentials)).then(res => {
const token = getState().authentication.token;
return dispatch(getMe(token));
});
};
};
The error I have :
How can I perform this ?
Most of your work should happen in the thunk (the action). dispatch does not return a promise. So you have to handle your promise inside your thunk and dispatch the corresponding action, it will then be send to the reducer. The new state will reflects the changes.
Here is a thunk which should give you an idea of how it works :
export const signInAndFetchUser = (credentials: SignInOpts) => {
return (dispatch, getState) => {
dispatch(action.startLoading);
signIn(credentials)
.then((res) => {
// do something with res : maybe recover token, and store it to the store ?
// if the token is in the store already why do you need to signIn ?
dispatch(action.signedInSuccess(res.token));
getMe(res.token)
.then((user) => {
dispatch(action.getMeSucceded(user));
})
.catch((err) => dispatch(action.getMeFailed(err)));
})
.catch((err) => dispatch(action.signInFailed(err)));
};
};
Or using async/await :
export const signInAndFetchUser = (credentials: SignInOpts) => {
return async (dispatch, getState) => {
dispatch(action.startLoading);
try {
const res = await signIn(credentials);
dispatch(action.signedInSuccess(res.token));
try {
const user = await getMe(res.token);
dispatch(action.getMeSucceded(user));
} catch (err) {
dispatch(action.getMeFailed(err));
}
} catch {
dispatch(action.signInFailed(err));
}
};
};
Generally for thunks, dispatch(myThunk()) will return whatever the thunk returns and can be a Promise, as also the case in your signInAndFetchUser method.
The problem is that the normal useDispatch hook typings do not know which middlewares you are using and thus have no overload for that behaviour.
That is why the Redux TypeScript Quickstart Tutorial recommends you define your own, correctly-typed hooks:
// Use throughout your app instead of plain `useDispatch` and `useSelector`
export const useAppDispatch = () => useDispatch<AppDispatch>()
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
If you are using the official Redux Toolkit (especially with TypeScript, you definitely should as it cuts out almost all type annotations), you can just get
export type AppDispatch = typeof store.dispatch
If you are using old-style vanilla Redux, just use ThunkDispatch as AppDispatch.
dispatch do not return promise so you cannot pipe .then. From Action the pipeline flows to reducer, and reducer returns the state back to your component via useSelector for functional component and connect for class based component. So you need to hook those to receive login success object via state
On the outside it seems not an issue, but when I open the DevTools and then go to network tab. It shows that there are 500 requests made. So how can I refactor the code so this will not happens?
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(
"https://raw.githubusercontent.com/XiteTV/frontend-coding-exercise/main/data/dataset.json"
);
dispatch(getData(response.data));
console.log('input');
} catch (err) {
console.log(err);
}
};
fetchData();
}, [dispatch]);
with redux first create a function which will push your request data into redux state like that outside your react component
export const getMyData = () => {
return async (dispatch) => {
const response = await axios.get(
"https://raw.githubusercontent.com/frontend-coding-exercise/main/data/dataset.json"
);
dispatch(getData(response.data));
}
}
create a function that will extract data from redux. state is redux current state, ownProps is your component props, like <Component customProp={1}/>
const mapStateToProps = (state: any, ownProps: any) => {
return {
...ownProps,
myProps: state.user
};
};
In your connect Function pass the function which will store your data in redux state
export default connect(mapStateToProps, { getMyData })(MyReactComponent);
that way you'll be able to access your data via your component props and also access your getMyData function and also the redux state you mapped to props
props.getMyData();
I need to trigger firestore realtime listener on login to listen to user profile data changes and cancel it before logout. To do that I need to save realtime listener in the store where I get stuck. I'm trying to do this in redux
export const cancelListener = (cancelListener) => {
return {
type: actionTypes.CANCEL_LISTENER,
cancelListener: cancelListener
}
}
export const uDataListener = (uid) => {
return dispatch => {
dispatch(uDataStart())
const dbRef = db.collection("user").doc(uid)
const cancelSubscription = dbRef
.onSnapshot(
(doc) => {
dispatch(uDataSuccess(doc.data()))
}
, ((error) => {
dispatch(uDataFail(error.message))})
);
dispatch(cancelListener(cancelSubscription))
}
}
and on logout simply call it from the redux store
export const logout = (cancelListener) => {
cancelListener()
fire.auth().signOut()
return {
type: actionTypes.AUTH_LOGOUT
}
}
However nothing is being saved in cancelListener therefore it can not be triggered. How do I accomplish this task? Please
Thanks
I have woken up in the middle of the night with other idea. I tried to add the method in the constant in action instead of saving the method in the redux state or reducer. I'm not sure if this is the best approach but it does the job. Now I just don't understand why I didn't try this approach in the first place. Here is the code which will need a bit of tweaks yet but it works
let cancelListener = null
export const logout = () => {
cancelListener()
fire.auth().signOut()
return {
type: actionTypes.AUTH_LOGOUT
}
}
export const auth = (email, password) => {
return dispatch => {
dispatch(authStart())
fire.auth().signInWithEmailAndPassword(email, password).then((u) => {
dispatch(authSuccess(u.user))
const dbRef = db.collection("user").doc(u.user.uid)
cancelListener = dbRef.onSnapshot((doc) => {
dispatch(saveUserData(doc.data()))
})
}).catch((error) => {
dispatch(authFailed(error.message))
});
}
}
Thank you very much for your help anyway. I really appreciate that
Just a quick thought, in uDataListener call an action e.g. START_LISTENER and in reducer you can have:
import { store } from './yourStore';
let cancelListener, dbRef;
function reducer(state, action) {
switch (action.type) {
case "START_LISTENER":
dbRef = db.collection("user").doc(action.uid)
cancelSubscription = dbRef.onSnapshot(function(doc) {
store.dispatch(
yourAction(doc.data()); //Dispatch new action using store
)
})
return state;
case "STOP_LISTENER":
cancelListener()
return state;
default:
return state;
}
STOP_LISTENER will be dispached when you are doing logout
Below you can see link how to dispatch from outside a component
Update React component by dispatching action from non-react component
I'd like to know in which different ways can we use async/await in a React Redux (with thunk)
and if there are good conventions, what are they? I think it'll be nice to have:
In the container, not have to use the promise then(), as in this.props.myAction.then(...)
Can we NOT have the async keyword in the class method, but in the method body instead? For example:
async doSomething () {
const data = await this.props.myAction()
console.log(data)
}
// But maybe (it's failing for me, if the action is similar to my working action example *see below)
doSomething () {
const handler = async () => await this.props.myAction() // I guess the await here is redundant, but added to be clear to you
const data = handler()
console.log(data)
}
My working solution at the moment follows:
// MOCK data
const MOCK_DATA = [1, 2, 3, 4, 5]
// Action
export function myAction (payload) {
return async (dispatch) => {
const getter = () => {
const promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve({serverResponse: MOCK_DATA})
}, 1200)
})
return promise
}
try {
return await getter()
} catch (error) {
console.log(error)
}
}
}
// Container
class Foobar extends Component {
async doSomething () {
const data = await this.props.myAction()
}
render () {
return (
<div>
<button onClick={this.doSomething}>Do something!</button>
</div>
)
}
}
The problem with "await" is you are Blocking the event loop and with Thunk you have to handle the Promises & dispatcher directly.
There is an alternative to Thunk that is more easy to use. redux-auto
from the documantasion
redux-auto fixed this asynchronous problem simply by allowing you to create an "action" function that returns a promise. To accompany your "default" function action logic.
No need for other Redux async middleware. e.g. thunk, promise-middleware, saga
Easily allows you to pass a promise into redux and have it managed for you
Allows you to co-locate external service calls with where they will be transformed
Naming the file "init.js" will call it once at app start. This is good for loading data from the server at start
The idea is to have each action in a specific file. co-locating the server call in the file with reducer functions for "pending", "fulfilled" and "rejected". This makes handling promises very easy.
You example would look like this:
// MOCK data
const MOCK_DATA = [1, 2, 3, 4, 5]
// data/serverThing.js
export default function (data, payload, stage, result) {
switch(stage){
case 'FULFILLED':
return result.serverResponse;
case 'REJECTED':
const error = result;
console.log(error)
case 'PENDING':
default :
break;
}
return data;
}
export function action (payload) {
return Promise.resolve({serverResponse: MOCK_DATA})
}
// Container
import React from "react"
import actions from 'redux-auto'
import { connect } from 'react-redux'
class Foobar extends Component {
const loading = (true === this.props.data.async.serverThing) ? "loading..." : "";
render () {
return (
<div>
<button onClick={()=>actions.data.serverThing()}>Do something!</button> { loading }
</div>
)
}
}
const mapStateToProps = ( { data }) => {
return { data }
};
export default connect( mapStateToProps )(Foobar);
It also automatically attaches a helper object(called "async") to the prototype of your state, allowing you to track in your UI, requested transitions.