I have some hook for fetching countries array from api :
import { useSelector, useDispatch } from 'react-redux'
const useCountries = () => {
const dispatch = useDispatch()
const { countries } = useSelector(data => data)
useEffect(() => {
if (!countries.length) {
dispatch(getCountries())
}
}, [countries])
return {
countries
}
}
I have some components which aren't on the same level of DOM , and aren't related to each other
const FirstComponent = () => {
const {countries} = useCountries()
// some using of countries array
}
const SecondComponent = () => {
const {countries} = useCountries()
// some using of countries array
}
When page loading 2 of these components, are executed, and 2 actions are dispatched. Because countries aren't selected yet in first and in the second component it calls API request. My question is, how can I prevent sending the second request if some request was sent. Make some rule for sending request only once, if try to send the second one, then block it. Or what is the correct solution or approach in my case?
Also, I've tried a case like this :
const {
external: { response, isInitial }
} = useSelector(data => data)
useEffect(() => {
if (!response && isInitial) {
dispatch(setIsInitial(false))
fetch()
.then(res => res.json())
.then(data => {
setResponse(data)
})
}
}, [dispatch, response, isInitial])
Here is my reducer :
const initialState = {
response: null,
isInitial: true
}
export default function external(state = initialState, action) {
switch (action.type) {
case types.SET_RESPONSE:
return {
...state,
response: action.payload.data
}
case types.SET_INITIAL:
return {
...state,
isInitial: action.payload.data
}
default:
return state
}
}
But it sends 2 requests
You can modify your useCountries as below:
const useCountries = () => {
const dispatch = useDispatch()
const countries = useSelector(state => state.countries);
const isFetching = useSelector(state => state.isFetching);
useEffect(() => {
if (!countries.length && !isFetching) {
dispatch(getCountries())
}
}, [countries, isFetching])
return {
countries
}
}
You would need to flip the isFetching flag in your getCountries function and flip it again when the api has resolved.
------------------EDIT after additional information-----------------------
You would need to chain your dispatch calls. This is to ensure that flag is set before you make the api call.
To achieve that you can make dispatch thenable in the following way.
dispatch returns either of two:
For sync action (like dispatch ({type: 'ACTION'}) it will return
action object ({type: 'ACTION'} in my example)
For thunk actions (action creators which return functions) it returns
the same result returned from action creator.
This is the first case.
const syncAction = (arg1, arg2) => {
return (dispatch, getState) => {
return Promise.resolve(arg1 + arg2);
}
}
This would be the second case.
const asyncAction = (arg1, arg2) => {
return (dispatch, getState) => {
return fetch(/* some request */)
.then(response => dispatch({ type: "RESPONSE_RECEIVED", payload: response }));
};
};
Now armed with two above, you can do the following:
dispatch(syncAction(...args)).then(() => {
dispatch(asyncAction())
});
In your case syncAction - isInitial and asyncAction - getCountries().
Addind dispatch to useEffect dependencies array whould solve the issue.
const dispatch = useDispatch()
const { countries } = useSelector(data => data)
useEffect(() => {
if (!countries.length) {
dispatch(getCountries())
}
}, [countries, dispatch])
Related
I need help. I don't understand why my dispatch action doesn't work. I've redux store currency list and current currency.
My reducer:
export const currencyReducer = (
state: typeState = initialState,
action: TypeActionCurrency
): typeState => {
switch (action.type) {
case types.CURRENCY_FILL_LIST:
return { ...state, list: action.payload }
case types.CURRENCY_SET_CURRENT:
return {
...state,
current:
state.list.find(currency => currency._id === action.payload) ||
({} as ICurrency),
}
default:
return state
}
}
My actions:
export const setCurrencyList = (currencies: ICurrency[]) => ({
type: types.CURRENCY_FILL_LIST,
payload: currencies,
})
export const setCurrentCurrency = (_id: string) => ({
type: types.CURRENCY_SET_CURRENT,
payload: _id,
})
My useEffect:
useEffect(() => {
if (!list.length) {
const fetchCurrencies = async () => {
try {
const data = await $apiClient<ICurrency[]>({ url: '/currencies' })
dispatch(setCurrencyList(data))
if (!current._id) dispatch(setCurrentCurrency(data[0]._id))
} catch (error) {
console.log(error)
}
}
fetchCurrencies()
}
}, [])
I want make request when load page and write currency list to Redux store, if we don't have current currency we write default currency from data.
There is one more strange thing, my redux extension shows that the state has changed, but when I receive it via the log or useSelector, it is empty
enter image description here
Thanks!
I am not 100% sure but it should work.
const [loader, setLoader] = useState(false);
const list = useSelector(state => state.list)
useEffect(() => {
if (!list.length) {
const fetchCurrencies = async () => {
try {
setLoader(true)
const data = await $apiClient<ICurrency[]>({ url: '/currencies' })
dispatch(setCurrencyList(data))
if (!current._id) dispatch(setCurrentCurrency(data[0]._id))
} catch (error) {
console.log(error)
} finally {
setLoader(false)
}
}
fetchCurrencies()
}
}, [])
useEffect(() => {
console.log(list);
}, [loader])
My axios transaction is all done in the redux actions so that I can re-use the function. The issue is that, I need to fetch the data first which is done by redux and then re-assign the value in a state, but the data cannot be populated in the state. Below is how my code looks like.
Setting.js
...
import { getUserDetail } from './redux/actions/settingActions';
export default function Setting() {
const dispatch = useDispatch()
const { user } = useSelector(state => state.settingReducer)
const [userDetail, setUserDetail] = useState()
useEffect(() => {
dispatch(getUserDetail())
setUserDetail(user) // I want to set the user here
}, [])
...
}
settingActions.js
export const getUserDetail = () => (dispatch, getState) => {
axios.get('url-goes-here')
.then(res => {
dispatch({
type: SET_USER_DETAIL,
payload: { res.data }
})
})
.catch(error => {
throw error;
})
}
settingReducer
function initialState() {
return {
...
user: {}
}
}
export default function (state = initialState(), action) {
const { type, payload } = action;
switch (type) {
case SET_USER_DETAIL:
return {
...state,
user: payload
}
default:
return state
}
}
My purpose of doing this is because I want to do some user details update but I want it to be done within the same file.
put user and dispatch as dependency in useEffect
useEffect(() => {
dispatch(getUserDetail())
setUserDetail(user)
}, [user,dispatch])
I am using redux-toolkit with createAsyncThunk to handle async requests.
I have two kinds of async operations:
get the data from the API server
update the data on the API server
export const updateData = createAsyncThunk('data/update', async (params) => {
return await sdkClient.update({ params })
})
export const getData = createAsyncThunk('data/request', async () => {
const { data } = await sdkClient.request()
return data
})
And I add them in extraReducers in one slice
const slice = createSlice({
name: 'data',
initialState,
reducers: {},
extraReducers: (builder: any) => {
builder.addCase(getData.pending, (state) => {
//...
})
builder.addCase(getData.rejected, (state) => {
//...
})
builder.addCase(
getData.fulfilled,
(state, { payload }: PayloadAction<{ data: any }>) => {
state.data = payload.data
}
)
builder.addCase(updateData.pending, (state) => {
//...
})
builder.addCase(updateData.rejected, (state) => {
//...
})
builder.addCase(updateData.fulfilled, (state) => {
//<--- here I want to dispatch `getData` action to pull the updated data
})
},
})
In my component, I have a button that triggers dispatching of the update action. However I found after clicking on the button, despite the fact that the data is getting updated on the server, the data on the page is not getting updated simultaneously.
function MyComponent() {
const dispatch = useDispatch()
const data = useSelector((state) => state.data)
useEffect(() => {
dispatch(getData())
}, [dispatch])
const handleUpdate = () => {
dispatch(updateData())
}
return (
<div>
<ul>
// data goes in here
</ul>
<button onClick={handleUpdate}>update</button>
</div>
)
}
I tried to add dispatch(getData()) in handleUpdate after updating the data. However it doesn't work because of the async thunk. I wonder if I can dispatch the getData action in the lifecycle action of updateData i.e.
builder.addCase(updateData.fulfilled, (state) => {
dispatch(getData())//<--- here I want to dispatch `getData` action to pull the updated data
})
Possibly it's not actual and the question is outdated, but there is thunkAPI as second parameter in payload creator of createAsyncThunk, so it can be used like so
export const updateData = createAsyncThunk('data/update', async (params, {dispatch}) => {
const result = await sdkClient.update({ params })
dispatch(getData())
return result
})
First of all: please note that reducers always need to be pure functions without side effects. So you can never dispatch anything there, as that would be a side effect. Even if you would somehow manage to do that, redux would warn you about it.
Now on to the problem at hand.
You could create a thunk that dispatches & awaits completion of your updateData call and then dispatches your getData call:
export const updateAndThenGet = (params) => async (dispatch) => {
await dispatch(updateData(params))
return await dispatch(getData())
}
//use it like this
dispatch(updateAndThenGet(params))
Or if both steps always get dispatched together anyways, you could just consider combining them:
export const updateDataAndGet = createAsyncThunk('data/update', async (params) => {
await sdkClient.update({ params })
const { data } = await sdkClient.request()
return data
})
I been stuck to this quite a bit, I am trying to pass in my state to the redux but it seems like I am doing it wrong.
This are my code:
This is my submit function
popForm() {
let states = this.state.orders;
let d = states.filter((data) => {
return data !== null && data !== undefined
});
// console.log("d",d);
this.props.LogInClick(d);
// LogInClick(state);
}
This is my mapToDispatch
const mapDispatchToProps = (dispatch) => {
return {
LogInClick : (data) => dispatch(Actions.addDynamic(data)),
}
}
Action call
export const addDynamic = ({data}) => {
console.log("Manage to get to here");
console.log("dataInAction",data);
}
My reducer
case Actions.ADD_DYNAMIC: {
return {
...state,
data: action.payload
};
}
Your synchronous action should to return an object with type and payload.
When dealing with async actions, you need thunk(or saga etc) middleware. Your code seem to dispatch normal action (not async). So just make sure that your action returns type and payload.
Like this
export const addDynamic = ({data}) => {
console.log("Manage to get to here");
console.log("dataInAction",data);
return {
type: Action.ADD_DYNAMIC,
payload: data
}
}
How can I use the mapdispatchtoprops function correctly to dispatch to reducer? First, I get data from the server and want to send this data to the reducer. firebaseChatData function cannot be transferred to the mapdispatchtoprops because it is inside the component
Messages.js
const MessageUiBody = ( { messages, loading } ) => {
const userData = JSON.parse(localStorage.getItem("user-data"));
useEffect( () => {
const firebaseChatData = () => (dispatch) => {
firebaseDB.ref().child(API.firebaseEnv + "/messages/messageItem" + userData.account_id)
.on("value", snap => {
const firebaseChat = snap.val();
// console.log(firebaseChat)
dispatch(firebaseChatAction(firebaseChat))
});
};
}, []);
return(
<div> // code </div>
);
};
//Action
const firebaseChatAction = (firebaseChat) => ({
type: 'FIREBASE_MESSAGE',
firebaseChat
});
const mapDispatchToProps = (dispatch) => {
return {
data : () => {
dispatch(firebaseChatData())
}
}
};
export default connect(null, mapDispatchToProps)(MessageUiBody)
Reducer
export default function messages ( state = [], action = {}) {
switch (action.type) {
case 'FIREBASE_MESSAGE' :
state.data.messages.push(action.firebaseChat);
return {
...state
};
default:
return state
}
}
You'll have to change your code, because you're defining data as the prop function that will dispatch your action:
const mapDispatchToProps = dispatch => {
return {
data: (result) => dispatch(firebaseChatAction(result)),
}
}
After that change the line after the console log in your promise and use the data prop that you defined in your mapDispatch function:
const MessageUiBody = ( { data, messages, loading } ) => {
const userData = JSON.parse(localStorage.getItem("user-data"));
useEffect( () => {
const firebaseChatData = () => (dispatch) => {
firebaseDB.ref().child(API.firebaseEnv + "/messages/messageItem" + userData.account_id)
.on("value", snap => {
const firebaseChat = snap.val();
// here you call the data that will dispatch the firebaseChatAction
data(firebaseChat)
});
};
}, []);
return(
<div> // code </div>
);
};
Also is worth to notice that you don't have to push items in your state, you can't mutate the current state, so always try to generate new items instead of modifying the existing one, something like this:
export default function messages ( state = [], action = {}) {
switch (action.type) {
case 'FIREBASE_MESSAGE' :
return {
...state,
data: {
...state.data,
messages: [...state.data.messages, action.firebaseChat]
}
};
default:
return state
}
}
With the spread operator you are returning a new array that contains the original state.data.messages array and will add the firebaseChat item as well.