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

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>
))}
</>
);
};

Related

React: saving state of child component triggered by parent component

Is there a way to do this?
I want to save a ChildComponent's state into an array state from the GrandParentComponent whenever I click a button from the ParentComponent?
Here's an overview of my components:
const GrandParentComponent = () => {
const [array, setArray] = useState([]);
return (
<div>
<ParentComponent array={array} setArray={setArray} />
</div>
);
};
const ParentComponent = ({ array, setArray }) => {
const ref = useRef(null);
const handleClick = () => {
ref.current.setArray();
};
return (
<div>
<button onClick={handleClick}>save</button>
{array.map((item) => (
<ChildComponent array={array} setArray={setArray} ref={ref} />
))}
</div>
);
};
const ChildComponent = forwardRef(({ array, setArray }, ref) => {
const [childState, setChildState] = useState("")
useImperativeHandle(ref, () => {
return {
setArray: () => {
setArray((array) => [{ ...childState }, ...array]);
}
};
});
return <div>ChildComponent</div>;
});
The problem I'm encountering is it only saves the value of the last ChildComponent. What I want is to save all of the values from the ChildComponents.
What am I doing it wrong here? Or is there a better or correct way of doing what I'm trying to do?

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

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

Displaying response from API in react component

I'm trying to display the response from the API into my react component but it's not working. If I try to use it in the console, I can see the data and its value but not in the react component, it's empty when I try to show the value in a div.
Here is the code where I'm trying to display it in my react component:
const CharacterListing = () => {
const characters = useSelector(getAllCharacters);
console.log("Hello", characters);
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
<div>{value.name}</div>
})
return (
<div>
{renderCharacters}
</div>
);
};
export default CharacterListing;
This is the code for my Character Slice Component
const initialState = {
characters: {},
};
const characterSlice = createSlice({
name: 'characters',
initialState,
reducers: {
addCharacters: (state, { payload }) => {
state.characters = payload;
},
},
});
export const { addCharacters } = characterSlice.actions;
export const getAllCharacters = (state) => state.characters.characters;
export default characterSlice.reducer;
This is the code for my Home Component:
const Home = () => {
const dispatch = useDispatch();
useEffect(() => {
const fetchCharacters = async () => {
const response = await baseURL.get(`/characters`)
.catch(error => {
console.log("Error", error);
});
dispatch(addCharacters(response.data));
console.log("Success", response);
};
fetchCharacters();
}, [])
return (
<div>
Home
<CharacterListing />
</div>
);
};
export default Home;
Thank you
You forgot to return item into your map func
Try this :
const renderCharacters = Object.entries(characters).map(([key, value]) => {
console.log(value.name);
return <div key={key}>{value.name}</div>
})

Props and onChildClick not working together

