How to use state value in another js file - javascript

I have a file named Card.js. It contains two states called status and view .
Every time a button is clicked, my status state changes & according to this state my card is hidden.
At the same time, each of my cards has a view state. This increases by 1 according to the click event.
src/components/Card.js
const Card = (props) => {
const [status, setStatus] = useState(false);
const [view, setView] = useState(0);
const handleClick = () => {
setStatus(!status);
setView(view + 1);
};
return (
<div>
<Button onClick={handleClick}>{status ? "Show Component" : "Hide Component"}</Button>
{status ? null : <div className="card">{props.children} </div>}
</div>
);
};
export default Card;
In my app.js file, I return the data in my JSON file with the map method, I print this data to my card component.
src/App.js
const App= () => {
return (
<div>
{post.map((value, index) => {
return (
<Card key={index}>
{// here I want to print the "view" state in my Card.js file.}
</Card>
);
})}
</div>
);
};
export default App;
In App.js, I tried to create the view state using the useEffect hook, but every time I click the button, the view state of both my cards is updated at the same time. I don't want it to happen this way.

You can pass the data in your card as props so that the data is available in your Card module.
{
post.map((value, index) => {
return (
<Card key={index} status={false}>
{// here I want to print the "view" state in my Card.js file.}
</Card>
);
})
}
And in card module, pass the prop value to useState. I hope this will solve your problem.
const [status, setStatus] = useState(props.status);

Related

how to keep the last component styles?

i'm building a to-do app using React Js . inside the task component i used a state to apply a certain styles for the completed task and it works fine . but , after i cliked any delete button the style of the completed task deleted . how can i prevent that ?
import Task from "../Task/Task";
import style from "./TasksList.module.css";
const TasksList = ({ tasks, deleteTaskHandler }) => {
return (
<div className={style.tasks}>
<div className="container">
{tasks.map((task, idx) => {
return (
<Task
task={task}
id={idx}
key={Math.random()}
deleteTaskHandler={deleteTaskHandler}
/>
);
})}
</div>
</div>
);
};
export default TasksList;
import { useState } from "react";
import style from "./Task.module.css";
const Task = ({ task, id, deleteTaskHandler }) => {
const [isComplete, setIsComplete] = useState(false);
const markComplete = () => {
setIsComplete(!isComplete);
};
return (
<div
className={
isComplete ? `${style.task} ${style.completed}` : `${style.task}`
}
onClick={markComplete}
>
<label>{task.desc}</label>
<button onClick={() => deleteTaskHandler(id)}> Delete </button>
</div>
);
};
export default Task;
How are you maintaining the complete status of task in higher components?
Currently you are not initializing the complete state of Task.
If the task object contains the isComplete property, then you can use as shown below
const [isComplete, setIsComplete] = useState(task.isComplete);
however, you also need to update value of completed in task. So, I would suggest to have all lower components as stateless. and maintain the state at Higher component i.e. TaskList
import style from "./Task.module.css";
const Task = ({ task, id, deleteTaskHandler, setTaskCompleteHandler }) => {
const markComplete = () => {
setTaskCompleteHandler(!task.isComplete);
};
return (
<div
className={
task.isComplete ? `${style.task} ${style.completed}` : `${style.task}`
}
onClick={markComplete}
>
<label>{task.desc}</label>
<button onClick={() => deleteTaskHandler(id)}> Delete </button>
</div>
);
};
export default Task;
Implement setTaskCompleteHandler in TaskList and pass is as prop as part of Task component reandering.
Your problem is in position of your delete button, that wrapped by div with makrComplete handler, so then you click on your delete button, markComplete fired too, so your isComplete changed and styles deleted.
To prevent this behavor, you can do a little trick with prevent default. So, try to wrap your deleteTaskHandler in another function like that:
const deleteButtonClickHandler = (e) => {
e.preventDefault();
e.stopPropagation();
deleteTaskHandler(id)
}
and your delete button makrdown should be look like this:
<button onClick={deleteButtonClickHandler}> Delete </button>

Maintain the context data for each child component on React router change

I am beginner in React and working on React app where I am using the context to maintain the button state which can be in any one phase out of start, loading, stop.
I am passing the context to app component and have a React router to render the component on basis of route. I am rendering card component by looping through data where each card have one Button Component.
On button click of card1 the button1 should get in loading phase for 10-15 seconds depending on api response time. Once response comes it should be in stop phase. Similarly for button2 and button3 if clicked together. Now that seems to be working fine when I click on button1 and button2 instantly.
But when I click on 2 buttons together and move to another route and quickly come back I don't see my buttons to be in loading state though the api response is still pending. I should be seeing them in loading state and when response comes I should see them in start or stop phase.
I know I can use local or session storage but I don't want to due to some code restrictions.
Here is the stack blitz link : https://stackblitz.com/edit/node-3t59mt?file=src/App.js
Github Link: https://github.com/mehulk05/react-context-api
Button.jsx
import React, { useContext,useEffect, useState } from 'react'
import DbContext from '../Context/sharedContext'
function Button(props) {
console.log(props)
const {
requestStartDbObj,
setRequestStartDbObj
} = useContext(DbContext)
const [state, setstate] = useState(props.text?.status ?? "start")
useEffect(() => {
setstate(props.text?.status??"start")
return () => {
}
}, [state])
console.log(requestStartDbObj)
const start = ()=>{
setRequestStartDbObj({id:props.status.id, status:"loading"})
setstate("loading")
setTimeout(()=>{
setstate("stop")
setRequestStartDbObj({id:props.status.id, status:"stop"})
},5000)
}
return (
<div>
<button onClick={start}>{state}1</button>
</div>
)
}
export default Button
Card.jsx
function Card(props) {
const {
requestStartDbObj,
} = useContext(DbContext)
return (
<div>
<h1>{props.data.name}</h1>
<Button status={props.data} text={requestStartDbObj} />
</div>
)
}
Component1.jsx
function Component1() {
let data = [
{
id: 1,
name: "card1",
status: "start",
},
{
id: 2,
name: "card2",
status: "start",
},
{
id: 3,
name: "card3",
status: "start",
},
];
return (
<div>
<h1>Hello</h1>
{data.map((d, i) => (
<Card key={i} data={d} />
))}
</div>
);
}
ComponentWrapper.jsx
<h3>Wrpper</h3>
<Routes>
<Route path="/" element={<Component1 />} />
<Route path="about" element={<Component2 />} />
</Routes>
</div>
App.js
function App() {
return (
<div className="App">
<BrowserRouter>
<Link to="/">Home</Link> <br></br>
<Link to="/about">Comp 2</Link>
<DbProvider>
<ComponentWrapper/>
</DbProvider>
</BrowserRouter>
</div>
);
}
The issue is that your DbProvider context isn't the source of truth as to the status, it's not the component maintaining the requestStartDbObj state. Each Button is duplicating the state locally and using its own start function. Each Button is also replacing the requestStartDbObj state of the context, so when switching back to the home path all the buttons get the same initial state value. Upon navigating away from the home path the Button component is unmounted, so the state updates on timeout are lost.
You should move the start logic to the sharedContext so DbProvider maintains control over the state updates. start should consume an id argument so it can correctly update the status for that specific object.
DbProvider
const DbProvider = (props) => {
const [requestStartDbObj, setRequestStartDbObj] = useState({});
const { children } = props;
const start = (id) => {
setRequestStartDbObj((state) => ({
...state,
[id]: { status: "loading" }
}));
setTimeout(() => {
setRequestStartDbObj((state) => ({
...state,
[id]: { status: "stop" }
}));
}, 5000);
};
return (
<DbContext.Provider
value={{
requestStartDbObj,
start
}}
>
{children}
</DbContext.Provider>
);
};
Card
Only pass the id prop through to Button from data prop that was passed from Component1 when mapped.
function Card({ data }) {
return (
<div>
<h1>{data.name}</h1>
<Button id={data.id} />
</div>
);
}
Button
Use the id prop to pass to start function provided by the context. Also use the id to access the current status.
function Button({ id }) {
const { requestStartDbObj, start } = useContext(DbContext);
return (
<div>
<button onClick={() => start(id)}>
{requestStartDbObj[id]?.status || "start"}-{id}
</button>
</div>
);
}

Can't update parent component state with React UseState

im facing this weird behavior when trying to update the parent component with an set function to the child with props
this hook is to open and close the modal to edit an element
//PARENT FILE
//hook
const [isEditModalOpen, setEditModalOpen] = useState(false)
//more code...
//modal
{isEditModalOpen && <EditExcerciseModal setEditModalOpen={setEditModalOpen} isEditModalOpen={isEditModalOpen} />}
and this is the child code
//CHILD FILE
export const EditExcerciseModal = ({setEditModalOpen, excerciseInfo,fetchExcercisesFromRoutine})
//more code etc etc
<div className="addExcerciseModalContainer">
<span onClick={() =>{ setEditModalOpen(false) }} className="xModal">X</span>
i checked and the onClick is working. if i change the parent state manually the Modal works fine and closes.
the weird case when it its working is when instead of calling the set function i create a function with a setTimeout without time like this:
function closeModal(){
setTimeout(() => { setEditModalOpen(false)}, 0);
}
any ideas?
thanks for the help
You need to create a separation of concern. A Modal consists of three parts
The Modal of its Self.
The Content of the Modal.
And the container of the two.
You should be using the useState() hook and calling setEditModalOpen in the same containing component.
You need to make sure that you're declaring and setting state inside the same component.
// children would be the content of the modal
const Modal = ({ children, selector, open, setOpen }) => {
// we need the useEffect hook so that when we change open to false
// the modal component will re-render and the portal will not be created
useEffect(() => {
setOpen(false);
//provide useEffect hook with clean up.
return () => setOpen(true);
}, [selector]);
return open ? createPortal(children, selector) : null;
};
export const EditExerciseModal = ({ close }) => {
return (
<div>
{/* Instead of creating a handler inside this component we can create it in it's parent element */}
<span onClick={close}>X</span>
{/* Content */}
</div>
);
};
export const ModalBtn = () => {
const [isEditModalOpen, setEditModalOpen] = useState(false);
// this is where it all comes together,
// our button element will keep track of the isEditModalOpen variable,
// which in turn will update both child elements
// when true useEffect hook will re-render Modal Component only now it "will" createPortal()
// when our EditExerciseModal alls close it will set change the isEditModalOpen to false
// which will be passed to the Modal component which
// will then cause the component to re-render and not call createPortal()
return (
<>
<button onClick={() => setEditModalOpen(true)}>EditExerciseModal</button>
{setEditModalOpen && (
<Modal
open={isEditModalOpen}
setOpen={setEditModalOpen}
selector={'#portal'}>
<div className='overlay'>
<EditExerciseModal close={() => setEditModalOpen(false)} />
</div>
</Modal>
)}
</>
);
};

Change state of individual div (that is inside a map) instead of changing the state of all div's inside the map

I have a component that is checking if some state is true or false. I show a <p> tag if true and hide a <h3>. I am pulling the data from a gaphQL query so there is a map method and there are three <Card>'s now if I click the card and run my showFull function it shows the p tags on all the elements, instead I just want to isolate the specific one it is clicking on.
Here is my component
<Testimonials className="testimonaials">
{data.allDatoCmsTestimonial.edges.map(({ node: testimonial }) => (
<Card onClick={showFull} background={testimonial.testimonialImage.url}>
{testimonialFull ?
<p>{testimonial.fullLengthQuote}</p>
:
<h3>{testimonial.shortQuote}</h3>
}
</Card>
))}
</Testimonials>
Here is my state and my function
const [testimonialFull, setTestimonialFull] = useState(false)
const showFull = () => {
setTestimonialFull(true)
}
Attempting Alexander's answer. The issue I am having now is Cannot read property 'testimonialImage' of undefined
Here is the component
const IndexPage = ({ data }) => {
const TestimonialCard = ({testimonial})=>{
const [showFull, setShowFull] = useState(false)
const handleClick = useCallback(()=>{
setShowFull(true)
//setShowFull(s=>!s)//If you want toggle behaviour
},[])
return <Card onClick={handleClick} background={testimonial.testimonialImage.url}>
{showFull ?
<p>{testimonial.fullLengthQuote}</p>
:
<h3>{testimonial.shortQuote}</h3>
}
</Card>
}
return (
...
Here is where I invoke it in the map function
...
return (
... (bunch of other jsx/html)
<Testimonials className="testimonaials">
{data.allDatoCmsTestimonial.edges.map(({ node: testimonial }) => (
<TestimonialCard/>
))}
</Testimonials>
...
Wrap the cards in a custom component
const TestimonialCard = ({testimonial})=>{
const [showFull, setShowFull] = useState(false)
const handleClick = useCallback(()=>{
setShowFull(true)
//setShowFull(s=>!s)//If you want toggle behaviour
},[])
return <Card onClick={handleClick} background={testimonial.testimonialImage.url}>
{showFull ?
<p>{testimonial.fullLengthQuote}</p>
:
<h3>{testimonial.shortQuote}</h3>
}
</Card>
}

data does not re-render after clicking the sort button

I have milestoneCards.
I want to add a sort button, that upon clicking this button the cards will be sorted by the card heading.
The sort takes place, but it does not re-render the list in the sorted order.
please advise.
thank you so much for helping me here.
import React from "react";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { Card, CardBody, CardTitle } from "reactstrap";
const MyMilestones = props => {
let sortClicked = false;
let milestoneCards =
props.milestones.length > 0
? props.milestones.map(m => (
<p key={m.id}>
<Link to={`/milestones/${m.id}`}>{m.attributes.heading}</Link>
</p>
))
: null;
const sortedMilestoneCards = [...props.milestones]
.sort((a, b) => (a.attributes.heading > b.attributes.heading ? 1 : -1))
.map(m => (
<p key={m.id}>
<Link to={`/milestones/${m.id}`}>{m.attributes.heading}</Link>
</p>
));
return (
<div className="MilestoneCards">
{
<Card>
<CardBody>
<CardTitle>
<h4>My Milestones</h4>
</CardTitle>
<button
onClick={() => {
sortClicked = true;
console.log("before", milestoneCards);
milestoneCards = sortedMilestoneCards;
console.log("after", milestoneCards);
return (milestoneCards = sortedMilestoneCards);
}}
>
Sort
</button>
sortClicked ? ({sortedMilestoneCards}) : {milestoneCards}
</CardBody>
</Card>
}
</div>
);
};
const mapStateToProps = state => {
return {
milestones: state.myMilestones
};
};
export default connect(mapStateToProps)(MyMilestones);
It's because you need to have sortClicked to be tracked by React.
When let sortClicked = false is declared inside MyMilestones component, it's declared once on the first component mount and won't be updated when the component is re-rendered.
So you can save sortClicked in a state using React.useState and update it onClick. useState is a one-off way of storing this.state value for Class Component but for one state. (I won't get into it too deep as React documentation has a thorough coverage on Introducing Hooks)
const MyMilestones = props => {
// let sortClicked = false;
// Initialize it to "false" by default.
let [sortClicked, setSortClicked] = React.useState(false)
let milestoneCards = ...;
const sortedMilestoneCards = ...;
return (
<div className="MilestoneCards">
{
<Card>
<CardBody>
<CardTitle>
<h4>My Milestones</h4>
</CardTitle>
<button
onClick={() => {
// Notify "React" to re-render.
setSortClicked(true)
// No need to return a new reference here.
}}
>
Sort
</button>
{/* 👇 Note that {} is wrapped around the whole block. */}
{sortClicked ? sortedMilestoneCards : milestoneCards}
</CardBody>
</Card>
}
</div>
);
};
It's because you're not updating the milestones correctly. Since they're stored on Redux state, you need to add and dispatch the action that modifies the state.
I recommend you look at the Redux documentation.

Categories

Resources