When I click a "Thread"/button handleClick get's called and the currentIndex is updated/set which triggers the button's className ternary statement to execute as true. However, as there are many <li> elements, the other elements do not re-render className to the false statement. currentIndex should not == thread.id for those elements.
ThreadItem component
import { useState } from 'react'
const ThreadItem = ({ thread, changeThread }) => {
const [currentIndex, setCurrentIndex] = useState('')
// On thread click
const handleClick = (threadId) => {
setCurrentIndex(threadId)
}
return (
<li className="chat-item">
<form onSubmit={changeThread}>
<button className={currentIndex == thread.id ? 'side-menu-links-focus threads' : 'side-menu-links threads'} onClick={() => handleClick(thread.id)}>{`#${thread.threadType}`}</button>
</form>
</li>
)
}
export default ThreadItem
Parent ThreadList component
import ThreadItem from './ThreadItem'
const ThreadList = ({ threads, changeThread }) => {
return (
<ul className='chat-list'>
{threads.map((thread) => (
<ThreadItem
key={thread.id}
thread={thread}
changeThread={changeThread}
/>
))}
</ul>
)
}
export default ThreadList
If I understand your problem correctly, you want to click a button, and have a single ThreadItem to change className.
The problem I see is that you have added useState to every ThreadItem. Instead, you should move the state above to the ThreadList component.
This way, there is only one instance of state for the entire list of items. Simply pass the active prop and an onClick handler to each ThreadItem.
ThreadList
import React, { useState } from 'react';
import ThreadItem from './ThreadItem';
const ThreadList = ({ threads, changeThread }) => {
const [currentIndex, setCurrentIndex] = useState('');
// On thread click
const handleClick = (threadId) => {
setCurrentIndex(threadId);
};
return (
<ul className="chat-list">
{threads.map((thread) => (
<ThreadItem
key={thread.id}
thread={thread}
changeThread={changeThread}
isActive={currentIndex === thread.id}
onClick={() => handleClick(thread.id)}
/>
))}
</ul>
);
};
export default ThreadList;
ThreadItem
import React from 'react';
const ThreadItem = ({ thread, changeThread, isActive, onClick }) => {
return (
<li className="chat-item">
<form onSubmit={changeThread}>
<button
className={isActive ? 'side-menu-links-focus threads' : 'side-menu-links threads'}
onClick={onClick}
>{`#${thread.threadType}`}</button>
</form>
</li>
);
};
export default ThreadItem;
Related
I'm doing a broker simulator and I need to pass the data (side,price,volume,timestamp) into different tab, which is like grandparent's brother. The problem is I have no idea, how to transport data that far
Modalbuy.js
import React from "react";
import "./modal.css";
const Modalbuy = ({active, setActive,price}) => {
return (
<div className={active ? "modal active" : "modal"} onClick={() => setActive(false)}>
<div className="modal__content" onClick={e => e.stopPropagation()}>
<header>Make order</header>
<p>BUY {price}</p>
<input placeholder="Volume">{volume}</input>
<div>
<button onClick={() =>{this.props.addData("BUY", price, volume,)}}>Ok</button>
<button>Cancel</button>
</div>
</div>
</div>
)
}
export default Modalbuy;
Select.js
import React, {useState} from "react";
import './select.css'
import Modalbuy from "../popup/Modalbuy"
import Modalsell from "../popup/Modalsell"
import { useEffect } from "react";
const Select = () => {
const [value, setValue] = useState("");
const [usdrub, setUsdrub] = useState(Math.random() * (64-61) + 61);
useEffect(() => {
const interval = setInterval(() => {
setUsdrub(Math.random() * (64-61) + 61);
}, 10000);
return () => clearInterval(interval);
}, [])
const [rubusd, setRubusd] = useState(Math.random() * 2);
useEffect(() => {
const interval = setInterval(() => {
setRubusd(Math.random() * 2);
}, 10000);
return () => clearInterval(interval);
}, [])
function changeSelect(event) {
setValue(event.target.value)
}
const [modalBuyActive, setModalBuyActive] = useState(false)
const [modalSellActive, setModalSellActive] = useState(false)
function addData(side, price, volume, timestamp) {
this.setState({side:side, price:price, volume:volume, timestamp:timestamp})
}
return (
<div>
<select value = {value} onChange={changeSelect}>
<option>Choose instrument</option>
<option name="USD/RUB" value={usdrub}>USD/RUB</option>
<option name="RUB/USD" value={rubusd}>RUB/USD</option>
</select>
<div className="Curr">
<div className="Buy" name="buy"> <button className="Buy" type="btn" onClick={() => setModalBuyActive(true)}>BUY {value + 1} </button>
</div>
<div className="Sell" name="sell"><button className="Sell" type="btn" onClick={() => setModalSellActive(true)}>SELL {value}</button></div>
</div>
<Modalbuy active={modalBuyActive} setActive={setModalBuyActive} price={value + 1} addData={addData}/>
<Modalsell active={modalSellActive} setActive={setModalSellActive} price={value}/>
</div>
)
}
export default Select
Trading.js
import React from "react";
import Timer from "./Timer";
import Select from "./select_curr/Select";
const Trading = () => {
return (
<div>
<Timer/>
<Select/>
</div>
)
}
export default Trading;
Page.js
import React from 'react'
import { Tabs, TabList, TabPanel, Tab } from 'react-re-super-tabs'
import CustomTab from './customTab'
import Trading from '../trading/Trading.js'
import Table from '../archive/table'
const Page = () => {
return (
<div>
<Tabs activeTab='about'>
<TabList>
<Tab component={CustomTab} label='Trading' id='trading' />
<Tab component={CustomTab} label='Archive' id='archive' />
</TabList>
<TabList>
<TabPanel component={Trading} id='trading' />
<TabPanel component={Table} id='table' />
</TabList>
</Tabs>
</div>
)
}
export default Page;
So I need to make a table in Table.js and import there data I got from module dialogue
To summarize the way I understand your problem, The component Modalbuy need to share data with your component Table.
Here are some way I can advise you.
1. The common Parent manage the state
const Page = () => {
const [yourData, setYourData] = useState([])
return (
[...]
<TabPanel component={props=>(<Trading {...props} value={yourData} onChange={setYourData} />)} id='trading' />
<TabPanel component={props=>(<Table {...props} value={yourData} onChange={setYourData} />)} id='table' />
[...]
)
}
This implies intermediate component are in charge to follow those new props to Modalbuy
2. Using Redux librairy
Redux is basicly a librairy to help you to define a global state for your app. You define actions your components can call to transform this global state. On the other way, components can subscribe to that state to be refreshed when a change happen.
Don't hesitate to have a look to tutorials https://react-redux.js.org/tutorials/quick-start
3. Using React Context
export const YourDataContext = React.createContext({})
const Page = () => {
const [yourData, setYourData] = useState([])
return (
<YourDataContext.Provider value={{yourData, setYourData}}>
[...]
</YourDataContext.Provider>
)
}
const Modalbuy = ({active, setActive,price}) => {
const { yourData, setYourData } = React.useContext(YourDataContext)
[...]
})
You define a context YourDataContext. You share a value to this context, here we create a state and passing the getter/setter. All child of the Provider can access the attribute value with the hook React.useContext(YourDataContext)
Here I am making a shopping app and I have a working cart in it and below is my code for my cart component and here in cart I want to render order button conditionally for that I have isFound state and first I am getting data from my redux store and then I am checking below in useEffect hook if my list is not empty(list is const where I am storing my redux fetched data) then I will set my state=true and initially it is false but the problem is that useEffect is chanigng state to true if there is nothing inside of my list const means even if cart is empty and even though I am setting useEfect dependency proprly as well but it is showing order button all the time so someone can please help thanks:
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import classes from "./Cart.module.css";
const Cart = () => {
const navigate = useNavigate();
const [isFound, setIsFound] = useState(false);
const orders = useSelector((state) => state.data.DUMMY_DATA);
const list = orders.map(
(data, key) =>
data.product_count > 0 && (
<div className={classes.wrapper}>
<div className={classes.item}>
Item: {data.product_name}{" "}
</div>
<div className={classes.amount}>
Amount: {data.product_count}{" "}
</div>
<div className={classes.price}>
Price: {data.product_price}
</div>
</div>
)
);
useEffect(() => {
if (list !== "") {
setIsFound(true);
}
}, [list]);
return (
<div className={classes.modal}>
<div className={classes.root}>
<span
className={classes.close}
onClick={() => navigate("/", { replace: true })}
>
×
</span>
{list}
{isFound && (
<div className={classes.order_button_wrapper}>
<button className={classes.order_button}>Order</button>
</div>
)}
</div>
</div>
);
};
export default Cart;
.map alway return an array. So list !== "" will alway be true.
Here is useEffect, you have an array not a string as list value:
useEffect(() => {
if (list.length > 0) {
setIsFound(true);
}
}, [list]);
You have placed a watcher on the list variable, that's why useEffect is not calling, you need to place a watcher on the state because the state is being rendered and when any changes to the state useEffect will be called, variable is not rendered That's is why useEffect is not being called and changes to your component are not being replicated. You have to create a state and put the list value in the state and you have to call the function in the useEffect because you only have one called otherwise your function will be calling, as you code below can see.
import { useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { useNavigate } from "react-router-dom";
import classes from "./Cart.module.css";
const Cart = () => {
const navigate = useNavigate();
const [isFound, setIsFound] = useState(false);
const orders = useSelector((state) => state.data.DUMMY_DATA);
const [ordersList, setOrdersList] = useState("");
useEffect(() => {
const list = orders.map(
(data, key) =>
data.product_count > 0 && (
<div className={classes.wrapper}>
<div className={classes.item}>
Item: {data.product_name}{" "}
</div>
<div className={classes.amount}>
Amount: {data.product_count}{" "}
</div>
<div className={classes.price}>
Price: {data.product_price}
</div>
</div>
)
);
setOrdersList(list);
}, [])
useEffect(() => {
if (ordersList !== "") {
setIsFound(true);
}
}, [ordersList]);
return (
<div className={classes.modal}>
<div className={classes.root}>
<span
className={classes.close}
onClick={() => navigate("/", { replace: true })}
>
×
</span>
{ordersList}
{isFound && (
<div className={classes.order_button_wrapper}>
<button className={classes.order_button}>Order</button>
</div>
)}
</div>
</div>
);
};
export default Cart;
I am new to React and using React 18 in this app. My problem is that if I click one button inside a map function, it reflects information about all the items. I want only that item information to show for which I clicked the button. The isShown === true part in the CountryInfo.js file is what should reflect only one item; currently clicking the show button shows all item information on the UI (I don't want this to happen). How do I do this?
Visually, this is my UI,
If you see the image above, clicking any show button returns all countries information, which should not happen.
Below is my code:
App.js
import { useState, useEffect } from 'react';
import axios from "axios";
import CountryInfo from './components/CountryInfo';
const App = () => {
const [countries, setCountries] = useState([]);
const [searchCountry, setSearchCountry] = useState("");
const handleCountryChange = event => {
setSearchCountry(event.target.value);
}
const getAllCountriesData = () => {
axios.get("https://restcountries.com/v3.1/all")
.then(response => {
setCountries(response.data);
})
}
useEffect(() => {
getAllCountriesData();
}, []);
return (
<>
<h2>Data for countries</h2>
find countries:
<input value={searchCountry} onChange={handleCountryChange} />
{searchCountry.length > 0 && <CountryInfo countries={countries} searchCountry={searchCountry} />}
</>
)
}
export default App;
CountryInfo.js
import React from "react";
import { useState } from "react";
const CountryInfo = ({ countries, searchCountry }) => {
const [isShown, setIsShown] = useState(false);
let filteredList = countries.filter(country =>
country.name.common.toLowerCase().includes(searchCountry.toLowerCase()));
const handleClick = () => {
setIsShown(true);
}
if (filteredList.length > 10) {
return <div>Too many matches, specify another filter</div>
}
else {
return filteredList.map(country => {
return (
<>
<div key={country.name.common}>
{!isShown &&
<div>
{country.name.common}
<button type="submit" onClick={handleClick}>show</button>
</div>
}
{isShown &&
<div key={country.name.common}>
<h2>{country.name.common}</h2>
<p>
Capital: {country.capital}
{'\n'}
Area: {country.area}
</p>
Languages:
<ul>
{
Object.values(country.languages)
.map((language, index) => <li key={index}>{language}</li>)
}
</ul>
<img src={country.flags.png} alt={`${country.name.common} flag`} height={150} />
</div>
}
</div>
</>
)
})
}
}
export default CountryInfo;
I'm new to react.js and I want to apply the toggle feature at 'place-box' by using 'isOpen' state and my intention is it only works when I click single place-box div so I added onClick event at 'place-box' div. but all of the elements are toggled at the same time.
I guess it's because they all have the same class name.
how can I fix this?
import React, { useState, useEffect } from "react";
import { useQuery } from "#apollo/client";
import { FETCH_CITIES_QUERY } from "../../server/Data/RentQueries";
import PlaceResult from "../Rent/PlaceResult";
const CityResult = (props) => {
const [placeId, setPlaceId] = useState();
const [isOpen, setIsOpen] = useState(false);
const { loading, error, data } = useQuery(FETCH_CITIES_QUERY, {
variables: { cityName: cityName },
});
const showPlaceInfo = (placeId, e) => {
e.preventDefault();
setPlaceId(placeId);
setIsOpen((isOpen) => !isOpen);
};
return (
<div>
{data &&
data.cities.map((city) => {
return (
<div className="city-box">
{city.places.map((place) => {
return (
// this is place-box div and I added onClick event here
<div
className="place-box"
key={place.id}
onClick={(e) => {
e.stopPropagation();
showPlaceInfo(place.id, e);
}}
>
<li className="place-name">{place.name}</li>
{isOpen && (
<PlaceResult className="place-indiv" placeId={placeId} />
)}
{!isOpen && (
<div className="place-info-box">
<li>{place.address}</li>
{conditionCheck(city.condition)}
<li>{place.phone}</li>
</div>
)}
</div>
);
})}
</div>
);
})}
</div>
);
};
export default CityResult;
Your variable isOpen is used for all cities. If you change isOpen to true all place-boxes are opened. You should store the id of the currently opened city inside a variable and compare against it to check if the current city in the for loop should be opened.
import React, { useState, useEffect } from "react";
import { useQuery } from "#apollo/client";
import { FETCH_CITIES_QUERY } from "../../server/Data/RentQueries";
import PlaceResult from "../Rent/PlaceResult";
const CityResult = (props) => {
const [placeId, setPlaceId] = useState();
const [openedPlaceId, setOpenedPlaceId] = useState(undefined);
const { loading, error, data } = useQuery(FETCH_CITIES_QUERY, {
variables: { cityName: cityName },
});
const showPlaceInfo = (placeId, e) => {
e.preventDefault();
setPlaceId(placeId);
setOpenedPlaceId(placeId);
};
return (
<div>
{data &&
data.cities.map((city) => {
return (
<div className="city-box">
{city.places.map((place) => {
return (
// this is place-box div and I added onClick event here
<div
className="place-box"
key={place.id}
onClick={(e) => {
e.stopPropagation();
showPlaceInfo(place.id, e);
}}
>
<li className="place-name">{place.name}</li>
{openedPlaceId === place.id && (
<PlaceResult className="place-indiv" placeId={placeId} />
)}
{!(openedPlaceId === place.id) && (
<div className="place-info-box">
<li>{place.address}</li>
{conditionCheck(city.condition)}
<li>{place.phone}</li>
</div>
)}
</div>
);
})}
</div>
);
})}
</div>
);
};
export default CityResult;
This way only the clicked place will be opened.
I'm looking for the easiest solution to pass data from a child component to his parent.
I've heard about using Context, pass trough properties or update props, but I don't know which one is the best solution.
I'm building an admin interface, with a PageComponent that contains a ChildComponent with a table where I can select multiple line. I want to send to my parent PageComponent the number of line I've selected in my ChildComponent.
Something like that :
PageComponent :
<div className="App">
<EnhancedTable />
<h2>count 0</h2>
(count should be updated from child)
</div>
ChildComponent :
const EnhancedTable = () => {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count + 1)}>
Click me {count}
</button>
)
};
I'm sure it's a pretty simple thing to do, I don't want to use redux for that.
A common technique for these situations is to lift the state up to the first common ancestor of all the components that needs to use the state (i.e. the PageComponent in this case) and pass down the state and state-altering functions to the child components as props.
Example
const { useState } = React;
function PageComponent() {
const [count, setCount] = useState(0);
const increment = () => {
setCount(count + 1)
}
return (
<div className="App">
<ChildComponent onClick={increment} count={count} />
<h2>count {count}</h2>
(count should be updated from child)
</div>
);
}
const ChildComponent = ({ onClick, count }) => {
return (
<button onClick={onClick}>
Click me {count}
</button>
)
};
ReactDOM.render(<PageComponent />, document.getElementById("root"));
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
You can create a method in your parent component, pass it to child component and call it from props every time child's state changes, keeping the state in child component.
const EnhancedTable = ({ parentCallback }) => {
const [count, setCount] = useState(0);
return (
<button onClick={() => {
const newValue = count + 1;
setCount(newValue);
parentCallback(newValue);
}}>
Click me {count}
</button>
)
};
class PageComponent extends React.Component {
callback = (count) => {
// do something with value in parent component, like save to state
}
render() {
return (
<div className="App">
<EnhancedTable parentCallback={this.callback} />
<h2>count 0</h2>
(count should be updated from child)
</div>
)
}
}
To make things super simple you can actually share state setters to children and now they have the access to set the state of its parent.
example:
Assume there are 4 components as below,
function App() {
return (
<div className="App">
<GrandParent />
</div>
);
}
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
return (
<>
<div>{name}</div>
<Parent setName={setName} />
</>
);
};
const Parent = params => {
return (
<>
<button onClick={() => params.setName("i'm from Parent")}>
from Parent
</button>
<Child setName={params.setName} />
</>
);
};
const Child = params => {
return (
<>
<button onClick={() => params.setName("i'm from Child")}>
from Child
</button>
</>
);
};
so grandparent component has the actual state and by sharing the setter method (setName) to parent and child, they get the access to change the state of the grandparent.
you can find the working code in below sandbox,
https://codesandbox.io/embed/async-fire-kl197
IF we Have Parent Class Component and Child function component this is how we going to access child component useStates hooks value :--
class parent extends Component() {
constructor(props){
super(props)
this.ChildComponentRef = React.createRef()
}
render(){
console.log(' check child stateValue: ',
this.ChildComponentRef.current.info);
return (<> <ChildComponent ref={this.ChildComponentRef} /> </>)
}
}
Child Component we would create using
React.forwardRef((props, ref) => (<></>))
. and
useImperativeHandle(ref, createHandle, [deps])
to customizes the instance value that is exposed to parent components
const childComponent = React.forwardRef((props, ref) => {
const [info, setInfo] = useState("")
useEffect(() => {
axios.get("someUrl").then((data)=>setInfo(data))
})
useImperativeHandle(ref, () => {
return {
info: info
}
})
return (<> <h2> Child Component <h2> </>)
})
I had to do this in type script. The object-oriented aspect would need the dev to add this callback method as a field in the interface after inheriting from parent and the type of this prop would be Function. I found this cool!
Here's an another example of how we can pass state directly to the parent.
I modified a component example from react-select library which is a CreatableSelect component. The component was originally developed as class based component, I turned it into a functional component and changed state manipulation algorithm.
import React, {KeyboardEventHandler} from 'react';
import CreatableSelect from 'react-select/creatable';
import { ActionMeta, OnChangeValue } from 'react-select';
const MultiSelectTextInput = (props) => {
const components = {
DropdownIndicator: null,
};
interface Option {
readonly label: string;
readonly value: string;
}
const createOption = (label: string) => ({
label,
value: label,
});
const handleChange = (value: OnChangeValue<Option, true>, actionMeta: ActionMeta<Option>) => {
console.group('Value Changed');
console.log(value);
console.log(`action: ${actionMeta.action}`);
console.groupEnd();
props.setValue(value);
};
const handleInputChange = (inputValue: string) => {
props.setInputValue(inputValue);
};
const handleKeyDown: KeyboardEventHandler<HTMLDivElement> = (event) => {
if (!props.inputValue) return;
switch (event.key) {
case 'Enter':
case 'Tab':
console.group('Value Added');
console.log(props.value);
console.groupEnd();
props.setInputValue('');
props.setValue([...props.value, createOption(props.inputValue)])
event.preventDefault();
}
};
return (
<CreatableSelect
id={props.id}
instanceId={props.id}
className="w-100"
components={components}
inputValue={props.inputValue}
isClearable
isMulti
menuIsOpen={false}
onChange={handleChange}
onInputChange={handleInputChange}
onKeyDown={handleKeyDown}
placeholder="Type something and press enter..."
value={props.value}
/>
);
};
export default MultiSelectTextInput;
I call it from the pages of my next js project like this
import MultiSelectTextInput from "../components/Form/MultiSelect/MultiSelectTextInput";
const NcciLite = () => {
const [value, setValue] = useState<any>([]);
const [inputValue, setInputValue] = useState<any>('');
return (
<React.Fragment>
....
<div className="d-inline-flex col-md-9">
<MultiSelectTextInput
id="codes"
value={value}
setValue={setValue}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</div>
...
</React.Fragment>
);
};
As seen, the component modifies the page's (parent page's) state in which it is called.
I've had to deal with a similar issue, and found another approach, using an object to reference the states between different functions, and in the same file.
import React, { useState } from "react";
let myState = {};
const GrandParent = () => {
const [name, setName] = useState("i'm Grand Parent");
myState.name=name;
myState.setName=setName;
return (
<>
<div>{name}</div>
<Parent />
</>
);
};
export default GrandParent;
const Parent = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Parent")}>
from Parent
</button>
<Child />
</>
);
};
const Child = () => {
return (
<>
<button onClick={() => myState.setName("i'm from Child")}>
from Child
</button>
</>
);
};