How do I get passed this racing condition from Redux Toolkit? - javascript

This is not happening in any other slice I created, and it's driving me nuts. I'm out of ideas to try. What I want is simple, pull the payload from my Slice so I can set it to my state and use it! But somehow I am not capturing it. I put a console.log on the Slice for the payload and I am successfully fetching See Pic Below
Now checkout this screenshot with the component on the screen. I noticed that I was getting an error for the 'country' being undefined in pullrank in line 285 which it doesnt mean the path in incorrect or that country doesnt exist, it just breaks because the whole data is empty. If I refresh it all comes up normally, so I commented out the part where I am setting setMinRange and setMaxRange and that's when I realize that allmapdata was setting up empty, Twice nontheless! And then once again with the data in it. Obviously a racing condition, the API is fetching before the data is called, so how do I delay this API call?
Here is my mapSlice
import { createSlice, createAsyncThunk } from "#reduxjs/toolkit";
import axios from "axios";
const KEY = process.env.REACT_APP_API_KEY
const BASE_URL = process.env.REACT_APP_BASE_URL
const GLOBALVIEWS_API = `${BASE_URL}/countrymap`
const initialState = {
mapdata:[],
status: 'idle', //'idle', 'loading', 'succeeded', 'failed'
error:null
}
export const fetchMapData = createAsyncThunk(
'mapdata/fetchMapData',
async (id) => {
try {
console.log("Firing Map reducer");
const response = await axios.get(
GLOBALVIEWS_API,
{
headers: {
'Content-Type': 'application/json',
'X-API-KEY': KEY,
},
params: {
titleId: id,
}
}
)
return response.data;
} catch (error) {
console.error('API call error:', error.message);
}
}
)
const mapSlice = createSlice({
name: 'mapdata',
initialState,
reducers:{
fetchMap(state, action) {},
},
extraReducers(builder) {
builder
.addCase(fetchMapData.pending, (state, action) => {
state.status = 'loading'
})
.addCase(fetchMapData.fulfilled, (state, action) => {
state.status = 'succeeded'
const loadedMapData = action.payload.Item.season_met
state.mapdata = loadedMapData
console.log("loadedMapData: ", loadedMapData);
})
.addCase(fetchMapData.rejected, (state, action) => {
state.status = 'failed'
state.error = action.error.message
})
}
})
// SELECTORS
// allMapData and fetchMap aren't both needed, I'm just trying different ways
export const allMapData = (state) => state.mapdata.mapdata;
export const {fetchMap} = mapSlice.actions;
export const getMapStatus = (state) => state.mapdata.status;
export const getMapError = (state) => state.mapdata.error;
export default mapSlice.reducer
Here's my component (reduced for brevity)
const MetricsCharts = ({selectedIndex}) => {
//STATES
const [minrange, setMinRange] = useState();
const [maxrange, setMaxRange] = useState();
const [map, setMap] = useState([])
const colorScale = scaleLog().domain([minrange, maxrange]).range(["#BAC7FF", "#6128BD"]);
const {id} = useParams();
const dispatch = useDispatch();
const allmapdata = useSelector(allMapData)
const mapdata = useSelector(fetchMap)
const mapStatus = useSelector(getMapStatus)
const error = useSelector(getMapError)
useEffect(() => {
if (mapStatus === 'idle') {
dispatch(fetchMapData(id))
}
setMap(allmapdata)
// const pullrank = allmapdata[selectedIndex].country.map(data => data.lifetime_viewing_subs)
// setMinRange(Math.min(...pullrank))
// setMaxRange(Math.max(...pullrank))
console.log("allmapdata: ", allmapdata);
}, [id, selectedIndex, dispatch, allmapdata, mapStatus, mapdata])
let map_component;
if (mapStatus === 'loading') {
map_component =
<Box className="loading">
<LoadingButton loading
loadingIndicator={
<CircularProgress color="primary" size={50} />
}>
</LoadingButton>
<Typography variant='subtitle2'>Loading content, please wait...</Typography>
</Box>
} else if (mapStatus === 'succeeded') {
map_component =
<>
{map.length > 0 && //checking if something is in the state
<Box sx={{mt:2}}>
<ReactTooltip>{content}</ReactTooltip>
<ComposableMap
width={900}
height={400}
data-tip=""
projectionConfig={{ rotate: [-10, 0, 0], scale:147 }}>
<Sphere stroke="#000" strokeWidth={0.3} />
<Graticule stroke="#000" strokeWidth={0.3} />
<Geographies geography={geoUrl}>
{({ geographies }) => geographies.map((geo, index) => {
const countries = map[selectedIndex].country.find( (s) => s.ISO3 === geo.properties.ISO_A3);
return (
<Fragment key={index}>
<Geography
key={geo.rsmKey}
geography={geo}
onMouseEnter={() => {
setContent(
countries
? `${geo.properties.NAME_LONG} — ${rounded(Math.round(countries.lifetime_viewing_subs))}`
: ""
);
}}
fill={
countries
? colorScale(countries["lifetime_viewing_subs"])
: "#333"
}
/>
</Fragment>
);
})
}
</Geographies>
</ComposableMap>
</Box>
}
</>
} else if (mapStatus === 'failed') {
map_component =
<Box className="loading">
<Typography variant="subtitle2">{error}</Typography>
</Box>
}
return (
<div>
{map_component}
</div>
)
}
export default MetricsCharts
As you can see, I can't just give you a CODEPEN cause the map dependency is incredibly large and complex. I'm sorry about that. I do appreciate any help

