Passing states between components in React - javascript

I am wondering the best way to tackle this problem.
I intend to create a state in the parent component of my application, pass the set state to a component that holds the button (sibling A), and the value to another component where it will be displayed and updated (sibling B). I have a rough idea of how to do this, but ultimately I am lost and am seeking direction. Thank you!
My parent component is as follows
//playlist state
const [playlistItem, setPlaylistItem] = useState()
const mainCards = Data.map(card => {
return(
<MainCard
key={card.id}
id={card.id}
image={card.url}
title={card.title}
playbutton={card.playbutton}
addbutton={card.addbutton}
playlistState={setPlaylistItem}
/>
)
})
const sideCards = SideData.map(card => {
return(
<SideCard
image={card.sideurl}
key={card.id}
title={card.sidetitle}
playbutton={card.playbutton}
addbutton={card.addbutton}
playlistItem={playlistItem}
/>
)
})
return (
<div className="App">
{console.log("main cards" + mainCards[0])}
{console.log("side cards" + sideCards.sidetitle)}
<Navbar />
<Header />
<Playlist />
<CardContainer />
<div className="maincards">
{mainCards}
</div>
<div className="sidecards">
{sideCards}
</div>
</div>
)
}
Sibling A
const handleAdd = (id) => {
console.log(id)
}
return(
<div>
<div className="mainCardObject">
<div className="cardObj">
<img src={props.image} className ="mainCardImage"/>
<img src={props.playbutton} className="playbutton"/>
<img src={props.addbutton} onClick={() => handleAdd(props.id)} className="addbutton" />
</div>
</div>
</div>
)
}
Sibling B
function Playlist(props){
return(
<div className="playlistContainer">
<ul>
<li></li>
</ul>
</div>
)
}

You can pass both getter and setter as parameters, and use them normally in your components:
Sibling A
const handleAdd = (id) => {
//your logic
props.playlistState(prevValue => [])
}
Sibling B
return (
<div className="playlistContainer">
<ul>
<li>
{props.playlistItem}
</li>
</ul>
</div>
)
You can also create a function in your parent component, using set state, and pass this function as a parameter to your component:
Parent
const addPlayListItem = (playListItem) => {
setPlaylistItem(prev => [...prev, playListItem])
}
return (
<SiblingA
addPlayListItem={addPlayListItem}
/>
)
Sibling A
function MainCard(props) {
const handleAdd = () => {
//your logic
props.addPlayListItem({ name: 'rock', title: 'Foo Fighters', year: 2011 })
}
return (
<div>
<img src={props.addbutton} onClick={handleAdd} className="addbutton" />
</div>
)
}

Sibling A:
const handleAdd = (id) => {
props.playlistState((prev) => ([...prev, id]))
}

Related

Prop is an empty object in React child

