I'm trying to make a dashboard using React. There are few components: App, Block and other child components, let's call them Content.
Block is a simple bootstrap card with title, classes and some css.
In App I call Block and pass in the Content components.
But in some Content components there are functions, which can change Block component (e.g. add or remove classes).
Now I use states in App and Content components to change Block, but I don't think this is the right approach.
Adding setState in Block component is impossible, as I know, because there is no way to change props.
How can I change Block states from within Content components?
Example:
function App() {
return (
<div>
<Block
id="users-component"
title="Users Table"
classes=[]
content={
<MyTable class="users" />
}
/>
<Block
id="status-component"
title="Status Component"
classes=[]
content={
<Status class="status" />
}
/>
<Block
id="bdays-component"
title="Bdays Component"
classes=[]
content={
<Bdays class="bdays" />
}
/>
</div>
)
}
function Block(props) {
return (
<div id={props.id} className={props.classes.join(" ")}>
<h2>{props.title}</h2>
{props.content}
</div>
)
}
function MyTable(props) {
return (
<table className={props.class}></table>
)
}
function Status(props) {
const handleClick = (title) => {
changeTitle(title) // changes title in Block
}
return (
<div className={props.class}>
<button onClick={() => handleClick("newTitle")}>New Title</button>
</div>
)
}
function Bdays(props) {
const handleClick = (class) => {
addNewClass(class) // add new class to array "classes" in it's block
}
return (
<div className={props.class}>
<button onClick={() => handleClick("newClass")}>New Class</button>
</div>
)
}
P.S. Sorry for my English)
Problem is solved by moving Content components into Block body and using React.cloneElement. Now, it's look like this:
function App() {
return (
<div>
<Block
id="users-component"
title="Users Table"
>
<MyTable class="users" />
</Block>
<Block
id="status-component"
title="Status Component"
>
<Status class="status" />
</Block>
<Block
id="bdays-component"
title="Bdays Component"
>
<Bdays class="bdays" />
</Block>
</div>
)
}
function Block(props) {
[classes, addClasses] = useState([""])
[title, renewTitle] = useState(props.title)
return (
<div id={props.id} className={classes.join(" ")}>
<h2>{props.title}</h2>
{React.cloneElement(children, {addClasses, renewTitle})}
</div>
)
}
function MyTable(props) {
return (
<table> </table>
)
}
function Status(props) {
const handleClick = (title) => {
props.renewTitle(title) // changes title in Block
}
return (
<div>
<button onClick={() => handleClick("newTitle")}>New Title</button>
</div>
)
}
function Bdays(props) {
const handleClick = (class) => {
props.addClasses([class]) // add new class to array "classes" in it's block
}
return (
<div className={props.class}>
<button onClick={() => handleClick("newClass")}>New Class</button>
</div>
)
}
Doing it that way, you won't be able to share state between those components, since they're siblings (although I might get corrected on that)
You could instead, make myTable to be a child of Block, and pass a useState function TO myTable as a prop, that way you can change the state of Block from myTable
This is a sample implementation
import { useState } from "react";
export default function App() {
return (
<Block
id="users-component"
content={
<MyTable class="users" />
}
/>
);
}
function Block(props) {
const [headerText, setHeaderText] = useState("Marco!")
return (
<div id={props.id}>
<h1> {headerText} </h1>
<MyTable changeHeaderText = {setHeaderText} />
</div>
)
}
function MyTable(props) {
return (
<table className={props.class}>
<tbody>
<tr>
<td>
I'm a table and I can change my parent's header text!
</td>
</tr>
<tr>
<td>
<button
onClick = {() => props.changeHeaderText("polo!")} >Click me!</button>
</td>
</tr>
</tbody>
</table>
)
}
Related
I got the container with children coming from props.[Container][1]
[1]: https://i.stack.imgur.com/3Y7Qm.png . When i click the arrow button it shows the content of the container. [Content][1]
[1]: https://i.stack.imgur.com/A8eZH.png . When i open the content container i want other containers to close . For now i can only close them with clicking the arrow button again.[Open Content][1]
[1]: https://i.stack.imgur.com/REh57.png .Here is my code `
import { useState } from "react";
export default function Question(props) {
const [clicked, setClicked] = useState(false);
function clickedElement() {
return setClicked(!clicked);
}
return (
<div className="question-cont">
<div className="question-cont-inner">
<h3>{props.head}</h3>
<button onClick={() => clickedElement()}>
{clicked ? (
<img src={props.img2} />
) : (
<img src={props.img} />
)}{" "}
</button>
</div>
{clicked ? <p>{props.description}</p> : ""}
</div>
);
}
Here is the my parent component
import Question from "../components/Question";
import questions from "../components/Questions";
export default function Sorular() {
const questionList = questions.map((question) => {
return (
<Question
key={question.id}
id={question.id}
head={question.head}
description={question.description}
img={question.img}
img2={question.img2}
/>
);
});
return (
<div className="sorular-container">
<div className="sorular-top">
<div className="sorular-top-back-img">
<a href="/">
<img
src="./images/right-arrow-colorful.png"
id="right-arrow-img"
/>
</a>
</div>
<div className="sorular-top-head">
<img src="./images/conversation.png" />
<h4>Sıkça Sorulan Sorular</h4>
</div>
</div>
<div className="sorular-bottom">{questionList}</div>
</div>
);
}
`
You need to remove your const [clicked, setClicked] = useState(false); state variable from the component itself and move it into parent:
In parent add this at the beggining and modify questionList:
const [clickedElementId, setClickedElementId] = useState(null);
const questionList = questions.map((question) => {
return (
<Question
key={question.id}
id={question.id}
head={question.head}
description={question.description}
img={question.img}
img2={question.img2}
isOpened={question.id === clickedElementId}
onClickedElement={() => setClickedElementId(
question.id === clickedElementId ? null : question.id
)}
/>
);
});
And in the Question.jsx, swap button for the following:
<button onClick={() => props.onClickedElement()}>
{props.isOpened ? (
<img src={props.img2} />
) : (
<img src={props.img} />
)}{" "}
</button>
// and later:
{props.isOpened ? <p>{props.description}</p> : ""}
This works by your app holding id of only one, currently open question, and swap it based on clicked element.
Note that questionId should be unique amongst all Question components, but you probably use .map to render them so you should use the same variable as you are passing into Question's key prop while rendering.
How to reload/refresh a parent component on button click.
In the code below i refresh page with javascript vanilla (window.location.reload(false)
Is there a way to reload the parent component or page without refreshing the page?
return product ? (
<div>
<div>
<img src={product.images[0]} alt={product.title} />
</div>
<div>
{product.title}
<br />
${product.price}
<br />
{product.description}
</div>
<div>
<button onClick={
() => {
setLiked(!liked)
if (favProduct) {
window.location.reload(false)
}
}
}>
<Icon size="28px" />
</button>
</div>
</div>
) : <p>Loading Product... </p>;
Not sure why you would want to do something like that, but here is one way to do it:
const Parent = () => {
const [toggleRefresh, setToggleRefresh] = useState(false)
return (
<Child setToggleRefresh={setToggleRefresh}/>
)
}
const Child = (props) => {
return (
<button onClick={
() => {
setLiked(!liked)
if (favProduct) {
//this will toggle the state of the parent, forcing a parent rerender
props.setToggleRefresh(prev => !prev)
}
}
}>
)
}
import Buttons from "./components/Buttons"
function Calculator() {
function handleButtons(i) {
return(
<Buttons value={i} onClick={printNumber}/>
)
}
function printNumber() {
console.log("Hello");
}
return (
<>
<div>
{handleButtons(1)}
{handleButtons(2)}
{handleButtons(3)}
</div>
<div>
{handleButtons(4)}
{handleButtons(5)}
{handleButtons(6)}
</div>
<div>
{handleButtons(7)}
{handleButtons(8)}
{handleButtons(9)}
</div>
</>
)
}
export default Calculator
I'm trying to print out the number that corresponds to the button that is being clicked. But I dont know how to access the properties(value) of Button component from this file.
Here is the other file
function Buttons(props) {
return (
<button onClick={props.onClick}>{props.value}</button>
)
}
export default Buttons
Im new to react btw
Change your code too,
import Buttons from "./components/Buttons"
function Calculator() {
function printNumber(num) {
console.log(num);
}
return (
<>
<div>
<Button value={1} onClick={() => printNumber(1)}/>
<Button value={2} onClick={() => printNumber(2)}/>
<Button value={3} onClick={() => printNumber(3)}/>
</div>
</>
)
}
export default Calculator
I have list of data that render it with map - I need to add an event just in one of the item from that list.
const UserModal = (props) => {
const {user,setUser} = props ;
const list = [,{id:3,text:'گفت وگو ها',icon:<BsChat />},{id:5,text:'خروج',icon:<BiExit />},];
/this is my list for making navigation bar
return (
<div className={style.main}>
<div style={{bordeBottom:'1px solid black'}}>
<BiUser />
<p>{user.username}</p>
</div>
{ //this is where I render a list to show and make component
list.map((item)=>
<div key={item.id}>
{item.icon}
<p>{item.text}</p>
</div>)
}
</div>
);
};
export default UserModal;
this my code and for example I need to add an event on specific object that has id=5 in that list .
how can I do that
I don't know if there is some sort of built-in solution for this, but here is a simple workaround:
I changed a few things for simplicity's sake
The important part is the if statement with checks if item ID is 5 then if so adds a div with the desired event
function App() {
const list = [
,
{ id: 3, text: "comonent 3" },
{ id: 5, text: "comonent 5 (target)" }
];
return (
<>
<h1>Hello world<h1/>
{list.map((item) => (
<div key={item.id} style={{ backgroundColor: "red" }}>
<p>{item.text}</p>
{item.id == 5 ? (
<div
onClick={() => {
alert("This component has a event");
}}
>
{" "}
event
</div>
) : (
<></>
)}
</div>
))}
</>
);
}
const UserModal = (props) => {
const {user,setUser} = props ;
const myEvent = () => alert('event fired');
const list = [,{id:3,text:'گفت وگو ها',icon:<BsChat /> , event : myEvent},{id:5,text:'خروج',icon:<BiExit />},];
/this is my list for making navigation bar
return (
<div className={style.main}>
<div style={{bordeBottom:'1px solid black'}}>
<BiUser />
<p>{user.username}</p>
</div>
{ //this is where I render a list to show and make component
list.map((item)=>
<div key={item.id}>
{item.icon}
<p onClick={item.event}>{item.text}</p>
</div>)
}
</div>
);
};
export default UserModal;
list.map((item, i)=> (
item.id == 5 ?
<div onClick={handleClick} key={i}></div>
:
<div key={i}></div>
)
When I click on a card, the loadAboutInfo function works through which I transfer data to another component and display it there. But if I click again on the same card, then it is duplicated. How can I fix it?I have check which take card id and then if it the same it render but I click again it render one more card, but i need if it already exist than new card mustn't render
loadAboutInfo=(pokemonValue,pockemonImg,pokemonId)=>{
this.setState(prevState => ({
pokemonValue:[...prevState.pokemonValue, pokemonValue],
pockemonImg,
pokemonId
}))
}
render() {
return (
<div className="wrapper">
<div className="pokemonlist__inner__cards">
<div className="pokemonlist__cards">
{this.state.pokemonList.map((value,index)=>{
let pokemonImgTemplate = this.state.pokemonImgTemplate;
let pokemonId = value.id;
let pockemonImg = pokemonImgTemplate.replace('{id}',pokemonId);
return(
<div className="pokemonListCard" key={index} onClick={()=>this.loadAboutInfo(value,pockemonImg,pokemonId)}>
<PokemonCard
pockemonImg={pockemonImg}
pokemonName={value.name}
pokemonTypes={value.types}
/>
</div>
)
})}
</div>
<PokemonLoadMore
loadMore={this.loadMore}
currentPage={this.state.currentPage}
/>
</div>
</div>
);
}
}
component where i map get data
render() {
return (
<div className="pokemon__about">
{this.props.pokemonValue.map((value,index)=>{
let totalMoves = value.moves.length;
return(
<div className="pokemon__about__wrapper" key={index}>
{this.props.pokemonId == value.id ?
<div className="pokemon__about__inner" key={index}>
<AboutImage
pockemonImg={this.props.pockemonImg}
/>
<AboutName
pockemonName={value.name}
/>
<div className="pokemon__about__table">
<AboutPokemonTypes
pokemonTypes={value.types}
/>
<table>
<AboutPokemonWeight
pockemonWeight={value.weight}
/>
<AboutPokemonMoves
totalMoves={totalMoves}
/>
</table>
</div>
</div>
:
null
}
</div>
)
})}
</div>
);
On the loadAboutInfo you can check if there is already a pokemon with the same id on pokemonValue array, something like this:
loadAboutInfo = (pokemonValue,pockemonImg,pokemonId) => {
// this will get the first element that matches the id
const exists = this.state.pokemonValue.find(pokemon => pokemon.id === pokemonId)
if (!exists) {
this.setState(prevState => ({
pokemonValue:[...prevState.pokemonValue, pokemonValue],
pockemonImg,
pokemonId
}))
}
}
So it will update the state only if the clicked pokemon isn't in the pokemonValue array