How to pass data to React components with redux? - reactjs

I am completely new to the field which is why I might be having a silly question over here.
I have set up a react-redux app. All the redux boilerplate is ready and is functioning properly. Now, I am willing to add a feature that will take data from one React Component and pass it to another, while hiding it or deleting it from the first component.
My question is, should I set up new action and update my root reducer in order to do that or there is an easier way to do that?
This is my first component which I am willing to take data from by clicking the "Rent" button. Also, I want the data piece to temporarily disappear from this component and appear in the second one.
class Available extends Component {
componentDidMount() {
this.props.getRentals();
}
onDeleteClick = (id) => {
this.props.deleteRental(id);
}
render() {
const { rentals } = this.props.rental
return (
<Container fluid>
<h2>List of Available Bikes</h2>
<ListGroup>
<TransitionGroup className="rental-list">
{ rentals.map(({ _id, name, bikeType, price }) => (
<CSSTransition key={_id} timeout={500} classNames="fade">
<ListGroupItem>
{name} / {bikeType} / ${price} / per hour
<Button
className="remove-btn float-right"
color="danger"
size="sm"
onClick={this.onDeleteClick.bind(this, _id)}
>Delete</Button>
<Button
className="remove-btn float-right"
color="primary"
size="sm"
onClick={this.onRentClick(this, _id)}
>Rent</Button>
</ListGroupItem>
</CSSTransition>
))}
</TransitionGroup>
</ListGroup>
</Container>
)
}
}
And this is the second component that I am willing to pass the data to. Also, I am willing to add a feature that will bring the data piece back to the first component when I click the "Cancel Rent" button.
class Rented extends Component {
componentDidMount() {
this.props.getRentals();
}
render() {
const { rentals } = this.props.rental
return (
<Container fluid>
<h2>List of Rented Bikes</h2>
<ListGroup>
<TransitionGroup className="rental-list">
{ rentals.map(({ _id, name, bikeType, price }) => (
<CSSTransition key={_id} timeout={500} classNames="fade">
<ListGroupItem>
{name} / {bikeType} / ${price} / per hour
<Button
className="remove-btn float-right"
color="danger"
size="sm"
>Cancel Rent</Button>
</ListGroupItem>
</CSSTransition>
))}
</TransitionGroup>
</ListGroup>
</Container>
)
}
}
Please let me know what is the best solution here :)

Related

Pass search value from one component to another container and use it in React

I am pretty new to React.
Previously the search functionality and the productsPage code lived in one file and search worked as expected.
Now I want to decouple the search functionality from productsPage.
This is products.js file where I need items to be searched.
function ProductsPage() {
var categories = data.categories;
const [search, setSearch] = useState(null);
return (
<>
<Container fluid id="searchBar">
<SearchForm searchValue={search} />
</Container>
<Container fluid id="productPage">
<h2 className="text-center py-5">Our Products</h2>
<CardColumns className="pt-3 mb-5">
{categories.map((catNames) => (
<Fragment key={uuid()}>
{catNames.sub_categories.map((subCat) => (
<Fragment key={uuid()}>
{subCat.items
.filter((item) => {
if (search == null) return item;
else if (
item.itemName
.toLowerCase()
.includes(search.toLowerCase())
) {
return item;
}
})
.map((item) => {
return (
<Card className="mb-4 p-3" key={uuid()}>
<div className="prodBorder">
<Card.Title className="pt-5 pb-4 text-center">
{item.itemName}
</Card.Title>
<Card.Img
src={`${item.image.url}`}
className="d-block w-50 mx-auto my-3"
/>
<Card.Subtitle className="pb-4 text-center text-muted">
{item.itemDesc}
</Card.Subtitle>
</div>
</Card>
);
})}
</Fragment>
))}
</Fragment>
))}
</CardColumns>
</Container>
</>
);
}
export default ProductsPage;
This one is search.js file where the onChange handler lives
function SearchForm(props) {
const [search, setSearch] = useState(null);
const searchSpace = (event) => {
let search = event.target.value;
setSearch(search);
};
return (
<Form inline className="position-relative mt-4 mx-auto w-75">
<FontAwesomeIcon icon={faSearch} className="productsSearchIcon" />
<FormControl
id="searchfield"
type="text"
placeholder="Search products..."
onChange={(e) => searchSpace(e)}
className="ml-0 ml-md-3 w-100"
/>
</Form>
);
}
export default SearchForm;
There are two ways you could do this. You could use state lifting.
Docs: https://reactjs.org/docs/lifting-state-up.html
I've made this example to show how that works:
https://codesandbox.io/s/stupefied-noether-rb1yi?file=/src/App.js
If your app is not small or is going to grow you are better using some sort of global state management such as redux or react context API
Docs: https://redux.js.org/tutorials/essentials/part-1-overview-concepts
Docs: https://reactjs.org/docs/context.html
I have made this example using the react context api to give you an idea of how global state management works.
https://codesandbox.io/s/confident-satoshi-upo4c?file=/src/App.js
You will see in this example you can pass data to nested components without the need to pass data through props. When your app grows in size this will make your code a lot cleaner, more efficient and less prone to error. Redux is an alternative (earlier and more popular solution) for global state management.
As a beginner you should try to understand the concepts of state lifting before global state management.
However, if your app is big with nested components, a global state management approach would be the preferred final solution.
I stumbled upon this question after searching a lot
How to pass state back to parent in React?
This is the explanation and answer that worked
https://stackoverflow.com/a/67084024/3807542

