reactjs createRef not work in component arrays - javascript

like this i hava a array of components need ref to trigger the comment component collapse, so i need to create some refs to reference each commentListItem, but it doesn't work, how do i do this work?
import React, { useRef, createRef } from "react";
import PropTypes from "prop-types";
import { map, isArray } from "lodash/fp";
import Divider from "#material-ui/core/Divider";
import CommentListItem from "./CommentListItem";
import CommentCollapse from "./CommentCollapse";
function CommentList({ list = [], ...props }) {
const { count = 0 } = props;
const refList = map((o) => {
/* o.ref = createRef(null); */
return o;
})(list);
const onShow = () => {
console.log(refList);
};
return (
<div className="ke-comment-list">
{map.convert({ cap: false })((o, i) => (
<div key={i} className="ke-comment-list-item">
<CommentListItem listItem={o} onShow={onShow} />
{isArray(o.child) && o.child.length ? (
<CommentCollapse {...o}>
<CommentList list={o.child} count={count + 1} />
</CommentCollapse>
) : null}
{count > 0 && list.length - 1 === i ? null : <Divider />}
</div>
))(refList)}
</div>
);
}
CommentList.propTypes = {
list: PropTypes.arrayOf(PropTypes.object).isRequired,
};
export default CommentList;
there is CommentCollapse component for show or hide subcomment.
import React, { useState, forwardRef, useImperativeHandle } from "react";
import ButtonBase from "#material-ui/core/ButtonBase";
import Collapse from "#material-ui/core/Collapse";
const CommentCollapse = ({ children }, ref) => {
const [show, setShow] = useState(false);
const showMore = () => {
setShow((prev) => !prev);
};
const collapseText = () => (show ? "收起" : "展开");
useImperativeHandle(ref, () => ({
showMore: showMore()
}));
return (
<div className="ke-comment-list-children">
<Collapse in={show}>{children}</Collapse>
<ButtonBase size="small" onClick={showMore}>
{collapseText()}
</ButtonBase>
</div>
);
};
export default forwardRef(CommentCollapse);
catch errors
Uncaught Error: Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
have any idear for this situation?

is fixed, just not trigger showMore function in ref.
import React, { useState, forwardRef, useImperativeHandle } from "react";
import ButtonBase from "#material-ui/core/ButtonBase";
import Collapse from "#material-ui/core/Collapse";
const CommentCollapse = ({ children }, ref) => {
const [show, setShow] = useState(false);
const showMore = () => {
setShow((prev) => !prev);
};
const collapseText = () => (show ? "收起" : "展开");
useImperativeHandle(ref, () => ({
showMore
}));
return (
<div className="ke-comment-list-children">
<Collapse in={show}>{children}</Collapse>
<ButtonBase size="small" onClick={showMore}>
{collapseText()}
</ButtonBase>
</div>
);
};
export default forwardRef(CommentCollapse);

Related

Why is my global State not updating across other components

