I am learning React Redux. My Action is like below
import Axios from 'axios';
export const getAddress = valueModal => dispatch => {
return Axios.get('/api/address')
.then(response => {
var addressData = response.data;
dispatch({
type: 'getAddresses',
payload: { addressData, valueModal }
});
})
.catch(function(error) {
console.log(error);
});
};
export const uploadImage = (formData, id, config) => dispatch => {
return Axios.post('/api/address/upload', formData, config)
.then(response => {
dispatch({
type: 'uploadImage',
payload: response.data
});
})
.catch(function(error) {
console.log(error);
});
};
export default { getAddress, addAddress, uploadImage };
My Reducer is like below
const initialState = {
address: {}
};
const addressReducer = (state = initialState, action) => {
switch (action.type) {
case 'getAddresses': {
return {
controlModal: action.payload.valueModal,
address: action.payload.addressData
};
}
case 'uploadImage': {
return {
uploadImage: action.payload
};
}
default:
return state;
}
};
export default addressReducer;
I would like to call getAddresses and uploadImage jointly. How can I do that ?
const initialState = {
address: {}
};
const addressReducer = (state = initialState, action) => {
switch (action.type) {
case 'getAddresses': {
return {
...state,
controlModal: action.payload.valueModal,
address: action.payload.addressData
};
}
case 'uploadImage': {
return {
...state,
uploadImage: action.payload
};
}
default:
return state;
}
};
export default addressReducer;
You need to spread the object state out otherwise there is never a reference to the state before the update.
The object spread syntax lets you use the spread ... operator to copy enumerable properties from one object to another in a more succinct way.
Related
I have a async action name editLoginIdData() in loginsIdSlice.js,
which i am dispatching from certain component, which edits the data in mongoDB database, then when the action is fullfilled, i mutate the state in extraReducers
editLoginIdData.fulfilled.
But now what i want to do that whenever the editLoginIdData action is fullfilled
i want to also add the updated(which i will get from server responese -> updatedData at editLoginIdData()) data to activitiesData state, which i am handling in activitiesSlice.js
So basically is there a way that when editLoginIdData action is fullfilled we can
dispatch somehow mutate the state in other slice.
One approach i have taken is to import the editLoginIdData() action in activitiesSlice.js and then creating a extraReducer with editLoginIdData.fullfilled
and mutating activitiesData state.
I have done the above approach and seems its working correctly.
But there is a catch, like how show i get the response data at editLoginIdData()
to passed to activitiesSlice.js because i will required that updated data.
if the above appraoch is not correct, then how should i do it
loginsIdSlice.js
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import * as api from "../../api"
const initialState = {
loginsIdData: [],
}
export const fecthLoginIdsData = createAsyncThunk("loginIds/fetch", async ({ user_id }, { getState }) => {
const res = await api.fetchUserLoginIds(user_id);
console.log(res);
const { data } = res;
data.reverse();
return data;
});
export const addNewLoginIdData = createAsyncThunk("loginIds/add", async ({ data, user_id }, { getState }) => {
const res = await api.addNewLoginIdA(data, user_id)
const { loginIdsArray } = res.data;
return loginIdsArray[loginIdsArray.length - 1];
});
export const editLoginIdData = createAsyncThunk("loginIds/edit", async ({ updatedData, login_id }, { getState }) => {
const res = await api.editLoginId(login_id, updatedData);
// console.log(updatedData);
return updatedData;
});
export const deleteLoginData = createAsyncThunk("loginIds/delete", async ({ login_id, user_id }, { getState }) => {
const res = await api.deleteLoginId(login_id, user_id);
// console.log(res);
const { data } = res;
// console.log(data);
return data.reverse();
});
//* Slice
const loginsIdSlice = createSlice({
name: 'loginsId',
initialState: initialState,
extraReducers: (builder) => {
builder.
addCase(fecthLoginIdsData.fulfilled, (state, action) => {
return {
...state,
loginsIdData: action.payload
};
}).
addCase(addNewLoginIdData.fulfilled, (state, action) => {
return {
...state,
loginsIdData: [action.payload, ...state.loginsIdData]
};
}).
addCase(editLoginIdData.fulfilled, (state, action) => {
const newArray = state.loginsIdData.map((loginId) => {
if (loginId._id === action.payload._id) {
return action.payload;
} else {
return loginId;
}
});
return {
...state,
loginsIdData: newArray,
};
}).
addCase(deleteLoginData.fulfilled, (state, action) => {
return {
...state,
loginsIdData: action.payload
};
})
}
})
export const { deleteLoginId, editLoginId } = loginsIdSlice.actions;
export default loginsIdSlice.reducer;
activitiesSlice
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import * as api from "../../api"
import { editLoginIdData } from "../loginsId/loginsIdSlice"
const initialState = {
activitiesData: [],
}
const activitiesSlice = createSlice({
name: 'activities',
initialState: initialState,
extraReducers: (builder) => {
builder.
addCase(editLoginIdData.fullfilled, (state, action) => {
console.log("ss")
return {
...state,
activitiesData: []
};
})
}
})
export default activitiesSlice.reducer;
Is there a way that when editLoginIdData action is fullfilled we can
dispatch somehow mutate the state in other slice.
I am having trouble to dispatch component state value to redux store .My idea is that i will dispatch to redux store only API response data and after can access it from different screens:
When i try it , i am getting error: ReferenceError: action is not defined
at loginSuccessful (http://packager.yz-9ze.anonymous.easy…}
Here is my component code:
import { loginSuccessful } from "../../redux/actions/loginacction";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
user: {
login: "",
password: ""
},
activity: false,
response: {},
showpassword: true
};
}
_login = async () => {
if (this.state.user.login !== "" && this.state.user.password !== "") {
console.log(this.state);
this.setState({ activity: true });
try {
await Axios.post(LoginAPI, this.state, {
headers: { appversion: 1.4 }
})
.then(response => {
console.log(response);
const status = response.status;
if (status === 200) {
this.setState({ activity: false });
this.setState({ response: response.data });
const userData = this.state.response;
this.props.dispatch(loginSuccessful(userData));
//this.props.login_api_call;
//this.props.navigation.navigate("CreateMove");
console.log(this.state);
}
})
.catch(error => {
console.log({ error });
this.setState({ activity: false });
Alert.alert("Error", error.response.data.error);
});
} catch (error) {}
} else {
Alert.alert("Support", "Enter Email and Password");
}
};
const mapStateToProps = state => {
return {
userData: state.loginCredentialsReducer
};
};
// const mapDispatchToProps = dispatch => {
// return {
// login_api_call: userData => dispatch(loginSuccessful(userData))
// };
// };
export default connect(mapStateToProps)(Login);
Here is my action type and creator:
export const LOGINSUCCESSFUL = "LOGINSUCCESSFUL";
export const loginSuccessful = userData => {
console.log(action);
return {
type: LOGINSUCCESSFUL,
payload: userData
};
};
And here is my reducer
import {LOGINSUCCESSFUL} from "../actions/actiontypes";
const initialState = {
userData: {}
};
const loginCredentialsReducer = (state = initialState, action) => {
switch (action.type) {
case LOGINSUCCESSFUL:
return {
...state,
userData: action.payload
};
default:
return state;
}
};
export default loginCredentialsReducer;
You're calling console.log(action) here, but action isn't declared anywhere. Hence the error.
export const LOGINSUCCESSFUL = "LOGINSUCCESSFUL";
export const loginSuccessful = userData => {
console.log(action); // <-- action is not declared anywhere
return {
type: LOGINSUCCESSFUL,
payload: userData
};
};
In this:
export const loginSuccessful = userData => {
console.log(action);
return {
type: LOGINSUCCESSFUL,
payload: userData
};
};
you never defined action hence the error.
I am new to redux and I am trying to make it work with my application, but I have problems with understanding how to work with async actions in it. I have action that is api call. This action should be called as soon as my other state is not empty. I do not get any mistakes but do not think that my action is called since the data is empty. Can anybody help to understand what I am doing wrong?
Here is my actions.js. The wordsFetchData is the action I need to call:
export function wordsFetchDataSuccess(items){
return{
type: 'WORDS_FETCH_DATA_SUCCESS',
items
};
}
export function wordsAreFetching(bool){
return{
type: 'WORDS_ARE_FETCHING',
areFetching: bool
}
}
export function wordsHasErrored(bool) {
return {
type: 'WORDS_HAS_ERRORED',
hasErrored: bool
};
}
export function wordsFetchData(parsed) {
return (dispatch) => {
dispatch(wordsAreFetching(true));
fetch('URL', {
method: "POST",
headers: {
"Content-type": "application/json"
},body: JSON.stringify({
words: parsed
})
})
.then((response) => {
if (!response.ok) {
throw Error(response.statusText);
}
dispatch(wordsAreFetching(false));
return response;
})
.then((response) => response.json())
.then((items) => dispatch(wordsFetchDataSuccess(items)))
.catch(() => dispatch(wordsHasErrored(true)));
};
}
Here are my reducers:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_FETCH_DATA_SUCCESS':
return action.items;
default:
return state;
}
}
export function wordsAreFetching(state = false, action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return action.areFetching;
default:
return state;
}
}
export function wordsFetchHasErrored(state = false, action) {
switch (action.type) {
case 'WORDS_HAS_ERRORED':
return action.hasErrored;
default:
return state;
}
}
This is my componentDidMount function:
componentDidMount = (state) => {
this.props.fetchData(state);
};
This is the function after terminating which the action should be called:
parseInput = async () => {
console.log(this.state.textInput);
let tempArray = this.state.textInput.split(" "); // `convert
string into array`
let newArray = tempArray.filter(word => word.endsWith("*"));
let filterArray = newArray.map(word => word.replace('*', ''));
await this.setState({filterArray: filterArray});
await this.props.updateData(this.state.filterArray);
if (this.state.projectID === "" && this.state.entity === "")
this.dialog.current.handleClickOpen();
else
if (this.state.filterArray.length !== 0)
this.componentDidMount(this.state.filterArray);
};
These are the mapStateToProps and mapDispatchToProps functions.
const mapStateToProps = (state) => {
return {
items: state.items,
hasErrored: state.wordsFetchHasErrored,
areFetching: state.wordsAreFetching
};
};
const mapDispatchToProps = (dispatch) => {
return {
fetchData: wordsFetchData
};
};
You only need one action for executing fetching (i.e WORDS_ARE_FETCHING), the rest of the cases (i.e WORDS_HAS_ERRORED & WORDS_FETCH_DATA_SUCCESS) can be handled inside your reducer.
Your action:
export function wordsAreFetching(){
return{
type: 'WORDS_ARE_FETCHING',
}
}
Your new reducer:
export function word(state = [], action) {
switch (action.type) {
case 'WORDS_ARE_FETCHING':
return {...state, error: false, areFetching: true};
case 'WORDS_FETCH_DATA_SUCCESS':
return {...state, items: action.payload , areFetching: false};
case 'WORDS_HAS_ERRORED':
return {...state, error: true, areFetching: false};
default:
return state;
}
Then you can trigger WORDS_FETCH_DATA_SUCCESS after you get the data from here:
export function wordsFetchData() {
try {
const response = await axios.get(YOUR_URL);
return dispatch({ type: WORDS_FETCH_DATA_SUCCESS, payload: response.data });
} catch (err) {
return dispatch({ type: WORDS_HAS_ERRORED });
}
}
Take a look at this example, it uses axios that can help you with async calls.
A couple of things:
No need to pass state into your componentDidMount, your mapDispatchToProps is not using it.
Here is a suggestion to structure those functions. They are a bit more concise and readable.
const mapStateToProps = ({items, wordsAreFetching, wordsFetchHasError}) => ({
items,
hasErrored: wordsFetchHasErrored,
areFetching: wordsAreFetching,
});
const mapDispatchToProps = () => ({
fetchData: wordsFetchData(),
});
Other notes and helpful things:
If you're using thunk, you'll have access to your entire redux store in here as a second argument. For example:
return (dispatch, getState) => {
dispatch(wordsAreFetching(true));
console.log('getState', getState());
const { words } = getState().items;
// This is a great place to do some checks to see if you _need_ to fetch any data!
// Maybe you already have it in your state?
if (!words.length) {
fetch('URL', {
method: "POST",
headers: {
......
}
})
I hope this helps, if you need anything else feel free to ask.
Summary
In order to learn Redux, I am incorporating some state, actions, reducers, and trying to see how they are used in React Components.
I have set up a test object...
const initialState = {
navigationCount : 0,
someNumber : 500,
someList : ['aa',22,'c5d6','45615'],
};
...and aim to:
increment the navigationCount by 1 when visiting pages
add or subtract from someNumber
push() & pop() elements from someList.
Versions
Currently using gatsby ^2.5.0, react ^16.8.6, and react-redux ^6.0.1.
Code
actions & reducers
import { combineReducers } from 'redux';
import {
PAGE_INCREMENT,
NUMBER_INCREASE,
NUMBER_DECREASE,
LIST_PUSH,
LIST_POP,
} from './actionTypes.js';
// state
const initialState = {
navigationCount : 0,
someNumber : 500,
someList : ['aa',22,'c5d6','45615'],
};
// action creators returning actions
export const pageIncrementer = navigationCount => {
return {
type: PAGE_INCREMENT,
navigationCount,
};
};
export const numberAdder = numberToAdd => {
return {
type: NUMBER_INCREASE,
numberToAdd,
};
};
export const numberMinuser = numberToMinus => {
return {
type: NUMBER_DECREASE,
numberToMinus,
};
};
export const listPusher = itemToAdd => {
return {
type: LIST_PUSH,
itemToAdd,
}
};
export const listPopper = () => {
return {
type: LIST_POP,
}
};
// reducers
const pageIncrementReducer = (state = initialState, action) => {
switch (action.type) {
case PAGE_INCREMENT:
return Object.assign({}, ...state, {
navigationCount: action.navigationCount+1
});
default:
return state.navigationCount;
}
};
const numberChanger = (state = initialState, action) => {
switch (action.type) {
case NUMBER_INCREASE:
return Object.assign({}, ...state, {
someNumber: state.someNumber+action.numberToAdd,
});
case NUMBER_DECREASE:
return Object.assign({}, ...state, {
someNumber: state.someNumber-action.numberToMinus,
});
default:
return state.someNumber;
};
};
const listChanger = (state = initialState, action) => {
switch (action.type) {
case LIST_POP:
return Object.assign({}, ...state, {
someList: state.someList.pop(),
});
case LIST_PUSH:
return Object.assign({}, ...state, {
someList: state.someList.push(action.itemToAdd),
});
default:
return state.someList;
}
}
// store
const rootReducer = combineReducers({
pageIncrementReducer,
numberChanger,
listChanger,
});
export default rootReducer;
React Component
import React from 'react';
import Layout from '../components/common/Layout.jsx';
import LandingBanner from '../components/landing/LandingBanner.jsx';
import LandingNavgrid from '../components/landing/LandingNavgrid.jsx';
import LandingApp from '../components/landing/LandingApp.jsx';
import { connect } from 'react-redux';
import {
PAGE_INCREMENT,
NUMBER_INCREASE,
NUMBER_DECREASE,
LIST_PUSH,
LIST_POP,
} from '../state/actionTypes';
class LandingPage extends React.Component {
constructor(props){
super(props);
this.state = {
appliedNum: 2000,
};
}
componentDidMount(){
// this.props.pageIncrement(); // => numberChanger returned undefined
// this.props.numberIncrease(4444); // => pageIncrementReducer returned undefined
// this.props.numberDecrease(4444); // => pageIncrementReducer returned undefined
// this.props.listPush(4444); // => pageIncrementReducer returned undefined
this.props.listPop();
}
render(){
return (
<Layout>
<LandingBanner/>
<LandingNavgrid/>
<LandingApp/>
</Layout>
)
}
}
const filterNumbers = (list=[]) => {
console.log('filterNumbers list: ', list);
return list.filter(listElement => !!Number(listElement));
};
const mapStateToProps = (state, ownProps) => {
return {
someNumber: state.someNumber,
someList: filterNumbers(state.someList),
navigationCount: state.navigationCount,
};
};
const mapDispatchToProps = (dispatch) => {
return {
pageIncrement: () => dispatch({ type: PAGE_INCREMENT }),
numberIncrease: () => dispatch({ type: NUMBER_INCREASE }),
numberDecrease: () => dispatch({ type: NUMBER_DECREASE }),
listPush: () => dispatch({ type: LIST_PUSH }),
listPop: () => dispatch({ type: LIST_POP }),
}
}
export default connect(
mapStateToProps,
mapDispatchToProps,
)(LandingPage);
Errors
redux.js:449 Uncaught Error: Given action "LIST_POP", reducer
"pageIncrementReducer" returned undefined. To ignore an action, you
must explicitly return the previous state. If you want this reducer to
hold no value, you can return null instead of undefined.
first of all, you always need to return state on the default switch case.
default:
return state;
I am using multiple reducers in my project and then combining them with combineReducers() function and have all actions in single file. when i dispatch the action, it is returning me state values to undefined. I think It can't find out because of multiple reducerse. But when i use single reducer file. It is working fine. Can anyone please tell me what the issue.It is how i am combining the reducers.
const rootReducer = combineReducers({
isMobileReducer,
imageSliderReducer
})
and now passing to store, like below:
let store = createStore(rootReducer,applyMiddleware(thunk))
and in frontend how i am accessing state
const mapStateToProps = (state) => ({
images: state.images,
isMobile: state && state.isMobile
})
imageSliderReducer.js
import {
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from '../actions/actionTypes'
const initialState = {
images:[],
error:null
}
const imageSliderReducer = (state = initialState, action) => {
switch (action.type) {
case FETCH_IMAGES_BEGIN:
return {...state,error:null}
case FETCH_IMAGES_SUCCESS:
return {...state,images:action.payload.images}
case FETCH_IMAGES_FAILURE:
return {...state,error:action.payload.error,images:[]}
default:
return state
}
}
export default imageSliderReducer;
isMobileReducer.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
} from '../actions/actionTypes'
const initialState = {
isMenuOpen: null,
isMobile: false
}
const isMobileReducer = (state = initialState, action) => {
switch (action.type) {
case OPEN_MENU:
return {...state, isMenuOpen: true}
case CLOSE_MENU:
return {...state, isMenuOpen: false}
case SET_DEVICE_TYPE:
return {...state, isMobile: action.isMobile}
default:
return state
}
}
export default isMobileReducer;
actionCreator.js
import {
OPEN_MENU,
CLOSE_MENU,
SET_DEVICE_TYPE,
FETCH_IMAGES_BEGIN,
FETCH_IMAGES_SUCCESS,
FETCH_IMAGES_FAILURE
} from './actionTypes'
export function openMenu(isMobile) {
return {
type: OPEN_MENU
}
}
export function closeMenu(isMobile) {
return {
type: CLOSE_MENU
}
}
export function setDeviceType (isMobile) {
return {
type: SET_DEVICE_TYPE,
isMobile: isMobile
}
}
export function fetchImages() {
return dispatch => {
dispatch(fetchImagesBegin());
return fetch("https://7344.rio.com/wp-json/customapi/homeslider")
.then(handleErrors)
.then(res => res.json())
.then(json => {
dispatch(fetchImagesSuccess(json.posts));
return json.posts;
})
.catch(error => dispatch(fetchImagesFailure(error)));
};
}
function handleErrors(response) {
if (!response.ok) {
throw Error(response.statusText);
}
return response;
}
export const fetchImagesBegin = () => ({
type: FETCH_IMAGES_BEGIN
});
export const fetchImagesSuccess = images => ({
type: FETCH_IMAGES_SUCCESS,
payload: { images }
});
export const fetchImagesFailure = error => ({
type: FETCH_IMAGES_FAILURE,
payload: { error }
});
Try using this:
const mapStateToProps = (state) => ({
images: state.imageSliderReducer.images,
isMobile: state.isMobileReducer.isMobile
})