Re-Rendering a component - reactjs

I'm doing a simple todo list using React. What I fail to do is to remove an item once I click on the button.
However, if I click delete and then add a new item, it's working, but only if I add a new todo.
Edit:I've edited the post and added the parent componenet of AddMission.
import React,{useState}from 'react';
import { Button } from '../UI/Button/Button';
import Card from '../UI/Card/Card';
import classes from '../toDo/AddMission.module.css'
const AddMission = (props) => {
const [done,setDone]=useState(true);
const doneHandler=(m)=>{
m.isDeleted=true;
}
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li className={mission.isDeleted?classes.done:''} key={mission.id}>
{mission.mission1}
<div className={classes.btn2}>
<Button onClick={()=>{
doneHandler(mission)
}} className={classes.btn}>Done</Button>
</div>
</li>
)) }
</ul>
</Card>
);
};
export default AddMission;
import './App.css';
import React,{useState} from 'react';
import { Mission } from './components/toDo/Mission';
import AddMission from './components/toDo/AddMission';
function App() {
const [mission,setMission]=useState([]);
const [isEmpty,setIsEmpty]=useState(true);
const addMissionHandler = (miss) =>{
setIsEmpty(false);
setMission((prevMission)=>{
return[
...prevMission,
{mission1:miss,isDeleted:false,id:Math.random().toString()},
];
});
};
return (
<div className="">
<div className="App">
<Mission onAddMission={addMissionHandler}/>
{isEmpty?<h1 className="header-title">Start Your Day!</h1>:(<AddMission isVisible={mission.isDeleted} missions={mission}/>)}
</div>
</div>
);
}

const doneHandler=(m)=>{
m.isDeleted=true;
}
This is what is causing your issue, you are mutating an object directly instead of moving this edit up into the parent. In react we don't directly mutate objects because it causes side-effects such as the issue you are having, a component should only re-render when its props change and in your case you aren't changing missions, you are only changing a single object you passed in to your handler.
Because you haven't included the code which is passing in the missions props, I can't give you a very specific solution, but you need to pass something like an onChange prop into <AddMission /> so that you can pass your edited mission back.
You will also need to change your function to something like this...
const doneHandler = (m) =>{
props.onChange({
...m,
isDeleted: true,
});
}
And in your parent component you'll then need to edit the missions variable so when it is passed back in a proper re-render is called with the changed data.

Like others have mentioned it is because you are not changing any state, react will only re-render once state has been modified.
Perhaps you could do something like the below and create an array that logs all of the ids of the done missions?
I'm suggesting that way as it looks like you are styling the list items to look done, rather than filtering them out before mapping.
import React, { useState } from "react";
import { Button } from "../UI/Button/Button";
import Card from "../UI/Card/Card";
import classes from "../toDo/AddMission.module.css";
const AddMission = (props) => {
const [doneMissions, setDoneMissions] = useState([]);
return (
<Card className={classes.users}>
<ul>
{props.missions.map((mission) => (
<li
className={
doneMissions.includes(mission.id)
? classes.done
: ""
}
key={mission.id}
>
{mission.mission1}
<div className={classes.btn2}>
<Button
onClick={() => {
setDoneMissions((prevState) => {
return [...prevState, mission.id];
});
}}
className={classes.btn}
>
Done
</Button>
</div>
</li>
))}
</ul>
</Card>
);
};
export default AddMission;
Hope that helps a bit!

m.isDeleted = true;
m is mutated, so React has no way of knowing that the state has changed.
Pass a function as a prop from the parent component that allows you to update the missions state.
<Button
onClick={() => {
props.deleteMission(mission.id);
}}
className={classes.btn}
>
Done
</Button>;
In the parent component:
const deleteMission = (missionId) => {
setMissions(prevMissions => prevMissions.map(mission => mission.id === missionId ? {...mission, isDeleted: true} : mission))
}
<AddMission missions={mission} deleteMission={deleteMission} />

Related

Is there a way of supplying a void function and supplying the innards of function when invoked in another place?