So I am trying to store a global state using context to allow me to use the same state across different components.
The issue I am having is that when I set the global state in 1 component and try to access it in the other component to use the state. It appears to be null and I cannot figure out why?
The first component where I set the global state in will always be rendered before the component shown that seems to have an empty value for the global state.
GlobalStateProvider component:
import React from "react";
import { useState, useEffect } from "react";
import axios from "axios";
const defaultActivitiesState = [];
const globalStateContext = React.createContext(defaultActivitiesState);
const dispatchStateContext = React.createContext([]);
export const useGlobalState = () =>
[
React.useContext(globalStateContext),
React.useContext(dispatchStateContext)
];
const GlobalStateProvider = ({ children }) => {
const [state, dispatch] = React.useReducer((state, newValue) => (state, newValue),
defaultActivitiesState
);
return (
<globalStateContext.Provider value={state}>
<dispatchStateContext.Provider value={dispatch}>
{children}
</dispatchStateContext.Provider>
</globalStateContext.Provider>
);
}
export default GlobalStateProvider;
Component I set the global state in:
import react from "react";
import { useState, useEffect, useMemo } from "react";
import { MapContainer, TileLayer, Popup, Polyline } from "react-leaflet";
import axios from "axios";
import polyline from "#mapbox/polyline";
import MapComp from "./MapComp";
import { useGlobalState } from "./GlobalStateProvider";
function Map() {
// ------- global state
const [activities, setActivities] = useGlobalState(); // global state
//const [activities, setActivities] = useState([]);
//const [polylines, setPolylines] = useState(null); // as empty array value is still truthy
const [isLoading, setIsLoading] = useState(true);
const [mapMode, setMapMode] = useState("light");
const [mapStyle, setMapStyle] = useState(
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
);
const [showMarkers, setShowMarkers] = useState(true);
useEffect(() => {
setActivitieData();
console.log("activities after useEffect", activities)
}, []);
const polylines = useMemo(() => {
console.log("activities inside memo", activities)
console.log("activities.len =", activities.length);
if (activities.length) {
console.log("past len");
const polylineArray = [];
for (const item of activities) {
const polylineData = item.map.summary_polyline;
const activityName = item.name;
const activityType = item.type;
polylineArray.push({
positions: polyline.decode(polylineData),
name: activityName,
activityType: activityType,
});
}
setIsLoading(false);
return polylineArray;
}
return null;
}, [activities]);
const toggleMarkers = () => {
setShowMarkers((show) => !show);
};
const getActivityData = async () => {
console.log("calling")
const response = await axios.get(
"http://localhost:8800/api/"
);
return response.data;
};
const setActivitieData = async () => {
const activityData = await getActivityData();
setActivities(activityData);
console.log("Global activities state = ", activities);
};
return !isLoading && polylines ? (
<>
<div className="select-container">
<button className="toggle-markers" onClick={() => toggleMarkers()}>
Toggle Markers
</button>
</div>
<MapComp
className={`${mapMode}`}
activityData={{ polylines }}
showMarkers={showMarkers}
/>
</>
) : (
<div>
<p>Loading...</p>
</div>
);
}
export default Map;
component that has an empty value for global state:
import React from 'react';
import { useGlobalState } from './GlobalStateProvider';
function ActivityList() {
const [activities, setActivities] = useGlobalState();
let displayValues;
displayValues =
activities.map((activity) => {
return (
<div>
<p>{activity.name}</p>
<p>{activity.distance}m</p>
</div>
);
})
return (
<>
<p>Values</p>
{displayValues}
</>
);
}
export default ActivityList;
App.js:
function App() {
return (
<GlobalStateProvider>
<div className="App">
<NavBar />
<AllRoutes />
</div>
</GlobalStateProvider>
);
}
export default App;

Trying to display one element from an Array -ReactJs

