Using store.dispatch with redux toolkit slice - reactjs

I have a redux toolkit slice as follow
import { createSlice } from '#reduxjs/toolkit';
const initialState = {
value: null,
};
export const authSlice = createSlice({
name: 'auth',
initialState,
reducers: {
setAuth: (state, action) => {
state.value = action.payload;
},
},
});
export const { setAuth } = authSlice.actions;
export default authSlice.reducer;
I can use useDispatch in function component as follow to setAuth
const dispatch = useDispatch();
const auth = useSelector((state) => state.auth.value);
const setAuthValue = (value) => {
dispatch(setAuth(value));
};
However, I have a scenario where I am not able to useDispatch hooks for this. (eg: inside axios interceptor)
Is that possible that I directly use store.dispatch({'type': '', payload: {}} like normal redux with above redux toolkit slice?
Or what is the better approach for it?

I had the same question and just got it working. In the plain js file (outside a component function context), just import your setAuth action that you're exporting from your slice. Then, you can dispatch it like so: store.dispatch(setAuth(payload))

This is what useDispatch function in react-redux library:
function useDispatch() {
const store = useStore()
return store.dispatch
}
You can export the store from store.js and use store.dispatch

Related

Why do I have an error: 'Actions must be plain objects. Use custom middleware for async actions.' when using redux?

So I have a movie app, and I have a page for a single movie. I have a section on that page where I display all of the videos from an API related to a certain movie.
So my Videos component looks like this:
const Videos = ({videos} :{videos:IVideos | null}) => {
return (
<div>{videos?.results.map((video, i) =>
<div key={i}>{video.name}</div>
)}</div>
)
}
It's just a basic component which gets props from a higher component. But the main thing is redux slice, which looks like this:
Initial state:
const initialState: IMovieVideosState = {
movieVideos: null,
fetchStatus: null,
}
export interface IMovieVideosState {
movieVideos: IVideos | null;
fetchStatus: FetchStatus | null;
}
And finally slice:
const videosSlice = createSlice({
name:'videos',
initialState,
reducers:{},
extraReducers(builder) {
builder
.addCase(fetchVideos.pending, (state, action) => {
state.fetchStatus = FetchStatus.PENDING
})
.addCase(fetchVideos.fulfilled, (state, action) => {
state.fetchStatus = FetchStatus.SUCCESS
state.movieVideos = action.payload
})
.addCase(fetchVideos.rejected, (state, action) => {
state.fetchStatus = FetchStatus.FAILURE
//state.error = action.error.message
})
}
})
As you see, these are basic reducers, where if promise is successful I assign payload to an existing array.
And also thunk function:
export const fetchVideos = createAsyncThunk('videos/fetchVideos', async (id: number) => {
const response = await axios.get<IVideos>(`${API_BASE}movie/${id}/videos?api_key=${TMDB_API_KEY}`);
console.log(response.data);
return response.data;
})
But in the browser I have the next error:
Uncaught Error: Actions must be plain objects. Use custom middleware for async actions.
And also another one:
A non-serializable value was detected in an action, in the path: `<root>`. Value:
Promise { <state>: "pending" }
Take a look at the logic that dispatched this action:
Promise { <state>: "pending" }
I have no idea why I could have these errors, because my reducer is the same as another one in my project, but this one doesn't work for some reason.
UseEffect for dispatching all reducers:
useEffect(() =>{
dispatch(fetchDetail(Number(id)));
dispatch(fetchCredits(Number(id)));
dispatch(fetchPhotos(Number(id)));
dispatch(fetchRecommended(Number(id)));
dispatch(fetchSimilar(Number(id)));
dispatch(fetchVideos(Number(id))); //dispatching fetchVideos()
}, [dispatch, id])
So in my case, all of the other functions work fine besides fetchVideos().
Another example of a thunk for movie details:
export const fetchDetail = createAsyncThunk('detail/fetchDetail', async (id: number) => {
const response = await axios.get<IMovie>(`${API_BASE}movie/${id}?api_key=${TMDB_API_KEY}`);
console.log(response.data);
return response.data;
})
My store file:
import thunk from "redux-thunk";
export const store = configureStore({
reducer: {
popular,
top_rated,
playing,
upcoming,
detail,
credits,
videos,
photos,
recommended,
similar
},
middleware: [thunk]
})
export type RootState = ReturnType<typeof store.getState>;
instead of using create Async Thunk method add think malware where you create store of videos then you can pass Async actions into it without nothing.
import { applyMiddleware, combineReducers, createStore } from "redux";
import { composeWithDevTools } from "redux-devtools-extension";
import thunk from "redux-thunk";
// import your videos reducer here from file
export interface State {
videos: IVideos;
}
const rootReducer = combineReducers<State>({
videos: VideosReducer,
});
export const rootStore = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(thunk))
);