Basically what I'm trying to achieve is that, let's say that I have a function in a parent component in React. In a child component i want to do some calculation like lets say distance to nearest elements containing box. But I want to invoke this method via a button click or something in the parent. I have refs pointing to my child component and I can achieve this if I add the click to these children. But since I don't have acces to the parent method; how am I to achieve such behaviour?
Here's my child component:
import { FC, useRef } from 'react'
import styles from './Card.module.scss'
const Card: FC<CardProps> = ({ image, title, info }) => {
const cardRef = useRef<HTMLDivElement>(null)
const scroll = (offset: number) => {
if (cardRef.current) {
cardRef.current.scrollLeft += offset
}
}
return (
<div className={styles.card} ref={cardRef}>
<img src={image} alt={image.split('.')[0]} height={170} width={96} />
<div className={styles['card-info-container']}>
<h3>{title}</h3>
<p>{info}</p>
<img src="Chevron.svg" alt="Chevron" />
</div>
</div>
)
}
export default Card
interface CardProps {
image: string
title: string
info: string
}
and here is my parent component:
import Card from '../Card/Card'
import styles from './Campaigns.module.scss'
import { campaignData } from '../../mockdata/campaigndata'
const Campaigns = () => {
const scroll = (offset: number) => {}
return (
<section className={styles.campaigns}>
<h3></h3>
<div className={styles['cards-container']}>
{campaignData.map(({ image, title, description }, index) => (
<Card image={image} title={title} info={description} key={index} />
))}
</div>
</section>
)
}
export default Campaigns
I think I've found a solution but it's not really an elegant one in my opinion. Instead of agonizing over creating refs directly in the child I've opted to create an array of refs from the length of the content using React's createRef fucntion here's what I have in my parent component.
import Card from '../Card/Card'
import styles from './Campaigns.module.scss'
import { campaignData } from '../../mockdata/campaigndata'
import { createRef } from 'react'
const Campaigns = () => {
const refs = campaignData.map(() => createRef())
console.log(refs)
return (
<section className={styles.campaigns}>
<h3></h3>
<div className={styles['cards-container']}>
{campaignData.map(({ image, title, description }, index) => (
<Card
image={image}
title={title}
info={description}
key={index}
forwardRef={refs[index]}
/>
))}
</div>
</section>
)
}
export default Campaigns
Now I can reference every element as a seperate entity and without the hassle

React how to update array useState on button click

I have seen a hundred videos and read about this, and it still doesn't make sense to me. I'm trying to update an array on a button click but when I log out the items array, it's one behind, meaning it's not reactive and gets added on the next paint. Also, I wrapped my Employee component inside a div only to be able to add an 'onClick' to it. Ideally, I'd like the onClick to be on the Employee component itself, but it doesn't work. Thanks.
import React, {ReactElement, useState } from "react";
import {TextContainer, Text } from "react-md";
import model from '../Models'
import Employee from './Employee/Employee'
import styles from '../home.module.scss'
export default function Home(): ReactElement {
const [items, setItems] = useState<Array<any>>([]);
const addItem = (val:any) => {
setItems([...items, val ])
console.log('items :', items)
}
return (
<div className='center'>
<TextContainer className='center'>
<Text type='headline-4' style={{color: 'white'}}>Employee List</Text>
</TextContainer>
<section className={styles.emp_list}>
{model.map((props, index) =><div key={index} onClick={() => addItem(props.name)}><Employee key={index} name={props.name} role={props.role} markets={props.markets} image={props.image}/></div>)}
</section>
</div>
)}
And my Employee component:
import React from "react";
import {MediaContainer} from "#react-md/media";
import styles from './employee.module.scss'
import { Card, CardContent, CardHeader} from "react-md";
function Employee(props: any) {
return (
<Card className={styles.emp_card}>
<CardHeader>
<div className={styles.emp_text}>
<p key={props.name}>Name: {props.name}</p>
<p key={props.role}>Title: {props.role}</p>
<p key={props.markets}>Markets: {props.markets[0]} {props.markets[1] && <span>and {props.markets[1]}</span>}</p>
</div>
</CardHeader>
<CardContent>
<MediaContainer>
<img key={props.image} src={props.image} alt="employee"/>
</MediaContainer>
</CardContent>
</Card>
)
}
export default Employee;
I don't think the documentation is clear enough on this, but if you want to setState using the previous state, then you should pass a function to useState. For example:
const addItem = (val:any) => {
setItems(prevItems => [...prevItems, val ])
}
https://reactjs.org/docs/hooks-reference.html#functional-updates