I am trying to make a flashcard web app for language learning and/or rote learning. I have managed to show the first element of the array which contains the data that I'm fetching from the backend but I can't switch from the first element to the subsequent elements.
Here is my code in React:
// Decklist component that displays the flashcard
import { React, useEffect, useState, useContext } from "react";
import Card from "./Card";
import cardContext from "../store/cardContext";
const axios = require("axios");
export default function Decklist() {
//State for data fetched from db
const [data, setData] = useState([]);
//State for array element to be displayed from the "data" state
const [position, setPosition] = useState(0);
//function to change the array element to be displayed after user reads card
const setVisibility = () => {
setPosition(position++);
};
//function to change the difficulty of a card
const difficultyHandler = (difficulty, id) => {
console.log(difficulty);
setData(
data.map((ele) => {
if (ele.ID === id) {
return { ...ele, type: difficulty };
}
return ele;
})
);
};
//useEffect for fetching data from db
useEffect(() => {
axios
.get("/api/cards")
.then((res) => {
if (res.data) {
console.log(res.data);
setData(res.data.sort(() => (Math.random() > 0.5 ? 1 : -1)));
}
})
.catch((err) => {
console.log(err);
});
}, []);
return (
<cardContext.Provider
value={{ cardData: data, setDifficulty: difficultyHandler }}
>
{data.length && (
<Card
position={position}
// dataIndex={index}
visible={setVisibility}
id={data[position].ID}
front={data[position].Front}
back={data[position].Back}
/>
)}
</cardContext.Provider>
);
}
//Card component
import { React, useState, useEffect } from "react";
import Options from "./Options";
export default function Card(props) {
//State for showing or hiding the answer
const [reverse, setReverse] = useState(false);
const [display, setDisplay] = useState(true);
//function for showing the answer
const reversalHandler = () => {
setReverse(true);
};
return (
<div>
{reverse ? (
<div className="card">
{props.front} {props.back}
<button
onClick={() => {
props.visible();
}}
>
Next Card
</button>
</div>
) : (
<div className="card">{props.front}</div>
)}
<Options
visible={props.visible}
reverse={reversalHandler}
id={props.id}
/>
</div>
);
}
//Options Component
import { React, useContext, useState } from "react";
import cardContext from "../store/cardContext";
export default function Options(props) {
const ctx = useContext(cardContext);
const [display, setDisplay] = useState(true);
return (
<>
<div className={display ? "" : "inactive"}>
<button
onClick={() => {
setDisplay(false);
props.reverse();
ctx.setDifficulty("easy", props.id);
}}
>
Easy
</button>
<button
onClick={() => {
setDisplay(false);
props.reverse();
ctx.setDifficulty("medium", props.id);
}}
>
Medium
</button>
<button
onClick={() => {
setDisplay(false);
props.reverse();
ctx.setDifficulty("hard", props.id);
}}
>
Hard
</button>
</div>
</>
);
}
The setVisibility function in the Decklist component is working fine and setting the position state properly. However, I don't know how to re-render the Card component so that it acts on the position state that has changed.
One way to force a re-render of a component is to set its state to itself
onClick={() => {
props.visible();
setReverse(reverse);
}}
However this probably isn't your issue as components will automatically re-render when their state changes or a parent re-renders. This means that for some reason the Card component isn't actually changing the parent component.

Content provider data no being loaded into a component

I have the main page 'feed' where i used to have three functions, but I moved it into custom context. The console logs in context file output all the objects correctly, but nothing is visible in feed when i concole.log them.
context file:
import React, { useEffect, createContext, useContext, useState } from 'react'
import { useMemo } from 'react';
import getPosts from '../api/getPosts';
import filterImportedPosts from '../utils/filterImportedPosts';
export const ItemContext = createContext({
postData: {}, setPostData: () => { }
});
export const FilteredItemsContext = createContext({ filteredItems: [], setFilteredItems: () => { } })
export const FilterContext = createContext({ filter: '', setFilter: () => { } })
export function useItemContext() {
return useContext(ItemContext)
}
export function useFilteredItemsContext() {
return useContext(FilteredItemsContext)
}
export function useFilterContext() {
return useContext(FilterContext)
}
export default function PostProvider({ children }) {
const [postData, setPostData] = useState([]);
const [filteredItems, setFilteredItems] = useState([]);
const [filter, setFilter] = useState('');
useEffect(() => {
getPosts(setPostData)
console.log('postData: ', postData)
}, []);
useEffect(() => {
// console.log(filter);
const tempFiltItems = filterImportedPosts(postData, filter);
setFilteredItems(tempFiltItems);
console.log('tempFiltItems: ', filteredItems)
}, [filter, postData]);
const filteredItemsState = useMemo(() => {
return { filteredItems, setFilteredItems }
}, [filteredItems, setFilteredItems])
return (
<FilterContext.Provider value={{ filter, setFilter }}>
<FilteredItemsContext.Provider value={filteredItemsState}>
<ItemContext.Provider value={{ postData, setPostData }}>
{children}
</ItemContext.Provider>
</FilteredItemsContext.Provider >
</FilterContext.Provider>
)
}
and here the feed file:
import React, { useState, useEffect } from 'react';
import SinglePost from '../components/singlePost/singlePost';
import FilterPane from '../components/filterPane/filterPane.feedPost';
import { Box, Spinner, Text } from '#chakra-ui/react';
import getPosts from '../api/getPosts';
import Loader from '../../common/Loader';
import filterImportedPosts from '../utils/filterImportedPosts';
import PostProvider, { useFilterContext, useFilteredItemsContext, useItemContext } from './../context/PostDataContext';
export default function Feed() {
//-----------------IMPORT DATA FROM SERVER----------------------
const [error, setError] = useState(null);
const { postData, setPostData } = useItemContext();
const { filter, setFilter } = useFilterContext();
const { filteredItems, setFilteredItems } = useFilteredItemsContext();
useEffect(() => {
console.log(filteredItems)
}, [filteredItems, setFilteredItems])
// this helps while the data is loaded
// if (postData.length === 0) {
// return (
// <Box pos='absolute' top='45vh' left='40%'>
// <Loader />
// </Box>
// )
// }
// console.log(filteredItems);
return (
<PostProvider>
<Box mt={'7vh'} mb={'7vh'} ml={'3vw'} mr={'3vw'} zIndex={200}>
<FilterPane
setFilter={setFilter}
filter={filter}
filteredItems={filteredItems}
/>
{error && (
<div>Error occurred while loading profile info. Details: {error}</div>
)}
{!error && (
<>
{filteredItems.map((item, index) => {
return <SinglePost key={index} item={item} />;
})}
</>
)}
</Box>
</PostProvider>
);
}
console window printscreen. As you can see the filteredItems in context exist but nothing gets shown in the actual feed - the objects are empty. Could someone assist please?

