React - setting one item from parent's state as child's state - javascript

I am a React newbie and trying to learn it by building a simple quote generator where a quote is generated depending on the mood a user selects. The App component holds the state: quotes and moods (where each element is a nested object) and its children are Mood components.
Now, the state of the App component consists of four moods and what I would like to happen is: when a user clicks a button inside the Mood component, s/he is redirected to that mood's page and the Mood component's state is set to that particular mood.
The solution I worked out by myself is very crude and I'm looking for a way to make it more elegant/functional.
Here is the moods object that is the App's state:
const moods = {
mood1: {
type: 'upset',
image: 'abc.png',
},
mood2: {
type: 'unmotivated',
image: 'abc.png',
},
mood3: {
type: 'anxious',
image: 'abc.png',
},
}
the App component:
state ={
moods: moods,
}
render(){
return (
<div className="Container">
<ul className='moods'>
{
Object.keys(this.state.moods).map(key => <Mood
moodsData = {this.state.moods}
key={key}
indexKey = {key}
index={this.state.moods[key].type}
details={this.state.moods[key]}
/>)
}
</ul>
</div>
);}}
And this is how far I got inside the Mood component, where the onClick function on the button is:
handleClick = (e) => {
this.setState({moods: e.target.value});
}
I will be grateful for any pointers/suggestions! Spent so many hours on this I feel like my brain doesn't accept any more Youtube tutorials/Medium articles.

Well, the first thing i notice is that you are trying to use map on a javascript object instead of an array, this could bring some problems when using some functions, so i advice to make it an array.
If you just have one Mood component and based on the mood type it receives change its style, it doesn't actually need to manage state from inside, you can just pass the props to the Mood component and work around what props receives.
For example:
Moods as an array:
const moods = [
{
type: 'upset',
image: 'abc.png',
},
{
type: 'unmotivated',
image: 'abc.png',
},
{
type: 'anxious',
image: 'abc.png',
},
]
i'm assuming you get the Mood list from a server or an external source so that's why i'm keeping moods in state instead of just mapping through the const moods.
state ={
moods: moods,
mood:null,
}
onClick= (key) =>{
console.log(this.state.moods[key]);
this.setState({
mood:this.state.moods[key],
})
}
render(){
return (
<div className="Container">
<ul className='moods'>
{
Object.keys(this.state.moods).map((key) => <div key={key}>
<a onClick={() =>{this.onClick(key)}}>Click here to change the mood to {this.state.moods[key].type}</a>
</div>)
}
</ul>
{this.state.mood ? <Mood actualMood={this.state.mood}/> : null}
</div>
);
}
and the Mood component just gets some props and display something based on what it gets:
class Mood extends Component
{
render()
{
console.log(this.props.actualMood.type);
return (
<div>
<p>You're looking at {this.props.actualMood.type} mood</p>
</div>
);
}
}
this can be easily achieved using react hooks but class based components need a tricky solution and may not look easy at first glance.
If what you want to achieve is to move to another component, you can have a parent Component which only manages the state, then based on a condition (if mood has been selected) render a component either ChooseAMoodComponent or MoodComponent.
Another way to achieve this is through React Router which you can pass some params via URL get params.
but the best practice should be using Redux.

Related

dynamically generate react components from data from an object as props

