I'm trying to trigger a simple action using #reduxjs/Toolkit but it's not working.
I see that the action is dispatched but it's like the slice reducer is not listening to it or something.
const say = createAction("ui/say", what => ({ payload: what }));
const uiSlice = createSlice({
name: "ui",
initialState: { said: "" },
reducers: {
[say.type]: (state, action) => {
console.log("saying", action.payload); //<-- not showing, why?
state.currentList = action.payload;
}
}
});
const store = configureStore({
reducer: combineReducers({
ui: uiSlice.reducer
})
});
const Chat = () => {
const dispatch = useDispatch();
const [whatToSay, setWhatToSay] = useState("");
const whatWasSaid = useSelector(state => state.ui.said);
const onSubmit = e => {
e.preventDefault();
dispatch(say(whatToSay));
setWhatToSay("");
};
return (
<div>
<form onSubmit={onSubmit}>
<input type="text" onChange={e => setWhatToSay(e.target.value)} />
<button>Say</button>
</form>
{whatWasSaid ? <p>You said: {whatWasSaid}</p> : <p>Say something</p>}
</div>
);
};
Here's a minimal reproducing example:
https://codesandbox.io/s/redux-toolkit-0tzxs?file=/src/index.js
I think you mismatched the createSlice API.
From your code, you trying to implement a listener for an action, so you might want to use extraReducers instead:
const uiSlice = createSlice({
name: "ui",
initialState: { said: "" },
// Not reducers: {}
extraReducers: {
[say.type]: (state, action) => {
console.log("saying", action.payload);
state.currentList = action.payload;
}
}
});
Note the reducers prop of createSlice API:
reducers: Object<string, ReducerFunction | ReducerAndPrepareObject>
If you want to use say in reducers it should be:
const say = (state, payload) => {
console.log("saying", payload);
state.currentList = payload;
};
const uiSlice = createSlice({
name: "ui",
initialState: { said: "" },
reducers: { say }
});
// Usage
dispatch(uiSlice.actions.say(whatToSay));
#markerikson: with createSlice, the reducers field is for defining reducers and generating actions that will match those reducers. The extraReducers field is for handling actions that were already defined elsewhere.
Related
in react-redux:
API file, returnes data from DB:
import axios from "axios";
const URL = "http://127.0.0.1:8000/GetAllExercises/";
export function GetAllExercies(){
return new Promise((resolve) =>
axios.get(URL).then((res)=> resolve({data: res.data})))
}
Slice File, calling the async function and setting the payload to the varibale- exercises:
import { createAsyncThunk, createSlice, PayloadAction } from "#reduxjs/toolkit";
import { RootState, AppThunk } from "../../app/store";
import { GetAllExercies } from "../API/GetAllExercisesAPI";
export interface CounterState {
exercieses: string[];
status: "idle" | "loading" | "failed";
}
const initialState: CounterState = {
exercieses: [],
status: "idle",
};
export const GetAllExerciesAsync = createAsyncThunk(
"get_all_exercises/GetAllExercies",
async () => {
const response = await GetAllExercies();
return response;
}
);
export const GetAllExerciesSlice = createSlice({
name: "get_all_exercises",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(GetAllExerciesAsync.pending, (state) => {
state.status = "loading";
})
.addCase(
GetAllExerciesAsync.fulfilled,
(state, action: PayloadAction<any>) => {
state.status = "idle";
console.log(action.payload);
state.exercieses = action.payload;
--> console.log(state.exercieses);
}
);
},
});
export const selectExercises = (state: RootState) => state.get_all_exercises.exercieses;
export default GetAllExerciesSlice.reducer;
at the line with arrow i get this at the console debugger(f12):
{data:Array(17)}
how can i accesses the data inside of it?
If you want access the data duting setting state then you can simply access data from payload
export const GetAllExerciesSlice = createSlice({
name: "get_all_exercises",
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(GetAllExerciesAsync.pending, (state) => {
state.status = "loading";
})
.addCase(
GetAllExerciesAsync.fulfilled,
(state, action: PayloadAction<any>) => {
state.status = "idle";
// data is inside action.payload
state.exercieses = action.payload.data;
console.log(state.exercieses);
}
);
},
});
Apparently you are assigning an object to your "exercieses" variable. If using redux there should be a reducer where you do this.
Something similar to this:
const { response } = action.payload
return {...state, exercises:response.data}
Profile Slice:
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit'
import { IMAGE_API, ACCESS_KEY } from "../../app/utils/constant";
export const getImages = createAsyncThunk('images', async () => {
return fetch(`${IMAGE_API + ACCESS_KEY}`).then((res) =>
res.json()
)
})
console.log(IMAGE_API + ACCESS_KEY);
const ProfilePicSlice = createSlice({
name: 'imageList',
initialState: {
images: [],
loading: false,
},
extraReducers: (builder) => {
builder.addCase(getImages.pending, (state) => {
state.loading = true;
})
builder.addCase(getImages.fulfilled, (state, action) => {
state.loading = false;
state.images.push(action.payload);
console.log(action.payload)
})
builder.addCase(getImages.rejected, (state) => {
state.loading = true;
})
}
});
export default ProfilePicSlice.reducer
Form Slice:
import { createSlice } from "#reduxjs/toolkit";
const initialState = []
const UserSlice = createSlice({
name: 'users',
initialState,
reducers: {
addUser: (state, action) => {
state.push(action.payload);
}
}
});
export const {addUser} = UserSlice.actions;
export default UserSlice.reducer;
I want to add custom param in API URL in asyncThunk '${IMAGE_API + 'custom param' + ACCESS_KEY}
custom param should come from form slice data.
redux-toolkit async thunks are able to access the ThunkAPI, which includes a function to get the current redux state object, it is the second argument passed to the thunk callback. From here you can select the state value you need.
See PayloadCreator.
Example:
export const getImages = createAsyncThunk(
'imageList /images',
(_, { getState }) => {
const store = getState();
const param = state.users./* access into state to get property */;
return fetch(`${IMAGE_API}${param}${ACCESS_KEY}`)
.then((res) => res.json());
}
);
im dispaching my id to the productsSlice , and from there im call to getProductById() that in my productService, the productService communicate with async-storage-service and i think the problem comes from here.
i want to get the product from the id i sent.
i dont have an error in the console.
//ProductDetails.jsx
export function ProductDetails() {
let [loading, setLoading] = useState(true);
const dispatch = useDispatch()
// let [color, setColor] = useState("#ffffff");
const { id } = useParams();
console.log(id);
const [product, setProduct] = useState(null);
useEffect(() => {
dispatch(getProduct(id))
}, [id, product]);
//productsSlice.js
const productsSlice = createSlice({
name: 'products',
initialState,
reducers: {
getProduct: (state, action) => {
productsService.getProductById(action.payload)
}
},
//products-service.js
async function query() {
const response = await axios.get('https://dummyjson.com/products')
const products = await response.data
localStorage.setItem(PRODUCTS_KEY, JSON.stringify(products.products))
return products
}
async function getProductById(id) {
return storageService.get(PRODUCTS_KEY, id)
}
//async-storage-service.js
export const storageService = {
query,
get,
post,
put,
remove,
postMany
}
function query(entityType) {
var entities = JSON.parse(localStorage.getItem(entityType)) || []
return Promise.resolve(entities);
}
function get(entityType, entityId) {
return query(entityType)
.then(entities => entities.find(entity => entity.id === entityId))
}
Redux reducer functions are to be considered pure, synchronous functions. Your getProduct action is calling asynchronous code and it doesn't update any state.
Convert getProduct to an asynchronous action and specify an extraReducers reducer function to handle the fulfilled Promise returned by it.
import { createAsyncThunk, createSlice } from '#reduxjs/toolkit';
const getProduct = createAsyncThunk(
"/products/getProduct",
async (id, thunkAPI) => {
return productsService.getProductById(id);
},
);
const productsSlice = createSlice({
name: 'products',
initialState,
reducers: {
...
},
extraReducers: builder => {
builder
.addCase(getProduct.fulfilled, (state, action) => {
state.product = action.payload; // * Note
});
},
};
*Note: You didn't include what initialState is so we've no way of knowing what state should actually be updated, but it's just a normal Redux state update from here. Tweak this part to fit your actual use case.
I’m working on a little proof of concept project, using React and Redux, and useSelector and useDispatch hooks. I’m trying to fetch some data asynchronously and I use thunks for that. I think I'm conceptually missing something. Even though my state works as expected, I can not get my data from api using useSelector.
Here is the code. My action:
import axios from "axios";
export const API_FETCH_POSTS = 'API_FETCH_POSTS';
export const fetchPosts = (postId) => { // to simulate post request
return (dispatch) => {
let baseUrl = 'https://jsonplaceholder.typicode.com/';
let postFix = 'comments?postId=';
let url = `${baseUrl}${postFix}${postId}`;
axios.get(url)
.then(response => {
const data = response.data;
console.log(JSON.stringify(data)); // work!
dispatch(fetchPostsSuccess(data));
});
}
};
const fetchPostsSuccess = posts => {
return {
type: API_FETCH_POSTS,
payload: posts
}
};
My reducer:
import {API_FETCH_POSTS} from "./apiActions";
const initialState = {
getPostsReq : {
posts: [],
}
};
const apiReducer = (state = initialState, action) => {
let getPostsReq;
switch (action.type) {
case API_FETCH_POSTS:
getPostsReq = {
posts: [...state.getPostsReq.posts]
};
return {
...state,
getPostsReq
};
default: return state;
}
};
export default apiReducer;
And rootReducer:
import {combineReducers} from 'redux';
import apiReducer from "./api/apiReducer";
export default combineReducers({
api: apiReducer
})
And store:
const initialState = {};
const store = createStore(
rootReducer,
initialState,
composeWithDevTools(
applyMiddleware(thunk)
)
);
export default store;
I have a problem with my React component:
function PostContainer(props) {
const posts = useSelector(state => state.api.getPostsReq.posts);
const dispatch = useDispatch();
const logPosts = () => {
{/*why doesn't this line work???*/}
console.log(JSON.stringify(posts));
}
return (
<div>
<button onClick={() => {
dispatch(fetchPosts(1));
logPosts();
}}>Fetch Posts</button>
<div>
{/*why doesn't this line work???*/}
{posts.map(post => <p>{post.body}</p>)}
</div>
</div>
);
}
export default PostContainer;
I expect that after I press the button, the function fetchPosts gets dispatched and because I use thunk I shouldn’t have any problems with asynchronicity. But by some reason I can’t get my state, using useSelector() hook. I can neither render the state, nor log it in the console.
What am I missing here?
Here is the whole code if it is more convenient - https://github.com/JavavaJ/use-select-problem
Problem: Not Storing Posts
Your selector is fine, it's your reducer that's the problem! You dispatch an action which has an array of posts in the payload:
const fetchPostsSuccess = posts => {
return {
type: API_FETCH_POSTS,
payload: posts
}
};
But when you respond to this action in the reducer, you completely ignore the payload and instead just return the same posts that you already had:
const apiReducer = (state = initialState, action) => {
let getPostsReq;
switch (action.type) {
case API_FETCH_POSTS:
getPostsReq = {
posts: [...state.getPostsReq.posts]
};
return {
...state,
getPostsReq
};
default: return state;
}
};
Solution: Add Posts from Action
You can rewrite your reducer like this to append the posts using Redux immutable update patterns.
const apiReducer = (state = initialState, action) => {
switch (action.type) {
case API_FETCH_POSTS:
return {
...state,
getPostsReq: {
...state.getPostsReq,
posts: [...state.getPostsReq.posts, ...action.payload]
}
};
default:
return state;
}
};
It's a lot easier if you use Redux Toolkit! With the toolkit you can "mutate" the draft state in your reducers, so we don't need to copy everything.
const apiReducer = createReducer(initialState, {
[API_FETCH_POSTS]: (state, action) => {
// use ... to push individual items separately
state.getPostsReq.posts.push(...action.payload);
}
});
I am trying to use Redux Toolkit in my new website and I have a problem using the createEntityAdapter.
I am fetching some data from my database but my state is never updated with the data fetched and I do not understand why.
My slice and fetch functions:
export const getTransports = createAsyncThunk('marketplace/transports/getTransports', async email => {
const response = await axios.get(`${API_URL}/transport/published/company/${email}/notActive`);
console.log('response', response);
const data = await response.data;
console.log('DATA', data);
return data;
});
const transportsAdapter = createEntityAdapter({});
export const { selectAll: selectTransports, selectById: selectTransportById } = transportsAdapter.getSelectors(
state => state.marketplace.transports
);
const transportsSlice = createSlice({
name: 'marketplace/transports',
initialState: transportsAdapter.getInitialState({
searchText: ''
}),
reducers: {
setTransportsSearchText: {
reducer: (state, action) => {
state.searchText = action.payload;
},
prepare: event => ({ payload: event.target.value || '' })
},
extraReducers: {
[getTransports.fulfilled]: transportsAdapter.setAll
}
}
});
export const { setTransportsSearchText } = transportsSlice.actions;
export default transportsSlice.reducer;
The data fetch is working well and the state between the request and de fullfilled looks like working as it should be, but as you can see in the console, the transports state is never updated.
Redux Logger
I do not understand why is not working the setAll function from the transportsAdapter.
The entities that are being retrieved have and id and the entity information, so it should work correctly but it does not.
I hope you can help me.
Thank you very much.
I was facing the same issue following along the Redux Essentials Tutorial.
I fixed it by specifying the parameters to the function setAll() and using the builder callback approach.
const transportsSlice = createSlice({
name: 'marketplace/transports',
initialState: transportsAdapter.getInitialState({
searchText: ''
}),
reducers: {
setTransportsSearchText: {
reducer: (state, action) => {
state.searchText = action.payload;
},
prepare: event => ({ payload: event.target.value || '' })
},
extraReducers: (builder) => {
builder.addCase(getTransports.fulfilled, (state, action) => {
transportsAdapter.setAll(state, action.payload);
});
},
});