Reducer/Context Api

So I have a Context created with reducer. In reducer I have some logic, that in theory should work. I have Show Component that is iterating the data from data.js and has a button.I also have a windows Component that is iterating the data. Anyway the problem is that when I click on button in Show Component it should remove the item/id of data.js in Windows Component and in Show Component, but when I click on it nothing happens. I would be very grateful if someone could help me. Kind regards
App.js
const App =()=>{
const[isShowlOpen, setIsShowOpen]=React.useState(false)
const Show = useRef(null)
function openShow(){
setIsShowOpen(true)
}
function closeShowl(){
setIsShowOpen(false)
}
const handleShow =(e)=>{
if(show.current&& !showl.current.contains(e.target)){
closeShow()
}
}
useEffect(()=>{
document.addEventListener('click',handleShow)
return () =>{
document.removeEventListener('click', handleShow)
}
},[])
return (
<div>
<div ref={show}>
<img className='taskbar__iconsRight' onClick={() =>
setIsShowOpen(!isShowOpen)}
src="https://winaero.com/blog/wp-content/uploads/2017/07/Control-
-icon.png"/>
{isShowOpen ? <Show closeShow={closeShow} />: null}
</div>
)
}
```Context```
import React, { useState, useContext, useReducer, useEffect } from 'react'
import {windowsIcons} from './data'
import reducer from './reducer'
const AppContext = React.createContext()
const initialState = {
icons: windowsIcons
}
const AppProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState)
const remove = (id) => {
dispatch({ type: 'REMOVE', payload: id })
}
return (
<AppContext.Provider
value={{
...state,
remove,
}}
>
{children}
</AppContext.Provider>
)
}
export const useGlobalContext = () => {
return useContext(AppContext)
}
export { AppContext, AppProvider }
reducer.js
const reducer = (state, action) => {
if (action.type === 'REMOVE') {
return {
...state,
icons: state.icons.filter((windowsIcons) => windowsIcons.id !== action.payload),
}
}
}
export default reducer
``data.js```
export const windowsIcons =[
{
id:15,
url:"something/",
name:"yes",
img:"/images/icons/crud.png",
},
{
id:16,
url:"something/",
name:"nine",
img:"/images/icons/stermm.png",
},
{
id:17,
url:"domething/",
name:"ten",
img:"/images/icons/ll.png",
},
{
id:18,
url:"whatever",
name:"twenty",
img:"/images/icons/icons848.png",
},
{
id:19,
url:"hello",
name:"yeaa",
img:"/images/icons/icons8-96.png",
},
]
``` Show Component```
import React from 'react'
import { useGlobalContext } from '../../context'
import WindowsIcons from '../../WindowsIcons/WindowsIcons'
const Show = () => {
const { remove, } = useGlobalContext()
return (
<div className='control'>
{windowsIcons.map((unin)=>{
const { name, img, id} = unin
return (
<li className='control' key ={id}>
<div className='img__text'>
<img className='control__Img' src={img} />
<h4 className='control__name'>{name}</h4>
</div>
<button className='unin__button' onClick={() => remove(id)} >remove</button>
</li> )
</div>
)
}
export default Show
import React from 'react'
import {windowsIcons} from "../data"
import './WindowsIcons.css'
const WindowsIcons = ({id, url, img, name}) => {
return (
<>
{windowsIcons.map((icons)=>{
const {id, name , img ,url} =icons
return(
<div className='windows__icon' >
<li className='windows__list' key={id}>
<a href={url}>
<img className='windows__image' src={img}/>
<h4 className='windows__text'>{name}</h4>
</a>
</li>
</div>
)
})}
</>
)
}
Issue
In the reducer you are setting the initial state to your data list.
This is all correct.
However, then in your Show component you are directly importing windowsIcons and looping over it to render. So you are no longer looping over the state the reducer is handling. If the state changes, you won't see it.
Solution
In your Show component instead loop over the state that you have in the reducer:
const { remove, icons } = useGlobalContext()
{icons.map((unin) => {
// Render stuff
}
Now if you click remove it will modify the internal state and the icons variable will get updated.
Codesandbox working example

How to use React useRef hook for removing classes within map

I'm new to React. I'm attempting to add an onClick event to a div element that will remove a className from another element. These elements are part of a loop in a map. I am attempting to use the useRef hook for this. I specifically don't want to toggle classNames, I want to remove it, and that's it. Then add it back with another onclick event from another element. This requirement is specific to my application. My current code removes the className from the last element, not the one I am targeting. Thanks in advance for any help!
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import "./styles.css";
export default function App() {
const [kitchenItems, setkitchenItems] = useState([]);
useEffect(() => {
axios.get("./data.json").then((res) => {
setkitchenItems(res.data.kitchen);
});
}, []);
const navRef = React.useRef(null);
const onRemoveClick = (e) => {
navRef.current.classList.remove("red");
};
return (
<main>
{kitchenItems.map((item, index) => (
<div key={index} className="item">
<div onClick={onRemoveClick}>
<h2>{item.name}</h2>
<p ref={navRef} className="red">
{item.text}
</p>
</div>
</div>
))}
</main>
);
}
Here it is in CodeSandbox:
https://codesandbox.io/s/lively-moon-d7mm3
Save the indicator of whether an item should have the class or not into the kitchenItems state. Remove the ref.
useEffect(() => {
axios.get("./data.json").then((res) => {
setkitchenItems(res.data.kitchen.map(item => ({ ...item, red: true })));
});
}, []);
const onRemoveClick = (i) => {
setkitchenItems(
kitchenItems.map((item, j) => j !== i ? item : ({ ...item, red: false }))
);
};
<div onClick={() => onRemoveClick(i)}>
<h2>{item.name}</h2>
<p className={item.red ? 'red' : ''}>
The way you are approaching this is the way it's done with jQuery, that you change the dom elements. In React you would instead update the state and let the component render. I have added a new property on your objects but you may have another list or some other logic if you wish.
import React, { useState, useEffect, useRef } from "react";
import axios from "axios";
import "./styles.css";
export default function App() {
const [kitchenItems, setkitchenItems] = useState([]);
useEffect(() => {
axios.get("./data.json").then((res) => {
setkitchenItems(res.data.kitchen);
});
}, []);
const onRemoveClick = (index) => {
debugger;
const tmpItems = [...kitchenItems];
tmpItems[index].isRed = !tmpItems[index].isRed;
setkitchenItems(tmpItems);
};
return (
<main>
{kitchenItems.map((item, index) => (
<div key={index} className="item">
<div onClick={() => onRemoveClick(index)}>
<h2>{item.name}</h2>
<p className={item.isRed ? "red" : ""}>{item.text}</p>
</div>
</div>
))}
</main>
);
}
In CodeSandbox: https://codesandbox.io/s/keen-kirch-55whe?file=/src/App.js
Why not just use pureJS?
const onRemoveClick = (e) => {
var target = e.target;
if (target) {
if (target.classList && target.classList.contains("red")) {
target.classList.remove("red");
} else {
target.classList.add("red");
}
}
};

Categories