I need to implement a kind of Master/Detail View for a Web Application in React. Since the app should be integrated into a CakePHP app I can't use React Router for handling the routes (since CakePHP would process them).
I have a List of Items and want to navigate through them, showing a Detail View. Items are nested, so there're SubItems to navigate to.
For now I got a ItemList Component, showing a list of Cards with a clickhandler. How can I change the View without changing the url?
ItemList Component looks like:
class ItemList extends React.Component {
constructor(props) {
super(props);
this.state = {
itemList: []
}
}
componentDidMount() {
fetchItems(...)
}
render() {
return(
<div>
{this.state.itemList.map(item => (
<Item key={item.id} item={item} />
))}
</div>
);
}
}
Item Component looks like:
class Item extends React.Component {
constructor(props) {
super(props);
this.state = {
item: props.item,
}
}
handleClick = () => {
// How to navigate to another Component?
}
render() {
return(
<div>
<div className="card my-2" onClick={this.handleClick}>
<div className="card-body">
<h5 className="card-title">{this.state.item.title}</h5>
<p className="card-text">{this.state.item.description}</p>
</div>
</div>
</div>
);
}
}
Thanks in advance!
You should have a parent component (let's say MainView) that has a state (let's say selectedItemId).
class MainView extends React.Component {
constructor(props) {
super(props);
this.state = {
selectedItemId: [null]
}
}
componentDidMount() {
}
render() {
return(
{!selectedItemId && (<ItemList />)}
{selectedItemId && (
<ItemDetail id={selectedItemId} />
)}
);
}
}
As you can see, it renders different components based on the selectedItemId state value.
Inside the ItemList handleClick you call the setState of the parent MainView to set the selected item ID.
So using conditional rendering inside the render() function of MainView you can render the ItemList when no item is selected and ItemDetail when you have selected one.
I'm not really used to ES6 syntax components so my code can be wrong somewhere, but you can get the message ;)
Related
I'm loading some react components on demand (among with other information) depending on user input.
The components to render are kept in an array and the render method uses array.map to include the components.
The problem is, that if I trigger a forceUpdate() of the main app component, the mapped components won't update.
Code example: https://codesandbox.io/s/react-components-map-from-array-ekfb7
The dates are not updating because you are creating the instance of the component in your add function, and from then on you are referencing that instance without letting react manage the updates.
This is why storing component instances in state or in other variables is an anti-pattern.
Demonstration of the problem
Below I've created a working example still using forceUpdate just to prove what the issue is.
Notice instead of putting the component in state, I'm just pushing to the array to increase it's length. Then React can manage the updates correctly.
class TestComponent extends React.Component {
render() {
return <p>{Date.now()}</p>;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.comps = [1];
}
add() {
this.comps.push(1);
this.forceUpdate();
}
render() {
return (
<div className="App">
<h1>Components map example</h1>
<p></p>
<h2>Static TestComponent (ok):</h2>
<TestComponent />
<h2>TestComponents mapped from an array (not ok):</h2>
{this.comps.map((comp, id) => {
return <div key={id}><TestComponent /></div>;
})}
<h2>All should update when the App component renders</h2>
<p>
<button onClick={() => this.add()}>Add TestComponent</button>
<button onClick={() => this.forceUpdate()}>forceUpdate App</button>
</p>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
This is still a less than ideal solution. But it does show where the issue lies.
A better solution
If you need to know more about each component instance up front, you can make the array more complex.
I would also suggest using state to store the comps array, and removing forceUpdate completely.
class TestComponent extends React.Component {
render() {
return <p>{Date.now()} {this.props.a} {this.props.b}</p>;
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
comps: [{ a: 'a', b: 'b' }]
}
}
add = () => {
// add your custom props here
this.setState(prev => ({comps: [ ...prev.comps, { a: 'c', b: 'd' } ]}));
}
render() {
return (
<div className="App">
<h1>Components map example</h1>
<p></p>
<h2>Static TestComponent (ok):</h2>
<TestComponent />
<h2>TestComponents mapped from an array (not ok):</h2>
{this.state.comps.map((compProps, id) => {
return <div key={id}><TestComponent {...compProps} /></div>;
})}
<h2>All should update when the App component renders</h2>
<p>
<button onClick={() => this.add()}>Add TestComponent</button>
</p>
</div>
);
}
}
ReactDOM.render(<App/>,document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Now notice that each component in the map callback can have it's own unique set of props based on whatever logic you what. But the parts that should re-render will do so correctly.
In order to update in React, you have to put your data in the state and then setState.
setState() schedules an update to a component’s state object. When state changes, the component responds by re-rendering which means updating the screen with the new state.
import React from "react";
import "./styles.css";
class TestComponent extends React.Component {
render() {
return <p>{Date.now()}</p>;
}
}
export class App extends React.Component {
constructor(props) {
super(props);
this.state = {
comps: [<TestComponent />],
}
}
add = () => {
this.setState({ comps: this.state.comps.concat(<TestComponent />) })
}
render() {
return (
<div className="App">
<h1>Components map example</h1>
<p></p>
<h2>Static TestComponent (ok):</h2>
<TestComponent />
<h2>TestComponents mapped from an array (not ok):</h2>
{
this.state.comps.map((comp, id) => {
return <div key={id}>{comp}</div>;
})
}
<h2>All should update when the App component renders</h2>
<p>
<button onClick={this.add}>Add TestComponent</button>
</p>
</div>
);
}
}
I am trying to add a className to a child component of layouts/index.js in React with Gatsby. How can I pass the className in props to be used with the component when the onClick is registered in another component?
index.js
class Template extends React.Component {
constructor(props) {
super(props)
this.state = {
navIsVisible: false,
}
this.handleNavIsVisible = this.handleNavIsVisible.bind(this)
}
handleNavIsVisible() {
this.setState({
navIsVisible: !this.state.navIsVisible
})
}
render() {
const { children } = this.props
return (
<div>
<MenuButton navIsVisible={this.handleNavIsVisible}/>
<div className="page">
...
</div>
{/* Adding the class here seems to be the best option but does not activate onClick, yet does if adding to a div with Menu contained */}
<Menu className={`${this.state.navIsVisible ? 'nav-is-visible' : ''}`}/>
</div>
)
}
}
MenuButton.js to activate the class onClick
class MenuButton extends Component {
constructor(props) {
super(props)
this.menuClick = this.menuClick.bind(this)
}
menuClick(){
this.props.navIsVisible();
}
render () {
return (
<div className="sticky-menu-button">
<span onClick={this.menuClick}>Menu</span>
</div>
)
}
}
MenuButton.propTypes = {
navIsVisible: PropTypes.func,
}
Alternatively, within Menu.js but unsure how to pass the state change to this component?
The reason it works on a div and not on your Menu component is that when you pass it to a div it adds that class "directly" to the div HTML element but when you pass it to your component it just passes it as a prop. It really depends on what you do inside the render method of the Menu component and what you return from it. If you make sure to grab that prop and attach it to whatever it renders it will work just as it did on a div.
eg:
class Menu extends Component {
render () {
return (
<div className={this.props.className}>
<p> Menu Component </p>
</div>
)
}
}
I have a component (LoginScreen). In that component I want to display my Login component as the first thing the user sees. When user clicks on sign up button, in my Loginscreen component, the Signup Component should be rendered instead. From the signup Component the user finds a button 'Back to Login' and when clicked, again the Login Component should be rendered insight the componentt Loginscreen. Im new to React and trying to follow tutorials about how to share data among parent/child and among siblings but am completely confused. Any help would be amazing!
class Loginscreen extends React.Component{
constructor(props) {
super(props)
this.state = {
status:false
}
this.changeStatus = this.changeStatus.bind(this);
}
changeStatus(status) {
this.setState({
status: true
});
}
render() {
return (
<div>
<Login status={this.state.status}/>
<Signup status={this.state.status}/>
<p>No account yet?</p>
<button onClick={this.changeStatus}>Sign up</button>
// if Signup Component is rendered insight Loginscreen, then this button should also not be rendered.
</div>
)
}
}
class Signup extends React.Component {
...
//where can I even call this function in my case?
handleChange() {
const status:true;
this.props.onClick(status);
}
...
<button><Link to='/loginscreen'>Back to Login</Link></button>
...
}
class Login extends React.Component {
...
...
}
Ok, I believe you are looking for routing?
Solution 1 (recommended):
Using React-Router to handle the routing and the React-Router/Link component will handle the switching.
Solution 2:
Using a simple state routing, saving the view name in the parent component and display the view based on it, also passing a function to update this view:
class App extends React.Component{
constructor(props) {
super(props)
this.state = {
view: 'login' // its login because we want it to be the default
}
this.changeView = this.changeView.bind(this);
}
changeView(view) {
this.setState({
view // ES6, if the key & value variable name the same, just type it once.
});
}
render() {
const { view } = this.state; // thats es6 destructuring, use it to make the variables clean instead of repeating the this.state/this.props
return (
<div>
{
view == 'login'
? (<Login changeView={this.changeView}/>)
: (<Signup changeView={this.changeView}/>)
}
</div>
)
}
}
class Signup extends React.Component {
...
render(){
const { changeView } = this.props;
<div className="Signup">
{/* Signup Form Here */}
<p>Already registered?</p>
{/* Wrapping the event in arrow function to avoid auto invoke */}
<button onClick={() => changeView('login')}>Login</button>
</div>
}
...
}
class Login extends React.Component {
...
render(){
const { changeView } = this.props;
<div className="Login">
{/* Login Form Here */}
<p>No account yet?</p>
<button onClick={() => changeView('signup')}>Sign up</button>
</div>
}
...
}
If there are more than 2 views you can wrap the return in a normal If statement, or move it in a separate method.
or you can use a dynamic component rendering, something like this:
render() {
const { view } = this.state;
const ViewComponent = require(`./views/${view}.jsx`);
return (<div><ViewComponent.default changeView={this.changeView} /></div>);
}
I want to create a reusable component where the DOM structure can be different each time the component is rendered. Let's say I have this
class Comp extends React.Component {
constructor() {
super()
this.state = {
click: null,
}
}
render() {
return(
<div>
{this.props.chidren}
</div>
)
}
handleButton1() {
this.setState({click: 'button1'});
}
handleButton2() {
this.setState({click: 'button2'});
}
}
class SubComp1 extends React.Component {
render() {
return(
<button onClick={() => this.props.handleButton1()}>Button 1</button>
)
}
}
class SubComp2 extends React.Component {
render() {
return (
<button onClick={() => this.props.handleButton2()}>Button 2</button>
)
}
}
ReactDOM.render((
<Comp>
<div id="somediv">
<div id="andanother">
<SubComp1 />
</div>
</div>
<div id="andanotherother">
<SubComp2 />
</div>
</Comp>), document.getElementById('app'))
Currently, the two subcomponents do not have access to their respective handler functions. What's the best way of passing the functions handleButton1 and handleButton2 to the subcomponents assuming that their position in the DOM is dynamic and might change depending on the layout of the page.
I have thought of 2 solutions so far:
Iterating inside the props.children until I find the element of interest then clone it with the property
Using ref and somehow render the subcomponents after the main component has been rendered through the componentDidMount callback.
What are your thoughts on this?
This is a place where using React's Context would be the most straightforward solution.
Another solution would be to use Redux actions, but that would make your component less reusable and more tightly coupled with your application, which you may or may not care about.
Why not do something like this:
class Comp extends React.Component {
constructor() {
super()
this.state = {
click: null,
}
}
render() {
return(
<div>
{this.props.chidren}
</div>
)
}
handleButton(button) {
this.setState({click: button});
}
}
Then in the subcomponents you can do something like
class SubComp1 extends React.Component {
render() {
return(
<button onClick={() => this.props.handleButton('button1')}>Button 1</button>
)
}
}
class SubComp2 extends React.Component {
render() {
return (
<button onClick={() => this.props.handleButton('button2')}>Button 2</button>
)
}
}
One Alternative option which might fit your needs is to build a higher order component, which decorates another component with some additional functionality, below is a quick example of how this may work for you,
The higher order component:
const Comp = ComposedComponent =>
class Comp extends React.Component {
constructor(props) {
super(props)
this.handleButton = this.handleButton.bind(this);
this.state = {
click: null,
}
}
handleButton(button) {
this.setState({click: button});
}
render() {
return(
<ComposedComponent
onClick={this.handleButton}
/>
)
}
}
export default Comp;
The child component:
class SubComp1 extends React.Component {
render() {
return(
<button onClick={() => this.props.onClick('button1')}>Button 1</button>
)
}
}
How to use it:
const ExtendedComp = Comp(SubComp1);
<ExtendedComp />
would this be suitable for your task?
There is a main component, which uses a menu component. The menu component is using a state property to save the information about selected menu item. But now I need to get the selected module in the main component. How do I do that?
class Main extends Component {
doSomething(module) {
console.log(module) // should get 'targetValue'
// I need to get the info, which module is selected.
// This info is stored as a state value in the `MainMenu` Component
// How do I get this information? I can't use the parameter `selectModule` as it is done here.
}
render() {
return (
<div>
<MainMenu />
<Button
onClick={ this.doSomething.bind(this, selectedModule) }
/>
</div>
)
}
}
In this component a menu is generated for each module (of modules array). By clicking on one item, this module is stored into module state variable.
class MainMenu extends Component {
constructor(props) {
super(props)
this.state = {
module: 'initialValue'
}
}
selectModule(module) {
this.setState({ module })
}
render() {
return (
<Menu>
<Menu.Item onClick={ this.selectModule.bind(this, 'targetValue') } >
{ title }
</Menu.Item>
</Menu>
)
}
}
Instead of doing some magic and examining internal state if children components lift the state to parent. Child becomes stateless.
class Main extends Component {
state = {
module: 'initialValue'
}
setActiveModule = (module) => {
this.setState({ module })
}
render() {
return (
<MainMenu onChange={this.setActiveModule} />
)
}
}
class MainMenu extends Component {
onClick = (module) => () => {
this.props.onChange(module)
}
render() {
return (
<Menu>
<Menu.Item onClick={this.onClick(title)} >
{title}
</Menu.Item>
</Menu>
)
}
}
Instead on maintaining the state in MainMenu component, maintain in parent component Main, and pass the module value in props, also pass a function to MainMenu to update the state of parent component Main from child MainMenu.
Write it like this:
class Main extends Component {
constructor(props) {
super(props)
this.state = {
module: 'initialValue'
}
this.update = this.update.bind(this);
}
update(value){
this.setState({
module: value
});
}
doSomething(){
console.log(this.state.module);
}
render() {
return (
<div>
<MainMenu module={this.state.module} update={this.update}/>
<Button
onClick={ this.doSomething.bind(this) }
/>
</div>
)
}
}
class MainMenu extends Component {
selectModule(module) {
this.props.update(module);
}
render() {
console.log(this.props.module);
return (
<Menu>
<Menu.Item onClick={this.selectModule.bind(this, 'targetValue') } >
{ title }
</Menu.Item>
</Menu>
)
}
}
Sharing state with react is sometimes a bit hard.
The react philosophy tends to say that we have to manage state from top to bottom. The idea is to modify the state in your parent, and pass the informations as props. For example, let's imagine the following scenario :
class Main extends React.Component {
contructor(props) {
super(props);
this.state = { currentMenuSelected: 'Home' };
}
onPageChange(newPage) {
this.setState({ currentMenuSelected: newPage });
}
render() {
return(
<div>
<AnotherComponent currentMenu={this.state.currentMenuSelected} />
<MenuWrapper onMenuPress={this.onPageChange} />
</div>
)
}
}
In my example, we tell the MenuWrapper to use the Main.onPageChange when changing page. This way, we're now able to pass that current selected menu to AnotherComponent using props.
This is the first way to manage state sharing using react, and the default one provided by the library
If you want to manage more complex stuff, sharing more state, you should take a look at the flux architecture https://facebook.github.io/flux/docs/overview.html
and the most common implementation of flux : http://redux.js.org/
Store the menu state in the main component, and pass the state updater down to the menu.
This is quite helpful in getting into top-down state
https://facebook.github.io/react/docs/thinking-in-react.html