How to get updated state immediately with redux? - javascript

After user submits a form I dispatched the create course function.
const handleCreateCourse = () => {
dispatch(createCourse(courseData));
// ??
};
How can I get and use the "_id" of newly created course immediately which will come from backend and will be saved in the updated state? So that I can do the following:
const courses = useSelector(state => state.courses);
const handleCreateCourse = () => {
dispatch(createCourse(courseData));
// ??
const newCourse = courses[courses.length-1];
history.push(`/course/${newCourse._id}`);
};
The createCourse function is using redux-thunk and looks like this:
export const createCourse = (course) => async (dispatch) => {
try {
const { data } = await api.createCourse(course);
dispatch({ type: CREATE_COURSE, payload: data });
} catch (error) {
console.log(error);
}
};

You can return API responses in thunk after dispatching the action.
E.g.
import { createStore, applyMiddleware, combineReducers } from 'redux';
import ReduxThunk from 'redux-thunk';
const thunk = ReduxThunk.default;
const api = {
async createCourse(name) {
return { data: { _id: '100', name } };
},
};
function coursesReducer(state = [], action) {
return state;
}
const rootReducer = combineReducers({
courses: coursesReducer,
});
const store = createStore(rootReducer, applyMiddleware(thunk));
const CREATE_COURSE = 'CREATE_COURSE';
export const createCourse = (course) => async (dispatch) => {
try {
const { data } = await api.createCourse(course);
dispatch({ type: CREATE_COURSE, payload: data });
return data;
} catch (error) {
console.log(error);
}
};
const handleCreateCourse = () => {
store.dispatch(createCourse({ name: 'math' })).then((newCourse) => {
console.log('newCourse: ', newCourse);
console.log(`/course/${newCourse._id}`);
});
};
handleCreateCourse();
The exeuction result:
newCourse: { _id: '100', name: { name: 'math' } }
/course/100

Related

How to call an object using useSelector hook in react

