get the same state for different requests - redux

i have this action:
export const connectToServer = (url, config, method) => {
return (dispatch) => {
dispatch({type: CONNECTION_START});
axios({
method: method,
url: url,
data: config
})
.then((response) => {
dispatch({type: CONNECTION_LOADING_SUCCESS, payload: response.data});
})
.catch((error) => {
dispatch({type: CONNECTION_LOADING_ERROR, payload: error.response.data});
})
}
};
And 2 identical reducers:
const initialState = {
data: null,
isLoading: false,
error: null
};
export const connectToServerReducer = (state = initialState, action) => {
switch (action.type) {
case CONNECTION_START :
return {...state, isLoading: true};
case CONNECTION_LOADING_SUCCESS :
return {...state, isLoading: false, data: action.payload, error: null};
case CONNECTION_LOADING_ERROR:
return {...state, isLoading: false, data: null, error: action.payload};
default :
return state
}
};
export const differentUrlConnectToServerReducerTest = (state = initialState, action) => {
switch (action.type) {
case CONNECTION_START :
return {...state, isLoading: true};
case CONNECTION_LOADING_SUCCESS :
return {...state, isLoading: false, data: action.payload, error: null};
case CONNECTION_LOADING_ERROR:
return {...state, isLoading: false, data: null, error: action.payload};
default :
return state
}
};
My store looks like this:
const rootReducer = combineReducers({
allUsersData: connectToServerReducer,
testData: differentUrlConnectToServerReducerTest
});
const configureStore = () => createStore(rootReducer, applyMiddleware(thunk));
export default configureStore
Then i use redux hooks to get a state with data in my components
const allUsersData = useSelector(state => state.allUsersData);
const testData = useSelector(state => state.testData);
const dispatch = useDispatch();
Finally i dispatch them
dispatch(connectToServer(`${BASE_URL}user/allUsersWithPets`, null, 'get'));
dispatch(connectToServer(`${BASE_URL}fakeUrl`, null, 'get'));
I receive a correct data in allUsersData, but also i receive it in testData but i should receive in testData an initial state(empty object), because url is a fake
Where am i wrong?

You need to separate the reducers, use different initial states for example:
connectToServer.js
connectToServerTest.js
Or you can try to add the test object to the initial state of connectToServerReducer.(not a good solution though)
const initialState = {
data: null,
testData: null,
isLoading: false,
error: null
};
Remember that arrays affections won't assign values but addresses, so the "data" array is the same array in both the connectToServerReducer and connectToServerReducerTest.
Second problem, you are calling the same action name in both reducers, this causes them not only to share the same variable from the previous problem I told you, but they share the same value assigned to them as well.
Just change them to:
CONNECTION_TEST_LOADING_SUCCESS
CONNECTION_TEST_LOADING_ERROR
CONNECTION_TEST_START
PS:
instead of using:
export const connectToServer = (url, config, method) => {
return (dispatch) => {
...
}
}
Use:
export const connectToServer = (url, config, method) => (dispatch) => {
...
}

Related

Redux State is set also to other State that is not Related