Context: trying to build a simple todo list app in react and typescript for educational purposes.
What I'm trying to do: Render as many ProjectMenuItem components as there are individual projects in my projects object, using the title of the project as props to pass into the components. "Project 1, Project 2, Project 3" should render to the screen as p elements.
What happens instead: The app does not compile and I get an error saying: Uncaught Error: Objects are not valid as a React child (found: object with keys {projectName}). If you meant to render a collection of children, use an array instead.
ProjectMenuItem component:
export function ProjectMenuItem(projectName: any){
return(
<p>{projectName}</p>
);
}
Parent Component:
export function ProjectList(){
const projects = {
project1: {
title: 'Project 1',
},
project2: {
title: 'Project 2',
},
project3: {
title: 'Project 3',
},
};
function generateProjectMenuItems() {
const projectMenuItems = [];
for (const project in projects) {
const projectName: string = projects[project as keyof typeof projects].title;
projectMenuItems.push(<ProjectMenuItem projectName={projectName} />);
}
return projectMenuItems;
}
return(
<div className="project-list flexbox">
<p>project components go here</p>
{generateProjectMenuItems()}
</div>
)
}
Have tried: I know it is saying I should use it as an array, so I tried mapping the projectMenuItems array to an array of span elements containing the components, but this results in the same error message.
return projectMenuItems.map(el => <span className="project-span" key={Math.random().toString()}>{el}</span>);
TL;DR I am trying to generate components for each object in a larger object and I'm not sure what I'm doing wrong. Very new to react.
Your ProjectMenuItem component isn't quite right. You want to accept the props as a (destructured) object:
export function ProjectMenuItem({ projectName }: { projectName: string }) {
As it is, React is providing it with the entire props object, which it receives as projectName. When it tries to render this in <p>{projectName}</p> it is giving you the error "Objects are not valid".
So this is probably not best practices, but this is how I ended up solving the problem yesterday. I am very open to feedback on things I could do better. I wrote it this way because it was the only way I could have my code work when it loaded from an empty (null) state when the app renders at first, because trying to run Object.keys() or map() on things that were null was making my app crash, so I wrapped my logic in a hook and conditional rendering to make things work.
Parent Component:
import React, {useContext} from "react";
import { AllContext } from "../App";
import ProjectMenuItem from "./ProjectMenuItem";
export default function ProjectList(){
//use context to pass state from root component
const {allProjects, selectedProject, currentTask} = React.useContext(AllContext);
const [allProjectsCopy, setAllProjects] = allProjects;
//make state for conditional rendering
const[displayProjects, setDisplayProjects] = React.useState<any>(null);
let projects: any;
//run this only when state updates
React.useEffect(() => {
console.log('Hello from the Project List component!', allProjectsCopy);
if(allProjectsCopy !== null){
console.log('allProjectsCopy updated and is NOT null.');
projects = Object.keys(allProjectsCopy);
let displayProjectsUpdate = projects.map((item: any)=>(
<div key={item}>
<ProjectMenuItem projectName={item} />
</div>
)
);
setDisplayProjects(displayProjectsUpdate);
}
}, [allProjectsCopy]);
return(
<div className="project-list flexbox">
<p>project components go here</p>
{(displayProjects !== null) ? displayProjects : null}
</div>
)
}
Child Components:
export default function ProjectMenuItem(props: any) {
return(
<div className="project-menu-item flexbox">
<img src="https://img.icons8.com/material-two-tone/48/null/overview-pages-3.png" alt="icon" role="button" tabIndex={0} aria-label="Clickable image" />
<h3 className="project-item-heading">{props.projectName}</h3>
<img src="https://img.icons8.com/ios/50/null/edit-property.png" alt="icon" role="button" tabIndex={0} aria-label="Clickable image"/>
</div>
);
}

Local storage in react todo list

I created to do list using react, but I want it to be local storage - so when the user refresh the page it still saved the items and will present them.
I read I need to use localStorage but I'm not sure where and how, attach the app.js and TodoItem component
class App extends Component {
state = {
items: [],
id: uuidv4(),
item: "",
editItem: false
};
handleChange = e => {
...
};
handleSubmit = e => {
e.preventDefault();
const newItem = {
id: this.state.id,
title: this.state.item
};
const updatedItems = [...this.state.items, newItem];
this.setState({
items: updatedItems,
item: "",
id: uuidv4(),
editItem: false
});
};
...
render() {
return (
<TodoInput
item={this.state.item}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
editItem={this.state.editItem}
/>
<TodoList
items={this.state.items}
clearList={this.clearList}
handleDelete={this.handleDelete}
handleEdit={this.handleEdit}
/>
);
}
}
export default class TodoItem extends Component {
state = {
avatarURL: '',
}
componentDidMount() {
imgGen().then(avatarURL => this.setState({ avatarURL }));
}
render() {
const { title, handleDelete, handleEdit } = this.props;
const { avatarURL } = this.state;
return (
<h6>{title}</h6>
<span className="mx-2 text-success" onClick={handleEdit}>
</span>
<span className="mx-2 text-danger" onClick={handleDelete}>
</span>
);
}
}
You can do it like this, mind the comments
class App extends Component {
state = {
// load items while initializing
items: window.localStorage.getItem('items') ? JSON.parse(window.localStorage.getItem('items')) : [],
id: uuidv4(),
item: "",
editItem: false
};
handleChange = e => {
// ...
};
handleSubmit = e => {
e.preventDefault();
const newItem = {
id: this.state.id,
title: this.state.item
};
const updatedItems = [...this.state.items, newItem];
// Save items while changing
window.localStorage.setItem('items', JSON.stringify(updatedItems));
this.setState({
items: updatedItems,
item: "",
id: uuidv4(),
editItem: false
});
};
// ...
render() {
return (
<>
<TodoInput
item={this.state.item}
handleChange={this.handleChange}
handleSubmit={this.handleSubmit}
editItem={this.state.editItem}
/>
<TodoList
items={this.state.items}
clearList={this.clearList}
handleDelete={this.handleDelete}
handleEdit={this.handleEdit}
/>
</>
);
}
}
Here's some simple logic you can use in your componentDidMount() method of your App.
const localStorageList = localStorage.getItem('todo-list')
if (!localStorageList) {return null} else {this.setState({items: localStorageList})
To add to the localStorage please look at this question
and this resource
Let me help you with this, using the least no. of codes. I have written a clear explanation of the steps, for you all to better understand, please bear with me , it is definitely with the time to read.
Also, note this solution is perfectly crafted for functional components. However I have mentioned how to do it in class components, you have to tweak some things if you are using class components. Like you can not use hooks in class-based components, but access this instance, so it will be fine, either ways
Please give it a full read, if you are having a tough time understanding the functionality, I have tried to break down the process in layman. The explanation is long, the lines of code is just under 10. happy to help
Persisting states of the todo app, upon page refresh, is pretty simple.
We can use State management libraries for it, or local storage as well.
Here, we will just go with the most simple one - using local storage.
Before we jump to the code, let us build the functionality visually.
So, after the user enters things in the todo space, we want few things to happen:
We want to store the list of items (which will essentially be an array) in the local storage. (We can skip the JSON.parse, here, since the array that will be saved, will be string, bcz user enters string in the todo-app, generally, however, it's not a bad idea to parse the userinputs).
useEffect(()=>{
window.localStorage.setItems("key" , value)
}, [dependency])
After you do this, make sure you check the dev-tools => application => localStorage => to see if the key and values are being stored. You shall be able to see them.
However, you will notice, that upon refresh, the localStorage values stay, but the data in the UI are lost. Not surprising.
This is the last and important step.
What we want upon page reload? Let us break it down :
We want to check if there is any data that is there in the localStorage. If there is: we will change the state of the app, based on the previous user inputs.
If there is no data in the LocalStorage, we will just pass an empty array.
Using hooks, in the functional component is actually What I prefer, class components require many boiler plates, so, the code...
import {useState} from 'react';/for functional components
//for class components you can just init the state , in the constructor(props) and
change it using the this.setState method
//to getItems from localStorage to render in the UI
useEffect(()=>{
const storedData = localStorage,getItems("keys" , value)
storedData ? setValue(value) : [];
},[])
[] : because we want it to render on every reload, once.
smake sure to initiliaze the state using useState;
const [value , setValue] = useState("")
//to setItems in localStorage
useEffect(()=>{
window.localStorage.setItems("key" , value)
}, [dependency])
useEffect is essentially a hook for functional components which is similar to componentDidMount in-class components.
If using class components, instead of using the useState, hook, use this.setState.
You could format your todolist into a JSON string and store it using :
localStorage.setItem("todolist", "your_JSON_string_here");
However, web Local Storage have storage limitations which will cause issues if the data stored are getting larger in time.
More info at here
Perhaps you could consider IndexedDB (if you are storing huge data) INFO

Manipulating state of multiple child components

I'm not sure whether or not it is best to keep state within the individual child component or the parent.
I have a parent component which will hold a child component which needs to be able to be duplicated on demand.
I have a few questions:
Where do i store the state for the individual component is it in the component itself or is it in the parent?
If it is in the child component how do I tell the parent to update the other children.
If it's in the parent how do I pass a function to the child which will update ITS state and not the parents state?
How do I access each of the components state and tell it to change based on another child state changing?
Currently I'm pushing a new "card" Component into an array which keeps track of all the Components I need to render on the "board".
I can't conceptualise the best way to manage the state of everything and how to access each child. Do they have an individual ID? how can I change all their states.
--------------------- BOARD ----------------------- *
import React from "react";
import Card from "./Card";
export default class Board extends React.Component {
constructor(props) {
super(props);
this.state = {
cards: [<Card />]
};
}
componentDidMount() {}
createCard() {
this.state.cards.push(<Card />);
this.forceUpdate();
}
render() {
return (
<div id="Board">
<button onClick={() => this.createCard()}>Create Card</button>
{this.state.cards.map(card => (
<Card />
))}
</div>
);
}
}
-------------------------- CARD ------------------------------ *
export default class Card extends React.Component {
constructor(props) {
super(props);
this.state = {
active: false
};
}
cardClick = () => {
this.setState({
active: !this.state.active
});
};
render(cardClick) {
return (
<div>
{this.state.active ? (
<div
className="activeCard"
id="card"
onClick={() => this.cardClick()}
>
Active
</div>
) : (
<div
className="inactiveCard"
id="card"
onClick={() => this.cardClick()}
>
Inactive
</div>
)}
</div>
);
}
} ```
Alrighty, let's take it from the top. I imagine you have some data you want to render as <Card />, one for each data item. You don't hold the components in the state, but rather, data. For instance:
this.state = {
data: [
{value: 'Cat 1'},
{value: 'Cat 2'},
{value: 'Cat 3'}
]
}
In this case, it's correct for the parent to hold the data. However, the whole concept of lifting the state up (React docs link) is not all that hard. Mostly, it's the question of: does more than one component care/depend on the same state? If the answer is yes, usually the parent should hold it. You're correct for having each card holding its own active state. However, the the parent could do it as well. Let's do that then shall we:
Board.js
import React from 'react';
import {Card} from './Card';
class Board extends React.Component{
state = {
data: [
{value: 'Cat 1'},
{value: 'Cat 2'},
{value: 'Cat 3'}
],
activeCard: null,
}
cardClick = id => {
this.setState({ activeCard: id })
}
addCard = () => {
const newCard = { value: 'Some val here' };
this.setState({ data: [...this.state.data, newCard] });
}
render(){
return(
<div id="Board">
<button onClick={this.addCard}>Add a card</button>
{this.state.data.map((item, index) =>
<Card key={index}
value={item.value}
active={this.state.activeCard === index}
cardClick={this.cardClick}
index={index}
/>
)}
</div>
)
}
}
export default Board;
By doing this, our parent manages everything. Not to say it's the only right way, but this lets us have the Card component as a 'dumb', functional component. Be careful - this specific approach relies on data never changing order, since it relies on the .map index, which is from 0 to length of data - 1.
Card.js
import React from 'react';
const Card = props => (
<div
className={props.active ? 'activeCard' : 'inactiveCard'}
onClick={() => props.cardClick(props.index)}
>
Active
</div>
);
export { Card };
There are a few key differences between what you posted and what I've done.
1) You were mapping cards with the same id="card" which is bad, id should be unique. Also, unless you really need it, you can omit it.
2) I'm toggling the className based off of the index of the active card. If the current card, let's say 2, is also the active card, it'll have activeCard className.
3) Finally, I'm passing in a function to the child that updates the parents state. By doing this, I have the state contained to the parent, and every time I update it, it'll reflect on the children as well. That's not to say your approach of having class based components for Cards is wrong, but this is simpler I think.
4) Just to throw it out there, WAI-ARIA doesn't really agree with a div having onClick events. To make the Internet a cool, accessible place, you can put a role="button" on the div, to signal it's a button. That also requires it be focusable, as well as a keyboard event listener, in which case, you're probably better of just using a <button> if the element should be clickable.
To answer your other questions:
The parent automatically propagates all state changes to all children that care for it
All child components are independent of the parent, eg if they have their own states, the parent doesn't care. It's only when they share a state AND a state update function that this becomes relevant, so only if you specifically pass such function to the children
If the children share a state, then it should be lift up, which is explained in the doc linked!
EDIT: for the given example, I assumed you only want one active card at a time. If that's not the case, either have each card hold their active states, like Keno Clayton suggested, or change activeCard into an array, checking index of each card against array of active cards
To answer your questions:
Where do i store the state for the individual component is it in the component itself or is it in the parent?
Best practice is to store the state in the component that needs it. If two sibling components need access to the same state then raise it to the parent.
You can then pass down individual props or pass the entire state down with the Context Api. You can use this to pass down functions to update the parent state if needed, and the children will automatically receive the props as they are updated.
For your specific scenario, each Card should have its own state to determine whether it is active or not. It would be possible to keep an array of active states in the parent as well, but that's not as intuitive and a bit more complex.
How do I access each of the components state and tell it to change based on another child state changing?
You shouldn't do that. You should maintain any information that you want to share with other components in the parent.
Where do i store the state for the individual component is it in the component itself or is it in the parent?
State of the individual component should be inside the component if that state is only controlled and needed within that component. For eg: active(boolean) in Card. Since a click on a card should make it selected, as well as make current active Card inactive we can conclude that it is better stored outside Card component in the Parent.
If it's in the parent how do I pass a function to the child which will update ITS state and not the parents state?
You can pass that function as a prop(after binding to the parent)
How do I access each of the components state and tell it to change based on another child state changing?
Since you keep list of cards and active boolean in parent Board component, you dont need to do this.
Keep a state like this in Board.
this.state = {
cards: [
{id: 1, name: 'Card1'},
{id: 1, name: 'Card2'},
{id: 1, name: 'Card3'},
],
activeId: 2
}

ReactJS - Infinite Loop calling Wrapped Method

I have the usual problem with infinite loop and I don't know why.
Im using reactJS 16.5.2
The loops generally occurs when you write a SetState where not allowed (for example in render method).
Im following this guide: https://medium.com/#baphemot/understanding-reactjs-component-life-cycle-823a640b3e8d
to pay attention about this issue.
I made several HOC(Decorators/Wrapper) components to concentrate general purpose methods in one point using props to propagate them to every children.
It generally works perfectly.
I tried to simplify my components structure below.
The problem is the FORM and its children.
One of the input has a DropDown that has to be populated with a method of the upper Wrapper. I put the call in componentDidMount(as the link above suggest). Unfortunally the wrapper setState seems to trigger a complete descrution and re-building of FORM Component. I put a console.log in every constructor from Wrapped to the form. Only the FORM and all its INPUTS are reacreated (and not updated).
This recreation generates an infinite loop because componentDidMountis triggered everytime.
I don't know how to fix this. I've checked every "key" properties and ALL components has their unique keys. I'm asking you WHY react recreate instead of update?
Is due to the form building method in parent render? And if so, which is the right design pattern to build a form with Async data population?
Simplify your life and instead of creating a bunch of wrappers, just create a single container-component that'll function the same way. For example, you would create a container that cares about data and state, then shares it and its methods with a reusable child component (as shown below, both function the same).
This would work exactly the same way with data fetched from an API. You'll retrieve data in componentDidMount, set it state, then pass down the state to the reuseable component.
You can get super granular with your reusable components. For example a reusable button that's sole purpose is to submit a form. Or a reusable input that only captures numbers between 1 and 100 and so on.
If your components are heavily nested, then consider using redux.
Working example: https://codesandbox.io/s/x2ol8wmzrp
containers/Form.js (container-component)
import React, { Component } from "react";
import Fields from "../components/Fields";
export default class Form extends Component {
state = {
buttonFields: [
{ id: "Apples", quantity: 1 },
{ id: "Strawberries", quantity: 1 },
{ id: "Grapes", quantity: 1 },
{ id: "Apricots", quantity: 1 }
]
};
handleButtonClick = id => {
this.setState(prevState => ({
buttonFields: prevState.buttonFields.map(
item =>
id === item.id ? { id, quantity: item.quantity + 1 } : { ...item }
)
}));
};
render = () => (
<Fields
{...this.state}
onButtonClick={this.handleButtonClick}
title="Container Component"
/>
);
}
components/Fields.js (reusable component)
import React from "react";
export default ({ buttonFields, onButtonClick, title }) => (
<div className="container">
<h1 style={{ textAlign: "center" }}>{title}</h1>
{buttonFields.map(({ id, quantity }) => (
<button
style={{ marginRight: 10 }}
className="uk-button uk-button-primary"
key={id}
onClick={() => onButtonClick(id)}
>
{id} ({quantity})
</button>
))}
</div>
);
containers/Wrapper.js (unnecessary wrapper)
import React, { Component } from "react";
export default WrappedComponent => {
class Form extends Component {
state = {
buttonFields: [
{ id: "Apples", quantity: 1 },
{ id: "Strawberries", quantity: 1 },
{ id: "Grapes", quantity: 1 },
{ id: "Apricots", quantity: 1 }
]
};
handleButtonClick = id => {
this.setState(prevState => ({
buttonFields: prevState.buttonFields.map(
item =>
id === item.id ? { id, quantity: item.quantity + 1 } : { ...item }
)
}));
};
render = () => (
<WrappedComponent
{...this.state}
onButtonClick={this.handleButtonClick}
title="Wrapper"
/>
);
}
return Form;
};
Thanking Matt Carlotta for his answer, I figure out what was the problem.
In the image above I simplified too much so I missed one important declaration.
In "FinalComponent" when I was creating the SomeFormComponent, due to its wrapping, I was doing something like this:
renderForm()
{
var WrappedFormComponent = FormHOC(SomeFormComponent();
return <WrappedFormComponent {...this.props} [...] />
}
It's obvious that with that syntax, the Form is instantatied every time due to renderForm method called in render method.
The solution is very simple. I moved that line above the component:
const WrappedFormComponent = FormHOC(SomeFormComponent();
export default class FinalComponent extends React.Component

ReactJS: Dynamic previous & next buttons

I am new to ReactJS and trying to understand how to accomplish a basic concept using best practices. The code below will render a list of items, when an item is clicked a stateful component is toggled and data is passed from to the state object before being consumed by on re-render.
What I need help with is a method to retrieve data from siblings from the array example below. Once the data has been fetched for previous/next projects that data should be accessible to the component. Can these siblings be accessed with a key value? If so, how would I use key?
DATA:
window.Seed = (function() {
const projects = [
{id:1, title:'Project 1', description:'Project 1 Description'},
{id:2, title:'Project 2', description:'Project 2 Description'},
{id:3, title:'Project 3', description:'Project 3 Description'}
]
};
REACT CODE:
class ProjectList extends React.Component {
state = {
projects: [],
isOpen: false,
modalTitle: '',
modalDescription: '',
modalId: ''
}
componentDidMount() {
this.setState({ projects: Seed.projects });
}
handleModalOpen = (id, title, description) => {
this.setState({
isOpen: true,
modalId: id,
modalTitle: title,
modalDescription: description
});
};
handleModalClose = () => {
this.setState({ isOpen: false });
};
render() {
const projects = this.state.projects;
// GOAL #1: Pass previous/next project data as props
// =================================================
// Pass projects.map data from previous & next siblings
// to the current component or explore a new method to
// retrieve data from state? I can already tell that this
// probably is not the most dynamic approach to handle
// previous/next data.
const projectComponents = projects.map((project) => (
<Project
key={'project-' + project.id}
id={project.id}
title={project.title}
url={project.url}
description={project.description}
background={project.background}
onModalOpen={this.handleModalOpen}
prevTitle={???}
nextTitle={???}
prevDescription={???}
nextDescription={???}
/>
));
if (this.state.isOpen) {
return (
<Modal
id={this.state.modalId}
title={this.state.modalTitle}
description={this.state.modalDescription}
onModalClose={this.handleModalClose}
/>
);
} else {
return (
<div className="projects-container">
<div id="projects" className="wrapper">
{projectComponents}
</div>
</div>
);
}
}
}
class Project extends React.Component {
render() {
return (
<aside className='item'>
<a onClick={this.props.onModalOpen.bind(
this, this.props.id, this.props.title, this.props.description
// I think that I need to bind previous/next data here on click.
// this.props.prevTitle, this.props.nextTitle
// this.props.prevDescription, this.props.nextDescription
)}>
<img src={this.props.background} />
</a>
</aside>
);
}
}
class Modal extends React.Component {
render() {
return (
<div className="modal">
// GOAL #2: Get sibling data from mapped array
// =================================================
// I would like to be able to pass previous and next
// project data as props to populate these headings.
<h2>Current Project: {this.props.title}</h2>
<h2>Previous Project: {this.props.prevTitle} </h2>
<h2>Next Project: {this.props.nextTitle} </h2>
// GOAL #3: Replace content with data stored in state
// =================================================
// I would like to be able to 'onClick' these buttons
// to replace the content of the <Modal />. Essentially,
// this should behave like a lightbox or carousel.
// Initial Project Description
<p>{this.props.description}</p>
// this.props.description = this.props.prevDescription
<button onClick={???}>Previous Project</button>
// this.props.description = this.props.nextDescription
<button onClick={???}>Next Project</button>
<button onClick={this.props.onModalClose}>Close Modal</button>
</div>
);
}
}
DEMO
Thanks for reading!
UPDATED SOLUTION
As a general principle, data should flow through React components from parent to child, not from sibling to sibling. In theory, it might be possible to pass data between siblings but this makes things orders of magnitude more complex. The beauty of the top-down data flow is that any time the data in the parent changes, the children will (probably, excluding optimizations like pure rendering) re-render. So you just have to change the data in the parent and the children will automatically update as they need to. Here are suggestions for approaching your goals this way. This isn't perfect but I hope it illustrates the point:
Goal #1: Array.prototype.map takes a function with these parameters: function(currentValue, index, array) {...}. For each project, the previous project is array[index - 1] and the next project is array[index + 1]. All you have to do is reference the previous and next projects this way in the map function to get their title, description, etc.
Goal #2: Instead of using this.state.isOpen, use this.state.activeProject. When the project is clicked, set this.state.activeProject to the index of that project. Then you can infer that the modal is open if !!this.state.activeProject (if this.state.activeProject is truthy). Use the activeProject id/index to pass desired data from that project to the Modal component in the ProjectList component render method.
Goal #3: Update this.state.activeProject to the index/id of the previous or next project. This will cause a re-render of ProjectList with the new props. To do this, you'll want to pass onNextClick and onPrevClick props. You can partially apply this.state.activeProject + 1 and this.state.activeProject - 1 to onNextClick and onPrevClick, respectively.

Categories

Resources