How do I update state to specific value?

I hope you're all well.
I would be so grateful if any of you can shed some light on the following question..
There are two relevant components:
Parent component which fetches data using GraphQL
let authors = "";
const { loading, data } = useQuery(FETCH_AUTHORS_QUERY);
console.log(`Loading: ${loading}`);
//console.log(data);
if (data) {
authors = { data: data.getAuthors };
}
return (
<Grid columns={3}>
<Grid.Row className="page-title">
<h1>Recent Essays</h1>
</Grid.Row>
{loading ? (
<h1>Loading essays..</h1>
) : (
authors.data &&
authors.data.map(author => (
<Grid.Column key={author.id} style={{ marginBottom: 20 }}>
<AuthorCard author={author} />
</Grid.Column>
))
)}
<Grid.Row></Grid.Row>
</Grid>
);
}
const FETCH_AUTHORS_QUERY = gql`
{
getAuthors {
id
Author
Description_1
Description_2
}
}
`
Child component called 'AuthorCard' (you can see placed in the parent component above):
function AuthorCard({ author: { Author, Description_1, id } }) {
const [writer, setWriter] = useState();
return (
<Card fluid>
<Card.Content>
<Image
floated="right"
size="mini"
src="https://image.cnbcfm.com/api/v1/image/105691887-1556634687926ray.jpg?v=1576249072"
/>
<Card.Header>{Author}</Card.Header>
<Card.Meta as={Link} to={`/essays`}></Card.Meta>
<Card.Description>{Description_1}</Card.Description>
</Card.Content>
<Card.Content extra>
<Button as="div" labelPosition="right">
<Button color="teal" basic>
<Icon name="heart" />
</Button>
<Label basic color="teal" pointing="left"></Label>
</Button>
<Button labelPosition="right" as={Link} to={`/essays`}>
<Button
color="blue"
basic
key={Author.id}
onClick={() => setWriter(Author)}
>
<Icon name="comments" />
</Button>
</Button>
</Card.Content>
</Card>
);
}
export default AuthorCard;
The issue is as follows:
When I click the as={Link} to={/essays} button in the child component, I would like to 'setWriter' state to the individual Author whose button I am clicking on.
However, I am not able to specify the individual author whose button is being clicked on. React thinks I want to 'setWriter' for the entire list of authors. For example, when I console.log(Author) within the button, I can see every author in the list printed in the console.
I have tried adding id, using event.target .value, and onChange instead of onClick.
I just haven't been able to target the individual author in order to update the state (which will be needed in a different component).
Please let me know if anything isn't clear or if it would be helpful to provide more details.
Thanks in advance and happy coding!!!
Dan
You should use a destructuring assignment expression that makes it possible to unpack values from arrays, or properties from objects, into distinct variables.
I think you should declare your child component like this:
function AuthorCard({ Author, Description_1, id }) {
...
...
...
}
Thanks for the reply.
I finally solved the issue. I had to use:
onClick={e => setWriter(e.currentTarget.Author)}
I hadn't known about using 'current target.'
I can then transfer the state to other components through useContext.
Hopefully this can help someone else!
Dan

state is not updating when the component re renders?

I'm making a Nextjs flashcard app. I'm passing a deck structure like this:
const deck = {
title: 'React 101',
flashcards: [flashcardOne, flashcardTwo],
};
as props to the Deck component. This component shows the first card in flashcards and a "next" button to increment the index and showing the next card in flashcards.
The Card component is very simple and shows the front and the back of the card depending of the state front.
This is what I got so far and it's working but if I click "next" when the card is showing the answer (flashcard.back), the next card is going to appear with the answer. And I'm not sure why, isn't the Card component re rendering when I click "next"? And if the component re renders, front is going to be set to true?
export default function Deck({ deck }) {
const [cardIndex, setCardIndex] = useState(0);
const { title, flashcards } = deck;
return (
<div className={styles.container}>
<main className={styles.main}>
<h1 className={styles.title}>{title}</h1>
{cardIndex < flashcards.length ? (
<>
<div className={styles.grid}>
<Card flashcard={flashcards[cardIndex]} />
</div>
<button onClick={() => setCardIndex((cardIndex) => cardIndex + 1)}>
Next
</button>
</>
) : (
<>
<div>End</div>
<button>
<Link href='/'>
<a>Go to Home</a>
</Link>
</button>
<button onClick={() => setCardIndex(0)}>Play again</button>
</>
)}
</main>
</div>
);
}
export function Card({ flashcard }) {
const [front, setFront] = useState(true);
return (
<>
{front ? (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(false)}
>
<p className={styles.front}>{flashcard.front}</p>
</div>
) : (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(true)}
>
<p className={styles.back}>{flashcard.back}</p>
</div>
)}
</>
);
}
When state changes, the card will re-render, but it will not re-mount. So, existing state will not be reset.
Call setFront(true) when the flashcard prop has changed:
const [front, setFront] = useState(true);
useLayoutEffect(() => {
setFront(true);
}, [flashcard]);
I'm using useLayoutEffect instead of useEffect to ensure front gets set ASAP, rather than after a paint cycle (which could cause flickering).
You can also significantly slim down the Card JSX:
export function Card({ flashcard }) {
const [front, setFront] = useState(true);
const face = front ? 'front' : 'back';
return (
<div
className={`${globalStyles.card} ${styles.card}`}
onClick={() => setFront(!front)}
>
<p className={styles[face]}>{flashcard[face]}</p>
</div>
);
}
Okay, I guess I had the same issue. Since you're using functional components, and you're re-using the same component or in better words, you're not unmounting and remounting the component really, you're just changing the props, this happens. For this, you need to do useEffect() and then setFront(true).
Here's the code I used in my App.
useEffect(() => {
setFront(true);
}, [flashcard]);
This is what I have used in my Word.js file.

