I friends,
I develop react redux toolkit slice everything ok but in function dispatch not working send to dispatchUserBlockingUpdate function in react-dom.development.js where is the my problem ?
I look changeProfitSortType function get data when need to send dispatch(changeProfitSort) not work !
This is: sortingSlice.js
import {createSlice} from "#reduxjs/toolkit";
export const sortingSlice = createSlice({
name: 'sorting',
initialState: {
profitSortType: false,
},
reducers: {
changeProfitSort: (state, action) => {
state.profitSortType = action.payload;
//return { ...state, profitSortType: action.payload }
},
},
});
export const { changeProfitSort } = sortingSlice.actions;
export const changeProfitSortType = (data) => async dispatch => {
console.log(data);
let status = data.status;
console.log(status);
await dispatch(changeProfitSort(status));
}
export default sortingSlice.reducer;
Your sortingSlice is fine.
changeProfitSort is not an asynchronous function, so await dispatch(changeProfitSort(status)); doesn't make sense.
You don't need the changeProfitSortType function at all. You can just call dispatch(changeProfitSort(data.status)) in your component instead of dispatch(changeProfitSortType(data)).
If the data is coming from an asynchronous function then you might want to use createAsyncThunk.
Related
I need to develop a global search filter to search the products based on the product title.
To solve the problem I'm using redux to manage the global formal state. I created a reducer to filter the titles, however, I don't know how to get the array of API objects and play in the initialState data: []; To call the API I'm using createAPI from the redux toolkit.
Some part of code:
import { createSlice } from "#reduxjs/toolkit";
const searchSlice = createSlice({
name: "search",
initialState: {
data: [], // array of objects from API
filteredTitle: [],
isLoading: true
},
reducers: {
getData: (state, action) => {
state.data = action.payload;
},
searchByName: (state, action) => {
const filteredResult = state.data.filter((product) =>
product.title.toLowerCase().includes(action.payload.toLowerCase())
);
return {
...state,
filteredTitle:
action.payload.length > 0 ? filteredResult : [...state.data]
};
}
}
});
export const { searchByName, getData } = searchSlice.actions;
export default searchSlice.reducer;
In App.js
import React, { useState, useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { searchByName, getData } from "./features/slice";
import { useGetAllProductsQuery } from "./services/api";
export default function App() {
const dispatch = useDispatch();
const filteredTitle = useSelector((state) => state.search.filteredTitle);
const [searchTerm, setSearch] = useState("");
const { data } = useGetAllProductsQuery();
console.log("data", data);
const changeSearchTerm = (e) => {
setSearch(e.target.value);
};
useEffect(() => {
dispatch(searchByName(searchTerm));
dispatch(getData(data));
}, [searchTerm, dispatch]);
return (
<div>
<input onChange={changeSearchTerm} type="text" value={searchTerm} />
<div>
{filteredTitle.map((user) => (
<div>{user.name}</div>
))}
</div>
</div>
);
}
CodeSandbox
Redux Toolkit Query is made, so you won't interact with Redux store with thunk functions directly (that's what they say in their documentation).
You can only use transformResponse once creating the query. I haven't come across any ways, to be able to add an extra reducer to the slices that are made with RTK Query.
The first way, is to store the data that you are getting from the query, inside another slice, and then use a reducer to filter your data.
The second way is to create a slice, and using createAsyncThunk, fetch your data, then treat your data as you want it. I can leave an example for you:
export const getName = createAsyncThunk('getNameFromBE',
async name => {
await fetch('https://example.com/studentID)
}
)
export const studnetSlice = createSlice({
name: 'studentSlice',
initialState,
reducers: {
aReducer : (state, action) => {state.name = action.payload}
},
extraReducers: {
[getName .pending]: state => {
state.loading = true
state.failed = false
},
[getName .fulfilled]: (state, action) => {
state.name= action.payload
state.loading = false
},
[getName .rejected]: state => {
state.loading = false
state.rejected = true
},
}
})
Here you have a slice, with thunk and normal reducers, with a state that is accessible, but the downside is that you have to manage fetch completely manually (can be an advantage too), and also the call state (pending, fulfilled, rejected) must be done manually, which takes a lot of boilerplate code writing.
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 have successfully gotten my React / Redux app to retrieve data from a REST API back end. I'm using the createAsyncThunk feature of Redux Toolkit, which automatically sets up reducers that are called when the HTTP fetch promise resolves with success or failure.
For this particular endpoint, I'd like the Redux store to reflect an error whenever an HTTP 404 Not Found is encountered. Currently that is not happening. The component shown below always returns "Loaded successfully". How can I make it display "Error" instead?
I understand that fetch doesn't resolve with an error on HTTP 4xx errors, and that I need to check the response code myself and resolve it as a failure. What I don't understand is where or how to do that in the code below. I struggle with understanding async/await conceptually, am new to Redux Toolkit, and the code below is already tweaking my brain pretty hard. Help?
Here is my full code:
features/recipeList/recipeListApi.js
export default async function recipeListApi(localApiKey) {
const response = await fetch('https://httpstat.us/404');
const responseJson = await response.json();
return responseJson;
}
features/recipeList/recipeListSlice.js
import { createAsyncThunk, createSlice } from "#reduxjs/toolkit";
import recipeListApi from "./recipeListApi";
const sliceName = "recipeList";
const initialState = {
loading: false,
error: null,
data: null
};
export const fetchRecipeList = createAsyncThunk("recipeList/fetchRecipeList", async (thunkAPI) => {
const response = await recipeListApi();
return JSON.stringify(response);
});
const recipeListSlice = createSlice({
name: sliceName,
initialState: initialState,
extraReducers: {
[fetchRecipeList.pending]: state => {
if (!state.loading) {
state.loading = true;
}
},
[fetchRecipeList.fulfilled]: (state, action) => {
if (state.loading) {
state.loading = false;
state.data = action.payload;
}
},
[fetchRecipeList.rejected]: (state, action) => {
if (state.loading) {
state.loading = false;
state.error = action.payload;
}
}
}
});
export const recipeListReducer = recipeListSlice.reducer;
components/RecipeList.js
import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchRecipeList } from '../features/recipeList/recipeListSlice';
export const RecipeList = () => {
const recipeList = useSelector(state => state.recipeList);
const dispatch = useDispatch();
/* Equivalent to componentDidMount() */
useEffect(() => {
dispatch(fetchRecipeList());
}, []);
return <>
{recipeList.loading && <h1>Loading</h1>}
{!recipeList.loading && recipeList.error !== null && <h1>Error</h1>}
{!recipeList.loading && recipeList.error === null && <h1>Loaded successfully</h1>}
</>;
}
Check if the response had a state of ok - or whatever condition you want to check your response for - and return a rejected promise like so:
export default async function recipeListApi(localApiKey) {
const response = await fetch('https://httpstat.us/404');
if(!response.ok) {
return Promise.reject();
}
return await response.json();
}
if your slice [rejected], the state.error should be receive from action.error
// features/recipeList/recipeListSlice.js
[fetchRecipeList.rejected]: (state, action) => {
if (state.loading) {
state.loading = false;
state.error = action.error;
}
}
I am migrating my component from a class component to a functional component using hooks. I need to access the states with useSelector by triggering an action when the state mounts. Below is what I have thus far. What am I doing wrong? Also when I log users to the console I get the whole initial state ie { isUpdated: false, users: {}}; instead of just users
reducers.js
const initialState = {
isUpdated: false,
users: {},
};
const generateUsersObject = array => array.reduce((obj, item) => {
const { id } = item;
obj[id] = item;
return obj;
}, {});
export default (state = { ...initialState }, action) => {
switch (action.type) {
case UPDATE_USERS_LIST: {
return {
...state,
users: generateUsersObject(dataSource),
};
}
//...
default:
return state;
}
};
action.js
export const updateUsersList = () => ({
type: UPDATE_USERS_LIST,
});
the component hooks I am using
const users = useSelector(state => state.users);
const isUpdated = useSelector(state => state.isUpdated);
const dispatch = useDispatch();
useEffect(() => {
const { updateUsersList } = actions;
dispatch(updateUsersList());
}, []);
first, it will be easier to help if the index/store etc will be copied as well. (did u used thunk?)
second, your action miss "dispatch" magic word -
export const updateUsersList = () =>
return (dispatch, getState) => dispatch({
type: UPDATE_USERS_LIST
});
it is highly suggested to wrap this code with { try } syntax and be able to catch an error if happened
third, and it might help with the console.log(users) error -
there is no need in { ... } at the reducer,
state = intialState
should be enough. this line it is just for the first run of the store.
and I don't understand where { dataSource } comes from.
I'm trying to handle a form submission to show a loading component when the data fetch is occuring. I'd like to display the data when it's been loaded into my Redux store.
Right now, I've set up my component to use React hooks. While the data loads into my redux store successfully, I'm not sure how to "await" the result of the action being completed. Here's a simplified version of what my component looks like:
const DataPage = (props) => {
const [isLoading, setIsLoading] = useState(false);
const [isError, setError] = useState(false);
useEffect(() => { // Reset Filters when dataSource changes...
setError(false);
setIsLoading(false);
}, [dataSource]);
const handleSubmit = (e, { dataSource }) => {
e.preventDefault();
setError(false)
setIsLoading(true);
//// IDEALLY THIS IS WHERE THE FIX WOULD GO? TURN THIS INTO ASYNC/AWAIT?
props.fetchData({ dataSource, token: localStorage.JWT_TOKEN });
};
return (
<div className="dataPage">
<form className="dataPage__filters" onSubmit={(e) => handleSubmit(e, { dataSource })}>
<DataSelector dataSource={dataSource} setDataSource={setDataSource}/>
<button className="button">
Search
</button>
</form>
{isError && <div>Something went wrong...</div>}
{ isLoading ? ( <div>...Loading </div> ) : (
<div className="dataPage__table">
<DataTable /> // This is connected to my redux-store separately through 'connect'
</div>
)}
</div>
);
};
const mapDispatchToProps = (dispatch) => ({
fetchData: ({ dataSource, token }) => dispatch(startFetchData({ dataSource, token }))
});
export default connect(null, mapDispatchToProps)(DataPage);
The relevant actions (startFetchData, and setData) are located in another file, and look like this:
export const setData = (data) => ({
type: "SET_DATA",
data
});
export const startFetchData = ({ dataSource, filter, filterTarget, token }) => {
return (dispatch) => {
axios.get(`${'http://localhost:8081'}/api/${dataSource}`, { headers: { authorization: token }})
.then((res) => {
dispatch(setData(result));
});
}
};
I'd like to be able to do this without introducing any new dependencies if possible.
A note for those using TypeScript: If you want to await a promise returned by an action using useDispatch() you may see TypeScript complaining about an unnecessary await.
In this case make sure to add the correct typing (see ThunkDispatch) to useDispatch via generics.
Also with useEffect() with async-await syntax make sure to wrap your async code in another closure because useEffect() expects a void return value and Typescript otherwise complains about you returning a Promise.
const dispatch = useDispatch<ThunkDispatch<any, any, Action>>();
useEffect(() => {
(async () => {
const myResult = await dispatch(...);
const anotherResult = await dispatch(...);
// ...
})();
});
I recommend you to use redux-thunk middleware. It's really easy and useful library to able your action to be, instead of objects, functions (including async functions). I'll give you an example:
Store.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers/index';
import api from './services/api';
// Note: this API requires redux#>=3.1.0
const store = createStore(
rootReducer,
// With extra argument, in this case, my API):
applyMiddleware(thunk.withExtraArgument(api));
);
AuthDuck.js
Giving this duck (types, actions and reducers in the same file, see more here)
// ----------------------------------------------------------------
// Types
// ----------------------------------------------------------------
const Types = {
SIGN_IN_START: 'SIGN_IN_START',
SIGN_IN_SUCCESS: 'SIGN_IN_SUCCESS',
SIGN_IN_FAIL: 'SIGN_IN_FAIL'
};
// ----------------------------------------------------------------
// Actions
// ----------------------------------------------------------------
const signin = function (user) {
// LOOK HERE!
// Redux Thunk able you to return a function instead of an object.
return async function (dispatch, getState, api) {
try {
dispatch({ type: Types.SIGN_IN_START });
const token = await api.access.signin(user);
dispatch({ type: Types.SIGN_IN_SUCCESS, payload: token });
} catch (error) {
dispatch({ type: Types.SIGN_IN_FAIL, payload: error });
}
};
};
export const Actions = { signin };
// ----------------------------------------------------------------
// Reducers
// ----------------------------------------------------------------
export default function reducer(state, action) {
switch (action.type) {
case VeasyCalendarTypes.SIGN_IN_START:
return { ...state, isLoading: true };
case VeasyCalendarTypes.SIGN_IN_SUCCESS:
return { ...state, isLoading: false, token: action.payload };
case VeasyCalendarTypes.SIGN_IN_FAIL:
return { ...state, isLoading: false, error: action.payload };
default:
return state;
}
};
I hope to helped you, let me know if it worked for your case :)
Best regards