Set each active className on react Tab components

I'm trying to add active class on each tabtitle so that it can have some styles when clicked. but I have no idea how to add active class on this components, please tell me some solutions, I really appreciate all the help.
App.tsx
import React from "react"
import Tabs from "../Tabs"
import Tab from "../Tabs/Tab"
function App() {
return (
<Tabs>
<Tab title="Lemon">Lemon is yellow</Tab>
<Tab title="Strawberry">Strawberry is red</Tab>
<Tab title="Pear">Pear is green</Tab>
</Tabs>
)
}
Tabs.tsx
import React, { ReactElement, useState } from "react"
import TabTitle from "./TabTitle"
type Props = {
children: ReactElement[]
}
const Tabs: React.FC<Props> = ({ children }) => {
const [selectedTab, setSelectedTab] = useState(0)
return (
<div>
<ul>
{children.map((item, index) => (
<TabTitle
key={index}
title={item.props.title}
index={index}
setSelectedTab={setSelectedTab}
/>
))}
</ul>
{children[selectedTab]}
</div>
)
}
export default Tabs
Tabtitle.tsx
import React, { useCallback } from "react"
type Props = {
title: string
index: number
setSelectedTab: (index: number) => void
}
const TabTitle: React.FC<Props> = ({ title, setSelectedTab, index }) => {
const onClick = useCallback(() => {
setSelectedTab(index)
}, [setSelectedTab, index])
return (
<li>
<button onClick={onClick}>{title}</button>
</li>
)
}
export default TabTitle
Tab.tsx
import React from 'react'
type Props = {
title: string
}
const Tab: React.FC<Props> = ({ children }) => {
return <div>{children}</div>
}
export default Tab
Here is the source.
https://medium.com/weekly-webtips/create-basic-tabs-component-react-typescript-231a2327f7b6
So personally what I would do is:
<TabTitle
key={index}
title={item.props.title}
click={() => setSelectedTab(index)}
selected = (selectedTab === index)
/>
and
const TabTitle: React.FC<Props> = ({ key, title, click, selected}) => {
return (
<li className={selected ? "shiny" : "not-shiny"} key={key}>
<button onClick={click}>{title}</button>
</li>
)
}
That way we don't need to worry about passing the index from tabTitle ALL the way back up, we can simply hand it a function to trigger with the variables pre-filled (we already have access to them within the map method!).
As for actually passing the info about whether or not the tab is selected, we can simply check in the map method whether the current index is = the one held in state. If this evaluates to true we give the tabtitle one class, and if not we give it another.
Note there are lots of different ways to achieve this, this is just the first one that came to mind.
If you wanted to share state information between many different components lower in the tree you might want to check out using React Context providers and the useContext hook.
Also I'm not sure item.props works like that?? I might be wrong though? Maybe someone else will confirm.
Hope this is helpful.
import React from "react"
import Tabs from "../Tabs"
import Tab from "../Tabs/Tab"
function App() {
return ( <Tabs>
<Tab title="Lemon">Lemon is yellow</Tab>
<Tab title="Strawberry">Strawberry is red</Tab>
<Tab title="Pear">Pear is green</Tab>
</Tabs> )
}

React - Importing the JSON object only once