I am currently trying to get the array of objects namely -Animearray in another component and i am getting an error of undefined in the console.
Here is the code of the Store component
import {
configureStore,
createAsyncThunk,
createSlice,
} from "#reduxjs/toolkit";
import { API_KEY, TMBD_BASE_URL } from "../utils/constent";
import axios from "axios";
const initialState = {
movies: [],
genresLoaded: false,
genres: [],
};
const initialAnime = {
anime: [],
genresLoaded: false,
genres: [],
};
const createArrayfromRawdata = (array, moviesArray, genres) => {
array.forEach((movie) => {
const movieGenres = [];
movie.genre_ids.forEach((genre) => {
const name = genres.find(({ id }) => id === genre);
if (name) movieGenres.push(name.name);
});
if (movie.backdrop_path)
moviesArray.push({
id: movie.id,
name: movie?.original_name ? movie.original_name : movie.original_title,
image: movie.backdrop_path,
genres: movieGenres.slice(0, 3),
});
});
};
async function createAnimeFromRawData(rawData, animeArray) {
const data = rawData;
console.log(animeArray);
for (let i = 0; i < data.length; i++) {
const anime = data[i];
if (anime) {
const genreArr = anime.genres.map((genre) => genre.name);
animeArray.push({
name: anime.title,
genre: genreArr,
score: anime.score,
image: anime.images.jpg.image_url,
trailer: anime.trailer.embed_url,
episodes: anime.episodes,
synopsis: anime.synopsis,
});
}
}
console.log(animeArray);
return animeArray;
}
export const RawdataAnime = async () => {
const Animearray = [];
for (let i = 1; Animearray.length < 60 && i < 10; i++) {
const { data } = await axios.get(`https://api.jikan.moe/v4/top/anime`); // Equivalent to response.data
const results = data?.data || [];
try {
await createAnimeFromRawData(results, Animearray);
await new Promise((resolve) => setTimeout(resolve, 1000));
} catch (error) {
console.error(error);
}
}
return Animearray;
};
const rawData = async (api, genres, paging) => {
const moviesArray = [];
for (let i = 1; moviesArray.length < 60 && i < 10; i++) {
const {
data: { results },
} = await axios.get(`${api}${paging ? `&page=${i}` : ""}`);
createArrayfromRawdata(results, moviesArray, genres);
}
return moviesArray;
};
export const fetchMovies = createAsyncThunk(
"neflix/trending",
async ({ type }, thunkAPI) => {
const {
netflix: { genres },
} = thunkAPI.getState();
return rawData(
`${TMBD_BASE_URL}/trending/${type}/week?api_key=${API_KEY}`,
genres,
true
);
}
);
//`${TMBD_BASE_URL}/discover/${type}?api_key=${API_KEY}&with_genres=${genres}`
export const getGenres = createAsyncThunk("netflix/genres", async () => {
const {
data: { genres },
} = await axios.get(`${TMBD_BASE_URL}/genre/movie/list?api_key=${API_KEY}`);
return genres;
});
const netflixSlice = createSlice({
name: "netflix",
initialState,
extraReducers: (builder) => {
builder.addCase(getGenres.fulfilled, (state, action) => {
state.genres = action.payload;
state.genresLoaded = true;
});
builder.addCase(fetchMovies.fulfilled, (state, action) => {
state.movies = action.payload;
});
},
});
const animeSlice = createSlice({
name: "anime",
initialState: initialAnime,
extraReducers: (builder) => {
builder.addCase(RawdataAnime.fulfilled, (state, action) => {
state.anime = action.payload;
});
},
});
export const store = configureStore({
reducer: {
netflix: netflixSlice.reducer,
anime: animeSlice.reducer,
},
});
and turns out when I tried to use the animeArray in my main component it did not load
import BackgroundVid from "../components/BackgroundVid";
import { fetchMovies, getGenres, RawdataAnime, setAnime } from "../store";
import Slider from "../components/Slider";
export default function Netflix() {
const [scrolled, isScrolled] = useState(false);
const genresLoaded = useSelector((state) => state.netflix.genresLoaded);
const movies = useSelector((state) => state.netflix.movies);
const anime = useSelector((state) => state.anime.anime);
const dispatch = useDispatch();
useEffect(() => {
dispatch(getGenres());
onAuthStateChanged(firbaseauth, (user) => {
if (user) {
setUser(user);
} else {
setUser(null);
}
});
}, []);
useEffect(() => {
if (genresLoaded) {
dispatch(fetchMovies({ type: "all" }));
}
});
If you have any suggestions on how can i get the data from that component do let me know.
The targeted data is stored in the component as animeArray
From what I can tell, the RawdataAnime function isn't an action creator. If you want to have a RawdataAnime.fulfilled reducer case then it should be converted to an asynchronous action so when the returned Promise resolves, i.e. it is fulfilled, the reducer can handle the returned payload.
Example:
store
import {
configureStore,
createAsyncThunk,
createSlice,
} from "#reduxjs/toolkit";
import axios from "axios";
const initialAnime = {
anime: [],
genresLoaded: false,
genres: [],
};
...
function createAnimeFromRawData(rawData) {
return rawData.map((anime) => ({
id: anime.mal_id,
name: anime.title,
genre: anime.genres.map((genre) => genre.name),
score: anime.score,
image: anime.images.jpg.image_url,
trailer: anime.trailer.embed_url,
episodes: anime.episodes,
synopsis: anime.synopsis
}));
}
export const RawdataAnime = createAsyncThunk("anime/fetchAnime", async () => {
const { data } = await axios.get(`https://api.jikan.moe/v4/top/anime`); // Equivalent to response.data
const results = data?.data || [];
return createAnimeFromRawData(results);
});
...
const animeSlice = createSlice({
name: "anime",
initialState: initialAnime,
extraReducers: (builder) => {
builder.addCase(RawdataAnime.fulfilled, (state, action) => {
state.anime = action.payload;
});
}
});
export const store = configureStore({
reducer: {
netflix: netflixSlice.reducer,
anime: animeSlice.reducer
}
});
App
const dispatch = useDispatch();
const anime = useSelector((state) => state.anime.anime);
useEffect(() => {
dispatch(RawdataAnime());
}, []);

Redux not showing result

i need help with redux toolkit, i'm using redux to fetch one array of objects but this not happen bellow is my redux code and get api.
GET API
export const getProductsApi = async () => {
const response = await fetch(
`${gateway.url}/products`,
{
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
}
);
const data = await response.json();
return data;
}
MY REDUX ACTION
import {
getProductsPending,
getProductsSuccess,
getProductsFailed,
} from "../../slices/get-products";
import { getProductsApi } from "../../../api/get-products";
export const getProducts = () => async (dispatch: any) => {
dispatch(getProductsPending());
try {
const response = await getProductsApi();
const data = response.data;
console.log('products redux here', data);
dispatch(getProductsSuccess(data));
} catch (error) {
dispatch(getProductsFailed(error));
}
};
REDUX SLICE
const getProductsSlice = createSlice({
name: "getProducts",
initialState,
reducers: {
getProductsPending: (state) => {
state.isLoading = true;
state.error = "";
},
getProductsSuccess: (state, action: PayloadAction<Product[]>) => {
state.isLoading = false;
state.products = action.payload;
state.error = "";
},
getProductsFailed: (state, action) => {
state.isLoading = false;
state.error = action.payload.error;
state.products = [];
},
},
});
const { reducer, actions } = getProductsSlice;
export const { getProductsPending, getProductsSuccess, getProductsFailed } =
actions;
export default reducer;
REDUX STORE
import { configureStore } from "#reduxjs/toolkit";
import getProductsReducer from "../redux/slices/get-products";
const store = configureStore({
reducer: {
products: getProductsReducer,
},
});
store.subscribe(() => console.log(store.getState()));
export default store;
in my browser console when is to appear the data, data appear so
whit my products array empty, and i don't know where is my error because anothers redux i made, i make like this and work correctly.
I appreciate every help to solve my question.
using redux tookit

How can i test custom fetch hook

I am struggling with an issue with the custom fetch hook.Simply i am trying to test my fetch hook if the data already fetched the hook needs to get data from cache instead of api.
The test case fails and looks like caching mechanism not working, but if i try on the browser with manual prop change caching mechanism works properly.
import { render, waitFor } from "#testing-library/react";
const renderList = (filterParams = testFilterParamsList[0]) =>
render(<List filterParams={filterParams} />);
it("should re-render without fetch", async () => {
const { rerender } = renderList(testFilterParamsList[0]);
rerender(<List filterParams={testFilterParamsList[1]} />);
expect(window.fetch).toHaveBeenCalledTimes(1);
});
// useFetch.js
import {useEffect, useReducer} from "react";
const cache = {};
const FETCH_REQUEST = "FETCH_REQUEST";
const FETCH_SUCCESS = "FETCH_SUCCESS";
const FETCH_ERROR = "FETCH_SUCCESS";
const INITIAL_STATE = {
isPending: false,
error: null,
data: [],
};
const useFetch = ({url, filterOptions}) => {
const [state, dispatch] = useReducer((state, action) => {
switch (action.type) {
case FETCH_REQUEST:return {...INITIAL_STATE, isPending: true};
case FETCH_SUCCESS: return {...INITIAL_STATE, isPending: false, data: action.payload};
case FETCH_ERROR: return {...INITIAL_STATE, isPending: false, error: action.payload};
default: return state;
}
}, INITIAL_STATE);
useEffect(() => {
const fetchData = async () => {
dispatch({type: FETCH_REQUEST});
if (cache[url]) {
const data = cache[url];
dispatch({type: FETCH_SUCCESS, payload: data});
} else {
try {
const response = await window.fetch(url);
let data = await response.json();
cache[url] = data
dispatch({type: FETCH_SUCCESS, payload: data});
} catch (err) {
dispatch({type: FETCH_ERROR, payload: err});
}
}
};
fetchData();
}, [filterOptions, url]);
return state;
};
export default useFetch;
// List.js
import useFetch from "../hooks/useFetch";
export const RocketsList = ({ filterParams }) => {
const { isPending, error, data } = useFetch({
url: "https://api.spacexdata.com/v3/launches/past",
name:filterParams.name,
});
return (
<div>
Doesn't matter
</div>
);
};

TypeError: Cannot read properties of undefined (reading 'fulfilled') in createAsyncThunk

Im using createasyncthunk for creating async action to dispatch async action with redux toolkit , im having all the async api functions in seperate file ,ive imported into my createslice and used in extrareducers.
import { createAsyncThunk } from '#reduxjs/toolkit'
import { DataService } from 'config.axios'
import { getItem } from 'utils/localStorageController'
import { activateAlert } from 'reduxStore/slices/alert/AlertSlice'
import { clearRuleData } from '../RuleDataSlice'
const study_id = getItem('study_id')
export const getEditCheckByStatus = createAsyncThunk(
'editchecks/getEditCheck',
async (params, thunkAPI) => {
let editCheckType = params?.type ? `/${params.type}` : ''
let searchParams = params?.search ? `&search=${params.search}` : ''
let page = params?.page ? `&page=${params.page}` : ''
let per_page = params?.per_page ? `&per_page=${params.per_page}` : ''
let sortField = params?.sortField ? `&sort_field=${params.sortField}` : ''
let sortOrder = params?.sortOrder ? `&order=${params.sortOrder}` : ''
try {
const { data } = await DataService.get(
`/edit-checks${editCheckType}?status=${params.status}&study_id=${study_id}${searchParams}${page}${per_page}${sortField}${sortOrder}`
)
return data
} catch (error) {
console.error('Get EC by status error:', error)
}
}
)
export const getEditCheckCount = createAsyncThunk(
'editchecks/getEditCheckCount',
async () => {
try {
const { data } = await DataService.get(`/edit-checks/count`)
return data
} catch (error) {
console.error('Get EC Count error:', error)
}
}
)
export const getEditCheckWithFilter = createAsyncThunk(
'editchecks/getEditCheckWithFilter',
async (params, thunkAPI) => {
let page = params?.page ? `&page=${params.page}` : ''
let per_page = params?.per_page ? `&per_page=${params.per_page}` : ''
let searchParams = params?.search ? `&search=${params.search}` : ''
let sortField = params?.sortField ? `&sort_field=${params.sortField}` : ''
let sortOrder = params?.sortOrder ? `&order=${params.sortOrder}` : ''
try {
const { data } = await DataService.get(
`/edit-checks/status/${params.inAction}?worked_for=${params.forAction}${searchParams}${page}${per_page}${sortField}${sortOrder}`
)
return data
} catch (error) {
console.error('Get Edit Checks with Filter error:', error)
}
}
)
export const updateWorkflow = createAsyncThunk(
'editchecks/updateworkflow',
async (params, thunkAPI) => {
try {
await DataService.put(
`/edit-checks/${params.id}/workflow/${params.action}`,
params.api_body
)
thunkAPI.dispatch(
activateAlert({ color: 'success', content: params.content })
)
} catch (error) {
console.error('Update workflow error:', error)
thunkAPI.dispatch(activateAlert({ color: 'danger', content: error }))
}
}
)
export const deleteEditCheck = createAsyncThunk(
'editchecks/deleteEditCheck',
async (id, thunkAPI) => {
try {
await DataService.delete(`/edit-checks/${id}/`)
thunkAPI.dispatch(
activateAlert({
color: 'success',
content: 'Edit Check Deleted Succesfully',
})
)
} catch (error) {
console.error('Delete EditCheck error:', error)
thunkAPI.dispatch(activateAlert({ color: 'danger', content: error }))
}
}
)
export const getEditCheckById = createAsyncThunk(
'ruledata/getEditCheckById',
async (id, thunkAPI) => {
try {
const { data } = await DataService.get(`/edit-checks/${id}/`)
return data.data
} catch (error) {
console.error('Get Edit Check with ID error:', error)
}
}
)
export const createRuleData = createAsyncThunk(
'ruledata/createrule',
async (params, thunkAPI) => {
try {
const { data } = await DataService.post(`/edit-checks/`, params.api_body)
thunkAPI.dispatch(
activateAlert({
color: 'success',
content: 'New Rule Request Created Succesfully',
})
)
thunkAPI.dispatch(clearRuleData())
if (params.send_for_development) {
thunkAPI.dispatch(
updateWorkflow({
api_body: {
status: 'DEVELOPMENT',
comments: 'Please develop the code',
},
id: data.data.id,
content: 'EC Sent to Development',
action: '',
})
)
}
return data.data.id
} catch (error) {
console.error('Create Rule data error:', error)
thunkAPI.dispatch(activateAlert({ color: 'danger', content: error }))
}
}
)
export const updateRuleDataForm = createAsyncThunk(
'ruledata/updaterule',
async (params, thunkAPI) => {
try {
const { data } = await DataService.put(
`/edit-checks/${params.api_body?.id}`,
params.api_body
)
thunkAPI.dispatch(
activateAlert({
color: 'success',
content: 'Edit Check Updated Succesfully',
})
)
thunkAPI.dispatch(clearRuleData())
if (params?.send_for_development) {
thunkAPI.dispatch(
updateWorkflow({
api_body: {
status: 'DEVELOPMENT',
comments: 'Please develop the code',
},
id: data.data.id,
content: 'EC Sent to Development',
action: '',
})
)
}
} catch (error) {
console.log('Update rule data form error:', error)
thunkAPI.dispatch(activateAlert({ color: 'danger', content: error }))
}
}
)
export const getECDomains = createAsyncThunk('ruledata/getdomain', async () => {
try {
const { data } = await DataService.get(`/studies/1/domains/`)
return data.data
} catch (error) {
console.log('Get domain error', error)
}
})
export const getECDomainCols = createAsyncThunk(
'ruledata/getdomaincols',
async (domain, thunkAPI) => {
try {
const { data } = await DataService.get(
`/studies/1/domains/${domain}/variables`
)
let cols = data.data.map(function (el) {
return el.name
})
let cols_data = cols.map((col) => ({ label: col, value: col }))
console.log(cols, cols_data, 'action cols')
return {domain , cols: cols_data }
} catch (error) {
console.error('Get Edit Check domain columns error:',error)
}
}
)
export const saveCodeScript = createAsyncThunk(
'editchecks/savecodescript',
async (params, thunkAPI) => {
const codeData = new FormData()
codeData.append('source_code', params.code)
try {
await DataService.post(
`/edit-checks/${params.id}/script/?study_id=75`,
codeData,
{
'Content-Type': 'application/x-www-form-urlencoded',
}
)
} catch (error) {
console.error('Save script error: ', error)
}
}
)
The below file is src/reduxStore/slices/edit-checks/services/editCheckthunk.js
This is in different file.
import { createSlice, createAction } from '#reduxjs/toolkit'
import { push } from 'connected-react-router'
import {
createRuleData,
updateRuleDataForm,
getECDomains,
getECDomainCols,
getEditCheckById,
} from './services/editCheckthunk'
const initialState = {
ruleData: {
name: '',
source_data_format: '',
severity: '',
processing_level: '',
description: '',
query_text: '',
dataset: {
primary: {
domain: '',
columns: [],
},
relational: {
domain: '',
columns: [],
},
},
},
// from the api
dataset_data: {
domains: [],
domain_cols: {},
},
ruleDataLoader: false,
}
export const clearRuleData = createAction(
'ruledata/clearformdata',
function prepare() {
return (dispatch) => {
dispatch(push('/edit-checks/new'))
}
}
)
const ruleDataSlice = createSlice({
name: 'ruledata',
initialState,
reducers: {
updateRuleData(state, action) {
const { key, value } = action.payload
state.ruleData[key] = value
},
updateDataset(state, action) {
const { key_1, key_2, value } = action.payload
state.ruleData.dataset[key_1] = {
...state.ruleData.dataset[key_1],
[key_2]: value,
}
},
},
extraReducers: {
[clearRuleData]: (state, action) => {
state.ruleData = initialState.ruleData
},
[getEditCheckById.fulfilled]: (state, action) => {
state.ruleData = action.payload
},
[createRuleData.pending]: (state, action) => {
state.ruleDataLoader = true
},
[createRuleData.fulfilled]: (state, action) => {
state.ruleDataLoader = false
state.ruleData.id = action.payload
},
[createRuleData.rejected]: (state, action) => {
state.ruleDataLoader = false
},
[updateRuleDataForm.pending]: (state, action) => {
state.ruleDataLoader = false
},
[updateRuleDataForm.fulfilled]: (state, action) => {
state.ruleDataLoader = false
state.ruleData.id = action.payload
},
[updateRuleDataForm.rejected]: (state, action) => {
state.ruleDataLoader = false
},
[getECDomains.fulfilled]: (state, action) => {
state.dataset_data.domains = action.payload
},
[getECDomainCols.fulfilled]: (state, action) => {
const { domain, cols } = action.payload
state.dataset_data.domain_cols = {
...state.dataset_data.domain_cols,
[domain]: cols,
}
},
},
})
export const { updateDataset, updateRuleData } = ruleDataSlice.actions
export default ruleDataSlice.reducer
These are my other reducers with createasyncthunk functions.
Im dispatching , say getEditCheckById action in component to call the API.
import React, {useState, useEffect} from "react";
import { useSelector, useDispatch } from "react-redux";
import { useHistory, useLocation } from 'react-router'
import Select from 'react-select'
import { updateRuleDataForm,createRuleData,getECDomains,getECDomainCols,getEditCheckById } from 'reduxStore/slices/edit-checks/services/editCheckthunk';
import { updateRuleData ,clearRuleData, updateDataset} from 'reduxStore/slices/edit-checks/RuleDataSlice';
import chevronLeftIcon from 'assets/img/icons/utils/fi_chevron-left.svg'
function CreateEditCheck(props) {
const { search,pathname } = useLocation()
const searchParams = new URLSearchParams(search)
const editCheckId = searchParams.get('id')
const isEditParam = searchParams.get('edit')
const [isEdit, setIsEdit] = useState(isEditParam === 'true' || isEditParam === null ? true : false)
const {ruleData , dataset_data} = useSelector((state) => state.editChecks.ruledata)
const dispatch = useDispatch()
const action = pathname.split('/')[2]
const history = useHistory()
useEffect(() => {
if (action === 'edit') {
dispatch(getEditCheckById(editCheckId))
}
dispatch(getECDomains())
}, [])
}
But im getting TypeError: Cannot read properties of undefined (reading 'fulfilled') while running the app. THank in advance

Calling one redux action from one store slice in another store slice

I am new to redux. In my app i am using some async actions with redux thunk.
For example i have this action for loading movies from API:
export const loadMovies = (url) => async (dispatch) => {
const response = await fetch(url);
const result = await response.json();
const { total_pages: totalPages, results: movies } = result;
dispatch(moviesLoaded(totalPages, movies));
};
And i have situationL what if there are no movies to load (search in database didnt give me results) so i want to update status (it is the other redux store slice) to 'no movies' for example. And based on that status render different component.
So my new action would be something like this:
export const loadMovies = (url) => async (dispatch) => {
const response = await fetch(url);
const result = await response.json();
if (result) {
const { total_pages: totalPages, results: movies } = result;
dispatch(moviesLoaded(totalPages, movies));
} else {
dispatch(updateStatus('no-movies')) // updateStatus is imported from another redux store slice
}
};
I am wondering is it ok to do so. Or it is a bad practice to import actions from one store slice into another. And what is a better way to handle this situatuion.
You don't need to import actions from another store slice. You can handle the same query movies action type in multiple store slices' reducer. For more info, see Allow Many Reducers to Respond to the Same Action
E.g.
import { createStore, applyMiddleware, combineReducers } from 'redux';
import thunk from 'redux-thunk';
const GET_MOVIES_FULFILLED = 'GET_MOVIES_FULFILLED';
function moviesLoaded(totalPages, movies) {
return {
type: GET_MOVIES_FULFILLED,
payload: {
totalPages,
movies,
},
};
}
export const loadMovies = () => async (dispatch) => {
// const response = await fetch(url);
// const result = await response.json();
// mock result
const result = { total_pages: 0, results: [] };
const { total_pages: totalPages, results: movies } = result;
dispatch(moviesLoaded(totalPages, movies));
};
const rootReducer = combineReducers({
sliceA(state = { totalPages: 0, movies: [] }, action) {
switch (action.type) {
case GET_MOVIES_FULFILLED:
return action.payload;
default:
return state;
}
},
sliceB(state = { status: '' }, action) {
switch (action.type) {
case GET_MOVIES_FULFILLED:
if (!action.payload.movies || !action.payload.movies.length) {
return {
status: 'no movies',
};
}
default:
return state;
}
},
});
const store = createStore(rootReducer, applyMiddleware(thunk));
store.dispatch(loadMovies() as any).then(() => {
console.log(store.getState());
});
Output:
{
sliceA: { totalPages: 0, movies: [] },
sliceB: { status: 'no movies' }
}
Dispatch the GET_MOVIES_FULFILLED action, handle it in sliceA and sliceB reducers.
If there are no movies, we set the state of sliceB to {status: 'no movies'}.

Categories

Resources