Example code:
const menu = [
{type: "home", visSelector: someSelector},
{type: "accounts", visSelector: anotherSelector}
]
const filterMenuItems = (state, menu) =>
menu.filter(i => i.visSelector(state))
const mapStateToProps = state => {
menuItems: filterMenuItems(state, menu)
}
What is the right way to memoize filterMenuItems?
visSelector are already memoized. Unfortunately, result of this filtration rerender component every time.
Provided the items are pure component, they won't all be re-rendered every time. Only the ones you're adding back after having previously removed them.
For instance, here we start out with all four children shown. If you uncheck one of the checkboxes, two of the children disappear, but no children are re-rendered. It's only if you add them back that they're re-rendered.
const Child = props => {
console.log("render", props.name);
return <div>{props.name}</div>
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.children = [
<Child key={0} name="zero" />,
<Child key={1} name="one" />,
<Child key={2} name="two" />,
<Child key={3} name="three" />
];
this.state = {
showOdds: true,
showEvens: true
};
}
evensClick = event => {
this.setState({showEvens: event.currentTarget.checked});
};
oddsClick = event => {
this.setState({showOdds: event.currentTarget.checked});
};
render() {
const {showEvens, showOdds, counter} = this.state;
return <div>
<label>
<input type="checkbox" onChange={this.evensClick} checked={showEvens} />
Show evens
</label>
<label>
<input type="checkbox" onChange={this.oddsClick} checked={showOdds} />
Show odds
</label>
<div>
{this.children.filter((child, index) => index % 2 == 0 ? showEvens : showOdds)}
</div>
</div>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.development.js"></script>
I don't think there's a way to prevent them being re-rendered when being added back.
Alternatively, you could use CSS to manager their visibility, which would prevent re-rendering them:
const Child = props => {
console.log("render", props.name);
return <div className={props.index % 2 == 0 ? "even" : "odd"}>{props.name}</div>
}
class Example extends React.Component {
constructor(...args) {
super(...args);
this.children = [
<Child index={0} key={0} name="zero" />,
<Child index={1} key={1} name="one" />,
<Child index={2} key={2} name="two" />,
<Child index={3} key={3} name="three" />
];
this.state = {
showOdds: true,
showEvens: true
};
}
evensClick = event => {
this.setState({showEvens: event.currentTarget.checked});
};
oddsClick = event => {
this.setState({showOdds: event.currentTarget.checked});
};
render() {
const {showEvens, showOdds, counter} = this.state;
const cls = (showEvens ? "" : "hide-evens ") + (showOdds ? "" : "hide-odds");
return <div>
<label>
<input type="checkbox" onChange={this.evensClick} checked={showEvens} />
Show evens
</label>
<label>
<input type="checkbox" onChange={this.oddsClick} checked={showOdds} />
Show odds
</label>
<div className={cls}>
{this.children}
</div>
</div>;
}
}
ReactDOM.render(
<Example />,
document.getElementById("root")
);
.hide-evens .even {
display: none;
}
.hide-odds .odd {
display: none;
}
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.4.0/umd/react.development.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.4.0/umd/react-dom.development.js"></script>
Array.filter() returns a new array on each call. You might want to try and memoize filterMenuItems based on your application requirements.
In the example below filterMenuItems, returns a new array only when state or menu arguments change:
import { createSelector } from 'reselect';
const filterMenuItems = createSelector(
state => state,
(state, menu) => menu,
(state, menu) => menu.filter(i => i.visSelector(state))
);
Related
I learn React and I faced some problems. I thought I understand controlled components, but well, it seems otherwise. Could you explain me, why after onChange event I receive props for rest Input components? If I want add label that depends on this.pops.name it gets messy (because of props I see in console.log). I would be grateful for explanation.
import React, { Component } from "react";
class Input extends Component {
handleChange = (e) => {
this.props.onInputChange(e);
};
chooseLabel = (props) => {
let { name } = props;
console.log(name);
if (name === "cost") {
return (
<label className="label" htmlFor={name}>
How much do you earn per hour?
</label>
);
} else if (name === "salary") {
return (
<label className="label" htmlFor={name}>
How much do you want to spend?
</label>
);
} else {
return null;
}
};
render() {
console.log("this.props.nevVal", this.props.newVal);
console.log("this.props.name", this.props.name);
return (
<div>
{this.chooseLabel(this.props)}
<input
className="Input"
value={this.props.newVal}
name={this.props.name}
placeholder={this.props.name}
onChange={this.handleChange}
/>
</div>
);
}
}
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
salary: "",
cost: "",
tip: ""
};
}
handleInputChange = (e) => {
console.log("E.target.name", e.target.name);
this.setState({ [e.target.name]: e.target.value });
};
render() {
return (
<div className="App">
<Input
name="salary"
newVal={this.state.salary}
onInputChange={this.handleInputChange}
/>
<Input
name="cost"
newVal={this.state.cost}
onInputChange={this.handleInputChange}
/>
<Input
name="tip"
newVal={this.state.tip}
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
Link to Codesandbox.io
When one of the inputs is changed, you update the state of your App component. And when a state of a component is updated, it re-renders, and all its children too. So all the inputs are re-rendered (and their newVal are logged to the console), even if you change the value of a single input.
I tried to illustrate it in the following snippet :
class Input extends React.Component {
handleChange = (e) => {
this.props.onInputChange(e);
};
chooseLabel = (props) => {
let { name } = props;
// console.log(name);
if (name === "cost") {
return (
<label className="label" htmlFor={name}>
How much do you earn per hour?
</label>
);
} else if (name === "salary") {
return (
<label className="label" htmlFor={name}>
How much do you want to spend?
</label>
);
} else {
return null;
}
};
render() {
console.log(`${this.props.name} input renderd`)
return (
<div>
{this.chooseLabel(this.props)}
<input
className="Input"
value={this.props.newVal}
name={this.props.name}
placeholder={this.props.name}
onChange={this.handleChange}
/>
</div>
);
}
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
salary: "",
cost: "",
tip: ""
};
}
handleInputChange = (e) => {
this.setState({ [e.target.name]: e.target.value });
};
render() {
console.log("App component and all its children rendered :")
return (
<div className="App">
<Input
name="salary"
newVal={this.state.salary}
onInputChange={this.handleInputChange}
/>
<Input
name="cost"
newVal={this.state.cost}
onInputChange={this.handleInputChange}
/>
<Input
name="tip"
newVal={this.state.tip}
onInputChange={this.handleInputChange}
/>
</div>
);
}
}
const rootElement = document.getElementById("root");
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
rootElement
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
Was that your question ?
Simply, imagine a List page with "add/edit" modal (bootstrap) to manage items in the list.
The list itself is a react component:
<List Items={items} />
The List component renders array of list items which are react components too:
<ListItem Item={item} />
The "add/edit" form is also a react component as well:
<ItemForm Item={item} />
each item in the list will contain edit link that will trigger add/edit modal to show and populate the form with the values of selected item to edit.
The question is how can I pass item object to the form <ItemForm /> for edit using the edit link in the <ListItem /> that is nested inside <List />
class ListItem extends React.Component {
render() {
return (
<li>
<label>{this.props.Item.Name}</label> -
<a> Edit </a>
</li>
);
}
}
class List extends React.Component {
render() {
const rows = [];
this.props.Items.forEach(item => {
rows.push(<ListItem key={item.ItemID} Item={item} />);
});
return (
<ul>
{rows}
</ul>
);
}
}
var sampleItems = [
{ItemID: 1, Name: 'item 1'},
{ItemID: 2, Name: 'item 2'},
{ItemID: 3, Name: 'item 3'}
]
ReactDOM.render(
<List Items={sampleItems} />,
document.getElementById("list")
);
class ItemForm extends React.Component {
constructor(props) {
super(props);
this.state = { Name: '' };
this.handleInputChange = this.handleInputChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
}
handleInputChange(event) {
const target = event.target;
const name = target.name
this.setState({
[name]: event.target.value
});
}
handleSubmit(event) {
event.preventDefault();
//handles submit code
alert(this.state.Name + ' saved!');
}
render() {
return (
<form key="item-form" onSubmit={this.handleSubmit}>
<div>
<label>Item Name:</label>
<input
name="Name"
type="text"
value={this.state.Name}
onChange={this.handleInputChange} />
</div>
<button type="submit">Save changes</button>
</form>
);
}
}
ReactDOM.render(
<ItemForm />,
document.getElementById("form-container")
);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<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="list"></div>
<hr/>
<div id="form-container"></div>
<hr/>
I want to print a new <ul> list of <li> movies.
I don't see any list nor elements.
I also get a warning:
index.js:2178 Warning: A component is changing an uncontrolled input of type text to be controlled. Input elements should not switch from uncontrolled to controlled (or vice versa). Decide between using a controlled or uncontrolled input element for the lifetime of the component. More info: https://reactjs.org/docs/forms.html#controlled-components
in input (at index.js:54)
in label (at index.js:52)
in form (at index.js:51)
in div (at index.js:50)
in Movie (at index.js:70)
This is my code:
class Movie extends React.Component {
constructor(props) {
super(props);
this.state = {value: '',
list: [],
checked: true
};
this.handleChange = this.handleChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this);
this.addMovie = this.addMovie.bind(this);
this.listMovies = this.listMovies.bind(this);
}
handleChange(event) {
const target = event.target;
const value = target.type === 'checkbox' ? target.checked : target.value;
const name = target.name;
this.setState({
[name]: value
});
}
handleSubmit(event) {
event.preventDefault();
this.addMovie();
}
addMovie(value){
this.setState({ list: [...this.state.list, value] });
console.log(...this.state.list);
}
listMovies(){
return(
<ul>
{this.state.list.map((item) => <li key={this.state.value}>{this.state.value}</li>)}
</ul>
);
}
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Movie name:
<input name="movieName" type="text" value={this.state.movieName} onChange={this.handleChange} />
Favorite?
<input name="favorite" type="checkbox" checked={this.state.favorite} onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
<button onClick={this.listMovies}>
List Movies
</button>
</div>
);
}
}
ReactDOM.render(
<Movie />,
document.getElementById('root')
);
I would really want to print only my Favorites movies
I'm guessing you want a simple movies list with favorites. Not the best one but working code:
import React from 'react';
import { render } from 'react-dom';
class App extends React.Component {
state = {
favorite: false,
movieName: "",
movies: [],
filter: true,
};
handleChange = (event) =>
event.target.name === "favorite"
? this.setState({ [event.target.name]: event.target.checked })
: this.setState( { [ event.target.name]: event.target.value } );
handleSubmit = ( event ) => {
event.preventDefault();
this.setState({
movies: [...this.state.movies, {name: this.state.movieName, favorite: this.state.favorite }]
});
}
listFavoriteMovies = () => (
<ul>
{this.state.movies
.filter( movie => movie.favorite )
.map( movie => <li>{movie.name}</li>)}
</ul>
);
listAllMovies = () => (
<ul>
{this.state.movies
.map(movie => <li>{movie.name}</li>)}
</ul>
);
changeFilter = () =>
this.setState( prevState => ( {
filter: !prevState.filter,
}))
render() {
return (
<div>
<form onSubmit={this.handleSubmit}>
<label>
Movie name:
<input name="movieName" type="text" onChange={this.handleChange} />
Favorite?
<input name="favorite" type="checkbox" onChange={this.handleChange} />
</label>
<input type="submit" value="Submit" />
</form>
<p>Showing only favorite movies.</p>
<ul>
{
this.state.filter
? this.listFavoriteMovies()
: this.listAllMovies()
}
</ul>
<button onClick={this.changeFilter}>Click toggle for all/favorites.</button>
</div>
);
}
}
render(<App />, document.getElementById('root'));
if you initially pass undefined or null as the value prop, the
component starts life as an "uncontrolled" component. Once you
interact with the component, we set a value and react changes it to a
"controlled" component, and issues the warning.
In your code initialise movieName in your state to get rid of warning.
For more information check here
I'd like to add a new input everytime the plus icon is clicked but instead it always adds it to the end. I want it to be added next to the item that was clicked.
Here is the React code that I've used.
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add</i>
<i>Remove</i>
</div>
</div>
);
class TodoApp extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [Input]
};
}
addInput = index => {
this.setState(prevState => ({
choices: update(prevState.choices, { $splice: [[index, 0, Input]] })
}));
};
render() {
return (
<div>
{this.state.choices.map((Element, index) => {
return (
<Element
key={index}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
ReactDOM.render(<TodoApp />, document.querySelector("#app"));
<div id="app"></div>
<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>
I must admit this get me stuck for a while but there was a problem with how react deals with key props. When you use an index as a key it doesn't work. But if you make sure inputs will always be assigned the same key even when the list changes it will work as expected:
const Input = props => (
<div className="answer-choice">
<input type="text" className="form-control" name={props.index} />
<div className="answer-choice-action">
<i onClick={props.addInput}>add </i>
<i>Remove</i>
</div>
</div>
);
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
choices: [],
nrOfElements: 0
};
}
addInput = index => {
this.setState(prevState => {
const choicesCopy = [...prevState.choices];
choicesCopy.splice(index, 0, `input_${prevState.nrOfElements}`);
return {
choices: choicesCopy,
nrOfElements: prevState.nrOfElements + 1
};
});
};
componentDidMount() {
this.addInput(0);
}
render() {
return (
<div>
{this.state.choices.map((name, index) => {
return (
<Input
key={name}
addInput={() => {
this.addInput(index);
}}
index={index}
/>
);
})}
</div>
);
}
}
Some reference from the docs:
Keys should be given to the elements inside the array to give the
elements a stable identity...
...We don’t recommend using indexes for keys if the order of items may
change. This can negatively impact performance and may cause issues
with component state.
What implementation is needed to render different components from render method. As you can see below the idea is that Survey component receives an array which contains different components names (could be Input, CheckList, Dropdown, File). The array passes as property to Survey Component is generated properly depending of what button is clicked, but at the time to render different components is not working. I'm using JsComplete to test it.
const Dropdown = () =>{
return(
<div>
<select>
<option value="initial" selected>Select...</option>
<option value="Option ">Option 1</option>
<option value="Option ">Option 2</option>
</select>
</div>
)
}
const Checklist = () =>{
return(
<div>
<h4>Question name</h4>
<label>
Option 1:
<input
name="pl"
type="checkbox" />
</label>
<label>
Option 2:
<input
name="tz"
type="checkbox" />
</label>
</div>
)
}
const Input = () =>{
return(
<div>
<label>
Question name:
<input
name="input"
type="text" />
</label>
</div>
)
}
const File = () =>{
return(
<div>
<label>
Upload:
<input
name="file"
type="file" />
</label>
</div>
)
}
class Survey extends React.Component {
constructor(props){
super(props);
}
render(){
var ChildName ;
for (var i = 0; i < this.props.components.length; i++) {
log("Log:" + this.props.components[i]);
ChildName = this.props.components[i];
return <ChildName />;
}
return (
false
)
}
}
class Form extends React.Component {
handleSubmit = (name) => {
this.props.onSubmit(name);
};
render() {
return (
<div id="components">
<button onClick={()=>this.handleSubmit("Input")} name="Input">Input</button>
<button onClick={()=>this.handleSubmit("Checklist")} name="Checklist">Checkbox</button>
<button onClick={()=>this.handleSubmit("Dropdown")} name="Dropdown">Dropdown</button>
<button onClick={()=>this.handleSubmit("File")} name="File">File</button>
<div id="new-question">
</div>
</div>
)
}
}
class App extends React.Component {
state = {
components: []
};
addNewElement = (element) => {
this.setState(prevState => ({
components: prevState.components.concat(element)
}));
};
render() {
return (
<div>
<Form onSubmit={this.addNewElement} />
<Survey components={this.state.components} />
</div>
);
}
}
ReactDOM.render(<App />, mountNode);
Try this. Dont pass string in handleSubmit method. Instead pass component itself like this:
class Form extends React.Component {
handleSubmit = (name) => {
this.props.onSubmit(name);
};
render() {
return (
<div id="components">
<button onClick={()=>this.handleSubmit(Input)} name="Input">Input</button>
<button onClick={()=>this.handleSubmit(Checklist)} name="Checklist">Checkbox</button>
<button onClick={()=>this.handleSubmit(Dropdown)} name="Dropdown">Dropdown</button>
<button onClick={()=>this.handleSubmit(File)} name="File">File</button>
<div id="new-question">
</div>
</div>
)
}
}
Also in you survey component return the elements like this
class Survey extends React.Component {
constructor(props) {
super(props);
}
render() {
if (this.props.components.length === 0) {
return null;
}
const renderCommpos = this.props.components.map((Elem, index) => {
return <Elem key={index} />
});
return (
<div>
{renderCommpos}
</div>
);
}
}
Also notice the Elem in map function. When it comes to react component jsx needs the first letter capital. So doesn't matter what variable you keep at place of Elem, you should always keep the first letter capital.
The render method for your survey component should be like this :
render(){
const { components } = this.props;
return (
<div>
{
components.map((c, index) => {
return (
<div key={`one-of-components-${index}`}>
{c}
</div>
);
})
}
</div>
);
}
Now it will return all the components in the props.
Try this out.
const Survey = ({ components }) => {
const Components = components.map(
( component, index ) => {
return (
<div key={ index }>
{ component }
</div>
);
}
);
return (
<div>
{ Components }
</div>
);
};
In your for loop you're returning from the function on the first component. Add them to an array and then return the array. On another note, I used a functional stateless component here. I don't see the need for the class overhead.