ReactJS - Toggle state or property from mapped children components? - javascript

I'm trying to see if multiple mapped children components can be passed a function to change some of their props. The deal is that, I have a children rows that are representing shift periods, like this:
Which is actually this child component code:
class ShiftRow extends React.Component {
rawMarkup() {
var md = createRemarkable();
var rawMarkup = md.render(this.props.children.toString());
return { __html: rawMarkup };
}
handleAvailableVal = () => {
props.onChange(event.target.checked);
}
render() {
return (
<tr className="shift" style={{ backgroundColor: this.props.availability ? 'green' : 'red', color: this.props.availability ? 'white' : 'black' }}>
{React.Children.map(this.props.children, (child, i) => {
// Ignore the first child
return child
})}
</tr>
);
}
}
The idea is, I want to be able to pass down initial values as the shifts are created in the parent ShiftList component, which are mapped out in the component like this:
if (this.props.data && this.props.data.length > 0) {
shiftNodes = this.props.data.map(shift => (
<ShiftRow key={shift.id} newInd={this.props.isNew} availability={shift.Available} ref={this.ShiftElement}>
<td>{shift.Description}</td>
<td style={{textAlign: 'center'}}>
<input
type="checkbox"
checked={shift.Available}
onChange={this.handleAvailableVal}
style={{ width: '1.5em', height: '1.5em'}}
/>
</td>
<td style={{ textAlign: 'center' }}>
<input readOnly
type="checkbox"
checked={shift.AllDay}
style={{ width: '1.5em', height: '1.5em' }}
/>
</td>
{this.determineShiftPeriod(shift)}
<td>{(shift.DayOfWeek && shift.ShiftType != 'Daily') ? shift.DayOfWeek : 'N/A (Daily)'}</td>
</ShiftRow>
));
}
Is there a way I can change the prop in a row by row fashion like this so that I can say, pass this full set of shift represented rows to save to a database? Let me know if I can clarify anything.
Example: I want to be able to click the "Available" checkbox and watch the props value of that row update for THAT row only, and then save the row as such with other rows.

Yes, the setState() function (or an anonymous function that calls it) could be passed down to child components so that they can modify parent state and by so doing, modify their props indirectly. Something like this:
class ParentComponent extends React.Component {
...
updateState = args => this.setState(args);
render() {
return (<ChildComponent key={shift.id} newInd={this.props.isNew} ... updateState={() => this.updateState()}>
</ChildComponent>);
}
}

Maybe you can do this:
<input
type="checkbox"
checked={shift.Available}
onChange={(evt) => this.handleAvailableVal({evt, shift})} // this way you'll have access to both shift and evt
style={{ width: '1.5em', height: '1.5em'}}
/>
Good Luck...

Use something like this to modify the children item's props. Hope this helps
function ANotherComponent(props) {
console.log("props", props);
return <div>Another component</div>;
}
function T(props) {
const newChildren = React.Children.map(props.children, (child, i) => {
// Ignore the first child
return React.cloneElement(
child,
{
newProps1: 1,
newProps2: 2,
},
child.props.children
);
});
return <div>{newChildren}</div>;
}
function App() {
return (
<div className="App">
<h1>Hello CodeSandbox</h1>
<h2>Start editing to see some magic happen!</h2>
<T>
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
<ANotherComponent hello="1" />
</T>
</div>
);
}
ReactDOM.render(
<App />,
document.getElementById("root")
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.12.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.12.0/umd/react-dom.production.min.js"></script>
<div id="root"></div>
So basically your handling of children will become something like this:
{React.Children.map(this.props.children, (child, i) => {
// Ignore the first child
return React.cloneElement(child, {
newProps1: 1,
newProps2: 2
}, child.props.children)
})}

Related

React Conditional Rendering of Component Inside Function Not Working

I am using Codesandbox to work on learning React. I am trying to conditionally render a functional React component inside of a function (inside of a class based component), that fires when a button is clicked.
Here is the link to the Codesandbox: https://codesandbox.io/embed/laughing-butterfly-mtjrq?fontsize=14&hidenavigation=1&theme=dark
The issue I have is that, without importing and rendering the Error and Meals in App.js, I never can get either component to render from the Booking component. In the function here:
if (!this.state.name) {
return (
<div>
<Error />
</div>
);
}
else {
return <Meals name={this.state.name} date={this.state.date} />;
}
}
I should be rendering Error, which should then show on the screen on click if no name is inputted but nothing happens and I am stumped.
Is there anything obvious that would be preventing me from seeing the Error component from loading on the click?
Thank you in advance!
Everything that is displayed on the screen comes from render method. You cann't return JSX from any function like that. You can do something like this:
class Bookings extends React.Component {
constructor(props) {
super(props);
this.state = {
name: "",
date: "",
display: false
};
}
guestInfoHandler = event => {
console.log(this.state, "what is the state");
this.setState({ name: event.target.value });
};
dateInfoHandler = event => {
this.setState({ date: event.target.value });
};
showMeals = () => {
this.setState({ display: true });
};
render() {
return (
<>
<div style={{ display: "inline-block" }}>
<form
className="theForm"
style={{
height: "50px",
width: "100px",
borderColor: "black",
borderWidth: "1px"
}}
>
<label className="theLabel">
Name:
<input
className="theInput"
type="text"
placeholder="guest name here"
onChange={this.guestInfoHandler}
value={this.state.value}
/>
</label>
</form>
<form>
<label>
Date:
<input
type="text"
placeholder="date here"
onChange={this.dateInfoHandler}
value={this.state.value}
/>
</label>
</form>
<button onClick={() => this.showMeals()}>Click</button>
</div>
{ display && name ? (
<Meals name={name} date={name} />
) : (
<Error />
)}
</>
);
}
}
export default Bookings;
Hope this works for you.
render() {
const name = this.state.name;
return (
<div>
{name ? (
<Meals name={name} date={name} />
) : (
<Error />
)}
</div>
);
}
nb:use render method in class component only.
there is various types conditional rendering mentioned in
https://reactjs.org/docs/conditional-rendering.html#