Why is my dispatch not performing the console.log?

SO im having a hard time understanding reducers and dispatch, and useSelectors.
In this example, im clicking a button via onRowItemClick. This should in theory, dispatch an action, which is playAudio.
That action is inside a reducer? And i would imagine it should console.log an empty array, which is the current state. However, when i click it, nothing happens in the console.
Slice:
import { createSlice } from '#reduxjs/toolkit';
const initialState = [];
const AudioSlice = createSlice({
name: 'AudioSlice',
initialState,
reducers: {
playAudio(state){
console.log(current(state));
}
},
});
export const { playAudio} = AudioSlice.actions;
export default AudioSlice.reducer;
The dispatch:
import React from 'react';
import { useDispatch } from 'react-redux';
import { playAudio } from 'slices/AudioSlice';
const ProjectPageAudioTab = () => {
const dispatch = useDispatch();
const onRowItemClick = () => {
dispatch(playAudio());
};
}
export default ProjectPageAudioTab;

Redux: Combining reducers

I am following the Redux tutorials on the Redux site and I am having difficulty using combine reducers. If I run my code without combine reducers, it works fine. As soon as I put the single reducer into the combined reducers function. I get this: Error: "reducer" is a required argument, and must be a function or an object of functions that can be passed to combineReducers. What am I doing wrong?
Here is the store before using combine reducers:
import { configureStore } from "#reduxjs/toolkit";
import CoinsAllReducer from "./slices/CoinsAll";
export const store = configureStore({
reducer: {
coinsAll: CoinsAllReducer,
},
});
and here is the slice:
import axios from "axios";
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
const initialState = {
coinsAll: [],
status: "idle",
error: null,
};
export const fetchAllCoins = createAsyncThunk(
"coinsAll/fetchAllCoins",
async () => {
const res = await axios.get(
`https://api.coingecko.com/api/v3/coins/markets/?vs_currency=usd&order=market_cap_desc&per_page=100&page=1&sparkline=false`
);
return res.data;
}
);
export const coinsAll = createSlice({
name: "coinsAll",
initialState,
reducers: {},
extraReducers(builder) {
builder
.addCase(fetchAllCoins.pending, (state) => {
state.status = "loading";
})
.addCase(fetchAllCoins.fulfilled, (state, action) => {
state.status = "succeeded";
state.coinsAll = action.payload;
})
.addCase(fetchAllCoins.rejected, (state, action) => {
state.status = "failed";
state.error = action.error.message;
});
},
});
export const selectCoinsAll = (state) => state.coinsAll.coinsAll;
export const selectStatus = (state) => state.coinsAll.status;
export default coinsAll.reducer;
The above code works and gets the data from the endpoint.
Here is the store modified to use the combined reducer:
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
export const store = configureStore(reducer);
Here is the rootReducer:
import { combineReducers } from "redux";
import coinsAllReducer from "./slices/coinsAll";
const reducer = combineReducers({
coinsAll: coinsAllReducer,
});
export default reducer;
I changed nothing in the slice.
Any help would be appreciated, thanks.
configureStore takes an object with a reducer key.
It should be
export const store = configureStore({
reducer, // <-- object shorthand for the imported root reducer
// other configurations, etc...
});
You have to pass the combined reducer inside the configuration object to the configureStore method
export const store = configureStore({
reducer:combinedReducer
});
Hence, your updated code should be
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
export const store = configureStore({reducer:reducer});
And since key and value is same, you can simply go with
import { configureStore } from "#reduxjs/toolkit";
import reducer from "./rootReducer";
export const store = configureStore({reducer});

