I have a simple voting system (Yes or No) if you click Yes the state needs to be a string up and if you click No the state needs to be down, the vote works fine in the voteHandler method, but the problem is that i have to refresh the page everytime i vote so i can see the vote changes! i have added the useEffect so the vote changes directly but when i did i got alot of requests and errors, for example, i have 5 questions and each question might have multiple answers and each answer has the vote component (Yes or No), the requests = the number of questions * the number of answers!!
Here is the component without useEffect :
import React, { useState, useContext } from 'react';
import {AnswerContext} from './AnswerWrapper';
const AnswerItem = (props) => {
let indexPlus;
const indexCount = (index) => {
indexPlus = index;
return indexPlus;
}
const { active, setActive } = useContext(AnswerContext)
const [vote, setVote] = useState();
const voteHandler = (e, index, value) => {
e.preventDefault();
setActive(index);
setVote(value)
// I am sending this to a parent component
props.onVote ({
vote : value,
answerID : index
})
}
return (
<div>
<button onClick={(e) => voteHandler(e, props.index, 'up')}>Yes <span>({props.ups !== null ? props.ups : 0 })</span></button>
<button onClick={(e) => voteHandler(e, props.index, 'down')}>No <span>({props.downs !== null ? props.downs : 0 })</span></button>
</div>
)
}
export default AnswerItem;
And here is the component with useEffect :
import React, { useState, useEffect, useContext } from 'react';
import {AnswerContext} from './AnswerWrapper';
const AnswerItem = (props) => {
let indexPlus;
const indexCount = (index) => {
indexPlus = index;
return indexPlus;
}
const { active, setActive } = useContext(AnswerContext)
const [vote, setVote] = useState();
useEffect(() => {
props.onVote ({
vote : vote,
answerID : active
})
}, [vote, active])
const voteHandler = (e, index, value) => {
e.preventDefault();
setActive(index);
setVote(value)
}
return (
<div>
<button onClick={(e) => voteHandler(e, props.index, 'up')}>Yes <span>({props.ups !== null ? props.ups : 0 })</span></button>
<button onClick={(e) => voteHandler(e, props.index, 'down')}>No <span>({props.downs !== null ? props.downs : 0 })</span></button>
</div>
)
}
export default AnswerItem;
And to make it a bit clear, here is the parent component of AnswerItem
import React, { useContext, useCallback } from 'react';
import AnswerItem from './AnswerItem';
const AccordionItem = (props) => {
const handleVote = (dataV) => {
props.voteChanged(dataV)
}
return (
<div>
{props.answers.map((data, index) => (
<AnswerItem key={index} index={data.id} ups={data.ups} downs={data.downs} onVote={handleVote} />
))}
</div>
)
}
export default AccordionItem;
And lastly here is my App.js code, i didn't mention above that i post the votes in my API Api.postVote :
import React, {Component} from "react";
import AccordionItem from './components/AccordionItem';
class App extends Component {
constructor(props) {
super(props);
this.state = {
questions: [],
};
}
handleVoteData = (dataV) => {
this.sendVoteUpsData(dataV)
}
sendVoteUpsData = (dataV) => {
let newData = {...this.state};
newData.params = {
client_id : this.props.client,
the_vote : dataV.vote,
the_answerID : dataV.answerID
}
Api.postVote(newData.params)
.then( response => {
if(response.data.status === 'success'){
return response.data.data;
}
})
}
render() {
return (
<React.Fragment>
<div>
{
this.state.questions.map((data, index) => (
<AccordionItem answers={data.answers} voteChanged={this.handleVoteData}/>
))
}
</div>
</React.Fragment>
);
}
}
export default App;
First, you do not need to use useEffect hook to trigger props.onVote method, the first code should work.
Second, you don't see vote changes because your component has to rerender after the vote happened, and in order to make your component AnswerItem rerender, the object that contains {ups & downs} should be a state of the parent component for example.
see below code snippet and you will get the idea
import React, { useState } from 'react'
import AnswerItem from './AnswerItem';
const AnswerWrapper = () => {
const [state, setState] = useState({ ups: { '1': 3 }, downs: { '1': 0 } })
const onVote = ({ vote, answerID }) => {
if (vote == 'up') {
setState({ ...state, ups: { ...state.ups, [answerID]: state.ups[answerID] + 1 } })
} else if (vote == 'down') {
setState({ ...state, downs: { ...state.downs, [answerID]: state.downs[answerID] + 1 } })
}
}
return (
<AnswerItem onVote={onVote} index={'1'} ups={state.ups['1']} downs={state.downs['1']} />
)
}
export default AnswerWrapper;
I assumed the above component is the parent, and since the ups and downs in the state, so any changes will rerender the component, then the AnswerItem component will re-render accordingly.
The idea is your component has to be re rendered.
Related
I have to press sort twice for the items to be rendered, and they are rendered with what should've been rendered on the first press. So it's one step behind. Any idea why it is not being rendered on the first press and why it's one step behind?
I tried making it asynchronous, I tried making a loading state and then render, but everything to no effect.
Here is my code:
import "./productlist.scss";
import ProductCard from "../../../../Main Page Components/Trending Now/Product/ProductCard";
import { Link } from "react-router-dom";
import { useSelector } from "react-redux";
import { useEffect } from "react";
import { useState } from "react";
const ProductList = (props) => {
const sortType = useSelector((state) => state.sort).sortType;
const [sortedProducts, setSortedProducts] = useState(props.products);
// const [isSorted, setIsSorted] = useState(false);
useEffect(() => {
setSortedProducts(sortProducts(sortedProducts, sortType));
}, [sortType, sortedProducts]);
const sortProducts = (products, sortMode) => {
console.log("sortMode", sortMode);
switch (sortMode) {
case "A-Z":
return products.sort((a, b) => a.name.localeCompare(b.name));
case "Z-A":
return products.sort((a, b) => a.name.localeCompare(b.name)).reverse();
case "default":
return products;
}
};
return (
<div className="product-list padding">
<div className="product-list-contents">
{sortedProducts.map((singleProduct) => {
console.log("singleProduct", singleProduct.name);
// if(product.categories[0].name.slice(0,1).toLowerCase() === props.category){
return (
<Link
to={`/product/${singleProduct.sku}`}
className="product-list-link"
key={singleProduct.sku}
>
<ProductCard product={singleProduct} />
</Link>
);
// )}
// }
})}
</div>
</div>
);
};
export default ProductList;
It seems that the useEffect is both setting and listening to sortedProducts which might be causing error.
Assuming that sortedProducts is based on products from props, and sortType is updated by useSelector, perhaps this component can sort props.products without keeping a state and useEffect. For a rough example:
const ProductList = (props) => {
const { sortType } = useSelector((state) => state.sort);
const sortProducts = (products, sortMode) => {
console.log("sortMode", sortMode);
switch (sortMode) {
case "A-Z":
return products.sort((a, b) => a.name.localeCompare(b.name));
case "Z-A":
return products.sort((a, b) => a.name.localeCompare(b.name)).reverse();
case "default":
return products;
}
};
const sortedProducts = sortProducts(props.products, sortType);
return (
<div className="product-list padding">
<div className="product-list-contents">
{sortedProducts.map((singleProduct) => {
console.log("singleProduct", singleProduct.name);
// if(product.categories[0].name.slice(0,1).toLowerCase() === props.category){
return (
<Link
to={`/product/${singleProduct.sku}`}
className="product-list-link"
key={singleProduct.sku}
>
<ProductCard product={singleProduct} />
</Link>
);
// )}
// }
})}
</div>
</div>
);
};
export default ProductList;
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.
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
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);
I am checking to see if isFetchingData then don't render yet but its not re-rendering once isFetchingData is set to false. I have the useEffect in the context and i would hope that would re-render once isFetchingData is set to false. Any ideas?
When I refresh the page it renders with the data. So I think its to do with re-rendering.
I am using react context to get the data and exposing functions to filter that data and get me what i need.
Context:
import React, { useEffect, useState } from 'react';
import getAllEmployees from 'my-services/employee/getAllEmployees';
import { arrayOf, node, oneOfType } from 'prop-types';
export const EmployeeContext = React.createContext({
allEmployees: [],
getActiveEmployees: () => [],
getTerminatedEmployees: () => []
});
const EmployeesProvider = ({ children }) => {
const [isFetchingData, setIsFetchingData] = useState(true);
const [allEmployees, setAllEmployees] = useState({});
useEffect(() => {
getAllEmployees().then(
//doing something
).then(employees => {
setAllEmployees(employees);
setIsFetchingData(false);
});
}, [isFetchingData])
const context = {
isFetchingData,
allEmployees,
getActiveEmployees: () =>
allEmployees.filter(x => x.status === 'Current'),
getTerminatedEmployees: () =>
allEmployees.filter(x => x.status === 'Terminated')
};
return (
<EmployeeContext.Provider value={context}>{children}</EmployeeContext.Provider>
);
};
EmployeesProvider.propTypes = {
children: oneOfType([node, arrayOf(node)])
};
EmployeesProvider.defaultProps = {
children: undefined
};
export default EmployeesProvider;
Component:
import React, { useContext } from 'react';
import styled from 'styled-components';
import { EmployeeContext } from 'my-contexts/EmployeeContext';
import EmployeeCard from '../../../components/EmployeeCard';
const EmployeesTab = () => {
const {
getActiveEmployees,
getTerminatedEmployees,
isFetchingData
} = useContext(EmployeeContext);
let activeEmployees = [];
let terminatedEmployees = [];
if (!isFetchingData) {
activeEmployees = getActiveEmployees();
terminatedEmployees = getTerminatedEmployees();
}
if(isFetchingData) {
return <p>Loading</p>;
}
return (
<Outer>
<TopHeader>
<H3>Employees ({activeEmployees.length})</H3>
</TopHeader>
<Wrapper>
{activeEmployees.map(employee => {
return (
<EmployeeCard
id={employee.id}
guid={employee.guid}
firstName={employee.name.first}
lastName={employee.name.last}
jobTitle={employee.jobTitle}
/>
);
})}
</Wrapper>
<H3>Terminated employees({terminatedEmployees.length})</H3>
<Wrapper>
{terminatedEmployees.map(employee => {
return (
<EmployeeCard
id={employee.id}
guid={employee.guid}
firstName={employee.name.first}
lastName={employee.name.last}
jobTitle={employee.jobTitle}
/>
);
})}
</Wrapper>
</Outer>
);
};
export default EmployeesTab;
I think many problems may exist.
At first, please check whether whole component is closed by context Provider.
For example
<EmployeesProvider>
<EmployeesTab/>
<EmployeesProvider/>
Please check this problem.