React JS Warnings: Failed propType and uncontrolled input

I'm struggling to fix some issues regarding React JS. Currently worked on a crash course, and I've been trying to improve the todoList. I'm very new and hopefully this might give me a new perspective after already 8 hours of troubleshooting.
My code - Input:
export class TodoItem extends Component {
getStyle = () => {
return {
background: '#233D4D',
padding: '15px',
borderBottom: '1px darkgray Ridge',
textDecoration: this.props.todo.completed ? 'line-through' :
'none',
color: this.props.todo.completed ? 'lightgreen' : 'white',
fontWeight: this.props.todo.completed ? 'bold' : 'none',
}
}
render() {
const { title } = this.props.todo;
return (
<div style={this.getStyle()}>
<p>
<input type="checkbox" onChange= .
{this.props.markComplete.bind(this)} checked= .
{this.props.todo.completed} /> {' '}
{title}
<button style={btnStyle} onClick= .
{this.props.delTodo.bind(this)}><FontAwesomeIcon size="2x" icon= .
{faTrash} /></button>
</p>
</div>
)
}
}
// PropTypes
TodoItem.propTypes = {
Todos: PropTypes.array.isRequired,
markComplete: PropTypes.func.isRequired,
delTodo: PropTypes.func.isRequired
}
My code - Failed propType:
render() {
const { title } = this.props.todo;
return (
<div style={this.getStyle()}>
<p>
<input type="checkbox"
onChange={this.props.markComplete.bind(this)}
checked={this.props.todo.completed} /> {' '}
{title}
<button style={btnStyle}
onClick={this.props.delTodo.bind(this)}>
<FontAwesomeIcon size="2x" icon={faTrash} />
</button>
</p>
</div>
)
}
// PropTypes
TodoItem.propTypes = {
Todos: PropTypes.array.isRequired,
markComplete: PropTypes.func.isRequired,
delTodo: PropTypes.func.isRequired
}
Heres my issues:
#1 - Prop Types
index.js:1446 Warning: Failed prop type: The prop `Todos` is marked as required in `TodoItem`, but its value is `undefined`.
in TodoItem (at Todos.js:12)
#2 - Component changing an uncontrolled input
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.`
======== EDIT =========
Heres where the components called, properties passed and the manipulation:
render() {
return (
<Router>
<div className="App">
<div className="container">
<Header />
<Route exact path="/" render={props => (
<React.Fragment>
<AddTodo addTodo={this.addTodo} />
<Todos todos={this.state.todo} markComplete= .
{this.markComplete}
delTodo={this.delTodo} />
</React.Fragment>
)} />
<Route path="/about" component={About} />
</div>
</div>
</Router>
);
class Todos extends Component {
render() {
// Mangler håndtering af ingen elementer
let output = undefined;
if(this.props.todos && this.props.todos.length > 0){
// lav object
let output = this.props.todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} markComplete=
{this.props.markComplete} delTodo={this.props.delTodo} />
))
return output;
}
return (
<div>
{output}
</div>
/*this.props.todos.map((todo) => (
<TodoItem key={todo.id} todo={todo} markComplete=
{this.props.markComplete} delTodo={this.props.delTodo} />
))*/
);
}
}
I cleaned the mess in your code a bit and it is now working for me:
const TodoItem = ({title, completed, delTodo, markComplete}) => (
<div>
<p>
<input type="checkbox" onChange={markComplete} checked={completed} />
{title}
<button onClick={delTodo}>Delete</button>
</p>
</div>
);
TodoItem.propTypes = {
title: PropTypes.string.isRequired,
completed: PropTypes.bool.isRequired,
markComplete: PropTypes.func.isRequired,
delTodo: PropTypes.func.isRequired
};
class Todos extends Component {
constructor(props) {
super(props);
this.state = {
todos: [
{id: 1, title: "First", completed: false},
{id: 2, title: "Second", completed: false},
{id: 3, title: "Third", completed: true}
]
};
}
markComplete = id => {
const index = this.state.todos.findIndex(t => t.id === id);
if (index > -1) {
const modifiedTodos = JSON.parse(JSON.stringify(this.state.todos));
modifiedTodos[index].completed = true;
this.setState({todos: modifiedTodos});
}
};
delTodo = id => {
const index = this.state.todos.findIndex(t => t.id === id);
if (index > -1) {
const modifiedTodos = JSON.parse(JSON.stringify(this.state.todos));
modifiedTodos.splice(index, 1);
this.setState({todos: modifiedTodos});
}
};
render() {
return (
<div>
{this.state.todos
? this.state.todos.map(todo => (
<TodoItem
key={todo.id}
title={todo.title}
completed={todo.completed}
markComplete={() => this.markComplete(todo.id)}
delTodo={() => this.delTodo(todo.id)}
/>
))
: null}
</div>
);
}
}
Some comments about your code:
[FIRST ERROR]: Via propTypes you had Todos as property for TodoItem, but you didn't set that property when using TodoItem and because you set it as required with .isRequired, the first error had been thrown.
[SECOND ERROR]: As far as I can tell, changing from uncontrolled to controlled inputs happens, when the change handler changes from undefined to some function. You didn't paste your code with that function, so I can not tell, what was going wrong exactly, but I think the problem is the binding of the functions markComplete and delTodo you provided TodoItem via prop. Usually this binds the this object to the current execution context (class TodoItem in this case) and because TodoItem has no member functions markComplete and delTodo itself, the binding returns undefined for them.
Next time you post a question try to write a minimal working example (MWE). Your code is really bloated with irrelevant stuff. Remove that and the folks here at SO will have a much better time helping you.
In your classes Todos and TodoItem you don't have any state, so better use stateless function components (much more compact).
On some places you had white spaces and dots separating your property names and your property values.

How do I clear the the array of a state?

So this is my code :
import React from "react";
import Navigation from './Navigation';
import Foot from './Foot';
import MovieCard from './MovieCard';
class Favorites extends React.Component {
render() {
const { onSearch, favorites, favoriteCallback, totalFavorites, searchKeyUpdate } = this.props;
return (
<div>
<Navigation
onSearch={onSearch}
totalFavorites={totalFavorites}
searchKeyUpdate={searchKeyUpdate} />
<div className="container">
<button onClick={()=> this.clearFavorites(favorites)}> Clear all movies </button>
{(favorites.length < 1) ?
<h1 style={{ fontSize: '13px', textAlign: 'center' }}>Please mark some of the movies as favorites!</h1>
:
<ul
className="movies">
{favorites
.map(movie => (
<MovieCard
movie={movie}
key={movie.imdbID}
toggleFavorite={favoriteCallback}
favorites={favorites}
/>
))}
</ul>
}
<Foot />
</div>
</div>
);
}
}
const clearFavorites = (favorites) => {
this.setState({ favorites: [] });
}
The thing I need for the button to do is that when i click it that it clears the whole state of favorites. The clearFavorites function is used to clear everything but when I try this I get an error:
Why doesn't this clear the state of favorites?
You have two problems:
clearFavorites function is not in your class. So you should put it inside.
You are trying to clear the data inside the favorites array, which is not part of your state, using the function clearFavorites. So, first of all, you should add favorites array to your state and then you can manipulate the information. I suggest you to use the function getDerivedStateFromProps.
As others mentioned, first moving clearFavorites function into Favorites class.
Second, your favorites list is not part of state object, but instead you pull it out from this.props.favorites, so instead of using this.setState, we should just change the props value.
Third, since you're emptying the array, the parameter in your clearFavorites probably not needed? Please refer to below:
First we define a constructor to get the value from props and pass it to state in the constructor as below:
constructor(props) {
super(props);
this.state = {favorites: this.props.favorites}
}
clearFavorites = () => {
this.setState({favorites: []});
};
Then at last in your render method change to following:
const { onSearch, favoriteCallback, totalFavorites, searchKeyUpdate } = this.props;
const favorites = this.state.favorites;// Or in your ul tag, instead of using favorites, change it to this.state.favorites
You can try to move the clearFavorites into your component
import React from "react";
import Navigation from "./Navigation";
import Foot from "./Foot";
import MovieCard from "./MovieCard";
class Favorites extends React.Component {
render() {
const {
onSearch,
favorites,
favoriteCallback,
totalFavorites,
searchKeyUpdate
} = this.props;
return (
<div>
<Navigation
onSearch={onSearch}
totalFavorites={totalFavorites}
searchKeyUpdate={searchKeyUpdate}
/>
<div className="container">
<button onClick={() => this.clearFavorites(favorites)}>
{" "}
Clear all movies{" "}
</button>
{favorites.length < 1 ? (
<h1 style={{ fontSize: "13px", textAlign: "center" }}>
Please mark some of the movies as favorites!
</h1>
) : (
<ul className="movies">
{favorites.map(movie => (
<MovieCard
movie={movie}
key={movie.imdbID}
toggleFavorite={favoriteCallback}
favorites={favorites}
/>
))}
</ul>
)}
<Foot />
</div>
</div>
);
}
clearFavorites = favorites => {
this.setState({ favorites: [] });
};
}
<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>

How re-render just copmonent to which I clicked - react

I'm trying to make a simple color memory game in React (find where there are two identical images).
When I click on one card all of the other cards re-render. How can I prevent this?
//app class
handleClick = index => {
this.setState((prevState) => {
var temp = [...prevState.clickedPicture, index]
return{clickedPicture: temp}
})
}
isClicked = (index) => this.state.clickedPicture.indexOf(index) === -1
render() {
return(
<div className="content">
<div className="header">
<h1>Memory</h1>
</div>
<div className="main">
_.shuffle(this.state.colors).map((current,index) =>
<Game
key={index}
index={index}
current={current}
status={this.state.status}
handleClick={this.handleClick}
bool={this.isClicked(index)}
/>)
</div>
}
// Game component
class Game extends Component {
clickHandle = () => {
if(this.props.bool){
this.props.handleClick(this.props.index)
}
}
render() {
return(
<div className={this.props.status}
style={{ backgroundColor: this.props.bool ?
'black' : this.props.current }}
onClick={this.clickHandle}>
</div>
);
}
}
Make Cards their own component so they have access to lifecycle hooks. then
make the Cards a PureComponents, therefore they will only update if the reference to a prop changes.

React DnD drags whole list of cards instead of single card

I am trying to use react DnD in my react Project. In my render method I define a variable named Populate like show below, which returns a list of cards like this
render() {
var isDragging = this.props.isDragging;
var connectDragSource = this.props.connectDragSource;
var Populate = this.props.mediaFiles.map((value) => {
return(
<div>
<MuiThemeProvider>
<Card style= {{marginBottom: 2, opacity: isDragging ? 0 : 1}} id={value.id} key={value.id}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
//onTouchTap={() => {this.handleClick(value.id)}}
zDepth={this.state.shadow}>
<CardHeader
title={value.Episode_Name}
//subtitle={value.description}
actAsExpander={false}
showExpandableButton={false}
/>
</Card>
</MuiThemeProvider>
</div>
)
});
And my return of render method looks like this
return connectDragSource (
<div>
<MuiThemeProvider>
<div className="mediaFilesComponent2">
{Populate}
</div>
</MuiThemeProvider>
</div>
)
Problem is when I try using drag, then the whole list of cards gets selected for drag. I want all the cards having individual drag functionality.
If you want each card to have drag functionality than you'll have to wrap each card in a DragSource, and not the entire list. I would split out the Card into it's own component, wrapped in a DragSource, like this:
import React, { Component, PropTypes } from 'react';
import { ItemTypes } from './Constants';
import { DragSource } from 'react-dnd';
const CardSource = {
beginDrag: function (props) {
return {};
}
};
function collect(connect, monitor) {
return {
connectDragSource: connect.dragSource(),
isDragging: monitor.isDragging()
}
}
class CardDragContainer extends React.Component {
render() {
return this.props.connectDragSource(
<div>
<Card style= {{marginBottom: 2, opacity: this.props.isDragging ? 0 : 1}} id={value.id} key={value.id}
onMouseOver={this.props.onMouseOver}
onMouseOut={this.props.onMouseOut}
zDepth={this.props.shadow}>
<CardHeader
title={props.title}
actAsExpander={false}
showExpandableButton={false}
/>
</Card>
</div>
)
}
}
export default DragSource(ItemTypes.<Your Item Type>, CardSource, collect)(CardDragContainer);
Then you would use this DragContainer in render of the higher level component like this:
render() {
var Populate = this.props.mediaFiles.map((value) => {
return(
<div>
<MuiThemeProvider>
<CardDragContainer
value={value}
onMouseOver={this.onMouseOver}
onMouseOut={this.onMouseOut}
shadow={this.state.shadow}
/>
</MuiThemeProvider>
</div>
)
});
return (
<div>
<MuiThemeProvider>
<div className="mediaFilesComponent2">
{Populate}
</div>
</MuiThemeProvider>
</div>
);
}
That should give you a list of Cards, each of which will be individually draggable.

Categories

Resources