I have action creator to get data from API and have another action creator for loading status and want change loading status when data completely fetched.
Now, I wrote following codes but not working good, Loading status changes to false before data fetched completely.
My ActionCreator:
export const loadingStatus = (bool) => {
return {
type: Constants.LOADING_STATUS,
isLoading: bool
};
}
const allFlashCards = (action) => {
return{
type: Constants.ALL_CARD,
...action
}
};
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then((data)=>{
console.warn(data);
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
and my Reducer:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action};
break;
default:
return state;
}
};
export const Loading = (status= false, action) => {
switch (action.type) {
case Constants.LOADING_STATUS:
return action.isLoading;
break;
default:
return status;
}
}
and in my component:
componentDidMount() {
this.props.fetchCards();
}
render() {
return(
<div>
{this.props.loading ?
<Loading/> :
Object.keys(this.props.cards.data).map(this.renderCard)
}
</div>
);
}
const mapStateToProps = (state) => ({
cards: state.main,
loading: state.loading
});
const mapDispatchToProps = (dispatch) => ({
fetchCards: bindActionCreators(fetchAllFlashCards, dispatch)
});
and combineReducer is:
import { combineReducers } from 'redux';
import FlashCard , { Loading } from './FlashCard.js';
import { routerReducer } from "react-router-redux";
export default combineReducers({
main: FlashCard,
loading: Loading,
routing: routerReducer
});
In my page, I have an error in console and it's:
Uncaught TypeError: Cannot read property 'data' of undefined and if put my codes in timeout fixed my bug :/
What should i do?
Your default state is wrong here:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action};
break;
default:
return state;
}
};
It should be an empty object {} instead of an empty array [], since you're returning objects.
This code
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then((data)=>{
console.warn(data);
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
Looks completely fine. loadingStatus(false) should not be called before setting the flash cards. Your reducers and action creators are synchronous (as they should). So, nothing of note there.
I saw that you're using action.data on the Constants.ADD_CARD action case, but in your code you do not dispatch any actions with that type. Do you do it somewhere else? Maybe that's where the error is?
EDIT:
Another place that you're using .data is in your renderer: this.props.cards.data. What's the value of the state.main?
How are you creating your rootReducer? It should be something like this:
const rootReducer = combineReducers({
main: FlashCard,
loading: Loading,
});
Are you using main there? Or maybe cards?
Finally, I fixed my problem:
In my actionCreator change fetchAllFlashCards method to following:
export const fetchAllFlashCards = () => {
return (dispatch) => {
dispatch(loadingStatus(true));
return axios.post(API.DISPLAY_ALL_CARDS)
.then(({data})=>{
dispatch(allFlashCards(data));
dispatch(loadingStatus(false));
}).catch((error)=>{
console.warn(error)
});
}
};
and in reducer change FlashCard reducer to following:
const FlashCard = (state = [], action) => {
switch (action.type) {
case Constants.ADD_CARD:
return {...state, data: action.data};
break;
case Constants.ALL_CARD:
return {...state, data: action.data};
break;
default:
return state;
}
};
Related
I have the following Reducer:
const initialState = {}
const dishReducer = (state = initialState, action) => {
switch (action.type) {
case 'LOAD_DISHES':
return (action.dishes)
case 'LOAD_DISHES_ERROR':
console.log("load dishes error")
return state
case 'LOAD_DISHES_SUCCESS':
console.log("load dishes success")
return state
default:
return state;
}
};
export default dishReducer;
And the following action(s):
import {database} from '../../config/fbConfig'
export const startLoadingDishes = (dishes) => {
return (dispatch) =>{
return database.ref('products-dishes').once('value').then((snapshot) => {
let dishes = {}
snapshot.forEach((childSnapshot) => {
let parentkey = childSnapshot.key
let dishArray = [];
childSnapshot.forEach((dish) =>{
dishArray.push(dish.val())
});
dishes[childSnapshot.key] = dishArray;
})
dispatch(loadDishes(dishes))
}).then(() => {
dispatch({ type: 'LOAD_DISHES_SUCCESS' });
}).catch(err => {
dispatch({ type: 'LOAD_DISHES_ERROR' }, err);
});
}
}
export const loadDishes = (dishes) => {
return {
type: 'LOAD_DISHES',
dishes
}
}
The 'startLoadingDishes' action is called inside the componentDidLoad() of a certain Component. However, I want to alter the initial state of my dishReducer so that it includes additional information, as follows:
const initialState = {
value : {},
loaded: false,
loading: false,
error: false
}
So now 'action.dishes' returned by reducer [in 'LOAD_DISHES' case] should be put inside the 'value' part of the state, instead of it being the whole state. Also, the 'loaded' part of the state should be set to true if dishes have already been loaded earlier, and so on. I understand this is fairly simple but as I am new to React+Redux, I don't know how to alter the Action/Reducer codes properly (while keeping state immutability). Any help is appreciated.
I originally asked the question, here is how I solved it (not sure if this is the 'best' way though):
New reducer file:
const initialState = {
value : {},
loaded: false,
loading: false,
error: false
}
const dishReducer = (state = initialState, action) => {
switch (action.type) {
case 'LOAD_DISHES':
return {
value: action.dishes,
loading: !state.loading,
loaded: false, //this might need to be set to true
error: false
}
case 'LOAD_DISHES_ERROR':
console.log("load dishes error")
return {
...state, //or: state.value, as the other parts of state are being overwritten below
loaded: false,
loading: false,
error: true
}
case 'LOAD_DISHES_SUCCESS':
console.log("load dishes success")
return {
...state, //better: state.value
loaded: true,
loading: false,
error: false
}
default:
return state;
}
};
export default dishReducer;
No change in actions file.
Now, inside the 'Main' component, I was originally accessing the state as such:
class Main extends Component {
componentDidMount() {
this.props.startLoadingDishes();
}
render() {
return (
//some code
)
}
}
const mapStateToProps = (state) => {
return {
dishes: state.dishes //to access dishes: dishes.value
}
}
export default connect(mapStateToProps, actions)(Main)
The Main component code also stayed the same, with the difference that now I use 'dishes.value' instead of just 'dishes' to access the value of dishes from the state (and dishes.loaded for loaded, and so on). And now the action caller inside componentDidMount is as follows:
componentDidMount() {
if(!this.props.dishes.loaded){
this.props.startLoadingDishes();
console.log("loading dishes from database")
}
}
I'm refactoring a React application to use hooks and have found some success, but I think I am incorrectly dispatching actions (using useReducer) within my application. I've discovered that the state doesn't change immediately when using hooks, and it's causing my application to behave differently than it's class-based counterpart. Here's a piece of my code that dispatches state in which I used console.logs to see if the state has changed or not. In both instances where state.gameOver is logged to the console, it's false, as well as state.userIsWrong. I've attached their actions my reducer as well. Thank you for the help in advance!
const wrongAnswer = () => {
dispatch(toggleGameOver());
console.log(`game over1 ` + state.gameOver)
console.log('wrong answer')
sounds["wrong"].play();
//a delay is used to so that the header will return back to "Click me to begin game" or the current level of the game
//and to return the background to normal
setTimeout(() => {
dispatch(toggleGameOver());
console.log(`game over2` + state.gameOver)
dispatch(turnOnUserIsWrong());
},500)
}
turnOnUserIsWrong action from action.js
export const turnOnUserIsWrong = () => ({
type: 'TURN_ON_USER_IS_WRONG'
})
reducer.js
export default (state, action) => {
switch (action.type) {
case 'SET_ACTIVE_STYLE':
return {
...state,
activeStyle: action.style
}
case 'UPDATE_LAST_COLOR':
return {
...state,
lastColor: action.color
}
case 'UPDATE_USER_PATTERN':
return {
...state,
userPattern: [...state.userPattern, action.id]
}
case 'UPDATE_GAME_PATTERN':
return {
...state,
gamePattern: [...action.newGamePattern]
}
case 'TOGGLE_PRESSED':
console.log(action.color)
return {
...state,
pressed: action.color
}
case 'TURN_ON_READY_FOR_USER_INPUT':
console.log(`here`)
return {
...state,
readyForUserInput: true
}
case 'TURN_OFF_READY_FOR_USER_INPUT':
return {
...state,
readyForUserInput: false
}
case 'RESET_GAME':
return {
...state,
gamePattern: [],
userPattern: [],
lastColor: "",
level: 0,
gameStarted: false,
userIsWrong: false,
readyForUserInput: false,
activeStyle: '',
strictRestart: false
}
case 'UPDATE_LEVEL':
return {
...state,
level: state.level + action.level
}
case 'TURN_OFF_USER_IS_WRONG':
return{
...state,
userIsWrong: false
}
case 'TURN_ON_USER_IS_WRONG':
return{
...state,
userIsWrong: true
}
case 'TOGGLE_STRICT_MODE':
return {
...state,
strictMode: !state.strictMode
}
case 'TOGGLE_GAME_STARTED':
return {
...state,
gameStarted: !state.gameStarted
}
case 'TOGGLE_GAME_OVER':
return {
...state,
gameOver: !state.gameOver
}
case 'EMPTY_USER_PATTERN':
return {
...state,
userPattern: []
}
case 'SET_PLAYER_LEVEL':
return{
...state,
level: action.level
}
default:
return {
...state
};
}
}
Not sure how you're retrieving your state using hooks, but I'm currently working on a React App using only hooks, I'll leave you an example that I hope it helps you:
import React from 'react';
import { useDispatch, useSelector } from 'react-redux';
//YOUR OTHER IMPORTS
const YOURCOMPONENTNAME = (props) => {
const gameOver = useSelector((state) => state.YOURREDUCERNAME.gameOver);
const dispatch = useDispatch();
const onToggleGameOver = () => dispatch(toggleGameOver());
const onTurnOnUserIsWrong = () => dispatch(turnOnUserIsWrong());
const wrongAnswer = () => {
onToggleGameOver();
console.log(`game over1 ` + gameOver)
console.log('wrong answer')
sounds["wrong"].play();
//a delay is used to so that the header will return back to "Click me to begin
//game" or the current level of the game and to return the background to normal
setTimeout(() => {
onToggleGameOver();
console.log(`game over2` + gameOver)
onTurnOnUserIsWrong();
},500)
}
// MORE CODE
}
export default YOURCOMPONENTNAME;
Not sure if this is of any help for you, hope it is. In case it´s not, I hope you find your answer!!
I'm fairly new to redux & thunk, and have been following tutorials to try and understand, and am managing to work it into my app ok. One thing i'm not understanding, is how i can get several state objects on the root level into one nested object. For example, right now my state looks like:
{
timeline: [Array] // My timeline data in an array of objects
timelineHasErrored: false,
timelineIsLoading: false
}
But what I really want is:
{
timeline : {
data: [Array] // My timeline data in an array of objects
hasErrored: false,
isLoading: false
}
}
and i'm really not quite sure how to nest these, or what the proper way to do that is. Below is my redux code, it's pretty simple so i'll post it all.
Reducers index
import { combineReducers } from 'redux'
import { timeline, timelineHasErrored, timelineIsLoading } from './timeline'
export default combineReducers({
timeline, timelineHasErrored, timelineIsLoading
});
Timeline Reducers
import { TIMELINE_HAS_ERRORED, TIMELINE_IS_LOADING, TIMELINE_FETCH_DATA_SUCCESS } from '../constants/action-types.js'
export function timelineHasErrored(state = false, action) {
switch (action.type) {
case TIMELINE_HAS_ERRORED:
return action.hasErrored;
default:
return state;
}
}
export function timelineIsLoading(state = false, action) {
switch (action.type) {
case TIMELINE_IS_LOADING:
return action.isLoading;
default:
return state;
}
}
export function timeline(state = [], action) {
switch (action.type) {
case TIMELINE_FETCH_DATA_SUCCESS:
return action.timeline;
default:
return state;
}
}
Actions
import { TIMELINE_HAS_ERRORED, TIMELINE_IS_LOADING, TIMELINE_FETCH_DATA_SUCCESS } from '../constants/action-types.js'
import api from '../services/api'
export function timelineHasErrored(bool) {
return {
type : TIMELINE_HAS_ERRORED,
hasErrored : bool
}
}
export function timelineIsLoading(bool) {
return {
type : TIMELINE_IS_LOADING,
isLoading : bool
}
}
export function timelineFetchDataSuccess(timeline) {
return {
type : TIMELINE_FETCH_DATA_SUCCESS,
timeline
}
}
export function timelineFetchData() {
return dispatch => {
dispatch( timelineIsLoading(true) )
api.getTracks().then(
res => {
dispatch( timelineIsLoading(false) )
dispatch( timelineFetchDataSuccess(res.body) )
},
err => {
dispatch( timelineIsLoading(false) )
dispatch( timelineHasErrored(true) )
}
)
}
}
And then in my react component I format the object like how i want it... but i think it would be better to have it nested in the actual state so i'm not creating extra work for myself if things change
// Redux State
const mapStateToProps = (state) => {
const obj = {
timeline : {
data : state.timeline,
hasErrored: state.tracksHasErrored,
isLoading: state.tracksIsLoading
}
}
return obj
}
// Redux Dispatch
const mapDispatchToProps = (dispatch) => {
return {
fetchData: () => dispatch( timelineFetchData() )
}
}
If anybody has any tips or corrections for me bring em on, i'm trying to get a solid grasp on redux, thanks!
Your timeline reducer is pretty small, so you could have it as a single reducer as follows:
const initialState = {
data: [],
hasErrored: false,
isLoading: false
};
export function timeline(state = initialState, action) {
switch (action.type) {
case TIMELINE_HAS_ERRORED:
return {
...state,
hasErrored: action.hasErrored
};
case TIMELINE_IS_LOADING:
return {
...state,
isLoading: action.isLoading
};
case TIMELINE_FETCH_DATA_SUCCESS:
return {
...state,
data: action.timeline
};
default:
return state;
}
}
Then you wouldn't need to call combineReducers(), unless you had other reducers.
I want to show a loading image when the user hits that particular URL and hide when API is successfully fetched the data.
I am not getting in react to show image initially where I should add code and how to hide when API response in success.
This is the action creator for fetching data.
export const fetchEvents = (requestData) => ({
type: FETCH_EVENTS,
payload: axios.get(requestData.url, fetchEventsConfig)
})
And this is the reducer for fetching data.
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_EVENTS_FULFILLED:
const oldState = Object.assign({}, state)
const response = Object.assign({}, action.payload.data)
const allResults = oldState.results.concat(response.results)
return {
...state,
...response,
results: allResults
}
}
}
I am new to this react redux so any help would be great
You can add a boolean property in your Redux state to indicate loading state. Let's call it isLoading. Set isLoading true when you dispatch FETCH_EVENTS. Set it false, when you dispatch FETCH_EVENTS_FULFILLED.
Pass value of isLoading to your React component using #connect HOF. Render a loading indicator when value is true, and content when it's false.
export default (state = initialState, action) => {
switch (action.type) {
case FETCH_EVENTS: {
return {...state, isLoading: true}
}
case FETCH_EVENTS_FULFILLED: {
const response = action.payload.data
const results = [...state.results, ...response.results]
return {
...state,
...response,
results,
isLoading: false
}
}
case FETCH_EVENTS_FAILURE: {
return {...state, isLoading: false}
}
}
}
P.S.
Containing code inside braces after each switch/case limits scope of your const inside that block.
Write a loader file (loader.js)
import React from 'react'
import {ClipLoader} from "react-spinners";
const Loader = ({ loading, message }) => {
return loading ? (
<div className='wrapper'>
<ClipLoader
size={40}
color={"#123abc"}
loading={loading}
/>
<span className='message'>
{message}
</span>
</div>
) : null
};
export default Loader;
In Actions:
export const GET_SERVICE = '[SERVICE REQUEST] GET LIST';
export const GET_SERVICE_SUCCESS = '[SERVICE REQUEST] GET LIST SUCCESS';
export const GET_SERVICE_FAILURE = '[SERVICE REQUEST] GET LIST FAILURE';
export function getService(options) {
const request = axios.post('api/services', {options});
return (dispatch) => {
dispatch({
type: GET_SERVICE
});
try {
request.then((response) =>
dispatch({
type: GET_SERVICE_SUCCESS,
payload: {data: response.data.data, meta: response.data.meta}
})
);
} catch (e) {
dispatch({
type: GET_SERVICE_FAILURE
});
}
}
}
In Reducer:
const initialState = {
services: [],
loading: false,
hasErrors: false,
};
const serviceReducer = function (state = initialState, action) {
switch (action.type) {
case Actions.GET_SERVICE:
return {...state, loading: true};
case Actions.GET_SERVICE_SUCCESS:
return {
...state,
loading: false,
services: [...action.payload.data],
page: action.payload.meta.page,
total: action.payload.meta.total
};
case Actions.GET_SERVICE_FAILURE:
return {...state, loading: false, hasErrors: true};
default:
return state;
}
};
export default serviceReducer;
Write following code in your component:
<div className="flex flex-1 w-full">
<Loader loading={loading} />
</div>
Im getting double nested state after api call. Here is it.
Screenshot from redux devtools
Here are my actions
export const getUserSuccess = user => ({
type: GET_USER_SUCCESS,
user,
});
export const getUserFailure = error => ({
type: GET_USER_FAILURE,
error,
});
export const getUserRequest = () => (
dispatch => (
axios.get('./user.json')
.then(response => dispatch(getUserSuccess(response.data)))
.catch(error => dispatch(getUserFailure(error)))
)
);
here is my user reducer
export default function user(state = {}, action) {
switch (action.type) {
case GET_USER_SUCCESS:
return {
...state,
user: action.user,
};
case GET_USER_FAILURE:
return {
...state,
error: action.error,
};
default:
return state;
}
}
and here is my root reducer
export default combineReducers({
user,
});
You need to spread the data which comes for user, no need to assign it to user. Your reducer is in it already
case GET_USER_SUCCESS:
return {
...state,
...action.user
};
If this reducer should update the state with the complete representation of the new user (and discard old data) you should just return the new user as state.
Can you give the following a shot?
export default function user(state = {}, action) {
switch (action.type) {
case GET_USER_SUCCESS:
return action.user;
case GET_USER_FAILURE:
return {
...state,
error: action.error,
};
default:
return state;
}
}
Judging by this quote
If I'll try that with array of users then array will be destroyed
Why not use Object.assign()?
return Object.assign({}, state, {user: [...state.user, ...action.user]})
Assuming that having multiple users is a possibility, why not just maintain the user property in your state as an array?