Related

react-redux how to show and hide nested element depends on parent?

I want to show and hide my child element depends on parent id. Assume if user click on parent id -> 1 then it should show all child element of parent id ->1. In my project I have lot of parent category and every parent category have lot of child category. I have an page where I am showing all of my main categories and sub-categories. But the problem if user click on parent category -> 1 then it's showing all child category from others parent category. see the screenshot
here is my redux slicer code:
const { createSlice, createAsyncThunk } = require('#reduxjs/toolkit');
const STATUSES = Object.freeze(
{
IDLE: 'idle',
ERROR: 'error',
LOADING: 'loading'
}
)
const initialState = {
categories: [],
status: STATUSES.IDLE,
}
export const adsSlice = createSlice({
name: "ads_category",
initialState,
reducers: {
},
extraReducers: (builder) => {
builder
.addCase(fetchProducts.pending, (state, action) => {
state.status = STATUSES.LOADING;
})
.addCase(fetchProducts.fulfilled, (state, action) => {
state.categories = action.payload;
state.status = STATUSES.IDLE;
})
.addCase(fetchProducts.rejected, (state, action) => {
state.status = STATUSES.ERROR;
});
},
})
// Action creators are generated for each case reducer function
export const { ads_category, showcategory} = adsSlice.actions
export default adsSlice.reducer
// Thunks
export const fetchProducts = createAsyncThunk('ads_category/fetch', async () => {
const res = await fetch('http://localhost:8000/ads_category/');
const data = await res.json();
return data;
});
here is my page.js
const PostAds = () => {
const [showcat, setShowCat] = useState(false)
const dispatch = useDispatch();
const show_data = useSelector(state=>state.ads)
useEffect(() => {
dispatch(fetchProducts());
}, []);
show_sub_category = ()=>{
setShowCat(true)
}
return (
<>
{/*showing all of my main category */}
{show_data.categories.map((data)=>(
<button onClick={show_sub_category}>
{data.main_category}
</button>
{showcat &&
{/*showing all of my sub category */}
{data.sub_cat_bp.map((data)=>({data.sub-category}))}
}
))}
here is my api data look like which getting fetching from redux:
If you are going to toggle multiple categories at the same time then i will suggest you to split your component to two pieces and then solving it becomes more easier. So you can do something like this
const PostAds = () => {
const dispatch = useDispatch();
const adsList = useSelector((state) => state.ads);
useEffect(() => {
dispatch(fetchProducts());
}, []);
return (
<>
{/* showing all of my main category */}
{adsList.categories.map((category) => (
<CategoryItem key={category.id} categoryData={category} />
))}
</>
);
};
And your CategoryItem should look like something this
const CategoryItem = ({ categoryData }) => {
const [isVisible, toggleVisibility] = useState(false);
return (
<>
<button onClick={() => toggleVisibility(!isVisible)} type="button">
{categoryData.name}
</button>
{isVisible &&
categoryData.sub_cat_bp.map((info) => (
<span key={info.id}>{info.sub_category}</span>
))}
</>
);
};

Component re-renders self after API call whilst using useRef

I'm working on a project and wanted to try and implement an infinitely scrollable page to list users. Filtering and such works fine and all but every time the scrolling component reaches the ref element the component makes an API call to append to the list and then the parent component re-renders self completely.
const UsersList = () => {
const [searchString, setSearchString] = useState('')
const [next, setNext] = useState('')
const { userList, error, nextPage, loading, hasMore } = useFetch(next)
const [usersList, setUsersList] = useState([])
const observer = useRef()
const lastElemRef = useCallback(
(node) => {
if (loading) return
if (observer.current) observer.current.disconnect()
observer.current = new IntersectionObserver((entries) => {
if (entries[0].isIntersecting && hasMore) {
setNext((prev) => (prev = nextPage))
setUsersList((prev) => new Set([...prev, ...userList]))
}
})
if (node) {
observer.current.observe(node)
}
},
[loading, nextPage, hasMore],
)
useEffect(() => {
setUsersList((prev) => new Set([...prev, ...userList]))
console.log(error)
}, [])
return (
<>
{loading ? (
<CSpinner variant="grow"></CSpinner>
) : (
<CContainer
className="w-100 justify-content-center"
style={{ maxWidth: 'inherit', overflowY: 'auto', height: 600 }}
>
<CFormInput
className="mt-2 "
id="userSearchInput"
value={searchString}
onChange={(e) => {
setSearchString(e.target.value)
}}
/>
{loading ? (
<CSpinner variant="grow"></CSpinner>
) : (
<>
{Array.from(usersList)
.filter((f) => f.username.includes(searchString) || searchString === '')
.map((user) => {
if (Array.from(usersList)[usersList.size - 1] === user) {
return (
<UserCard
key={user.id}
user={user}
parentRef={searchString ? null : lastElemRef}
/>
)
} else {
return <UserCard key={user.id} user={user} />
}
})}
</>
)}
</CContainer>
)}
</>
)
}
export default UsersList
This is the component entirely.
Here's my useFetch hook;
export const useFetch = (next) => {
const [userList, setUserList] = useState([])
const [loading, setLoading] = useState(true)
const [error, setError] = useState('')
const [nextPage, setNextPage] = useState(next)
const [hasMore, setHasMore] = useState(true)
useEffect(() => {
setLoading(true)
setError('')
axios
.get(next !== '' ? `${next}` : 'http://localhost:8000/api/getUsers/', {
headers: {
Authorization: 'Bearer ' + localStorage.getItem('access_token'),
},
})
.then((res) => {
setUserList((userList) => new Set([...userList, ...res.data.results]))
setNextPage((prev) => (prev = res.data.next))
if (res.data.next === null) setHasMore(false)
setLoading(false)
})
.catch((err) => {
setError(err)
})
}, [next])
return { userList, error, nextPage, loading, hasMore }
}
export default useFetch
I'm using Limit Offset Pagination provided by Django Rest Framework, next object just points to the next set of objects to fetch parameters include ?limit and ?offset added at the end of base API url. What is it that I'm doing wrong here ? I've tried many different solutions and nothing seems to work.
Solved
Apparently it was just my back-end not cooperating with my front-end so I've changed up the pagination type and now it seems to behave it self.

Redux State Management with a Input field

Bit of a noob to redux but this appears to be quite a difficult question! I hope someone may be able to help me :)
I have build a page where you can input a search for different types of jobs. From this, it will make a get request to my DB and get all the info on this job. As this page is multi-levelled, I want to use redux to dispatch and pass the state throughout. This will help me pass my data on the job, e.g Data Analyst, through to each component so it can use the data and populate fields.
However, this was how my input field was originally setup:
export function SearchBarComp(props) {
const [isExpanded, setExpanded] = useState(false);
const [parentRef, isClickedOutside ] = useClickOutside();
const inputRef = useRef();
const [searchQuery, setSearchQuery] = useState("");
const [isLoading, setLoading] = useState(false);
const [jobPostings, setjobPostings] = useState([]);
const [noRoles, setNoRoles] = useState(false)
const isEmpty = !jobPostings || jobPostings.length === 0;
const changeHandler = (e) => {
//prevents defaulting, autocomplete
e.preventDefault();
if(e.target.value.trim() === '') setNoRoles(false);
setSearchQuery(e.target.value);
}
const expandedContainer = () => {
setExpanded(true);
}
//LINK THE BACKEND!
const prepareSearchQuery = (query) => {
//const url = `http://localhost:5000/api/role/title?title=${query}`;
const url = `http://localhost:5000/api/role/titlerole?title=${query}`;
//replaces bad query in the url
return encodeURI(url);
}
const searchRolePosition = async () => {
if(!searchQuery || searchQuery.trim() === "")
return;
setLoading(true);
setNoRoles(false);
const URL = prepareSearchQuery(searchQuery);
const response = await axios.get(URL).catch((err) => {
console.log(err);
});
if(response) {
console.log("Response", response.data);
if(response.data && response.data === 0)
setNoRoles(true);
setjobPostings(response.data);
}
setLoading(false);
}
useDebounce(searchQuery, 500, searchRolePosition)
const collapseContainer = () => {
setExpanded(false);
setSearchQuery("");
setLoading(false);
setNoRoles(false);
if (inputRef.current) inputRef.current.value = "";
};
// console.log("Value", searchQuery)
useEffect(()=> {
if(isClickedOutside)
collapseContainer();
}, [isClickedOutside])
return (
<SearchBarContainer animate = {isExpanded ? "expanded" : "collapsed"}
variants={containerVariants} transition={containerTransition} ref={parentRef}>
<SearchInputContainer>
<SearchIconSpan>
<SearchIcon/>
</SearchIconSpan>
<SearchInput placeholder = "Search for Roles"
onFocus={expandedContainer}
ref={inputRef}
value={searchQuery}
onChange={changeHandler}
/>
<AnimatePresence>
{isExpanded && (<CloseIconSpan key="close-icon"
inital={{opacity:0, rotate: 0}}
animate={{opacity:1, rotate: 180}}
exit={{opacity:0, rotate: 0}}
transition={{duration: 0.2}}
onClick={collapseContainer}>
<CloseIcon/>
</CloseIconSpan>
)}
</AnimatePresence>
</SearchInputContainer>
{isExpanded && <LineSeperator/>}
{isExpanded && <SearchContent>
{!isLoading && isEmpty && !noRoles && (
<Typography color="gray" display="flex" flex="0.2" alignSelf="center" justifySelf="center">
Start typing to search
</Typography>
)}
{!isLoading && !isEmpty && <>
{jobPostings.map((searchRolePosition) => (
<JobSection
title={searchRolePosition.title}
//will need to do something like ----
//people = {searchRolePosition.title && searchRolePosition.title.average}
// future implementations
/>
))}
</>}
</SearchContent>}
</SearchBarContainer>
)
}
As you can see, the main thing is the 'query' this creates a backend request to my titlerole, such as getting the data on Data Analyst. This all works in my frontend right now, but I can't pass that data down to the next component etc
So i'm looking to use redux.
I've created the following slice:
import { createSlice } from "#reduxjs/toolkit";
const jobSearchSlice = createSlice({
name: "jobsearch",
initialState: {
currentRole: null,
isFetching: false,
error: false,
},
reducers: {
jobsearchStart: (state) => {
state.isFetching = true;
},
jobsearchSuccess: (state, action) => {
state.isFetching = false;
state.currentRole = action.payload;
},
jobsearchFailure: (state) => {
state.isFetching = false;
state.error = true;
},
},
});
export const { jobsearchStart, jobsearchSuccess, jobsearchFailure } = jobSearchSlice.actions;
export default jobSearchSlice.reducer;
With this, I'm also using the following apiCalls.js file:
import { jobsearchStart, jobsearchSuccess, jobsearchFailure } from "./jobsearchSlice";
import { publicRequest } from "../requestMethods";
export const roleSearchQuery = async (dispatch, jobsearch) => {
dispatch(jobsearchStart());
try{
const res = await publicRequest.get("`http://localhost:5000/api/role/titlerole?title=${query}`", jobsearch);
dispatch(jobsearchSuccess(res.data))
}catch(err){
dispatch(jobsearchFailure());
}
};
My question is as a Redux noob, how do I implement this query functionality into a redux API request? What's the way to do this properly as I begin to tranisition this to an app which uses standard state management!

Rendering only the specific component on Select option change. React

I have created a react app which will fetch the api using the values from the url params. which are modified using navigate prop without page refresh.
Here is the code.
const App = () => {
const [itemData, setItemData] = useState({});
const [itemError, setItemError] = useState({});
const [additionalData, setAdditionalData] = useState({});
const [additionalError, setAdditionalError] = useState({});
const [isLoading, setIsLoading] = useState(false);
const [showTrailer, setShowTrailer] = useState(false);
const [trailer, setTrailer] = useState({});
const [trailerError, setTrailerError] = useState({});
const [group, setGroup] = useState([])
const backend_url = process.env.REACT_APP_BACKEND;
const handleCloseTrailer = () => setShowTrailer(false);
const handleShowTrailer = () => setShowTrailer(true);
const location = useLocation();
const id = location.pathname.split("/")[2];
const [searchParams, setSearchParams] = useSearchParams();
const [people, setPeople] = useState([]);
const [groupId, setGroupId] = useState(searchParams.get("group_id"));
const navigate = useNavigate();
function handleChange(value) {
navigate(`?group_id=${value}`);
}
useEffect(() => {
const fetchMainApi = () => {
setIsLoading(true)
axios.get(`${backend_url}/api/v1/metadata?id=${id}`)
.then(function(response) {
if(response.data.content.apiId !== 'undefined') {
axios.get("API_URL")
.then(function (response) {
setAdditionalData(response.data);
})
.catch(function (error) {
setAdditionalError(error);
})
}
if(itemData && (itemData.apiId !== 'null' || 'undefined')) {
axios.get("API_URL")
.then(function(response) {
setTrailer(response.data)
})
.catch(function(error) {
setTrailerError(error)
})
}
if(type === "cat" && itemData.children) {
setGroup(itemData.children)
}
if(type === "cat" && itemData.children)
axios.get("API_URL" + groupId)
.then(function (response) {
setPeople(response.data.content.children);
})
.catch(function (error) {
console.log(error);
});
setItemData(response.data.content)
})
.catch(function(error) {
setItemError(error)
})
setIsLoading(false)
}
fetchMainApi()
}, [backend_url,id,type,itemData.apiId,itemData.api])
return (
<>
<Form.Select onChange={event => handleChange(event.target.value)} aria-label="Default select example">
<option>Group All</option>
{cluster.map((person, index) => (
<option key={guid()} value={group.id}>{group.name}</option>
))}
</Form.Select>
<People people={people}/>
</>
);
};
export default App;
Here is the People component
const People = ({people}) => {
return (
<Row className="m-2 pt-2">
<h2 className="color-white">People</h2>
{people && people.length > 0 && (people.map((people, index) => (
<Col key={index} className="p-lg-4 p-sm-3" xs={12} sm={6} md={4} lg={3} xl={3}>
....
</Col>
)))}
{ (!people || people.length === 0) && (<h5 className="color-white">No Persons Found</h5>) }
</Row>
);
};
export default People;
Working
The select menu updates the query param and then the value of param is taken inside useEffect hook when then provides the data.
Every thing works well but the problem is to update the data inside the component i need to refresh the page when then works as expected.
Is there a way to change or update only the people component without a page refresh.

Why are the elements from a mapping function not rendering?

Summarize the problem
I have a page within a Gatsby JS site that accepts state via a provider, and some of that activity is able to be used, however, I am unable to provide the contents from a mapping function that is given via context.
Expected result: the expected elements from the mapping function would render
Actual result: the elements in question are not rendered
No error messages
Describe what you've tried
I thought the issue was not explicitly entering in return on the arrow function in question, but that does not change any of the output
Also, rather than try to access the method directly on the page (via a context provider) I moved the method directly into the Provider hook. This did not change any of the rendering.
Show some code
here is Provider.js
import React, { useState, useEffect } from 'react';
import he from 'he';
export const myContext = React.createContext();
const Provider = props => {
const [state, setState] = useState({
loading: true,
error: false,
data: [],
});
const [page, setPage] = useState(1);
const [score, setScore] = useState(0);
const [correctAnswers, setCorrectAnswers] = useState([]);
const [allQuestions, setAllQuestions] = useState([]);
const [answers, setAnswers] = useState([]);
const [right, setRight] = useState([]);
const [wrong, setWrong] = useState([]);
function clearScore() {
updatedScore = 0;
}
function clearRights() {
while (rights.length > 0) {
rights.pop();
}
}
function clearWrongs() {
while (wrongs.length > 0) {
wrongs.pop();
}
}
let updatedScore = 0;
let rights = [];
let wrongs = [];
const calcScore = (x, y) => {
for (let i = 0; i < 10; i++) {
if (x[i] === y[i]) {
updatedScore = updatedScore + 1;
rights.push(i);
} else wrongs.push(i);
}
}
useEffect(() => {
fetch('https://opentdb.com/api.php?amount=10&difficulty=hard&type=boolean')
.then(response => {
return response.json()
})
.then(json => {
const correctAnswer = json.results.map(q => q['correct_answer']);
const questionBulk = json.results.map(q => q['question']);
setState({
data: json.results,
loading: false,
error: false,
});
setCorrectAnswers(correctAnswers.concat(correctAnswer));
setAllQuestions(allQuestions.concat(questionBulk));
})
.catch(err => {
setState({error: err})
})
}, [])
return (
<myContext.Provider
value={{
state, page, score, answers, right, wrong,
hitTrue: () => {setAnswers(answers.concat('True')); setPage(page + 1);},
hitFalse: () => {setAnswers(answers.concat('False')); setPage(page + 1);},
resetAll: () => {
setAnswers([]);
setPage(1);
setScore(0);
setRight([]);
setWrong([]);
clearScore();
clearWrongs();
clearRights();
},
calculateScore: () => calcScore(answers, correctAnswers),
updateScore: () => setScore(score + updatedScore),
updateRight: () => setRight(right.concat(rights)),
updateWrong: () => setWrong(wrong.concat(wrongs)),
showRightAnswers: () => {right.map((result, index) => {
return (
<p className="text-green-300 text-sm" key={index}>
+ {he.decode(`${allQuestions[result]}`)}
</p>)
})},
showWrongAnswers: () => {wrong.map((result, index) => {
return (
<p className="text-red-500 text-sm" key={index}>
- {he.decode(`${allQuestions[result]}`)}
</p>
)
})},
}}
>
{props.children}
</myContext.Provider>
);
}
export default ({ element }) => (
<Provider>
{element}
</Provider>
);
^the showRightAnswers() and showWrongAnswers() methods are the ones I am trying to figure out
and here is the results.js page.{context.showRightAnswers()} and {context.showWrongAnswers()} are where the mapped content is supposed to appear.
import React from 'react';
import Button from '../components/Button';
import { navigate } from 'gatsby';
import { myContext } from '../hooks/Provider';
const ResultsPage = () => {
return (
<myContext.Consumer>
{context => (
<>
<h1 className="">You Finished!</h1>
<p className="">Your score was {context.score}/10</p>
{context.showRightAnswers()}
{context.showWrongAnswers()}
<Button
buttonText="Try Again?"
buttonActions={() => {
context.resetAll();
navigate('/');
}}
/>
</>
)}
</myContext.Consumer>
);
}
export default ResultsPage;
You are returning inside your map, but you're not returning the map call itself - .map returns an array, and you have to return that array from your "show" functions, e.g.
showWrongAnswers: () => { return wrong.map((result, index) ...
^^^^
This will return the array .map generated from the showWrongAnswers function when it's called, and thus {context.showWrongAnswers()} will render that returned array

Categories