ReactJS conditional display of elements with map function is returning that my object or key is 'undefined'

Here I have passed my state from one component to another the same way I've done before. And when I console.log the passed state (before I add the map statement in JSX) I get the object im looking for. But when I go inside the return statement I get "cannot map 'undefined'". And at that point the console.log starts returning as undefined. But before I tried to map over the passed prop the console.log was returning the data.Can anyone help me see what im doing wrong?
function Items ({orgList}) {
console.log(orgList.items)
return (
<>
{orgList ?
<div>
{orgList.items.map((item, i) => (
<Card key={i} className= "w-100 m-2">
<CardHeader className="d-flex">
<p>Hello</p>
</CardHeader>
<CardBody>
<p>The Body</p>
<Row>
<Col md="5">
<dl>
<dt>GroupID</dt>
<dd>{item.GroupID}</dd>
<dt>Name</dt>
<dd>{item.name}</dd>
</dl>
</Col>
</Row>
</CardBody>
</Card>
))}
</div>
:
<div>
<PageLoadSpinner inProgress={inProgress} />
</div>
}
</>
)
}
export default Items;**strong text**
Here is the parent Component
function Orgs () {
const [orgList, setOrgList] = useState([]);
useEffect(() => {
loadOrgs();
}, []);
const loadOrgs = () => {
api.MonitoringOrg.list()
.then(response => {
console.log(response)
setOrgList(...orgList, response)
})
.catch(err => console.log(err))
}
return(
<>
<Items orgList={orgList}/>
</>
)
}
export default Orgs;
Errors that include undefined like the one you are observing likely mean one of two things:
(1) The variable is not defined or accessible to the function. This is not the case in your situation as you have shown that the variable is accessible outside of the scope of your conditional. Or,
(2) The render function has not yet evaluated your variable by the time it gets to rendering. This is likely the case in your situation.
I think the second situation is due to a race condition, and I leave the details of that to someone with better knowledge of the React source code to explain.
An easy way to avoid this situation is to update your conditional with the specific keys you will need. This will help prevent the render function from short circuiting your component. This way, your data will not be called upon if a render proceeds without it being there.
Given in your situation that you are drawing on orgList.items in your map function, let's update the code as per the following:
function Items ({orgList}) {
console.log(orgList.items)
return (
<>
{orgList?.items? {/* <------ update this line as seen here */}
<div>
{orgList.items.map((item, i) => (
<Card key={i} className= "w-100 m-2">
<CardHeader className="d-flex">
<p>Hello</p>
</CardHeader>
<CardBody>
<p>The Body</p>
<Row>
<Col md="5">
<dl>
<dt>GroupID</dt>
<dd>{item.GroupID}</dd>
<dt>Name</dt>
<dd>{item.name}</dd>
</dl>
</Col>
</Row>
</CardBody>
</Card>
))}
</div>
:
<div>
<PageLoadSpinner inProgress={inProgress} />
</div>
}
</>
)
}
export default Items;

React Reveal not working for array of data

Can't use React Reveal on array of data with .map() to produce effect from documentation.
https://www.react-reveal.com/examples/common/
Their documentation gives a nice example
<Fade left cascade>
<div>
<h2>React Reveal</h2>
<h2>React Reveal</h2>
<h2>React Reveal</h2>
</div>
</Fade>
I want to produce the same CASCADE effect with my data
<React.Fragment>
{projects.filter(project => project.category === category)
.map((project, index) => {
return (
<ProjectThumb key={index} project={project}
showDetails={showDetails}/>
)
})}
</React.Fragment>
The effect I'm getting is that the entire ProjectThumb component list fades in in one group, I need them to fade in individually and as i scroll. Thanks in advance.
Pass react-reveal props to your React component. It will work.
<Fade left cascade>
<div>
{
projects
.filter(project => project.category === category)
.map((project, index) => (
<ProjectThumb key={index} project={project} showDetails={showDetails} />
))
}
</div>
</Fade>
In your ProjectThumb.js
const ProjectThumb = props => {
return <Whatever {...props}>{...}</Whatever>
}

Resources