I have a parent component "Item" and a child component "Order".
Item has a button that toggles whether "Order" is displayed. If book is displayed, it passes the fetched details as props to the Order component, as well as the function for toggling if its open or closed.
Before adding the props to "Order", the toggle worked perfectly. After adding the props, the prop-handling works as it should, but now the function doesn't work. What am i doing wrong?
const Item = () => {
const [item, setItem] = useState('');
const [order, setOrder] = useState('');
//Api call to get item
const orderOpenClose = () => {
setOrder(!order);
};
return (
<>
<div onClick={orderOpenClose}>
<Button text="Order"></Button>
</div>
{order ? <Order acc={item} onChildClick={orderOpenClose}/> : ""}
</>
)
}
const Order = (props, { onChildClick }) => {
const { item } = props;
return (
<>
<div onClick={onChildClick}>
x
</div>
<p>{item.title}</p>
)
}```
This (props, { onChildClick }) is just not correct syntaxis, you can either destruct props or pass them as one object, but not both, so you can do either
const Book = ({acc, onChildClick })
or
const Book = (props) => {
const { acc,onChildClick } = props;

How to display posts on the current page from the API

I'm getting data from Django Rest API and React for Frontend, and I need to create the pagination with posts. I did it all in pagination component. I created the state with current page and I'm changing it by clicking on the page button in component like this:
const Paginator = () => {
const [count, setCount] = useState(null);
const [currentPage, setCurrentPage] = useState(1);
const [totalPages, setTotalPages] = useState(null);
const [nextPage, setNextPage] = useState(null);
const [previousPage, setPreviousPage] = useState(null);
const [valid, setValid] = useState(false);
useEffect(() => {
fetch(`http://127.0.0.1:8000/api/software/?p=${currentPage}`)
.then(response => response.json())
.then(data => {
setCount(data.count);
setTotalPages(data.total_pages)
setNextPage(data.links.next);
setPreviousPage(data.links.previous);
setValid(true);
})
}, [currentPage]);
...
return (
<>
{
...
<PbStart style={style} totalPages={range(1, totalPages+1)} setCurrentPage={setCurrentPage} />
...
}
</>
);
};
const PagItem = ({key, handleClick, className, title, name }) => {
return (
<li key={key} onClick={handleClick}>
<Link to='/' className={className} title={`Go to page ${title}`}>
{name}
</Link>
</li>
);
};
const PbStart = ({ style, totalPages, setCurrentPage }) => {
return (
...
{totalPages.map(p => (
<PagItem key={p} handleClick={() => setCurrentPage(p)} title={p} name={p} />
))}
...
);
};
And in posts component I don't know how to change current page, or getting it from the paginaton component. I've written that like this:
const Softwares = () => {
const [softwares, setSoftwares] = useState([]);
const [currentPage, setCurrentPage] = useState(1);
const [valid, setValid] = useState(false);
useEffect(() => {
fetch(`http://127.0.0.1:8000/api/software/?p=${currentPage}`)
.then(response => response.json())
.then(data => {
setSoftwares(data.results);
setValid(true);
})
}, [currentPage]);
return (
<>
{
...
{softwares.map(s => (
<Article key={s.id} pathname={s.id} title={s.title} image={s.image} pubdate={s.pub_date} icon={s.category.parent.img} categoryID={s.category.id} categoryName={s.category.name} dCount={s.counter} content={s.content} />
))}
...
}
</>
);
};
So, how to do that(get the current page from pagination component or another way)?
I think a Paginator's job is only moving between pages and updating current page state. It should not be fetching data by itself, you can provide functionality to do extra work with props.
I haven't tested this, but this might be a good starting point.
With the example below you'll have a list of articles and then below it next and previous buttons.
In Softwares, as you can see I am passing the same function for handling next and previous pages, you can refactor it to have one function like onPageMove and call this function handleNext and handlePrev.
I added two separate functions if you have want to handle something different in either.
const Paginator = ({
total, // Required: Total records
startPage = 1, // Start from page / initialize current page to
limit = 30, // items per page
onMoveNext = null, // function to call next page,
onMovePrev = null, // function to call previous page
}) => {
const [currentPage, setCurrentPage] = useState(startPage);
const canGoNext = total >= limit;
const canGoPrev = currentPage > 1;
function handleNext(e) {
if (canGoNext) {
setCurrentPage((prevState) => prevState+1);
onMoveNext && onMoveNext({ currentPage });
}
}
function handlePrev(e) {
if (canGoPrev) {
setCurrentPage((prevState) => prevState-1);
onMovePrev && onMovePrev({ currentPage });
}
}
return (
<div>
<button onClick={handlePrev} disabled={!canGoPrev}>Prev</button>
<button onClick={handleNext} disabled={!canGoNext}>Next</button>
</div>
);
};
Here is how you can use Paginator in other components.
const PER_PAGE = 30; // get max # of records per page
const Softwares = () => {
const [softwares, setSoftwares] = useState([]);
const [valid, setValid] = useState(false);
const onFetchData = ({ currentPage }) => {
fetch(`http://127.0.0.1:8000/api/software/?p=${currentPage}&per_page=${PER_PAGE}`)
.then(response => response.json())
.then(data => {
setSoftwares(data.results);
setValid(true);
})
}
useEffect(() => {
onFetchData({ currentPage: 1 })
}, []);
return (
<>
{softwares.map(s => (
<Article key={s.id} pathname={s.id} title={s.title} image={s.image} pubdate={s.pub_date} icon={s.category.parent.img} categoryID={s.category.id} categoryName={s.category.name} dCount={s.counter} content={s.content} />
))}
<Paginator total={softwares.length} limit={PER_PAGE} onMoveNext={onFetchData} onMovePrev={onFetchData} />
</>
);
};

Categories