I'm trying to add a search bar to a parent component.
All the logic is working fine in the console. With every character that is typed in the search field I get fewer results.
I try to pass it to a child component to render the card(s) result, but I get a blank card: I can not see data passed.
Parent Component <AllAssets>
class AllAssets extends Component {
state = {
cards: [],
searchField: '',
}
async componentDidMount() {
const { data } = await cardService.getAllCards();
if (data.length > 0) this.setState({ cards: data });
}
addToFavorites = (cardId, userId) => {
saveToFavorites(cardId, userId)
toast.error("The asset was added to your favorites.")
}
render() {
const { cards, searchField } = this.state;
const user = getCurrentUser();
const filteredAssets = cards.filter(card => (
card.assetName.toLowerCase().includes(searchField.toLowerCase())));
console.log(filteredAssets);
return (
<div className="container">
<SearchBox placeholder={"Enter asset name..."}
handleChange={(e) => this.setState({ searchField: e.target.value })}
/>
<PageHeader>Assets available for rent</PageHeader>
<div className="row">
<div className="col-12 mt-4">
{cards.length > 0 && <p>you can also add specific assets to your favorites and get back to them later...</p>}
</div>
</div>
<div className="row">
{!!filteredAssets.length ? filteredAssets.map(filteredAsset => <SearchResult addToFavorites={this.addToFavorites} filteredAsset={filteredAsset} user={user} key={filteredAsset._id} />) :
cards.map(card => <CardPublic addToFavorites={this.addToFavorites} card={card} user={user} key={card._id} />)
}
</div>
</div >
);
}
}
export default AllAssets;
Child Component <SearchResult>
const SearchResult = (addToFavorites, filteredAsset, card, user) => {
return (
<div className="col-lg-4 mb-3 d-flex align-items-stretch">
<div className="card ">
<img
className="card-img-top "
src={filteredAsset.assetImage}
width=""
alt={filteredAsset.assetName}
/>
<div className="card-body d-flex flex-column">
<h5 className="card-title">{filteredAsset.assetName}</h5>
<p className="card-text">{filteredAsset.assetDescription}</p>
<p className="card-text border-top pt-2">
<b>Tel: </b>
{filteredAsset.assetPhone}
<br />
<b>Address: </b>
{filteredAsset.assetAddress}
</p>
<p>
<i className="far fa-heart text-danger me-2"></i>
<Link to="#" className="text-danger" onClick={() => addToFavorites(card._id, user._id)}>Add to favorites</Link>
</p>
</div>
</div>
</div>
);
}
export default SearchResult;
When I console.log(filteredAsset) in <SearchResult> I get an empty object. What am I doing wrong?
This line is incorrect:
const SearchResult = (addToFavorites, filteredAsset, card, user) => {
You are passing in positional arguments, not named props. Do this instead:
const SearchResult = ({addToFavorites, filteredAsset, card, user}) => {
In your original code, React attaches all of your props as fields on the first argument. So they would be accessible in the child, but not in the way you're trying to access them. Try logging out the values of each of the arguments in the child, if you're curious to see what happens.
The corrected version passes in a single object with field names that match the names of your props. It's shorthand that's equivalent to:
const SearchResult = (
{
addToFavorites: addToFavorites,
filteredAsset: filteredAsset,
card: card,
user: user,
}
) => {

Cannot access updated value

Hello Am i need of some assistance here am stuck,tried to serach for solution on SO but cant find a solution ,Am learning react so decided to create a todo app.However i have been stuck when it comes to crossing off completed tasks. when i add a task i have a variable called tasks which basically is an object containing all the tasks in the following fomart:
enter image description here
Inside my app i have the following snippet of code
const FILTER_MAP = {
all_items:() => true,
Active: task => !task.completed,
Completed: task => task.completed
};
const FILTER_NAMES = Object.keys(FILTER_MAP);
function App(props) {
const [tasks, setTasks] = useState(props.tasks);
const [filter, setFilter] = useState('all_items');
function addTask(name) {
const newTask = {id: "todo-" + nanoid(),name: name, completed:false}
setTasks([...tasks, newTask]);
}
function toggleTaskCompleted(id) {
const updatedTasks = tasks.map(task => {
// if this task has the same ID as the edited task
if(id === task.id) {
// use object spread to make a new object
// whose `completed` prop has been inverted
return {...task,completed: !task.completed}
}
return task;
})
setTasks(updatedTasks);
}
function clearCompletedTasks(){
const completed = tasks.filter(task => task.completed === false)
setTasks(completed);
}
const taskList = tasks
.filter(FILTER_MAP[filter])
.map(task => (
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
toggleTaskCompleted={toggleTaskCompleted}
clearCompletedTasks={clearCompletedTasks}
/>
));
const filterList = FILTER_NAMES.map(name => (
<FilterButton
key={name}
name={name}
isPressed={name === filter}
setFilter={setFilter}
/>
));
const tasksNoun = taskList.length !== 1 ? 'items' : 'items';
const headingText = `${taskList.length} ${tasksNoun} left`;
return (
<div>
<header>
<h1 id="pageTitle">Todo</h1>
<div className="container">
<div className="main">
<section className="tasklist">
<Form addTask={addTask}/>
<ul className="listItems">
<li className={`todo-item ${ tasks.completed ? "completed" :'' }`} >{taskList}</li>
</ul>
</section>
</div>
<div className="footer">
<div className="footer-list">
<ul>
<li id="items">{headingText}</li>
<li id="all-items">{filterList[0]}</li>
<li id="active">{filterList[1]}</li>
<li id="completed">{filterList[2]}</li>
<li id="clear" onClick={clearCompletedTasks}>XClear Completed</li>
</ul>
</div>
</div>
</div>
</header>
</div>
);
}
export default App;
Todo
export default function Todo(props){
return (
<li>
<div className="todo">
<label htmlFor={props.id}>
{props.name}
</label>
<input id={props.id}
type="checkbox"
defaultChecked={props.completed}
onChange={() =>
props.toggleTaskCompleted(props.id)}
/>
</div>
</li>
);
}
Problem
When i click on checkbox to indicate the task is done i can see that the value in completed is updating to true as show below
enter image description here
However when i try to evaluate and apply the following css its not working.
<li className={`todo-item ${ tasks.completed ? "completed" :'' }`} >{taskList}</li>
How can i implement this so that "completed" style class is used when the state of completed value changes to true.Thank you
The problem is that you use a const to store an array which gets changed.
The correct thing to do would be the following:
<ul>{tasks
.filter(FILTER_MAP[filter])
.map(task => (
<li class={`todo-item ${ task.completed ? "completed" :'' }`}>
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
toggleTaskCompleted={toggleTaskCompleted}
clearCompletedTasks={clearCompletedTasks}
/></li>
))}
</ul>
This looks like a case of stale state. When your function closes over an old state (think closures), then whenever called in future it has access to the old state itself. Similar question
Check if moving your {tasksList} code inside the return method helps:
<li className={`todo-item ${ tasks.completed ? "completed" :'' }`} >{
tasks
.filter(FILTER_MAP[filter])
.map(task => (
<Todo
id={task.id}
name={task.name}
completed={task.completed}
key={task.id}
toggleTaskCompleted={toggleTaskCompleted}
clearCompletedTasks={clearCompletedTasks}
/>
));}</li>

Dynamic way to handle conditional render

Coming from jQuery into the world of React, i'm working on a problem that's asking to display details from a JSON response using a show/hide button. My current implementation just has me using CSS to show/hide a sibling div. Is there a way to instead dynamically render the specific component instead of loading all of them on the page and using CSS to control their display?
Component:
<ul>
{countries.map(country =>
<li key={country.Countriesalpha2Code}>
{country.name} <button onClick={showDetails}>show</button>
<div style={{display: 'none'}}>
<Details country={country} />
</div>
</li>
)}
</ul>
Function:
const showDetails = (event) => {
let target = event.target
let sibling = target.nextSibling
if(sibling.style.display == 'none'){
sibling.style.display = 'block'
} else {
sibling.style.display = 'none'
}
if(target.textContent == 'show') {
target.textContent = 'hide'
} else {
target.textContent = 'show'
}
}
You definitely shouldn't be modifying element states in callbacks. Instead, you could make the expanded/hidden state of each element a state atom, like so:
const CountryDetail = ({ country }) => {
const [expanded, setExpanded] = React.useState(false);
const toggleExpanded = React.useCallback(() => setExpanded((expanded) => !expanded), []);
return (
<li>
{country.name}
<button onClick={toggleExpanded}>show</button>
{expanded ? <Details country={country} /> : null}
</li>
);
};
const Countries = () => (
<ul>
{countries.map((country) => (
<CountryDetail key={country.Countriesalpha2Code} country={country} />
))}
</ul>
);

How to refactor react component separate for rendering and logic?

I have react component:
function FavoritesListItem({ merchant, config, isFavorited }) {
const {
name, id, logoUrls = {}, offersCount, rebate, showRebate,
} = merchant;
const { rebateOptions } = config;
return (
const renderActiveMerchant = () => (
<div
className="mn_favoriteMerchant"
data-merchant-id={id}
data-merchant-name={name}
role="listitem"
data-test="favorite-merchant"
>
<div className="mn_favoriteMerchantInner">
<MerchantExperienceLink
className="mn_favoriteMerchantLink"
merchant={merchant}
title={`Opens merchant detail page at ${name}`}
>
<FavoriteIcon
merchantId={id}
merchantName={name}
labelUnfavorite={`Remove ${name} from Favorites list`}
showSpinner={!isFavorited}
/>
<div className="mn_logo"><img data-test="favorited-merchant-logo" src={logoUrls._120x60} alt={name} /></div>
<p className="mn_offersCount" data-test="favorited-merchant-offers-count">{offersCount} offers available </p>
</MerchantExperienceLink>
{rebate && (
<MerchantClickUrlLink className="mn_favoriteMerchantRebateLink" merchant={merchant}>
<div className="mn_rebate">
{showRebate
? <MerchantRebate {...rebate} {...rebateOptions} />
: <MerchantNoRebateLabel />}
? <MerchantRebate {...rebate} {...rebateOptions} />
: <MerchantNoRebateLabel />}
</div>
</MerchantClickUrlLink>
)}
</div>
</div>
);
const renderDeactivatedMerchant = () => (
<div
className="mn_favoriteMerchant"
data-merchant-id={id}
data-merchant-name={name}
role="listitem"
data-test="favorite-merchant"
>
<div className="mn_favoriteMerchantInner">
<MerchantExperienceLink
className="mn_favoriteMerchantLink"
merchant={merchant}
title={`Opens merchant detail page at ${name}`}
>
<FavoriteIcon
merchantId={id}
merchantName={name}
labelUnfavorite={`Remove ${name} from Favorites list`}
showSpinner={!isFavorited}
/>
<div className="mn_logo mn_noRebateMerchantLogo">
<img data-test="favorited-merchant-logo" src={logoUrls._120x60} alt={name} />
</div>
{rebate && (
<div className="mn_rebate mn_deactivatedRebate">
{
showRebate
? <MerchantNoRebateLabel />
: <MerchantRebate {...rebate} />
}
</div>
)}
</MerchantExperienceLink>
</div>
</div>
);
return (
merchant.type === 'Deactivated Merchant' ? renderDeactivatedMerchant() : renderActiveMerchant()
);
}
const mapStateToProps = () => {
const selectFavoriteByMerchantId = makeSelectFavoriteByMerchantId();
return (state, { merchant }) => ({
isFavorited: selectFavoriteByMerchantId(state, merchant.id),
});
};
export default connect(mapStateToProps)(FavoritesListItem);
and need to refactor it to 2 separate components which will be just render renderDeactivatedMerchant and renderActiveMerchant. All other logic should be in this component FavoritesListItem
So I created components this way:
export class FavoritesListItemDeactivatedMerchant extends Component ({ merchant, config, isFavorited }) {
render() {
const { merchant, config, isFavorited } = this.props;
const {
name, id, logoUrls = {}, rebate, showRebate,
} = merchant;
const { rebateOptions } = config;
return (
<div
className="mn_favoriteMerchant"
data-merchant-id={id}
data-merchant-name={name}
role="listitem"
data-test="favorite-merchant"
>
<div className="mn_favoriteMerchantInner">
<MerchantExperienceLink
className="mn_favoriteMerchantLink"
merchant={merchant}
title={`Opens merchant detail page at ${name}`}
>
<FavoriteIcon
merchantId={id}
merchantName={name}
labelUnfavorite={`Remove ${name} from Favorites list`}
showSpinner={!isFavorited}
/>
<div className="mn_logo mn_noRebateMerchantLogo">
<img data-test="favorited-merchant-logo" src={logoUrls._120x60} alt={name} />
</div>
{rebate && (
<div className="mn_rebate mn_deactivatedRebate">
{
showRebate
? <MerchantNoRebateLabel />
: <MerchantRebate {...rebate} />
}
</div>
)}
</MerchantExperienceLink>
</div>
</div>
);
}
}
const mapStateToProps = () => {
const selectFavoriteByMerchantId = makeSelectFavoriteByMerchantId();
return (state, { merchant }) => ({
isFavorited: selectFavoriteByMerchantId(state, merchant.id),
});
};
export default connect(mapStateToProps)(FavoritesListItemDeactivatedMerchant);
Project builded without errors and also no errors in console. But it's not render this component in browser. What I'm doing wrong? Please, help.
You have confused the syntax for function and class components and created a weird mash-up that combines both:
export class FavoritesListItemDeactivatedMerchant extends Component ({ merchant, config, isFavorited }) {
Those props make no sense in a class! I think you meant to write this:
export const FavoritesListItemDeactivatedMerchant = ({ merchant, config, isFavorited }) => {
or
export function FavoritesListItemDeactivatedMerchant({ merchant, config, isFavorited }) {
Personally I think you can improve this code by having a shared RenderMerchant with a prop isDeactivated instead of having separate components for active and deactivated. There is a lot of repeated code between the two cases which you want to avoid.

Conditional rendering on React.js

render() {
const tableStyle = this.getTableStyle();
const tableSettings = this.getTableSettings();
return (
<div style={tables}>
<TablePosition
contextMenuOn={true}
step={this.props.step}
pdfData={this.props.pdfData}
tableSettings={tableSettings}
tableStyle={tableStyle}
fileName={this.state.fileName}
tableSize={this.getTableSize()}
tableOffset={this.state.tableOffset}
desiredWidth={700}
updateXOffset={x => this.updateXOffset(x)}
updateYOffset={y => this.updateYOffset(y)}
markTable={() => this.markTable()}
setOutputLabels={(row, col, val) => this.setOuputLabels(row, col, val)}
/>
</div>
);
if (!this.props.isThirdStep) {
return (
<div>
<div style={sideBySide}>
<PDFViewer
isThirdStep={this.props.isThirdStep}
paginationCallback={this.handlePageChange}
pdfData={this.state.pdfData}
desiredWidth={600}
selectedPage={this.props.savedPageNo}
/>
</div>
</div>
);
} else {
return (
<div>
<ReferenceMenu />
</div>
);
}
}
In my component's render, I try to render several components based on certain conditions.
So, basically, the TablePoisition always stays there, and the PDFViewer and ReferenceMenu renders conditionally.
However, what I see on both conditions is only the TablePosition component.
Is this not supposed to work?
As explained since you want to combine two components you should change your render logic. One component will be sit there always and the other one will be rendered conditionally. So, you need to render that last component with the sticky one in the same return. I would do something like this:
renderPDFViewer = () => (
<div>
<div style={sideBySide}>
<PDFViewer
isThirdStep={this.props.isThirdStep}
paginationCallback={this.handlePageChange}
pdfData={this.state.pdfData}
desiredWidth={600}
selectedPage={this.props.savedPageNo}
/>
</div>
</div>
);
render() {
const tableStyle = this.getTableStyle();
const tableSettings = this.getTableSettings();
return (
<div>
<div style={tables}>
<TablePosition
contextMenuOn={true}
step={this.props.step}
pdfData={this.props.pdfData}
tableSettings={tableSettings}
tableStyle={tableStyle}
fileName={this.state.fileName}
tableSize={this.getTableSize()}
tableOffset={this.state.tableOffset}
desiredWidth={700}
updateXOffset={x => this.updateXOffset(x)}
updateYOffset={y => this.updateYOffset(y)}
markTable={() => this.markTable()}
setOutputLabels={(row, col, val) => this.setOuputLabels(row, col, val)}
/>
</div>
{
!this.props.isThirdStep
? this.renderPDFViewer()
: ( <div><ReferenceMenu /></div> )
}
</div>
);
}
You need to place your conditional renders inside variables or something similar.
var conditionContent1 = null;
var conditionContent2 = null;
if(condition1){
conditionContent1 = <div>conditional content 1</div>;
}
if(condition2){
conditionContent2 = <div>conditional content 2</div>;
}
return (
<div id="wrapper">
<div>
content
</div>
{conditionContent1}
{conditionContent2}
</div>
);
I added a wrapper div; because, I believe render's return doesn't like having multiple root elements.
If the variables are null; then, it won't affect the overall render.

Categories

Resources