I'm trying to achieve optimistic UI loading with react and redux. This is my reducer:
const initState = {
user: {},
loading: true,
};
export default function (state = initState, action) {
switch (action.type) {
case GET_CURRENT_USER_BY_PROFILEID_LOADING:
return {
...state,
loading: true,
};
case GET_CURRENT_USER_BY_PROFILEID:
return {
...state,
loading: false,
user: action.payload.item,
};
default:
return state;
}
}
This is action creator:
export const getCurrentUserByProfileId = (profileId) => {
return (dispatch) => {
dispatch({ type: GET_CURRENT_USER_BY_PROFILEID_LOADING });
baseUrl
.get(
Constants.RELATIVEPATH +
Constants.USERS +
'/' +
profileId +
Constants.PROFILEID,
{ headers }
)
.then((response) => {
const data = response.data;
dispatch({
type: GET_CURRENT_USER_BY_PROFILEID,
payload: { item: data },
});
})
.catch((error) => {
console.log(error);
onError(error);
});
};
};
The ideal scenario would be: Loading --> New state and I am getting this only on the first load, after that it's: Flash of old state --> Loading --> New state
You could dispatch the GET_CURRENT_USER_BY_PROFILEID_LOADING in a different function, before displaying the component to the user.
That way you would have:
old state (not displayed)
GET_CURRENT_USER_BY_PROFILEID_LOADING
navigation, or whatever you do display the component
loading (displayed)
GET_CURRENT_USER_BY_PROFILEID
new state (displayed)
Related
In my action i am dispatching the type and the payload but what if i also want the res.status and a return JSON message to be included into my props. How would i do so in my action and reducer?
action
export const fetchUserPosts = () => (dispatch) => {
fetch(`${currentPort}/user/recipes`,
{
withCredentials: true,
credentials: 'include',
})
.then((res) => {
if (res.status !== 401) return res.json().then((data) => data);
return { message: { msgBody: 'UnAuthorized' }, msgError: true };
})
.then((posts) => dispatch({
type: FETCH_USER_POSTS,
payload: posts,
}));
};
reducer
export default function (state = initialState, action) {
switch (action.type) {
case FETCH_USER_POSTS:
return {
...state,
fetchUsersPosts: action.payload,
};
default:
return state;
}
}
You can combine multiple items into a single payload. I would create different actions for success and error. Using the same action complicates the reducers logic. It's also easier to work with async/await then with nested promises.
This is a working example that uses SpaceX open API:
const FETCH_USER_POSTS_SUCCESS = 'FETCH_USER_POSTS_SUCCESS'
const FETCH_USER_POSTS_FAILED = 'FETCH_USER_POSTS_FAILURE'
const fetchPostSuccessAction = (payload) => ({
type: 'FETCH_USER_POSTS_SUCCESS',
payload,
})
const fetchPostFailureAction = (payload) => ({
type: 'FETCH_USER_POSTS_FAILURE',
payload,
})
const fetchUserPosts = () => async dispatch => {
const res = await fetch('https://api.spacexdata.com/v3/launches/latest');
if (res.status !== 401) {
const { ships: posts } = await res.json();
dispatch(fetchPostSuccessAction({
posts,
status: res.status,
}))
} else {
dispatch(fetchPostFailureAction({
message: { msgBody: 'UnAuthorized' },
}))
}
};
fetchUserPosts()(console.log)
The reducer can handle the object by destructuring it, and the properties to the new state in any way you need. You can also change other properties, for example changing errMsg to true or false according to the action's type:
export default function (state = initialState, { type, payload }) {
switch (type) {
case FETCH_USER_POSTS_SUCCESS: {
const { posts, status } = payload;
return {
...state,
status,
fetchUsersPosts: posts,
msgError: false,
message: null
};
}
case FETCH_USER_POSTS_FAILURE: {
const { message } = payload;
return {
...state,
status: 401,
fetchUsersPosts: null,
msgError: true,
message
};
}
default:
return state;
}
}
If I am following correctly you are using this action inside of a component to send a fetch. You don't have access to the components props with the reducer. You can send the http request in the component and use that to store the response in the state. Or use connect from 'react-redux' package to map the redux store to access the fetch result.
import { connect } from 'react-redux'
const component = props => {
//to access redux state in component use props.myprop
return <div>{props.myprops.title}</div>
}
const mapStateToProps = state => {
return{
myprop: state.fetchUsersPosts
}
}
export default connect(mapStateToProps)(component)
If this was what you were looking for you can learn more at https://react-redux.js.org/api/connect
I'm using redux-saga to fetch an endpoint and want to present it on first page load using useEffect(). But mine is not fetching anything. The screen is blank and reduxDevTools is also not showing anything. I can't understand what did I miss.
My saga:
export function* watcherSaga() {
yield takeLatest("FETCH_TOP_NEWS_REQUEST", workerSaga);}
function fetchTopNews() {
return axios({
method: 'get',
url: 'https://newsapi.org/v2/top-headlines?country=us&apiKey=API_KEY'
});}
function* workerSaga() {
try{
const response = yield call(fetchTopNews);
const news = response.data.articles;
yield put({ type: "FETCH_TOP_NEWS_SUCCESS", news });
}
catch (error) {
yield put({ type: "FETCH_TOP_NEWS_ERROR", error });
}
}
I defined 3 actions:
const initialState = {
fetching: false,
error: null,
news: []
};
const NewsReducer = (state=initialState, action) => {
switch(action.type){
case types.fetchTopNewsRequest:
return { ...state, fetching: true, error: null };
case types.fetchTopNewsSuccess:
return { ...state, fetching: false, news: action.news[0] };
case types.fetchTopNewsError:
return { ...state, fetching: false, news: null, error: action.error };
default:
return state;
}
}
export default NewsReducer;
At last my component, I imported the fetchTopNewsRequest() action here:
const TopHeadline = (props) => {
const { news, getTopNews } = props;
useEffect(() => {
getTopNews();
}, [getTopNews]);
return (
<div className="newsItem">
<h1>Title: {news.title}</h1>
</div>
);}
const mapStateToProps= (state) => {
return {
news: state.news,
};
};
const mapDispatchToProps = (dispatch) => {
return {
getTopNews: () => dispatch( fetchTopNewsRequest() )
};
};
export default connect(mapStateToProps, mapDispatchToProps)(TopHeadline);
I'm trying to fetch only the articles.title.
DevTools shows it's successfully fetching the data:
Buy my states are not updating:
I'm trying to fetch props into my componentDidUpdate method.
Sadly second action from .then block doesn't dispatch.
I have the following:
My AddPerson.js which is a form to add Person. There I have following states:
constructor(props) {
super(props);
this.state = {
loading: false,
firstName: '',
secondName: '',
email: '',
date: '',
};
}
Whole is connected to redux:
function mapDispatchToProps(dispatch) {
return {
addPerson: data => dispatch(addPerson(data))
};
}
const mapStateToProps = state => {
return { data: state.data };
};
const Person = connect(
mapStateToProps,
mapDispatchToProps
)(AddPerson);
export default Person;
Then I have action dispatcher like that:
export const addPerson = (payload) => {
return dispatch => {
dispatch(addTodoStarted());
axios
.post(PATH + '/attendant', {
payload,
})
.then(res => {
dispatch(addTodoSuccess(res));
})
.catch(err => {
dispatch(addTodoFailure(err));
});
};
};
const addTodoSuccess = payload => ({
type: ADD_PERSON,
data: {
payload
}
});
const addTodoStarted = () => ({
type: ADD_PERSON,
data:
"loading"
});
const addTodoFailure = error => ({
type: ADD_PERSON,
data: {
error
}
});
And my reducer:
function reducer(state = {} , action) {
switch (action.type) {
case ADD_PERSON:
return Object.assign({}, state, {
data: action.data,
})
default:
return state;
}
}
export default reducer;
When fetch happens in the action, there is firstly dispatched type of action loading then after promise solves I want to dispatch action that is in .then block. What am I missing?
EDIT:
My componentDidUpdate looks like that:
componentDidUpdate(prevProps) {
console.log(prevProps)
if (prevProps.data !== this.state.data) {
console.log(prevProps.data)
}
}
I'm trying to implement an isFetching flag that indicates when my data is ready for rendering. But even if the flag works, i.e. jumps from isFetching = true to isFetching = false after the data has been successfully requested, there is still an error when I try to access data: cannot read property 'username' of null
Profile Component
class Profile extends React.Component {
render() {
const (isFetching, profile) = this.props.profile
console.log (isFetching)
console.log (profile)
return <h1>Hello, {isFetching = "false"? profile[0].username : null}</h1>;
}
}
function mapStateToProps(state, ownProps) {
const profile= state.profile
return { profile }
};
export default connect(
mapStateToProps,
{ logout }
)(Profile);
Action
export const getProfile = () => (dispatch, getState) => {
// Profile Loading
dispatch({ type: GET_PROFILE_REQUEST });
axios
.get(apiBase + "/profile/", tokenConfig(getState))
.then(res => {
dispatch({
type: GET_PROFILE_SUCCESS,
payload: res.data
});
})
.catch((err) => {
dispatch(returnErrors(err.response.data, err.response.status));
dispatch({
type: GET_PROFILE_FAILURE,
});
});
};
Reducer
const initialState = {
isFetching: false,
profile: null
};
export default function(state = initialState, action) {
switch (action.type) {
case GET_PROFILE_REQUEST:
return {
...state,
isFetching: true
};
case GET_PROFILE_SUCCESS:
return {
...state,
profile: action.payload,
isFetching: false
};
case GET_PROFILE_FAILURE:
return {
...state,
profile: action.payload,
isFetching: false
};
default:
return state;
}
}
Redux log for GET_PROFILE_SUCCESS
profile
isFetching: false
profile[
{
"username": "Daniel",
"id": 1,
"profile": {
"image": "Test",
"bio": "Test"
}
}
]
I'm happy for every clarification.
You have a small error in your code.
return <h1>Hello, {isFetching = "false"? profile.username : null}</h1>;
You are not checking for the value of isFetching but rather setting it again. Also, since profile is an array, you need to get the first element.Replace it with
return <h1>Hello, {!isFetching? profile[0].username : null}</h1>;
and it should work.
In route.jsx my path looks like:
<Route path='/my-work/:workName' component={WorkShow} />
My link looks like:
<Link to={`/my-work/${props.work.name}`}>
I want to display the name in the url, but pass the id into the params instead so that I can fetch this in my component and pass it into the api.
My api path takes in the ID as a parameter but I want to avoid displaying this in the URL.
How would I store this as a key value pair....
Here is my reducer.js:
import Constants from '../constants';
const initialState = {
projects: [],
project: [],
fetching: true,
};
export default function reducer(state = initialState, action = {}) {
switch (action.type) {
case Constants.PROJECTS_FETCHING:
return Object.assign({}, state, { fetching: true });
case Constants.PROJECTS_RECEIVED:
return Object.assign({}, state, { projects: action.proj, fetching: false });
case Constants.PROJECT_FETCHING:
return Object.assign({}, state, { fetching: true });
case Constants.PROJECT_RECEIVED:
return Object.assign({}, state, { project: action.project, fetching: false });
default:
return state;
}
}
And here is my action.js:
import Request from 'superagent';
import Constants from '../constants';
const Actions = {
fetchProjects: () => (dispatch) => {
dispatch({ type: Constants.PROJECTS_FETCHING });
Request.get('/api/v1/projects')
.then((data) => {
dispatch({
type: Constants.PROJECTS_RECEIVED,
proj: data.body,
});
});
}, fetchProject: projectId => (dispatch) => {
dispatch({ type: Constants.PROJECT_FETCHING });
Request.get('/api/v1/projects/' + projectId)
.then((data) => {
dispatch({
type: Constants.PROJECT_RECEIVED, project: data.body,
});
});
},
};
export default Actions;