I'm doing a singlepage application and would like to switch a component.
Here is an image how it looks like:
If I click on the button in component 3, I will switch the component 3 with 5.
So maybe like component 3 is a view of all projects and if I click on one project, I will see a detail view of the project with some information.
I created two different components for this.
All other components should stay at the same place.
Here is my code how I switch the components:
this.state.detailVisible
? <ProjectDetailView/>
: null
I'm not sure if is the correct react way to do it. Also I have two different css files for component 3 and 5. If I'm switching the two component, I have some class name irritations with my css.
If it's a better way to do it with routers?
How is the react way to do it?
thanks for your help :)
It all depends on your needs, if you need to render both component3 and component5 then a route won't be much of a help.
If you need to render only one of them then a route can be handy.
as for the syntax i prefer this:
this.state.detailVisible && <ProjectDetailView/>
here is a simple example:
const components = ["Component1", "Component2", "Component3"];
class Component extends React.Component {
constructor(props) {
super(props);
this.state = {
showDetails: false
};
this.onComponentClicked = this.onComponentClicked.bind(this);
}
onComponentClicked(e) {
this.setState({
showDetails: !this.state.showDetails
});
}
render() {
const { name } = this.props;
const { showDetails } = this.state;
return (
<div>
<div>Component{name}</div>
<button onClick={this.onComponentClicked}>Toggle Details</button>
{showDetails && <ComponentDetails name={name} />}
</div>
);
}
}
const ComponentDetails = ({ name }) => (
<div>
<div>Details of component{name}</div>
</div>
);
class App extends React.Component {
render() {
return (
<div>
<h2>Parent</h2>
{components.map(c => {
return (
<div>
<Component name={c} />
<hr />
</div>
);
})}
</div>
);
}
}
ReactDOM.render(<App />, document.getElementById("root"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Related
I have two components, one that contains a checkbox and one that's a button. The intent is that this former component let's call it Row, if the checkbox is changed, the edit button would enable itself, and if the checkboxes are not ticked anymore the Edit button disables itself. I was planning on adding onclick event listeners but then I recently read about states and figured this is probably a better idea.
This is my Button:
class EditButton extends React.Component {
constructor() {
super();
this.state = {
clickable: false
}
}
render() {
const {clickable} = this.state
if (clickable) {
return (
<Button className="button-amber">Edit</Button>
)
} else {
return (
<Button disabled>Edit</Button>
)
}
}
}
and this is my Row component
class MyRow extends React.Component {
constructor(props) {
super(props);
this.payload = this.props.payload;
}
render() {
const toRoute = "/foo/" + this.props.payload["id"]
const tags = []
for (const [index, value] of this.props.payload["tags"].entries()) {
tags.push(<Badge>{value}</Badge>)
}
return (
<tr className="white-text">
<td>
<Form.Group>
<Form.Check type="checkbox"/>
</Form.Group>
</td>
<td>
STUFF
</td>
<td>
{this.payload["label"]}
</td>
<td>
{tags}
</td>
<td>
</td>
</tr>
);
}
}
My main app render Might look like so
render(){
<div>
<EditButton/>
<Table>
<MyRow payload=.../>
<MyRow payload=.../>
</Table>
</div>
}
And my intent is if the checkbox is clicked, check the state of all checkboxes to ensure that something is checked, if any checkbox is checked, then change the EditButton clickable state to true. What's the right way to do this? Normally I would use event listeners and individual selectors, but given that I'm using react feels like there should be a more straightforward way to modify the state of that component
There are basically two ways to share state between multiple components:
Shift state into a parent component.
Store the state externally using React Context or a state framework like Redux.
For your specific use case, I would suggest going for the first option. A good way of deciding when to use each option is to decide if the state is local to the sibling components or if it should be globally visible. Meaning that not every bit of your app state needs to be centrally managed.
Using your example, I created this snippet to show how it might work:
class Button extends React.Component {
render() {
const {onClick, disabled} = this.props
return (
<button onClick={onClick} disabled={disabled}>
Button
</button>
)
}
}
class Row extends React.Component {
render() {
const {checked, onChange} = this.props
return (
<input type="checkbox" checked={checked} onChange={onChange} />
)
}
}
class App extends React.Component {
constructor() {
super()
this.state = {
checked: false
}
this.handleCheckboxChange = this.handleCheckboxChange.bind(this)
this.handleButtonClick = this.handleButtonClick.bind(this)
}
handleCheckboxChange(e) {
this.setState({
checked: e.target.checked
})
}
handleButtonClick() {
console.log("Clicked")
}
render() {
const {checked} = this.state
return (
<div>
<Row checked={checked} onChange={this.handleCheckboxChange} />
<Button disabled={!checked} onClick={this.handleButtonClick} />
</div>
)
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(<App />, rootElement);
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="root"/>
The App component handles the state of both the Row and Button components. You can handle how the child components will modify the parent state by providing callbacks that they will call upon some event, like toggling a checkbox.
React's built in state management is very limited, it's the reason a lot of users like to use Redux to handle more complex state were lots of controls interact. Personally I use my own state management using Proxys.
But you can pass setState's between components by passing them via props, below I've created a var called state that keeps track of the useStates for each component. This then allows independent setState calls between components.
I've also used React hooks here instead of class based, but the concept is the same for both..
function Button({state}) {
const butState = React.useState(false);
const [butEnabled] = butState;
state.butState = butState;
return <button
disabled={!butEnabled}
>Button</button>;
}
function Row({state, row}) {
const rowState = React.useState(false);
const [checked, setChecked] = rowState;
state.rows[row] = rowState;
function toggleChecked() {
state.rows[row][0] = !checked;
state.butState[1](state.rows.some(b => b[0]));
setChecked(state.rows[row][0]);
}
return <div>
<input value={checked} type="checkbox" onChange={toggleChecked}/>
</div>
}
function Page() {
const state = {
rows: []
}
return <div>
<Button state={state}/>
<Row state={state} row={1}/>
<Row state={state} row={2}/>
</div>
}
ReactDOM.render(
<Page/>,
document.querySelector('#mount'));
<script crossorigin src="https://unpkg.com/react#17/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom#17/umd/react-dom.development.js"></script>
<div id="mount"/>
I have 2 classes (both React.Component). Let's say, that one of these is my own component, which also built on another custom component (in my case, it's React Places Autocomplete).
Just look at this picture
Code here:
//App.js
import React, { Component } from 'react';
import './App.css';
import PlaceAutocomplete from "./places_autocomplete";
class App extends Component {
constructor(props) {
super(props);
this.state = { output: '' };
}
render() {
return (
<div className="App">
<PlaceAutocomplete/>
<p>{this.state.output}</p>
</div>
);
}
}
export default App;
//places_autocomplete.js
import React, { Component } from 'react';
import './PlaceAutocomplete.css';
import PlacesAutocomplete from 'react-places-autocomplete';
class PlaceAutocomplete extends Component {
constructor(props) {
super(props);
this.state = { address: '', output: '' };
}
handleChange = address => {
this.setState({ address });
};
handleSelect = async address => {
this.setState({address: address});
this.state.output = address;
document.getElementById('lsi').blur();
};
searchOptions = {
types: ['(cities)']
};
hidden = (suggest) => {
return suggest == null || suggest === ""
? "autocomplete-dropdown-container-hidden"
: "autocomplete-dropdown-container";
};
render() {
return (
<PlacesAutocomplete
value={this.state.address}
onChange={this.handleChange}
onSelect={this.handleSelect}
searchOptions={this.searchOptions}>
{({ getInputProps, suggestions, getSuggestionItemProps, loading }) => (
<div>
<input value={this.state.address}
id={'lsi'}
{...getInputProps({
placeholder: 'Select a city',
className: 'location-search-input',
})}/>
<div className={this.hidden(suggestions[1])}>
{loading && <div>Loading...</div>}
{suggestions.map(suggestion => {
const className = suggestion.active
? "suggestion-item--active"
: "suggestion-item";
// inline style for demonstration purpose
const style = suggestion.active
? { backgroundColor: '#fafafa', cursor: 'pointer' }
: { backgroundColor: '#ffffff', cursor: 'pointer' };
return (
<div
{...getSuggestionItemProps(suggestion, {
className: className,
style,
})}
>
<span>{suggestion.description}</span>
</div>
);
})}
</div>
</div>
)}
</PlacesAutocomplete>
);
}
}
export default PlaceAutocomplete;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
So, you can see how I tried to find a solution for this. This code mostly looks ugly, because I don't know any other way to implement these feautures.
System info:
The latest React for 17.08.2018 (I don't really remember, but I do know that it's the latest (I installed it just 1 week ago).
This application created by CRA (Create React Application) template. So please, if your solution won't work with this template (I think there's different styles, like ES6 etc. But it's not the point) then add at least an explanation to your answer.
Try lifting the state up to the parent component and use callbacks to share the data. As stated by the react docs, there should be a single "source of truth" for data changes in a React application - this reduces potential bugs and duplicated code. Take a look at . https://reactjs.org/docs/lifting-state-up.html
Let me start by saying that this example is very simple and can be solved with React.cloneElement. But I want more freedom and the project will be more complex, so I'd like to find a solution.
I would also like to understand what I'm missing :/
I want to be able to augment the children of a Parent component with props and methods (hence the HOC). It would start from here:
<Parent>
<aChild />
<anotherChild />
<yetAnotherChild />
</Parent>
And this is the Parent component (called Sequence in my project), so far:
import React, { Component } from 'react';
const withNotification = handler => Component => props => (
<Component onAnimationEnd={handler} {...props} />
);
class Sequence extends Component {
constructor(props) {
super(props);
this.state = {
pointer: 0,
};
this.notifyAnimationEnd = this.notifyAnimationEnd.bind(this);
this.Children = React.Children.map(this.props.children, Child =>
withNotification(this.notifyAnimationEnd)(Child)
);
}
notifyAnimationEnd() {
// do stuff
}
render() {
return (
<div>
{this.Children.map((Child, i) => {
if (i <= this.state.pointer) return <Child />;
return <div>nope</div>;
})}
</div>
);
}
}
export default Sequence;
I get the following error:
You can play with the code here: https://codesandbox.io/s/6w1n5wor9w
Thank you for any help!
This answer will not solve your problem but maybe gives a hint why this is not possible. At first I was surprised why your code does not work, even though I'm not an experienced React developer it seems ok map this.props.children through with React.Children.map and return the desired Component with your HOC. But it did not work.
I tried to debug it a little bit and did some search. I've learned props.children actually contains the elements itself not the instances of components. Even, React.Children.map does not have any effect on this.
Here is a working snippet proves that your problem is not related with the HOC. I've used an array of components instead of mapping through props.children.
class Line1 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 1</div>;
}
}
class Line2 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 2</div>;
}
}
class Line3 extends React.Component {
componentDidMount() {
setTimeout(this.props.onAnimationEnd, 1000);
}
render() {
return <div>Line 3</div>;
}
}
const withNotification = handler => Component => props => (
<Component onAnimationEnd={handler} {...props} />
);
class Sequence extends React.Component {
constructor(props) {
super(props);
this.state = {
pointer: 0
};
this.notifyAnimationEnd = this.notifyAnimationEnd.bind(this);
this.Arr = [ Line1, Line2, Line3 ];
this.Children = this.Arr.map(Child =>
withNotification(this.notifyAnimationEnd)(Child)
);
}
notifyAnimationEnd() {
this.next();
}
next() {
// Clearly, render the next element only if there is, a next element
if (this.state.pointer >= this.Arr.length - 1) {
return;
}
this.setState({ pointer: this.state.pointer + 1 });
}
render() {
return (
<div>
{this.Children.map((Child, i) => {
if (i <= this.state.pointer) return <Child />;
return <div>nope</div>;
})}
</div>
);
}
}
ReactDOM.render(
<Sequence />,
document.getElementById("app")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
You are returning <Child /> instead of Child in Sequence.js render method. Here is my edited copy - codesandbox
So, I have a class like this:
class Blah extends Component {
constructor(props) {
super(props);
}
handleComponent = (event) => {
let divid = event.target.getAttribute('id');
if (divid === 'col') {
// I want to render component by this condition
} else if (divid === 'ro') {
// or I want to render component by this condition
} else {
//or I want to render component by this condition
}
};
render() {
const { classes } = this.props;
return (
<div>
<div id = 'col' onClick={this.handleComponent}>Sheep</div>
<div id = 'ro' onClick={this.handleComponent}>Cow</div>
<div id = 'ball' onClick={this.handleComponent}>Dog</div>
{ I want to render my component here after click }
</div>
);
}
}
I have another class written on top of this:
class Flow extends Component {
constructor(props){
super(props);
};
render() {
return(
<div style={{background:'somecolor'...blah blah}}>Clap</div>
);
}
}
And I am Passing this by:
var foo = withStyles(styles)(Flow)
I have tried returning components but I am not getting anywhere.
I can use ternary operator but it still will render only one of two but I have three component have three design for each of them.
I want to render one of them to render on some condition as stated above.
If I use states that for toggle that will too have two components for render. Don't go on the code, this is made up, So any Ideas ? Fragments ? Any help will be appreciated. Thank you.
To render component by condition simply use switch statement.
In my example we use state to store current active component.
renderMyComponent method takes care of rendering one of three possible components.
handleChange method changes current state and triggers new render of App component.
This example use class properties plugin.
renderMyComponent = () => {
means autobind and is the same as using in constuctor method
this.renderMyComponent = this.renderMyComponent.bind(this);
Working example:
const ComponentOne = () => <div>Hi, i am component one</div>;
const ComponentTwo = () => <div>Hi, i am component two</div>;
const ComponentThree = () => <div>Hi, i am component three</div>;
class App extends React.Component {
state = { current: 0 }
renderMyComponent = () => {
// Our switch conditional render
switch(this.state.current) {
case 0:
return <ComponentOne />;
case 1:
return <ComponentTwo />;
case 2:
return <ComponentThree />;
default:
return null;
}
}
handleChange = (event) => {
// We are looking for data-trigger attribute
// In this example we expect type number but trigger holds string
// That's why we 'cast' to a number using Number()
const current = Number(event.target.dataset.trigger);
// Sets new state of current component and triggers new render
this.setState({ current })
}
render() {
return (
<div>
<div>
Pick component to render
<button
type="button"
data-trigger="0"
onClick={this.handleChange}
>
Render 1
</button>
<button
type="button"
data-trigger="1"
onClick={this.handleChange}
>
Render 2
</button>
<button
type="button"
data-trigger="2"
onClick={this.handleChange}
>
Render 3
</button>
</div>
{this.renderMyComponent()}
</div>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="root"></div>
Reviewing your code:
You don't need constructor here.
...
constructor(props){
super(props);
}
...
I asked a question on here earlier, but I think I should have been more specific. Say I have two components, which I call from a higher order component:
So my higher order component, containing these components, looks like this (or at least its render method):
<div>
<Navigation />
<View />
</div>
Now, Navigation /> contains a button. Whenever that button is clicked, I want to set the focus on an input field, which is in <View />.
If both things were in the very same component, I learned that I would do something like this:
<button onClick={() => {this.myInp.focus()}}>Focus Input</button>
<input type="text" ref={(ip) => this.myInp = ip} />
I am using Redux by the way, if this should make a difference.
Since they are descendants of the same element you can use a state element and function to change that state element in the parent div and pass down as props to View and Navigation.
constructor(){
super()
this.state = {
inputFocus:false;
}
this.setInputFocus = this.setInputFocus.bind(this)
}
setInputFocus(value){
this.setState({inputFocus:false});
}
Then in the render method of the div just pass them down as props
<div>
<Navigation setInputFocus={this.setInputFocus}/>
<View inputFocus={this.state.inputFocus}/>
</div>
In the Navigation
<button onClick={() => {this.props.setInputFocus(true)}}>Focus Input</button>
and in the View
<input type="text" ref={(ip) => this.myInp = ip}/>
and have this in the componentWillReceiveProps which detects when props change and runs actions then.
componentWillReceiveProps(nextProps){
if(nextProps.inputFocus)
this.myInp.focus()
}
So when the inputFocus changes in the parent it will fire the componentWillReceiveProps in the View element and focus the input.
Along with the refs, this problems also involves child to parent and parent to child interaction.
So what I am doing in the below snippet is form the Navigation component, I have to call the parent component since a component can't directly have access to its siblings. From the parent you can get access to the child component through refs and in turn you can also access the refs which are defined in the child component
See the snippet below
class App extends React.Component {
focusInput = () => {
this.myView.myInput.focus()
}
render() {
return (
<div>
<Navigation focusInput={this.focusInput}/>
<View ref={(view) => {this.myView = view}}/>
</div>
)
}
}
class Navigation extends React.Component {
render() {
return (
<button onClick={() => this.props.focusInput()}>Focus</button>
)
}
}
class View extends React.Component {
render() {
return (
<input type="text" ref={(ip) => this.myInput = ip}/>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
In case you have multiple buttons in navigation thorugh which you want to focus multiple different inputs, you can do so in the following manner
class App extends React.Component {
focusInput = (ip) => {
this.myView[ip].focus()
}
render() {
return (
<div>
<Navigation focusInput={(val) => this.focusInput(val)}/>
<View ref={(view) => {this.myView = view}}/>
</div>
)
}
}
class Navigation extends React.Component {
render() {
return (
<div>
<button onClick={() => this.props.focusInput("myInput1")}>Focus1</button>
<button onClick={() => this.props.focusInput("myInput2")}>Focus2</button>
</div>
)
}
}
class View extends React.Component {
render() {
return (
<div>
<input type="text" ref={(ip) => this.myInput1 = ip}/>
<input type="text" ref={(ip) => this.myInput2 = ip}/>
</div>
)
}
}
ReactDOM.render(<App/>, document.getElementById('app'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>