Alright, I'm going to do my best to explain how my project is setup so that you can appropriately aid me on my quest to figure out how to approach this configuration.
I have a parent component that is a smart component. Through this component all my data from my store is being accessed.
class DashboardPage extends React.Component {
componentDidMount() {
this.props.getTips();
}
render() {
return (
<div>
<div className="row">
<div className="col-sm-7">
<ContentBox
title="The Scoop"
footerText="Submit a Story"
showSlider
content={<TipOfTheDay tips={this.props.tips} />}
/>
</div>
</div>
</div>
)
}
}
DashboardPage.propTypes = {
getTips: PropTypes.func
}
function mapStateToProps(state, ownProps) {
tips: state.tips
}
function mapDispatchToProps(dispatch) {
return {
getTips: () => { dispatch(tipActions.loadTips());} ## This hits tipActions and runs the `action creator`: loadTips(). Which returns all tips from api.
}
}
export default connect(mapStateToProps, mapDispatchToProps)(DashboardPage);
As you can see, I have included two dumb components inside my smart component, <ContentBox/> & <TipOfTheDay/>. On the dashboardPage there are about 7 <ContentBox/> components, each inheriting special a title for the header/footer and also being told whether or not to display the footer through the showSlider boolean. Here is what <ContentBox/> looks like:
import React, {PropTypes} from 'react';
import Footer from './ContentBoxFooter';
const ContentBox = ({title, footerText, showSlider, content}) => {
return (
<div style={styles.root} className="col-sm-12">
<div style={styles.header} className="row">
<h3 style={styles.header.title}>{title}</h3>
<span style={styles.header.arrow} />
</div>
{content}
<Footer footerText={footerText} showSlider={showSlider} />
</div>
);
};
ContentBox.propTypes = {
title: PropTypes.string,
footerText: PropTypes.string,
showSlider: PropTypes.bool,
content: PropTypes.object
};
export default ContentBox;
And here is the footer:
import React, {PropTypes} from 'react';
import styles from './contentBoxStyles';
import Previous from './svg/Previous';
import Next from './svg/Next';
import Add from './svg/Add';
import consts from '../../styles/consts';
const ContentBoxFooter = ({footerText, showSlider}) => {
if (footerText != undefined) {
return (
<div style={styles.footer} className="row">
{
showSlider ?
<div>
<Previous fillColor={consts.orange} height="20px" width="20px"/>
<span style={styles.bar}>|</span>
<Next fillColor={consts.orange} width="20px" height="20px"/>
</div> : <div style={styles.emptyArrow} />
}
<div style={styles.footer.link}>
<span style={styles.footer.link.text}>{footerText}</span>
<Add fillColor={consts.orange} height="24px" width="24px" />
</div>
</div>
);
} else {
return(null);
}
};
ContentBoxFooter.propTypes = {
footerText: PropTypes.string,
showSlider: PropTypes.bool
};
export default ContentBoxFooter;
Few! So here is where I need to add the onClick functionality. This functionality needs to be added to the <Previous/> & <Next/> component that is an SVG. What I am attempting to do is create a slider for the tips that I have pulled in. Obviously there will be <Footer/> components that will need the same functionality, but controlling different data other than the tips. Because I am new to React & Redux, I am not sure how I can perform this and not just do it, but do it in the 'Redux` way.
How do I get these two svg components that are nested within other dumb components that are dumb components, to perform onClick functions for specific data on the page? I hope this made sense. For more clarity, here is what I am doing with the <TipOfTheDay/> component:
const tipOfTheDay = ({tips}) => {
return (
<div style={styles.tipBody} className="row">
{
tips.map(function(tip, key) {
return (
<div key={key} className="myTips">
<h3 style={styles.tipBody.header}>{tip.title}</h3>
<p style={styles.tipBody.content}>{tip.content}</p>
</div>
);
})
}
</div>
);
};
tipOfTheDay.propTypes = {
tips: PropTypes.array.isRequired
};
export default tipOfTheDay;
Thank you for anytime you spend reading/responded/assisting with this question. I am a fairly new developer and this is also new technology to me.
I'm not sure how you've implemented your Next and Previous Components, but since you've using React-Redux, you can create extra Containers to wrap those components and pass in a Redux Action to them, e.g.:
// PreviousComponent.jsx
var Previous React.createClass({
propTypes: {
goToPrevious: React.PropTypes.func,
},
render: function() {
return (
<div onClick={this.props.goToPrevious}>
...
</div>
);
}
};
export default Previous;
//PreviousContainer.jsx
import ReactRedux from 'react-redux';
import Previous from './PreviousComponent';
var mapStateToProps = (state, props) => {
return {};
};
var mapDispatchToProps = (dispatch) => {
return {
goToPrevious: () => {
dispatch(Actions.goToPrevious());
},
}
};
var PreviousContainer = ReactRedux.connect(
mapStateToProps,
mapDispatchToProps
)(Previous);
export default PreviousContainer;
By adding a container wrapper directly around your component, you can connect a redux action for going to the previous image/slide/whatever directly into your React component. Then, when you want to use the action in your ContentBoxFooter, you import the PreviousContainer and place it where you want the Previous component, e.g.:
//ContentBoxFooter.jsx
import PreviousContainer from './PreviousContainer'
const ContentBoxFooter = ({footerText, showSlider}) => {
if (footerText != undefined) {
return (
<div style={styles.footer} className="row">
{
showSlider ?
<div>
/*
* Add the PreviousContainer here where before you were only using your regular Previous component.
*/
<PreviousContainer fillColor={consts.orange} height="20px" width="20px"/>
<span style={styles.bar}>|</span>
<Next fillColor={consts.orange} width="20px" height="20px"/>
</div> : <div style={styles.emptyArrow} />
}
<div style={styles.footer.link}>
<span style={styles.footer.link.text}>{footerText}</span>
<Add fillColor={consts.orange} height="24px" width="24px" />
</div>
</div>
);
} else {
return(null);
}
};
ContentBoxFooter.propTypes = {
footerText: PropTypes.string,
showSlider: PropTypes.bool
};
By wrapping both the Next and Previous components in containers that pass actions into them, you can connect the Redux actions directly into your components without having to pass them from the root component of your application. Also, doing this allows you to isolate where certain actions are called. Your Previous button is probably the only component that would want to call a Previous action, so by placing it in a Container wrapper around the component, you're making sure that the Previous action is only used where it is needed.
Edit:
If you have to deal with multiple actions, it is better to define them at a higher level. In this case, since the ContentBox is the common breaking point, I would define separate Previous actions for each type of content box and pass them into each ContentBox instance:
var DashboardApp = React.createClass({
propTypes: {
TipsPrevious: React.PropTypes.function,
NewsPrevious: React.PropTypes.function,
},
render: function() {
<div>
<ContentBox
previousAction={this.props.TipsPrevious}
contentType='Tips'
/>
<ContentBox
previousAction={this.props.NewsPrevious}
contentType='News'
/>
...
</div>
},
});
Pass the actions down through the child components until you reach the Previous component and then attach the action to an 'onClick' handler on the Previous component.
The idea here behind this is that you want to limit the scope of parameters to the least amount of code possible. For example, if you added a profile component showing your user information on the page, you might want to add a container around that component and pass in the User-related information/actions without passing the information to the rest of your application. By doing this, it makes it easier to focus information where it is needed. It also helps you figure out where some change/action is taking place in your code if you have to fix a bug.
In the example above, if the Dashboard component is your root component, you'll just pass the Redux Actions into through a container wrapping it. However, if your dashboard component is a nested component itself, pass the actions into it through a custom container so that the actions aren't spread to code that don't need to see it:
<AppRoot>
<PageHeader/>
<DashboardContainer />
<PageSidebar />
<PageFooter />
</AppRoot>
Related
I am working on the following project https://github.com/codyc4321/react-udemy-course section 11 the videos app. The udemy course is found at https://www.udemy.com/course/react-redux/learn/lecture/12531374#overview.
The instructor is passing a callback down to multiple children and calling it in the lowest videoItem and the code is supposed to console log something out. I have no console log in my browser even though I've copied the code as written and double checked for spelling errors.
At the main level is App.js:
import React from 'react';
import youtube from '../apis/youtube';
import SearchBar from './SearchBar';
import VideoList from './VideoList';
class App extends React.Component {
state = {videos: [], selectedVideo: null};
onTermSubmit = async term => {
const response = await youtube.get('/search', {
params: {
q: term
}
});
// console.log(response.data.items);
this.setState({videos: response.data.items});
};
onVideoSelect = video => {
console.log('from the app', video);
}
render() {
return (
<div className="ui container">
<SearchBar onFormSubmit={this.onTermSubmit} />
<VideoList
onVideoSelect={this.onVideoSelect}
videos={this.state.videos} />
</div>
)
}
}
export default App;
videoList.js
import React from 'react';
import VideoItem from './VideoItem';
const VideoList = ({videos, onVideoSelect}) => {
const rendered_list = videos.map(video => {
return <VideoItem onVideoSelect={onVideoSelect} video={video} />
});
return <div className="ui relaxed divided list">{rendered_list}</div>;
};
export default VideoList;
the videoItem.js
import React from 'react';
import './VideoItem.css';
const VideoItem = ({video, onVideoSelect}) => {
return (
<div onClick={() => onVideoSelect(video)} className="item video-item">
<img
src={video.snippet.thumbnails.medium.url}
className="ui image"
/>
<div className="content">
<div className="header">{video.snippet.title}</div>
</div>
</div>
);
}
export default VideoItem;
The code that isn't running is
onVideoSelect = video => {
console.log('from the app', video);
}
My guess is that it has something to do with a key prop not being present in the map - I'm not super well versed with class components but I can't find anything else funky so maybe try adding a unique key prop in the map.
When rendering components through a map react needs help with assigning unique identifiers to keep track of re-renders etc for performance, that also applies to knowing which specific instance called a class method.
If you don't have a unique ID in the video prop you can use an index in a pinch, although ill advised, it can be found as the second parameter in the map function. The reason it's ill advised to use an index is if there are multiple children with the same index in the same rendering context, obviously the key parameter could be confused.
Okay-ish:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={index} onVideoSelect={onVideoSelect} video={video} />});
Better:
const rendered_list = videos.map((video, index) => {
return <VideoItem key={video.id} onVideoSelect={onVideoSelect} video={video} />});
how to pass the variable "devscreens" from loginform to app.js on the below criteria and move to the next screen in react js.
Loginform.js:
async doLogin(){
this.setState({invalid_user:false});
this.setState({welcome_user:false});
this.setState({devscreens:false});
if (!this.state.username){
return;
}
this.setState({
buttonDisabled: true
})
try{
const response = await axios.get(`${loginidURL}`)
if(response.status === 200)
{ this.setState({welcome_user:true});
console.log(response.data.username)
console.log(response.data)
console.log(response.data.userlevel)
if (response.data.userlevel == 11 )
{
this.setState({devscreens:true});
console.log(this.state.devscreens)
}
if (response.data.userlevel == 13 )
{
this.setState({devscreens:false});
}
userid=response.data.username
this.resetForm();
}
}
App.js:
function App() {
const classes = useStyles();
return (
<ThemeProvider theme={theme}>
<div className="app">
<div className='container'>
<h1>Application </h1>
<h3>App</h3>
<LoginForm/>
</div>
</div>
<div className={classes.appMain}>
<Header />
<Welcome username=""/>
<Board />
</div>
I am new to react working in logic screen and its next screen integration.
Wanted to pass the devscreen value from Loginform.js to App.js. and if devscreen is true then i need to execute the below in app.js in an separate screen.
<Header />
<Welcome username="Jo"/>
<Board />
</div>
Can some one help me with this.I tried using props but that did not work for me..
A common flow in React is that the App.js component contains all other components and logic within it. So the component is a container for all other components.
The logic is to then decide which child components to render depending on the state of parent components. You can pass objects to child components using props, which then help decide which pieces to render.
Please have a look through the examples here:
https://reactjs.org/tutorial/tutorial.html
I'm looking for advice on how to render one out of five forms in React on the click on one of their five related buttons. I've got it working actually for one form, but I'm pretty sure this is not how things should be done in React.
There is a sidebar with 5 buttons and a content area that displays some content. One of the forms should appear in the content area after a click on their respective buttons in the sidebar. Only one form should be displayed at a time. Both the sidebar and the content area are functions inside of a class called GraphArea that renders them.
AddNodes (one of five buttons in the sidebar):
const AddNodes = ({ showAdd }) => (
<li role="presentation" className="list-inline">
<a href="" className="nav-link" onClick={showAdd}>
<i className="fa fa-plus" aria-hidden="true"/> Add
</a>
</li>
);
export default AddNodes;
The problem is, there are five forms and buttons... There's no way one could reasonably continue like this.
I'm using Apollo link state. All 5 five forms won't be rendered by default, so they've got a default value of false. The values are written to the cache. Apollo queries and mutates a default value in the cache to true on click.
It would mean a lot to me if you could show me the right way here. I'm missing that 'click' of how things work in React and this would be a great oppertunity for it. This is the sidebar's and content area's parent full component:
GraphArea: (parent)
class GraphArea extends Component {
render() {
const { updateEditGraph, editGraph: { mode } } = this.props;
const showAdd = (e) => {
updateEditGraph(
{
variables: {
index: 'mode',
value: 'addNode'
}
});
e.preventDefault()
};
const showLink = (e) => {
updateEditGraph(
{
variables: {
index: 'mode',
value: 'addLink'
}
});
e.preventDefault()
};
return (
<div className="item">
<GraphSidebar showAdd={showAdd} showLink={showLink}/>
<GraphContent/>
</div>
)
}
}
export default compose(
graphql(updateEditGraph, {name: 'updateEditGraph'}),
graphql(getEditGraph, {
props: ({data: {editGraph}}) => ({
editGraph
})
})
)(GraphArea);
GraphContent:
class GraphContent extends Component {
render() {
let content;
if (this.props.editGraph.mode === 'addNode') {
content = <AddNodesForm/>
} else if (this.props.editGraph.mode === 'addLink') {
content = <LinkNodesForm/>
} else {
content = null;
}
return (
<div className="content">
{content}
<Graph/>
</div>
);
}
}
export default compose(
graphql(getEditGraph, {
props: ({data: {editGraph}}) => ({
editGraph
})
})
)(GraphContent);
GraphSidebar:
const GraphSidebar = ({ showAdd, showLink }) => (
<div className="avatars">
<ul>
<AddNodes showAdd={showAdd} />
<SequenceNodes />
<EditNodes />
<LinkNodes showLink={showLink} />
<DeleteNodes />
</ul>
</div>
);
I will answer what should happen in principle.
You say you have a top level component GraphArea that renders both the component for selecting a form, and the container of the forms:
<div>
<GraphSidebar onClickName={handleClick}/>
<GraphContent/>
</div>
GraphSidebar and GraphContent are related in such a way that GraphSidebar changes the state that should be presented on GraphContent, that is they relate to the same state. Because of that their common parent (GraphArea) should hold that state. You then pass the state to the GraphContent which contains the forms.
Make GraphArea a class:
class GraphArea extends React.Component {
constructor(){
super();
this.state = {formId: 1};
}
handleClick(formId) {
this.setState({fromId: formId});
}
render(){
return (
<div>
<GraphSidebar onClickName={handleClick}/>
<GraphContent formId={this.state.formId}/>
</div>
)
}
}
Also I think you say GraphContent doesn't directly render the forms but renders some other component that renders the form. You pass formId as property through the nested components all the way to the component that selects which form to render. For example if GraphContent renders Graph component that renders (selects) the forms you do like this:
<div className="content">
<Graph formId={this.props.formId}/>
</div>
or just
<div className="content">
<Graph formId={formId}/>
</div>
if GraphContent is not a class but a function like
GraphContent = ({formId}) => {
....
}
Let's say Graph is finally the component that renders (selects) the forms then you can use:
Graph = ({formId}) => {
if (formId === 1) {
return(<div>...code for form1</div>)
} else if (formId === 2) {
return(<div>...code for form2</div>)
} ...
}
I'm fairly new to React and OOD so bear with me. Ideally I want to write my application in such a way that it will be easy to reason about and scale well. Rather, make my code as S.O.L.I.D as possible. I'm also concerned about testing my components as I'm new to TDD, as well.
So for example I've already tried writing my code very similar to this:
App.jsx - First Trial
// all other code omitted
class App extends Component {
constructor(props) {
super(props);
this.state = {
roundScore: 0,
lives: 1,
modal: true,
};
}
render() {
return (
<div className="App">
<Header
modal={this.state.modal}
lives={this.state.lives}
score={this.state.score}
/>
</div>
);
}
}
Header.jsx - First Trial
const Header = function(props) {
if (props.modal) {
return (<Logo logo={logo} />);
} else {
return (
<div>
<Lives lives={props.lives} />
<Score score={props.score} />
</div>
);
}
};
const Logo = function(props) {
return (
<div>
<img
src={props.logo}
className="logo logo--xs"
alt="logo"
/>
</div>
);
};
const Score = function(props) {
const text = (props.text ? <strong>{props.text}</strong> : '');
return (
<p className={props.styles}>
{text}
{props.score} pts
</p>
);
};
const Lives = function(props) {
return (
<p className="lives lives--left">
Lives:
{props.lives}
</p>
);
};
export default Header;
The reason I initially setup my code like this was because I had a lot of components and the App.jsx file and wanted to pull some of them out and nest them in wrapper components like <Header /> <Body /> <Footer />.
I began writing some unit tests with Jest and Enzyme for the above mentioned and found that I was having some difficulty testing some of the components in isolation. For example, testing the <Score /> component proved difficult unless I changed my export statement to something like export { Header, Scores, Lives, Logo }; but that doesn't seem like something I should do just for testing?
On top of that I have some propTypes which are required on these components so shallow rendering the <Header /> component and then passing props to <Score /> just to avoid the warnings in my console didn't seem right to me.
So instead I modified my code to the following:
App.jsx - Second Trial
class App extends Component {
constructor(props) {
super(props);
this.state = {
roundScore: 0,
lives: 1,
modal: true,
};
}
render() {
return (
<div className="App">
<Logo modal={this.state.modal} logo={logo} />
<Lives modal={this.state.modal} lives={this.state.lives} />
<Score modal={this.state.modal} score={this.state.roundScore} />
</div>
);
}
}
Header.jsx - Second Trial
const Logo = function(props) {
const styles = (props.modal ? 'logo logo--xs' : 'hide');
return (
<div>
<img
src={props.logo}
className={styles}
alt="I-Spell-Its logo"
/>
</div>
);
};
const Score = function(props) {
const styles = (props.modal ? 'hide' : props.styles)
const text = (props.text ? <strong>{props.text}</strong> : '');
return (
<p className={styles}>
{text}
{props.score} pts
</p>
);
};
const Lives = function(props) {
const styles = (props.modal ? 'hide' : 'lives lives--left')
return (
<p className={styles}>
Lives:
{props.lives}
</p>
);
};
export { Score, Logo, Lives };
Already I've found this setup easier to test, however now I have a few concerns:
Is it okay to use CSS to hide components depending on the application state? Will I be taking any performance hits?
I have several other parent/wrapper components that conditionally render child components depending on state and I feel it will be very difficult (but not impossible) to rewrite a lot of them. Should I bother or accept my mistakes and avoid this next time around?
Ultimately, I'm curious to know if conditionally rendering child components is a violation of Single Responsibility Principle?
Please don't flag this as a question that provokes discussion or opinions. I have done my research; I have read 3 books on React, taken one online course and read several articles and style guides, but I still have difficulty wrapping my head around how to best structure react applications. It would be nice to hear from some of the more experienced members of the community.
I have two types of item, one of which can contain data similar to the other.
Currently when form is used to save an item it saves it then uses browserHistory.push to show the next page.
But I wish add a button that will
save the currently item
redirect them to the form to add the other item type,
partially fill out this form with the data from the first item.
Is there a way to do this using react and not using local storage or session variables?
You should take a look to Redux (or other Flux based libraries) to store data between components and routes, avoiding the excessive prop nesting.
browserHistory.push won't work. It only moves you to a certain location but it doesn't update the application state. You need to update application state, which then will reflect into location update, but not in the opposite direction. Keep in mind that, in React, data comes first, and its representation, even though mutable, doesn't change the data back. The same applies to the location.
To make the redirect alone work, I'd recommend wrapping your component into withRouter higher-order component.
import React, { Component } from 'react';
import { withRouter } from 'react-router';
class MyComponent extends Component {
render() {
return (
<div>
<button
onClick={() => this.props.router.push('/new-location')}>
Click me to go to /new-location
</button>
</div>
);
}
}
But if you need to pass data from one component to another, and the two aren't in hierarchy, I'd agree with Alomsimoy and recommend using Redux. But if, for some reason, it's not an option, you can store this data in a component that is parent to both forms:
class FormA extends Component {
render() {
return (
<form onSubmit={() => this.props.onSubmit()}>
<input
type="text"
value={this.props.inputA}
onChange={(event) => this.props.handleChangeA(event)} />
</form>
);
}
}
class FormB extends Component {
render() {
return (
<form onSubmit={() => this.props.onSubmit()}>
<input
type="text"
value={this.props.inputB}
onChange={(event) => this.props.handleChangeB(event)} />
</form>
);
}
}
while their parent would rule the location and state updates:
class Forms extends Component {
constructor() {
super();
this.state = {};
}
handleChange(name, value) {
this.setState({
[name]: value
});
}
renderForm() {
const {
params: {
stepId
}
} = this.props;
if (stepId === 'step-a') { // <- will be returned for location /form/step-a
return (
<FormA
inputA={this.state.inputA}
handleChangeA={(event) => this.handleChange('inputA', event.target.value)}
onSubmit={() => this.props.router.push('/form/step-b')} />
);
} else if (stepId === 'step-b') { // <- will be returned for location /form/step-b
return (
<FormB
inputB={this.state.inputB}
handleChangeB={{(event) => this.handleChange('inputA', event.target.value)} />
);
}
}
render() {
const {
children
} = this.props;
console.log(this.state); // track changes
return (
<div>
{this.renderForm()}
<button
onClick={() => this.props.router.push('/new-location')}>
Click me to go to /new-location
</button>
</div>
);
}
}
export default withRouter(Forms);
so the route for them would look like
<Route path="form/:stepId" component={Forms} />