Programming is weird, if you think not then check this case 🤣, I'm using createSlices as Redux and I have two slices with their own states.
First one is orderSlice:
export const orderSlice = createSlice({
name: 'order',
initialState: {
order: null,
message: null,
isLoading: true,
}
})
While the second slice is ordersSlice:
export const orderSlice = createSlice({
name: 'orders',
initialState: {
orders: null,
message: null,
isLoading: true,
}
})
And I have this method to fetch the order and the fulfilled phase where the state is set from:
Fetching the order:
export const fetchOrder = createAsyncThunk('', async ({ token, id }) => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
try {
const response = await fetch(`${api}/orders/view/${id}`, requestOptions);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
Filling the order state:
extraReducers: {
[fetchOrder.fulfilled]: (state, action) => {
state.order = action.payload.data;
state.message = 'Succesfully fetched the Order.';
state.isLoading = false;
}
}
While here is method for fetching the orders:
export const fetchAllOrders = createAsyncThunk('', async (token) => {
const requestOptions = {
method: 'GET',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer ' + token,
},
};
try {
const response = await fetch(`${api}/orders/all`, requestOptions);
const data = await response.json();
return data;
} catch (error) {
console.log(error);
}
});
And here updating the orders state:
extraReducers: {
[fetchAllOrders.fulfilled]: (state, action) => {
state.orders = action.payload.data;
state.message = 'Succesfully fetched all Orders.';
state.isLoading = false;
}
}
So the case is that I'm calling the fetchAllOrders in the Order page with UseEffect, here is how:
import { fetchAllOrders } from '../redux/ordersSlice';
useEffect(() => dispatch(fetchAllOrders(user.token)), [user]);
So this is how i run the method to fetch orders with dispatch and it works. But the problem is that when I run this function beside the orders state that is filled with the same data, also the order state is filled with the same data and this is impossible as I've cheked all the cases where I could misstyped a user,users typo but there is none I found, and I don't know.
And here is the store:
import orderSlice from './redux/orderSlice';
import ordersSlice from './redux/ordersSlice';
const store = configureStore({
reducer: {
order: orderSlice,
orders: ordersSlice
},
});
You have to give your thunks an unique name: If you name both '' they will be handled interchangably.
Also, you should be using the builder notation for extraReducers. We will deprecate the object notation you are using soon.

converting to redux tool kit and getting "Unhandled Rejection (TypeError): state.push is not a function"

i'm stuck while converting an old project to redux tool kit getting an "Unhandled Rejection (TypeError): state.push is not a function" error. i haven't got to grips with the action/thunk and reducer immutability yet. The alerts are working but then the error msg.
import axios from 'axios';
import { setAlert } from '../alerts/alertSlice';
const slice = createSlice({
name: 'auth',
initialState: {
token: localStorage.getItem('token'),
isAuthenticated: null,
loading: true,
user: null,
},
reducers: {
registerSuccess: (state, action) => {
const { payload } = action.payload;
state.push({
payload,
isAuthenticated: true,
loading: false,
});
},
registerFail: (state, action) => {
localStorage.removeItem('token');
state.push({
token: null,
isAuthenticated: false,
loading: false,
user: null,
});
},
},
});
const { registerSuccess, registerFail } = slice.actions;
export default slice.reducer;
// Register User
export const register =
({ name, email, password }) =>
async (dispatch) => {
const config = {
headers: {
'comtent-Type': 'application/json',
},
};
const body = JSON.stringify({ name, email, password });
try {
const res = await axios.post('/api/users', body, config);
dispatch({
type: registerSuccess,
payload: res.data,
});
} catch (err) {
const errors = err.response.data.errors;
if (errors) {
errors.forEach((error) => dispatch(setAlert(error.msg, 'danger')));
}
dispatch(registerFail());
}
};
.push is an array function to add a new item at the end of an array - your state is not an array.
You probably wanted to do something along the lines of
state.token = null
state.isAuthenticated = false
state.loading = false
state.user = null

Redux and Firebase

I've struggled to implement react-redux-firebase and redux-firestore into my app after configuring the redux store (struggled with this too, even though redux-toolkit simplified some things). Is it possible that I can communicate with firebase without using those two packages above? If so, how do I use firebase in any of my slices? e.g., auth slice below.
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import firebase from 'firebase/app';
export const authSlice = createSlice({
name: 'authSlice',
initialState: {
currentUser: null,
isLoggedIn: false,
isLoading: false,
},
reducers: {
login: async (state, action) => {},
registerUser: (state, action) => {},
changeProfile: (state, action) => {},
logout: async (state, action) => {},
setCurrentUser: (state, action) => {},
},
});
// Action creators are generated for each case reducer function
export const {
login,
registerUser,
changeProfile,
logout,
setCurrentUser,
} = authSlice.actions;
export default authSlice.reducer;
This is the query in a separate file.
import firestore from '#react-native-firebase/firestore';
export const getPopularProducts = firestore()
.collection('POPULAR')
.orderBy('count', 'desc')
.limit(10)
.get()
.then(querySnapshot => {
const views = [];
querySnapshot.forEach(doc => {
views.push({
key: doc.id,
count: doc.data().count,
product: doc.data().product,
});
});
return views;
})
.catch(error => {
alert('Error getting popular products: ', error);
});
In the reducer/slice, import getPopularProducts.
import {createSlice, createAsyncThunk} from '#reduxjs/toolkit';
import {getPopularProducts} from './../../lib/fetchData';
// Initial states
const initialState = {
products: [],
mainList: [],
popular: [],
};
// Get popular products from firebase
export const fetchPopularProducts = createAsyncThunk(
'prodSlice/fetchPopularProducts',
async () => {
const data = getPopularProducts;
const {_W} = data;
if (_W !== null) {
return _W;
}
},
);
export const productSlice = createSlice({
name: 'prodSlice',
initialState,
reducers: {
fetchData: (state, action) => {
state.isLoading = true;
state.mainList = action.payload;
state.products = action.payload;
}
},
extraReducers: {
[fetchPopularProducts.fulfilled]: (state, action) => {
state.popular = action.payload;
},
},
});
// Action creators are generated for each case reducer function
export const {fetchData} = productSlice.actions;
export const selectProducts = state => state.prodSlice;
export default productSlice.reducer;
Then you dispatch fetchPopularProducts inside the useEffect hook. I cases where I needed a parameter for the query, I'd put the query inside createAsyncThunk.

How can i write data that is coming from Firebase to store quickly?

Firstly, I'm working with React Native. I'm getting a data from Firebase and want to write to store (by Redux) quickly. But It doesn't work. You can find my all of codes below:
Function:
async getTumData (uid) {
const {selectedGroupDetail, getSelectedGroupDetail} = this.props;
var yeniGrupDetayi = {};
await firebase.database().ref("/groups/"+uid).once('value').then(
function(snapshot){
yeniGrupDetayi = {...snapshot.val(), uid: uid};
}).catch(e => console.log(e.message));
console.log("FONKSIYON ICERISINDEKI ITEM ==>", yeniGrupDetayi);
this.props.getSelectedGroupDetail(yeniGrupDetayi);
console.log("ACTION'DAN GELEN ITEM ===>", selectedGroupDetail);
}
Action:
export const getSelectedGroupDetail = (yeniGrupDetayi) => {
return {
type: GET_SELECTED_GROUP_DETAIL,
payload: yeniGrupDetayi
}
};
Reducer:
case GET_SELECTED_GROUP_DETAIL:
return { ...state, selectedGroupDetail: action.payload}
Çıktı:
FONKSIYON ICERISINDEKI ITEM ==> {admin: {…}, groupDescription: "Yaygın inancın tersine, Lorem Ipsum rastgele sözcü…erini incelediğinde kesin bir kaynağa ulaşmıştır.", groupName: "İnsan Kaynakları", groupProfilePic: "", members: {…}, …}
ACTION'DAN GELEN ITEM ===> {}
There is a FlatList in my page and I defined a button in renderItem of FlatList. When i click to this button, getTumData() function is working.
When i click to this button first time, selectedGroupDetail is null. Second time, it shows previous data.
How can i write a data to Store quickly and fast?
Thanks,
The thing is:
- You're using both async/await, and then/catch in your code.
- you're calling getSelectedGroupDetail before your async code resolves.
Fast Solution
getTumData = (uid) => {
const {selectedGroupDetail, getSelectedGroupDetail} = this.props;
var yeniGrupDetayi = {};
firebase.database().ref("/groups/"+uid).once('value').then(
(snapshot) => {
yeniGrupDetayi = {...snapshot.val(), uid: uid};
this.props.getSelectedGroupDetail(yeniGrupDetayi);
}).catch(e => console.log(e.message));
};
Better Solution:
1st: use Redux-Thunk middleware.
2nd: Move your Async code into your action creator: I mean this
async getTumData (uid) {
const {selectedGroupDetail, getSelectedGroupDetail} = this.props;
var yeniGrupDetayi = {};
await firebase.database().ref("/groups/"+uid).once('value').then(
function(snapshot){
yeniGrupDetayi = {...snapshot.val(), uid: uid};
}).catch(e => console.log(e.message));
console.log("FONKSIYON ICERISINDEKI ITEM ==>", yeniGrupDetayi);
this.props.getSelectedGroupDetail(yeniGrupDetayi);
console.log("ACTION'DAN GELEN ITEM ===>", selectedGroupDetail);
}
3rd: Your reducer should have another piece of data as an indicator for the time-gap before your selectedGroupDetail resolves:
// reducer initial state:
const INITIAL_STATE = { error: '', loading: false, selectedGroupDetail: null }
4th: Inside your action creator, you should dispatch 3 actions:
ACTION_NAME_START // This should should only set loading to true in your reducer.
ACTION_NAME_SUCCESS // set loading to false, and selectedGroupDetail to the new collection retured
ACTION_NAME_FAIL // in case op failed set error
5th: Your React component, should display a loading indicator (spinner or somthing), and maybe disable FlatList button during the loading state.
// Action creator
export const myAction = () => (dispatch) => {
dispatch({ type: ACTION_NAME_START });
firebase.database().ref("/groups/"+uid).once('value').then(
function(snapshot){
yeniGrupDetayi = {...snapshot.val(), uid: uid};
dispatch({ type: ACTION_NAME_SUCCESS, payload: yeniGrupDetayi });
}).catch(e => {
dispatch({ type: ACTION_NAME_FAIL, payload: e.message });
});
};
// Reducer
const INITIAL_STATE = {
loading: false,
error: '',
data: null,
};
export default (state = INITIAL_STATE, { type, payload }) => {
switch (type) {
case ACTION_NAME_START:
return {
...state,
error: '',
loading: true,
data: null,
};
case ACTION_NAME_SUCCESS:
return {
...state,
error: '',
loading: false,
data: payload,
};
case ACTION_NAME_FAIL:
return {
...state,
error: payload,
loading: false,
data: null,
};
default:
return state;
}
};

Action creators handling axios get.request with state access for param

I'm trying to pass some value from a component to a action creators which is doing a get request with axios. I'm trying to follow this pattern from Dan Abramov :
export const SOME_ACTION = 'SOME_ACTION';
export function someAction() {
return (dispatch, getState) => {
const {items} = getState().otherReducer;
dispatch(anotherAction(items));
}
}
However I can't make it work. I think I have trouble on two level : my component and my action creator. Would be great to have some helps.
my component :
const timeR = ({
selectedTimeRange,
timeRange = [],
onTimeChange }) => {
return (
<div>
<div>
Filters:
<div>
Year:
<select
defaultValue={selectedTimeRange}
onChange={onTimeChange}>
<option value="all" >All</option>
{timeRange.map((y, i) =>
<option key={i} value={y}>{y}</option>
)}
</select>
</div>
</div>
</div>
);
}
function mapStateToProps(state) {
var range = ['30daysAgo', '15daysAgo', '7daysAgo'];
return {
selectedTimeRange: state.timeReducer.timerange[0],
timeRange: range
};
};
const mapDispachToProps = (dispatch) => {
return {
onTimeChange: (e) => {dispatch (onSetTimeRange(e.target.value));},
};
};
const TimeRange = connect(mapStateToProps, mapDispachToProps)(timeR);
export default TimeRange;
This component give me a dropdown menu. When selecting a timerange, for example '30daysAgo', it update my redux store state so I can access the value from my reducer.
Here is the action associated to my dropdown menu :
export function onSetTimeRange(timerange) {
return {
type: 'SET_TIME_RANGE',
timerange
}
}
and here is the action dealing with axios.get :
export const fetchgadata = () => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null
});
var VIEW_ID = "ga:80820965";
return axios.get("http://localhost:3000/gadata", {
params: {
id: VIEW_ID
}
}).then(response => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching: false,
data: response.data.rows.map( ([x, y]) => ({ x, y }) )
});
})
.catch(err => {
dispatch({
type: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
};
My question :
How do I bring these two actions together. At the end I would like to be able, when doing onChange on my drop-down menu, to call a action with the value selected from my menu as a param for my axios.get request.
I feel like I need to nest two actions creators. I've tried this but doesn't work ("fetchgadata" is read-only error in my terminal)
export const SET_TIME_RANGE = 'SET_TIME_RANGE';
export function onSetTimeRange() {
return (dispatch, getState) => {
const {VIEW_ID} = getState().timerange;
dispatch(fetchgadata = (VIEW_ID) => (dispatch) => {
dispatch({
type: 'FETCH_DATA_REQUEST',
isFetching:true,
error:null,
id:VIEW_ID,
});
});
return axios.get("http://localhost:3000/gadata", {
params: {
id: VIEW_ID
}
}).then(response => {
dispatch({
type: 'FETCH_DATA_SUCCESS',
isFetching: false,
data: response.data.rows.map( ([x, y]) => ({ x, y }) )
});
})
.catch(err => {
dispatch({
ype: 'FETCH_DATA_FAILURE',
isFetching:false,
error:err
});
console.error("Failure: ", err);
});
}
}
Edit:
reducers for API call :
const initialState = {data:null,isFetching: false,error:null};
export const gaData = (state = initialState, action)=>{
switch (action.type) {
case 'FETCH_DATA_REQUEST':
case 'FETCH_DATA_FAILURE':
return { ...state, isFetching: action.isFetching, error: action.error };
case 'FETCH_DATA_SUCCESS':
return Object.assign({}, state, {data: action.data, isFetching: action.isFetching,
error: null });
default:return state;
}
};
reducers for Drop-down :
const items = [{timerange: '30daysAgo'},{timerange: '15daysAgo'},{timerange: '7daysAgo'}]
const timeReducer = (state = {
timerange: items
}, action) => {
switch (action.type) {
case 'SET_TIME_RANGE':
console.log(state,action);
return {
...state,
timerange: action.timerange,
};
default:
return state;
}
}
I see a little typo in the catch of your axios.get request, it reads ype: FETCH_DATA_FAILURE. Otherwise, can you add in your reducer for me, I don't see it up there? If I understand correctly, you want one action to update two different pieces of state, in which case you would simply dispatch an action and add it to both reducers. Really it's best to just demonstrate:
//axios call
axios.get("some route", { some params } )
.then(response => {
dispatch({
type: UPDATE_TWO_THINGS,
payload: some_value
})
}) .... catch, etc
//reducer 1
import { UPDATE_TWO_THINGS } from 'types';
const INITIAL_STATE = { userInfo: '' };
export default function (state = INITIAL_STATE, action) {
switch(action.type) {
case UPDATE_TWO_THINGS:
return {...state, userInfo: payload };
}
return state;
}
//reducer 2
import { UPDATE_TWO_THINGS } from 'types';
const INITIAL_STATE = { businessInfo: '' };
export default function (state = INITIAL_STATE, action) {
switch(action.type) {
case UPDATE_TWO_THINGS:
return {...state, businessInfo: payload };
}
return state;
}
Hopefully this helps, but let me know if not, I'll do my best to get this working with you! Thanks for asking!

Resources