Im new to react, now Im creating a simple app using redux and redux-thunk which calls an API asynchronously. Here is my game gameAction:
export const fetchGamesStartAsync = () => {
return dispatch => {
dispatch(fetchGamesStart());
axiosGenCfg.post('/game/get',{
"page" : 1,
"size" : 10
})
.then(({ res }) => {
dispatch(fetchGamesSuccess(res));
})
.catch(err => {
dispatch(fetchGamesFailure(err.message));
});
}
};
const fetchGamesStart = () => ({
type: gameActionTypes.FETCH_GAMES_START,
});
const fetchGamesFailure = () => ({
type: gameActionTypes.FETCH_GAMES_FAILURE,
});
const fetchGamesSuccess = (games) => ({
type: gameActionTypes.FETCH_GAMES_SUCCESS,
payload:{
...games
}
});
and this is my gameReducer:
const INITIAL_STATE= {
gamesList : null,
isFetching: false,
errorMessage : undefined
};
const gameReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
case gameActionTypes.FETCH_GAMES_START:
return{
...state,
isFetching: true
};
case gameActionTypes.FETCH_GAMES_SUCCESS:
return{
...state,
isFetching: false,
gamesList: action.payload
};
case gameActionTypes.FETCH_GAMES_FAILURE:
return{
...state,
isFetching: false,
errorMessage: action.payload
};
default:
return {
state
};
}
};
and in rootReducer
export default combineReducers({
admin : adminReducer,
game: gameReducer,
})
I also added redux-logger to check state and this is what i get in console
So why there are 2 levels of state in my game object? and also the same with admin object. before i add redux-thunk to project, I didn't have this problem. before adding redux-thunk currentAdmin was direct child of admin. But now there is a state object between.
default:
return {
state
};
should just be
default:
return state
Right now any time you hit the default, state.whatever is becoming state.state.whatever
Related
I wanted to know how I can pass the state into an action so that it can use the state to make an API call. the state I want to pass is the input because it's the image URL that gets sent to Clarifai's servers to predict the celebrity. The handle search is responsible for updating state to the URL.
I've tried to use get state with no luck
This is my action
export const requestPrediction = () => {
return function (dispatch, getState) {
dispatch(fetchCelebrequest)
let input = getState().input
app.models.predict(Clarifai.CELEBRITY_MODEL,
input)
.then(res => {
const data = res.outputs[0]['data']['regions'][0]['data'].concepts[0]
dispatch(fetchCelebSuccess(data))
})
.catch(err => {
const error = err.message
dispatch(fetchCelebFailed(error))
})
}
}
This is my reducer.js
import {
CHANGE_SEARCHFIELD,
REQUEST_PREDICTION_PENDING,
REQUEST_PREDICTION_SUCESS,
REQUEST_PREDICTION_FAILED
} from './constants'
const initialState = {
input: '',
imageUrl: '',
box: {},
isSignedIn: false,
isPending: false,
celeb: {},
error: '',
celebConfidence: [],
user: {
id: '',
name: '',
email: '',
entries: 0,
joined: ''
}
}
export const handleSearch = (state=initialState, action={}) => {
switch (action.type) {
case CHANGE_SEARCHFIELD:
return { ...state, input: action.payload }
default:
return state
}
}
export const requestPrediction = (state=initialState, action={}) => {
switch(action.type) {
case REQUEST_PREDICTION_PENDING:
return {...state, isPending: true}
case REQUEST_PREDICTION_SUCESS:
return {...state, celebName: action.payload, isPending: false}
case REQUEST_PREDICTION_FAILED:
return {...state, error: action.payload, isPending: false}
default:
return state
}
}
The proper way to do it is via setState since the updates my be asynchronous.
You may check out this link. How can I pass state to an action in React.js?
I am getting the response from backed which looks something like this.
But when i try to log the data like this.
render() {
const { reviews } = this.props;
console.log('rev', reviews.reviewList.data._embedded);
It gives me error saying this.
TypeError: reviews.reviewList.data is undefined
reviewDataReducer.jsx
const initialstate = {
isFetching: false,
reviewList: [],
page: null,
fetched: false,
error: null
};
export default (state = initialstate, action) => {
switch (action.type) {
case actionTypes.GET_PRODUCT_REVIEWS_LOAD:
return {
...state,
isFetching: true
};
case actionTypes.GET_PRODUCT_REVIEWS_SUCCESS:
return {
...state,
fetched: true,
reviewList: action.payload
};
case actionTypes.GET_PRODUCT_REVIEWS_ERROR:
return {
...state,
fetched: false,
isFetching: false,
error: action.error
};
default:
return state;
}
};
reviewActions.jsx
export const getProductReviews = pid => dispatch => {
console.log('rev pid',pid)
dispatch({
type: types.GET_PRODUCT_REVIEWS_LOAD
});
new _rest()
.get(`/buyer/product/${pid}/review`)
.then(res => {
console.log("Review Action Response", res);
dispatch({
type: types.GET_PRODUCT_REVIEWS_SUCCESS,
payload: res
});
})
.catch(error => {
dispatch({
type: types.GET_PRODUCT_REVIEWS_ERROR,
error: error
});
});
};
connect
const mapStateToprops = state => ({
reviews: state.reviews.data
});
const mapStateToDispatch = {
getProductReviews
};
export default connect(
mapStateToprops,
mapStateToDispatch
)(ReviewContainer);
Your information is limited but I will try to be best.
1] Error is because you'r traversing object wrong not because data in not there in this case.
2] render() {
const { reviews } = this.props;
Here I feel you mapping redux state to prop using (mapStateToProps) if so reducer is responsible how you set data in redux state.
The issue is there inside the mapStateToprops connection. Try to debug it there.
const mapStateToprops = state => {
debugger;
return ({
reviews: state.reviews.data
});
};
Open your browser console and check the value of state;
I can't use default state value in redux
Hello,
I have a function to dispatch Login action,
I want in the initial to be false,
but when I put this value in the render method, it's given me an error
can't read property 'isLogin' of undefined
although in the reducer i add an initialState
let initialState = {
isLogin: false,
};
const userReducer = (state = initialState, action) => {
switch (action.type) {
case IS_LOGIN:
state = {
...state,
isLogin: true,
};
break;
default:
return state;
}
return state;
};
here's Store
const store = createStore(
combineReducers({
user: userReducer,
count: countPlayReducer,
}),
);
Action
export const isLoginFunc = isLogin => {
return {
type: IS_LOGIN,
payload: isLogin,
};
};
UI/Dispatching
import {isLoginFunc} from '../../redux/actions/isLoginAction';
signIn = async data => {
this.setState({loading: true}); // maybe here the wrong?
API.post('/login', data)
.then(async response => {
let {
data: {
data: {
response: {token},
},
},
} = response;
this.props.dispatch(isLoginFunc(true));
await saveToken('id_token', token);
this.setState({loading: false}, () =>
this.props.navigation.navigate({
key: 'TabHome',
routeName: 'TabHome',
}),
);
})
.catch(error => {
console.log(error);
this.setState({loading: false, error: error.response.data});
});
};
render(){
...
<Text>{this.props.user.isLogin}</Text>
...
}
mapStateToProps = state => {
return {
isLogin: state.user,
};
};
export default connect(mapStateToProps)(Login);
It looks like you're mapping redux state using the following:
mapStateToProps = state => {
return {
isLogin: state.user,
};
};
But then you're trying to access isLogin via:
this.props.user.isLogin
Looks like this should be changed to:
this.props.isLogin.isLogin
However you might want to alter your mapping to the following instead:
mapStateToProps = state => {
return {
isLogin: state.user.isLogin,
};
};
So that you can simply use:
this.props.isLogin
I have a list of products called work items stored on my Redux store and I want to add an action that adds new work item or remove existing one when user picks up a a work item from the ui.
What I have so far is this workItemReducer:
import {
FETCH_WORKITEMS_BEGIN,
FETCH_WORKITEMS_SUCCESS,
FETCH_WORKITEMS_FAILURE,
SELECTED_WORKITEM
} from '../actions/workItemAction';
const initialState = {
workItems: [{"name":'work 1'}, {"name":'work 2'}, {"name":'work 3'}],
workItemsSelected: {},
loading: false,
error: null
};
export default function workItemReducer(state = initialState, action) {
switch(action.type) {
case FETCH_WORKITEMS_BEGIN:
return {
...state,
loading: true,
error: null
};
case FETCH_WORKITEMS_SUCCESS:
return {
...state,
loading: false,
workItems: action.payload.workItems
};
case FETCH_WORKITEMS_FAILURE:
return {
...state,
loading: false,
error: action.payload.error,
workItems: []
};
case SELECTED_WORKITEM:
return {
...state,
workItemsSelected: action.payload.workItem
};
default:
return state;
}
}
and the actions looks as below:
export const FETCH_WORKITEMS_BEGIN = 'FETCH_WORKITEMS_BEGIN';
export const FETCH_WORKITEMS_SUCCESS = 'FETCH_WORKITEMS_SUCCESS';
export const FETCH_WORKITEMS_FAILURE = 'FETCH_WORKITEMS_FAILURE';
export const SELECTED_WORKITEM = 'SELECTED_WORKITEM';
export const fetchWorkItemsBegin = () => ({
type: FETCH_WORKITEMS_BEGIN
});
export const fetchWorkItemsSuccess = workItems => ({
type: FETCH_WORKITEMS_SUCCESS,
payload: { workItems }
});
export const fetchWorkItemsFailure = error => ({
type: FETCH_WORKITEMS_FAILURE,
payload: { error }
});
export const selectedWorkItem = workItem => ({
type: SELECTED_WORKITEM,
payload: { workItem }
});
I have a container component that disptach or call these actions which I am a bit confused where the logic of adding a new one or removing existing one happens, either on the container/smart component or directly in the reducer.
Container component has this method:
onWorkItemSelect = (workItem) => {
this.props.dispatch(selectedWorkItem(workItem));
};
Anyone can help on writing the logic of adding new or remove existing one and where that code should live?
adding this to reducer works thou im not sure if all this code should remain into the reducer:
case SELECTED_WORKITEM:
let arr = [];
if (containsObject(action.payload.workItem, state.workItemsSelected)) {
arr = remove(state.workItemsSelected, action.payload.workItem);
} else {
arr = [...state.workItemsSelected, action.payload.workItem];
}
return {
...state,
workItemsSelected: arr
};
It should be done in the reducer
when adding one you could just spread the current array which you can get from the reducer state
const { workItems } = state;
const { workItem } = action.payload;
return {
// ...other stuff to return
workItems: [...workItems, workItem],
}
to delete one
const { workItems } = state;
const { workItem } = action.payload;
return {
// ...other stuff to return
workItems: workItems.filter(x => x.name === workItem.name),
}
I create app with react and redux and I need to fetch data. Is there any way to reuse function getData() and reducer. My actions looks like this
importing constants
const getDataRequested = () => {
return {
type: GET_DATA_REQUESTED
};
}
const getDataDone = data => {
return {
type: GET_DATA_DONE,
payload: data
};
}
const getDataFailed = () => {
return {
type: GET_DATA_FAILED
};
}
export const getData = () => dispatch => {
dispatch(getDataRequested());
fetch('url')
.then(response => response.json())
.then(data => {
dispatch(getDataDone(data));
})
.catch(error => {
dispatch(getDataFailed(error));
})
}
and reducer
importing constants
const initialState = {
isLoading: false,
isError: false,
data: [],
}
export default (state=initialState, action) => {
switch (action.type) {
case GET_DATA_REQUESTED:
return { ...state, isLoading: true };
case GET_DATA_DONE:
return { ...state, isLoading: false, data: action.payload };
case GET_DATA_FAILED:
return { ...state, isLoading: false, isError: true}
default:
return state;
}
};
Every time I fetch something with different url I create new action and new reducer. Is it ok or there is some way to reuse it?
You can pass a url parameter to your thunk. So, you could have something like this:
export const getData = (url) => dispatch => {
dispatch(getDataRequested());
fetch(url)
.then(response => response.json())
.then(data => {
dispatch(getDataDone(data));
})
.catch(error => {
dispatch(getDataFailed(error));
})
}
This way you can dispatch as many actions as you want changing only the url parameter, like this: getData('/user'), getData('/products').
You can also customize the way you store the state into redux by passing more parameters to the thunk. So it could be something like this:
const getDataDone = data => {
return {
type: GET_DATA_DONE,
payload: data
};
}
export const getData = (url, stateName) => dispatch => {
dispatch(getDataRequested());
fetch(url)
.then(response => response.json())
.then(data => {
dispatch(getDataDone({ stateName: data }));
})
.catch(error => {
dispatch(getDataFailed(error));
})
}
And the reducer could be something like this:
const initialState = {
isLoading: false,
isError: false,
data: {},
}
export default (state=initialState, action) => {
switch (action.type) {
case GET_DATA_REQUESTED:
return { ...state, isLoading: true };
case GET_DATA_DONE:
return { ...state, isLoading: false, [action.payload.stateName]: action.payload.data };
case GET_DATA_FAILED:
return { ...state, isLoading: false, isError: true}
default:
return state;
}
};
That way you can dispatch actions like getData('/user', 'user') or getData('/products', 'products') and have a state like this:
{
user: {
// your users data
},
products: {
// your products data
}
}
You can combine all actions in one function like
function getData() {
return {
types: [GET_DATA_REQUESTED, GET_DATA_DONE, GET_DATA_FAILED],
callApi: fetch('/')
}
}
but you need to connect your component and pass the function as props
function mapDispatchToProps(dispatch) {
return {
getData: () => dispatch(getData())
};
}
connect(null, mapDispatchToProps)(YourComponent)
now you can use the function in your component and it will return a promise.
check out the Docs for redux: https://github.com/reactjs/react-redux