My component shows incorrect value from store.
The request was successful and after request I've got fetching: false and contacts list in Redux state. But component doesn't update, console.log(fetching) shows true and UI shows "Loading".
In Redux dev tools there's only 'GET_CONTACTS_SUCCESS' action.
Why component doesn't update, where can be the problem? Seems like I do everything as in this answer https://stackoverflow.com/a/64614396/12994741
Component:
const { contacts, fetching } = useSelector(state => state.contact);
useEffect(() => {
dispatch({ type: 'GET_CONTACTS_REQUEST' });
}, [dispatch])
console.log(fetching);
return <div>
{fetching
? <p> Loading </p>
: (contacts.map(contact => <div key={contact.id}>{contact.id}</div>)}
</div>
Saga:
function* getContactsSaga() {
try {
const response = yield call('...');
if (response && response.data) {
yield put({
type: 'GET_CONTACTS_SUCCESS',
items: response.data,
})
} else {
yield put({
type: 'GET_CONTACTS_FAILED',
message: 'Data Access Error. Please Try again later',
});
}
} catch (e) {
yield put({
type: 'GET_CONTACTS_FAILED',
message: e.response.statusText,
});
}
}
function* contactSaga() {
yield takeLatest('GET_CONTACTS_REQUEST', getContactsSaga);
}
Reducer:
const initialState = {
error: '',
fetching: false,
contacts: [],
};
const ContactReducer = (state = initialState, action) => {
switch (action.type) {
case 'GET_CONTACTS_REQUEST':
return {
...state,
fetching: true,
};
case 'GET_CONTACTS_SUCCESS':
return {
...state,
error: '',
fetching: false,
contacts: action.items,
};
case 'GET_CONTACTS_FAILED':
return {
...state,
fetching: false,
error: action.message,
};
default:
return state;
}
}
Related
I have useReducer and I have action type for fetching data inside useEffect and dispatch it like this.
function reducer(state, action) {
switch (action.type) {
case 'FETCH_REQUEST':
return { ...state, loading: true, error: '' };
case 'FETCH_SUCCESS':
return { ...state, loading: false, orders: action.payload, error: '' };
case 'FETCH_FAIL':
return { ...state, loading: false, error: action.payload };
default:
return state;
}
}
export default function OrderHistoryScreen() {
const [{ loading, error, orders }, dispatch] = useReducer(reducer, {
loading: true,
orders: [],
error: '',
});
useEffect(() => {
const fetchOrders = async () => {
try {
dispatch({ type: 'FETCH_REQUEST' });
const { data } = await axios.get(`/api/orders/history`);
dispatch({ type: 'FETCH_SUCCESS', payload: data });
} catch (err) {
dispatch({ type: 'FETCH_FAIL', payload: getError(err) });
}
};
fetchOrders();
}, []);
how can I use SWR for this condition SWR cant be called inside useEffect and I want to use SWR to dispatch it to avoid fetching with useEffect
I'm struggling with react-redux variable for hours...hope someone can help.
The conditional returns to me that the variable order.name is not defined, although everything goes as it should in the reducer and action.
When isLoading === true, it continues rendering {order.name} and I know it is not defined at that point because it takes some time. At that time i set Loader to do it's job..
So it’s not clear to me why he continues to render even though there’s a conditional one that shouldn’t allow it... until isLoading === false.
Here is console.log of orderDetails
import { getOrderDetailsAction } from "../actions/orderAction";
const OrderScreen = ({ match }) => {
const orderId = match.params.id;
const dispatch = useDispatch();
useEffect(() => {
dispatch(getOrderDetailsAction(orderId));
}, [dispatch, orderId]);
const orderDetails = useSelector((state) => state.orderDetails);
const { order, isLoading } = orderDetails;
return isLoading ? <Loader /> : <>{order.name}</>;
};
export default OrderScreen;
Reducer
export const orderDetailsReducers = (
state = { isLoading: true, orderItems: [], shippingAddress: {} },
action
) => {
switch (action.type) {
case ORDER_DETAILS_REQUEST:
return {
...state,
isLoading: true,
};
case ORDER_DETAILS_SUCCESS:
return {
isLoading: false,
order: action.payload,
};
case ORDER_DETAILS_FAILED:
return {
isLoading: false,
error: action.payload,
};
default:
return { state };
}
};
Action
export const getOrderDetailsAction = (id) => async (dispatch, getState) => {
try {
dispatch({
type: ORDER_DETAILS_REQUEST,
});
//Getting TOKEN
const {
userLogin: { userInfo },
} = getState();
//Passing TOKEN
const config = {
headers: {
"auth-token": `${userInfo.token}`,
},
};
const { data } = await axios.get(`/api/orders/${id}`, config);
dispatch({
type: ORDER_DETAILS_SUCCESS,
payload: data,
});
} catch (error) {
dispatch({
type: ORDER_DETAILS_FAILED,
payload: error.response.data.msg,
});
}
};
Check redux action for how isLoading state is changed from redux dev tools
Check reducer name -> state.orderDetails (does this exists ?)
const orderDetails = useSelector((state) => state.orderDetails);
Also, we can correct this
state = { isLoading: true, orderItems: [], shippingAddress: {}, order: {} }
// return entire state and not just isLoading and order
case ORDER_DETAILS_SUCCESS:
return {
...state, <--------
isLoading: false,
order: action.payload,
};
case ORDER_DETAILS_FAILED:
return {
...state, <---------
isLoading: false,
error: action.payload,
};
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 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.
The problem is that when on of my reducer updates its own state, it also updates the state of another reducer.
//authActions.js
export const authActions = {
login: (props) => dispatch => {
// This will make sure the loading spinner will appear.
dispatch({
type: POST_LOGIN_PENDING,
payload: null
})
// make request to login user
axios.post(LOGIN_ENDPOINT, {
email: props.email,
password: props.password
}).then(res => dispatch({
type: POST_LOGIN_FULFILLED,
payload: res.data
})
).catch( () => dispatch({
type: POST_LOGIN_REJECTED,
payload: null
}))
},
logout: () => dispatch => {
dispatch({
type: LOGOUT,
payload: null
})
},
// authReducer.js
export const initialState = {
token: "",
userRole: "",
isLoading: false,
loginFailed: false,
isAuthenticated: false,
}
export function authReducer(state = initialState, action) {
switch (action.type) {
case POST_LOGIN_PENDING:
return {
...state,
isLoading: true,
}
case POST_LOGIN_FULFILLED:
return {
...state,
token: action.payload.token,
userRole: action.payload.userRole,
loginFailed: false,
isAuthenticated: true,
isLoading: false,
}
case POST_LOGIN_REJECTED:
return {
...state,
loginFailed: true,
isLoading: false,
}
// studentActions.js
export const studentActions = {
getAllStudents: props => dispatch => {
dispatch({
type: GET_ALL_STUDENTS_PENDING,
payload: null,
})
axios.get(STUDENTS_ENDPOINT, {
headers: {
'Authorization': `Bearer ${props.token}`
}
})
.then(res =>
dispatch({
type: GET_ALL_STUDENTS_FULFILLED,
payload: res.data
}))
.catch(err => dispatch({
type: GET_ALL_STUDENTS_FULFILLED,
payload: err
}))
},
// studentReducer.js
export const initialState = {
students: [],
err: "",
isLoading: false,
}
export function studentReducer(state = initialState, action) {
switch (action.type) {
case GET_ALL_STUDENTS_PENDING:
return {
...state,
isLoading: true,
}
case GET_ALL_STUDENTS_FULFILLED:
return {
...state,
students: action.payload,
isLoading: false,
}
case GET_ALL_STUDENTS_REJECTED:
return {
...state,
err: action.payload,
isLoading: false,
}
case DELETE_STUDENT_BY_ID_FULFILLED:
return state
default:
return state
}
}
When a user logs in and the POST_LOGIN_FULFILLED applies. I would expect only the initialstate of the authReducer to be updated, but when inspect with the redux devtools I can see that that the array "studens" which is part of the initialstate of the studentReducer also is updated. From what I understand this should not be possible.
After the user has logged in the students array is filled: (From redux devtools)
student: {
students: [] => {....some stuff}
isLoading: true => false
}
By reading you comments it looks like that GET_ALL_STUDENTS_FULFILLED refers to POST_LOGIN_FULFILLED . This must be the reason why your students array is updated. Change
export const GET_ALL_STUDENTS_PENDING = 'POST_LOGIN_PENDING';
export const GET_ALL_STUDENTS_REJECTED = 'POST_LOGIN_REJECTED';
export const GET_ALL_STUDENTS_FULFILLED = 'POST_LOGIN_FULFILLED';
to
export const GET_ALL_STUDENTS_PENDING = 'GET_ALL_STUDENTS_PENDING ';
export const GET_ALL_STUDENTS_REJECTED = 'GET_ALL_STUDENTS_REJECTED ';
export const GET_ALL_STUDENTS_FULFILLED = 'GET_ALL_STUDENTS_FULFILLED ';
Action types should be unique or else it might get triggered by some other action