The following simple React component is importing a JSON file (data.js) as an object and list the items inside it.
List.js
import React from 'react'
import jsonResponse from './data'
function ZooList ({ setID }) {
const setURL = (e) => {
window.history.pushState(null, null, '/' + e)
setID(e)
}
const list = jsonResponse.animals.map((item) => {
return (
<li key={item.id}>
<div>
<img
src={item.image_url}
alt={item.name}
onClick={() => setID(item.id)}
/>
<h3>{item.name}</h3>
<p>
<b>Distribution</b>: {item.distribution}
</p>
<button onClick={() => setURL(item.id)}>More...</button>
</div>
</li>
)
})
return (
<ul>
{list}
</ul>
)
}
export default List
Now in the above page, if you click on button "More...", it calls another React component called Tile.js as fallow:
Tile.js
import React from 'react'
import jsonResponse from './data'
function Tile ({ setID, newID }) {
const clearURL = (e) => {
window.history.pushState(null, null, '/')
setID(null)
}
return (
<div>
<div>
<img
src={jsonResponse.animals[newID].image_url}
alt={jsonResponse.animals[newID].name}
/>
<h2>{jsonResponse.animals[newID].name}</h2>
<p><b>Distribution</b>: {jsonResponse.animals[newID].distribution}</p>
<StyledParagraph>{jsonResponse.animals[newID].details.long}</StyledParagraph>
<button onClick={() => clearURL(null)}>Back to overview</button>
</div>
</div>
)
}
export default Tile
The problem is that the second component is also importing the JSON file (data.js).
How can I avoid importing the data.js twice?
Generally, what would be a better way to write this app?
Imports are cached, so if you return directly a JSON with import jsonResponse from './data', the first component will import it, while the second will get it from import cache;
You can try for example, to export an instance of a class, change one of its property and then check that property in another component that make use of that import.
A ready-to-pick and very common usage example of that cache is the configureStore of react-boilerplate: it exports the store instance so whatever component import it will refer to the same store.

Is there a way in React Javascript to pass props and use it in external import?

I want to pass props from one component to another, and use it in the second one for an import above the component declaration
This is for using the same component, with no need to create it 4 times, every time with another SVG.
I'm using React, Javascript, Webpack, babel.
I'm also using svgr/webpack to create a component from an SVG picture, and it's crucial for me to use SVG not < img >.
import React from 'react';
import RightNavItem from './right_nav_item';
const RightNav = ({navitems}) => {
const rightNavItems = navitems.map( (item) => {
return <RightNavItem name={ item }/>
});
return(
<div className="rightnav">
{rightNavItems}
</div>
);
};
.
export default RightNav;
import React from 'react';
const RightNavItem = ({ name }) => {
const svgpath = `../../../../resources/img/navbar/${name}.svg`;
return(
<div>
<img src={ svgpath } style={{height: '25px'}}/>
<span>{ name }</span>
</div>
);
};
export default RightNavItem;
And I want to achieve being able to do this:
import React from 'react';
import SvgPicture from '../../../../resources/img/navbar/{name}.svg';
const RightNavItem = ({ name }) => {
return(
<div>
<SvgPicture />
<span>{ name }</span>
</div>
);
};
export default RightNavItem;
.
Ok so I went back and implemented the whole thing on my local app to get exactly what you need. I am editing my original answer. Hope this solves your issue.
The parent:
import React from 'react';
import { ReactComponent as svg } from 'assets/img/free_sample.svg';
import RightNavItem from './RightNavItem';
const LOGOS = [
{ name: 'home', svg },
{ name: 'home', svg },
];
const RightNav = () => (
<div>
{LOGOS.map(logo => (
<RightNavItem name={logo.name}>
<logo.svg />
</RightNavItem>
))}
</div>
);
export default RightNav;
The child:
import React from 'react';
const RightNavItem = ({ name, children }) => (
<div>
{children}
<span>{name}</span>
</div>
);
export default RightNavItem;
You don't need to import the svg as I did, if you are able to use svg as a component in your webpack config then continue to do what you were doing before.
I managed to do it in a kind of ugly way, but it works.
The problem is if I have more than 4 items, then using it without the map() function can be really annoying.
I used {props.children}, and instead of using map(), I added the 4 times, each with different 'SVG' component child and different props 'name', that way the component only gets initialized at the RightNavItem level.
IF SOMEONE KNOWS how can I use this with the map() function, It'll help a lot!
Thanks to everyone who helped!
For example:
const RightNav = (props) => {
return(
<div className = "rightnav">
<RightNavItem name = {home}>
<HomeSVG />
</RightNavItem>
<RightNavItem name = {profile}>
<ProfileSVG />
</RightNavItem>
.
.
.
</div>
);
};
And in the RightNavItem:
const RightNavItem = (props) => {
return(
<div>
{props.children}
<span>{ props.name }</span>
</div>
);
};

Resources