Redux with getStaticProps

I'm struggling to understand how I can get around this issue?
I want to get data that I can share globally using redux**(using redux as I'm using it for other use cases in my app)**. my problem is I'm using getStaticProps to try and dispatch my ReduxThunk but I can't use Hooks inside getStaticProps and I have no idea what the workaround would be if anyone could point me to some docs I would appreciate it
Slice.js
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
export const fetchData = createAsyncThunk(
"fetchCoinData",
async (url, thunkApi) => {
const data = await fetch(url).then((res) => res.json());
return data;
}
);
const initialState = {
data: [],
status: null,
};
const getData = {};
export const dataSlice = createSlice({
name: "datafetch",
initialState,
extraReducers: {
[getData.pending]: (state) => {
state.status = "Loading!";
},
[getData.fulfilled]: (state, { payload }) => {
state.data = payload;
state.status = "Sucsess!";
},
[getData.rejected]: () => {
state.status = "Failed";
},
},
});
// Action creators are generated for each case reducer function
export const {} = dataSlice.actions;
export default dataSlice.reducer;
cardano.js
import React from "react";
import { useDispatch } from "react-redux";
import BasicCard from "../../Components/UI/Cards/BasicCard";
import { UsersIcon } from "#heroicons/react/outline";
import { fetchData } from "../../redux/slice/DataSlice";
const cardano = (props) => {
return (
<div>
<h1>Header</h1>
</div>
);
};
//PROBLEM IS HERE
export async function getStaticProps(context) {
const dispatch = useDispatch();
const priceQuery =
"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin%2Ccardano%2Cethereum&vs_currencies=USD";
const res = await dispatch(fetchData(priceQuery));
return {
props: {
theData: res,
}, // will be passed to the page component as props
};
}
export default cardano;
To use Hooks, it has to be located at the highest level. Another alternative that you may try is the lifecycle of React. They are the same as Hooks.
For something like this, you probably want to use https://github.com/kirill-konshin/next-redux-wrapper .
Generally you have to be aware though: just putting something in the global state during SSR does not mean your client has it - your client will have it's own Redux state, and each page that is server-side-rendered also has their own isolated Redux state and you will think about what of that you will "hydrate" from the server to the client to make it available there.
The docs of next-redux-wrapper go more into this than I could possibly explain myself, so give those a read!

Get Data using Redux with react hooks

So I just started learning hooks and I'm trying to use Redux with them. Ive been trying redux new api but now I can't seem to get the data that I want. When I do console.log() I see the promise getting resolved and inside the [[PromiseValue]] I see the data but how do I get it out of there into my Component.
const Main = ({props}) => {
const [cloth,setCloth]=useState([])
const items = useSelector((state) => state);
const dispatch = useDispatch()
let getItems = getAllItems(
() => dispatch({ type: 'GET_ITEMS' }),
[dispatch]
)
console.log(getItems)
here is the pic of my console.log()
first to use redux with async actions you need to use redux-thunk middleware:
npm i redux-thunk
and then use it like so:
import { createStore, applyMiddleware } from 'redux';
const store = createStore(rootReducer, applyMiddleware(thunk));
now for the actions here a simple example:
first action to fetch Items and then action to set Items in store:
//actions.js
const setItems = (payload)=>({type:SET_ITEMS,payload})
export const fetchItem = ()=>{
return dispatch =>{
axios.get("/items").then(response=>
dispatch(setItems(response.data))).catch(error=>throw error)
}
}
//reducer.js
const state = {items:[]}
export default function todosReducer(state = initialState, action) {
switch (action.type) {
case "SET_ITEMS":
return {
...state,
items: payload
};
default:
return state;
}
}
now from react component we fetch items on mount :
import React,{useEffect} from "react"
import {useDispatch,useSelector} from "react-redux"
import {fetchItems} from "action.js"
const Items = ()=>{
const items= useSelector(state=>state.items)
const dispatch = useDispatch()
useEffect(()=> dispatch(fetchItems()),[])
return items?items.map(item =><p key={item.name}>{item.name}</p>):<p>items not available</p>
hope this what you are looking for.

Resources