Manipulating state through an out-of-class function (or alternatives) - javascript

hope you're having a good day. I may be about to annoy you slightly with this question given how much of a newbie I am with React.
Here's the setup: I have a "projects" page for my portfolio website where I feature one project in full with the full size image and details on the side, and under that I have a listing of all other projects. As soon as one of those is clicked, the featured project should change. A pretty simple thing to most, I'm sure, but I seem to be stuck.
Here's how I currently have it set up:
An array called 'projects' outside of the component class to store them, one project looks like this:
{
key: 0,
title: "Project title",
description:
"Lorem ipsum",
details: [
"...",
"...",
"..."
].map((detail, i) => <li key={i}>{detail}</li>),
image: ImageObject
},
This function (which is referenced within the class' render method) also outside of the main component class which makes a div for each project
function ProjectList(props) {
const projects = props.projects;
const projectItems = projects.map(project => (
<div key={project.key}>
<a href="#">
<img src={project.image} alt={project.title} />
</a>
</div>
));
return <div className="row">{projectItems}</div>;
}
and the reference in the render method goes like this:
<ProjectList projects={projects} />
Now we're going within the Projects class, with one state value called 'currentProjectIndex', and a 'setProject' function:
setProject = index => {
this.setState({ currentProjectIndex: index });
};
Everything works and displays correctly, and if I manually edit the state it does show the relevant project, but I'm not sure how to proceed here. I've tried adding an onClick attribute to the div and the a tags, but it seems I can't reference setState outside of the main class, which I suppose makes sense, but the ProjectList function isn't recognized in the render method if I place it inside the main class.
I realize this may be some very basic stuff but I have a feeling I might have gone about this process entirely wrongly and that I'm on the completely wrong track here. Either that or I'm just being stupid, both of which are entirely possible. Either way, I'm super thankful for any suggestions!

You can try this: add a button where the click event calls setProject and pass the index (that is the project.key, right?) as parameter. You also may want to add an event.preventDefault. Hope it helps. Here is the code:
setProject = (event, index) => {
event.preventDefault();
this.setState({ currentProjectIndex: index });
};
function ProjectList(props) {
const projects = props.projects;
const projectItems = projects.map(project => (
<div key={project.key}>
<a href="#">
<img src={project.image} alt={project.title} />
</a>
<button
type='button'
onClick={event => { this.setProject(event, project.key) }}
>
Select this project
</button>
</div>
));
return <div className="row">{projectItems}</div>;
}

Related

How do I add style to a react element within the return function?

I'm trying to add style to an element in my return of a react component, but I want to achieve this without adding a class. My text editor auto fills a style option, but I believe the syntax is wrong, since when trying to add a function on an onClick event, its a little different when its in the return of a react element. For example, instead of
onClick=function()
its
onClick={() => {function()}}
I'm hoping that instead of style={"background-color: green;"} its a different syntax to actually allow style changes once it hits the dom.
In-line styles can be done, and here is a code example as you have not provided one.
for example, lets inline style an h1 tag
<h1 style={{background-color:'green', color:'white'}}>This is a tilte</h1>
more can be found here
additionally, I would not recommend inline styling as it's not industry-standard and can cause your code to become bloted.
Style tags in react can indeed contain a references to functions.
I am not fully sure if you are working with React component classes or component functions, but your syntax can besides as follows. Create a variable called customStyle which will contain a function that returns your required style object:
customStyle = () => { return { color: 'red' } };
You can then reference customStyle inside markup as follows:
<div style={this.customStyle()}>My Element</div>
idont know if i understood your question well, You can achieve what you want by making a style state, then mutate it whatever style you want with setState
const [style, setStyle] = useState({})
const App = () => {
return <div style={style}>
<button onClick={() => setStyle({color: 'red'})}>handler button </button>
</div>
}

Looping a ReactJS component inside of its parent component

I have this component imported that returns a bunch of cards but I want to style this in a row instead of a column and it seems the way to do that is to loop the component as a <li> and then adding css but I cannot seem to make the component loop correctly.
the component looks like this inside of the parent element:
<div id="newsFeed" className='feed'>
<Feed theme={this.state.theme} articles = {this.state.articles} />
</div>
I have tried solutions such as:
var feedContent = <Feed theme={this.state.theme} articles = {this.state.articles} />
///////////////////////
{feedContent.map(item => <Feed key={item} value={item} />)}
</div>
but cannot seem to find any luck.
Any help would be greatly appreciated
map is a built in array method that is used a bunch in React. You need to use it on an array or else you will throw an error. I am assuming the value for articles is an array here:
//Feed component
class Feed extends React.Component {
constructor(props) {
super(props);
this.state = {
articles = []
};
}
componentDidMount = () => { // maybe call an API to populate the articles array }
render = () => {
return (
<ul className="someClass" >
{this.state.articles.map((item, index) => <li key={index} className="someOtherClass">{item}</li>)}
</ul>
);
}
}
alternatively you could create a li component, perhaps called FeedItem, import it and map it with the value prop from each item in the articles array.
// render method for Feed Component
render = () => {
return(
<ul className="someClass">
{this.state.articles.map((item, index) => <FeedItem key={index} value={item} />)}
</ul>
);
}
// FeedItem component
const FeedItem = ({value}) => {
return(
<li className="someOtherClass">{value}</li>
);
}
so, you are using map to create a list with the items in your articles array, and the map function loops through each article and returns a list component. Hopefully this helps! here's the React docs for reference: https://reactjs.org/docs/lists-and-keys.html
note: React docs advise against using an index as a key, but I don't know if your article array elements contain something unique. If they do (like an id of some kind), use that instead of index.
I think you need to change the approach.
I'd recommend you create an Article component, example:
function Article({ title }) {
<div>
{title}
<div>
}
After that, you might use the articles array to show each one:
//Feed component
this.state.articles.map((article, i) => <Article title={article.title}>)
In that way you can stylize the articles component as you want.
I was able to figure it out with simple CSS believe it or not. Basically <Feed /> was wrapped in a div in the parent component and was not responding to Flexbox CSS code, however, After I used changed the className of the div that was wrapping the component inside of its <div> within its own file the div responded to CSS! Thank you so much for your help everyone, I appreciate it!
Have you tried adding a className to the div that is the parent of the cards? Use that class to apply a flex display for example, and see what that gives you. If Newscard has a fixed width of 100% by chance, of course you'll need to adjust that to a small percentage / width to suit your needs.

How to fix rendering on a list-style schedule in React component

I am trying to create a 'simple' schedule of events for a React app. I'm using this Codepen for inspiration. There aren't events for every day and some days have more events than others.
I was finally able to create one 'date' header (on the left) for each date, without repeating, and to then display one time block on the right for all of the events in that day. However, I can't get it to render properly. I've tried everything I can think of in terms of padding, margins, alignment, etc. I've also tried moving things into and out of different divs to see if that was the issue.
I'm not sure how to get this issue to display in a Codepen or JSFiddle with the JSX and React components, etc. I've copied the code into this Codepen regardless -- hopefully it's enough to convey the issue. This is the main relevant section:
render() {
this.restructureData();
return (
<div>
<br></br>
<h1>Availability</h1>
{/* {this.renderTimeslots()} */}
<div className="schedule-div">
<ul className="main">{this.betterRenderTimeslots()}</ul>
</div>
</div>
);
betterRenderTimeslots = () => {
const newObject = this.restructureData();
let allDays = [];
for (let date in newObject) {
let onThisDay = (
<>
<h3>{date}</h3>
<li className="events">
<ul className="events-detail">
{newObject[date].map(timeslot => {
return (
<li>
{<span className="event-time">{timeslot.hour}:00</span>}
</li>
);
})}
</ul>
</li>
</>
);
allDays.push(onThisDay);
}
return allDays;
};
The accompanying CSS is pasted into the Codepen above. Here's a screenshot of what it looks like for me.
Thank you!
Your H3 header is not inside LI element, that's why it's falling out

Is it a bug to discard passed rest-props to a React component?

I'm developing my wrapper over flexbox in react: https://github.com/aush/react-stack. I have a top level component that creates a container div and sets flex properties to it and also I map over it's children and set flex properties to them (I don't create additional wrapper divs for children, just set flex properties directly to them). And it's works flawlessly except one case.
My component, stripped down:
export default ({ someprops, style, children, ...rest }) =>
<div style={{ ...style, ...flexContainerStyle }} {...rest}>
{
React.Children.map(children, (child, i) =>
React.cloneElement(child,
{ key: i, style: { ...child.props.style, ...flexItemStyle } }))
}
</div>;
Consider this simple example, when all children are just standard divs:
codepen example 1
ReactDOM.render(
<Horizontal>
<div className="box1">1</div>
<div className="box2">2</div>
<Vertical>
<div className="box3">3</div>
<Horizontal grow={1}>
<div className="box4" align={'end'}>4</div>
<div className="box5" align={'end'}>5</div>
<div className="box6" align={'end'}>6</div>
</Horizontal>
</Vertical>
</Horizontal>,
document.getElementById('app')
);
Now, let's say I have these two react components: BuggedComponent and NotBuggedComponent and put them as children:
codepen example 2
const BuggedComponent = ({ children }) =>
<div className="box5">{children}</div>;
const NotBuggedComponent = ({ children, ...rest }) =>
<div className="box6" { ...rest }>{children}</div>;
ReactDOM.render(
<Horizontal>
<div className="box1">1</div>
<div className="box2">2</div>
<Vertical>
<div className="box3">3</div>
<Horizontal grow={1}>
<div className="box4" align={'end'}>4</div>
<BuggedComponent align={'end'}>5</BuggedComponent>
<NotBuggedComponent align={'end'}>6</NotBuggedComponent>
</Horizontal>
</Vertical>
</Horizontal>,
document.getElementById('app')
);
In this case, box 5 doesn't have any properties besides ones BuggedComponent sets itself.
It appears that properties set to the 'bugged' component are lost, obviously, because this component, essentially, discards rest-properties. For me it's a clear bug, not even just a bad practice. But I've taken a look at some github repos and I see that this quite a common practice and I cannot find any articles/guides which state this issue and point it as a bad practice.
The only argument for not passing rest that I have is that it's not clear, where rest-props should be passed. But since a react component can has only one root, it's rather natural that rest-props should be passed to it.
So, the question is: is it really, unfortunatelly very common, bad practice, or there is some explanation to why discarding rest-properties is the right way to develop react components?
It comes down to how much control over own child components does the dev intends to hand out to the consumers. In this case the BuggedComponent is generating a child div and it makes sense for it to pass on the rest properties to it. However, if the child would have been a more complex control where allowing arbitrary attributes does not make sense, there won't be a need for passing them down. Yours is a very specific case where you need absolute control over what class the child control has, but this may not be true in a general scenario. A good example is the very popular material-ui library, if you look at any complex control, it minutely controls the allowed props and almost never passes them down. It could make keeping inner-markup change-proof very difficult, apart from the simplest of cases.

Show/Hide ReactJS components without losing their internal state?

I've been hiding/showing react components by not rendering them, for example:
render: function() {
var partial;
if (this.state.currentPage === 'home') {
partial = <Home />;
} else if (this.state.currentPage === 'bio') {
partial = <Bio />;
} else {
partial = <h1>Not found</h1>
}
return (
<div>
<div>I am a menu that stays here</div>
Home Bio
{partial}
</div>
);
}
but just say that the <Bio/> component has lots of internal state. Everytime I recreate the component, it loses it's internal state, and resets to it's original state.
I know of course that I could store the data for it somewhere, and pass it in via props or just globally access it, but this data doesn't really need to live outside of the component. I could also hide/show components using CSS (display:none), but I'd prefer to hide/show them as above.
What's the best practice here?
EDIT: Maybe a better way to state the problem is to use an example:
Ignore React, and assume you were just using a desktop app that had a configuration dialog with a Tab component called A, which has 2 tabs, named 1 and 2.
Say that tab A.1 has an email text field and you fill in your email address. Then you click on Tab A.2 for a second, then click back to Tab A.1. What's happened? Your email address wouldn't be there anymore, it would've been reset to nothing because the internal state wasn't stored anywhere.
Internalizing the state works as suggested in one of the answers below, but only for the component and it's immediate children. If you had components arbitrarily nested in other components, say Tabs in Tabs in Tabs, the only way for them to keep their internal state around is to either externalize it somewhere, or use the display:none approach which actually keeps all the child components around at all times.
It just seems to me that this type of data isn't data you want dirtying up your app state... or even want to even have to think about. It seems like data you should be able to control at a parent component level, and choose to either keep or discard, without using the display:none approach and without concerning yourself with details on how it's stored.
One option would be to move the conditional inside the component itself:
Bio = React.createClass({
render: function() {
if(this.props.show) {
return <p>bio comp</p>
} else {
return null;
}
}
});
<Bio show={isBioPage} />
Whether this is "best practise" or not probably depends on the exact situation.
Unfortunately, style={{display: 'none'}} trick only works on normal DOM element, not React component. I have to wrap component inside a div. So I don't have to cascade the state to subcomponent.
<div className="content">
<div className={this.state.curTab == 'securities' ? 'active' : ''}>
<Securities />
</div>
<div className={this.state.curTab == 'plugins' ? 'active' : ''}>
<Plugins />
</div>
</div>
Looks like official documentation suggests hiding stateful children with style={{display: 'none'}}
The fundamental problem here is that in React you're only allowed to mount component to its parent, which is not always the desired behavior. But how to address this issue?
I propose the solution, addressed to fix this issue. More detailed problem definition, src and examples can be found here: https://github.com/fckt/react-layer-stack#rationale
Rationale
react/react-dom comes comes with 2 basic assumptions/ideas:
every UI is hierarchical naturally. This why we have the idea of components which wrap each other
react-dom mounts (physically) child component to its parent DOM node by default
The problem is that sometimes the second property isn't what you want
in your case. Sometimes you want to mount your component into
different physical DOM node and hold logical connection between
parent and child at the same time.
Canonical example is Tooltip-like component: at some point of
development process you could find that you need to add some
description for your UI element: it'll render in fixed layer and
should know its coordinates (which are that UI element coord or
mouse coords) and at the same time it needs information whether it
needs to be shown right now or not, its content and some context from
parent components. This example shows that sometimes logical hierarchy
isn't match with the physical DOM hierarchy.
Take a look at https://github.com/fckt/react-layer-stack/blob/master/README.md#real-world-usage-example to see the concrete example which is answer to your question (take a look at the "use" property):
import { Layer, LayerContext } from 'react-layer-stack'
// ... for each `object` in array of `objects`
const modalId = 'DeleteObjectConfirmation' + objects[rowIndex].id
return (
<Cell {...props}>
// the layer definition. The content will show up in the LayerStackMountPoint when `show(modalId)` be fired in LayerContext
<Layer use={[objects[rowIndex], rowIndex]} id={modalId}> {({
hideMe, // alias for `hide(modalId)`
index } // useful to know to set zIndex, for example
, e) => // access to the arguments (click event data in this example)
<Modal onClick={ hideMe } zIndex={(index + 1) * 1000}>
<ConfirmationDialog
title={ 'Delete' }
message={ "You're about to delete to " + '"' + objects[rowIndex].name + '"' }
confirmButton={ <Button type="primary">DELETE</Button> }
onConfirm={ this.handleDeleteObject.bind(this, objects[rowIndex].name, hideMe) } // hide after confirmation
close={ hideMe } />
</Modal> }
</Layer>
// this is the toggle for Layer with `id === modalId` can be defined everywhere in the components tree
<LayerContext id={ modalId }> {({showMe}) => // showMe is alias for `show(modalId)`
<div style={styles.iconOverlay} onClick={ (e) => showMe(e) }> // additional arguments can be passed (like event)
<Icon type="trash" />
</div> }
</LayerContext>
</Cell>)
// ...

Categories

Resources