Is there a native way to add a style class name to a react element passed as a property WITHOUT using jQuery or any 3rd-party libraries.
The following example should demonstrate what I'm trying to do. Note, react class names are made up.
Edit: The point is to modify the class name of a react element that is passes as a property to the Books class! The Books class needs to modify the class name. Apparently, it does not have access to Authors class's state to use within Authors class.
File authors.js
class Authors {
render() {
return (
<ul>
<li>John Doe</li>
<li>Jane Doe</li>
</ul>
);
}
}
File shelf.js
class Shelf {
render() {
return (
<div>
<Books authors={<Authors/>}/>
</div>
);
}
}
File books.js
class Books {
this.props.authors.addClass('style-class-name'); <- HERE
render() {
return (
<div>
{this.props.authors}
</div>
);
}
}
Potentially need more context, but in this kind of scenario, I would use state to dynamically add/remove a class. A basic example would be:
const App = () => {
const [dynamicClass, setDynamicClass] = useState("");
return (
<div className={`normalClass ${dynamicClass}`}>
<button onClick={() => setDynamicClass("red")}>Red</button>
<button onClick={() => setDynamicClass("green")}>Green</button>
<button onClick={() => setDynamicClass("")}>Reset</button>
</div>
);
};
The state changes schedule a re-render, hence you end up with dynamic classes depending on the state. Could also pass the class in as a property, or however you want to inject it into the component.
React elements do have an attribute called className. You can use that to set CSS classes to your component. You can pass static data (strings) or dynamic ones (basically calculated ones):
<div className="fixedValue" />
<div className={fromThisVariable} />
Keep in mind, that you have to pass down your className, if you wrap native HTML elements in a component:
class Books {
render() {
const {
authors,
// other represents all attributes, that are not 'authors'
...other
}
return (
<div {...other}>
{this.props.authors}
</div>
);
}
}
If you want to add data to your authors attribute (which I assume is an array), you could implement a thing like the following:
let configuredAuthors = this.props.authors.map((author) => ({
return {
...author,
className: `${author.firstName}-${author.lastName}`
}
}))
Keep in mind, that either way, you have to manually assign this className property in your child components (I guess an Author component)
To handle updates from a child component, use functions: https://reactjs.org/docs/faq-functions.html
Full example:
import React from "react";
class Shelf extends React.Component {
render() {
const authors = [
{
firstName: "John",
lastName: "Tolkien"
},
{
firstName: "Stephen",
lastName: "King"
}
];
return (
<div>
<Books authors={authors} />
</div>
);
}
}
const Books = ({authors, ...other}) => {
const [configuredAuthors, setAuthors] = React.useState(authors)
const updateClassName = (authorIndex, newClassName) => {
const newAuthors = [...configuredAuthors]
newAuthors[authorIndex] = {
...configuredAuthors[authorIndex],
className: newClassName
}
setAuthors(newAuthors)
}
return (
<ul {...other}>
{configuredAuthors.map((author, index) => {
return <Author key={index} author={author}
index={index}
updateClassName={updateClassName}
/>;
})}
</ul>
);
}
const Author = ({ author, index, updateClassName, ...other }) => {
const [count, setCount] = React.useState(0);
return (
<li className={author.className} {...other}>
<span>{`${author.firstName} ${author.lastName}`}</span>
<button
onClick={() => {
updateClassName(index, `author-${count}`);
setCount(count + 1);
}}
>
update Class ()
{`current: ${author.className || '<none>'}`}
</button>
</li>
);
};
export default function App() {
return <Shelf />;
}
Related
I have the following class Component which reads data from localstorage.
The Localstorage has an array of objects. Those objects are rendered in a list as you can see below. In each list item there is a button I added in the code. If a user clicks the <ExtendButton /> I want to extend the {el.infoDays} of 7 days.
Can anyone help me with that, or at least with binding the button to the object it is in, so that if a user clicks the button I will get the whole object (where the button is in) displayed in the console.log?
I tried the following, I tried with e.target, this, etc. The onExtendBtnClick method is not well written.
let uniqid = require('uniqid');
class History extends Component {
state = {
loans: []
};
componentDidMount() {
const rawInfos = localStorage.getItem('infos');
const infos = JSON.parse(rawInfos);
this.setState({
infos: infos
});
}
render() {
const {infos} = this.state;
return (
<Container>
<Header />
<div>
<ul>
{infos.map((el) => (
<li key={uniqid()}>
<small>Requested date: {el.infoRequestTime}</small><br />
<div>
<span ><em>Requested amount</em><em>{el.infoAmount} €</em></span>
<span><em>In days</em><em>{el.infoDays}</em></span>
</div>
<spa>Give back: {el.infoCost} €</span>
<ExtendButton />
</li>
))}
</ul>
</div>
</Container>
);
}
}
export default History;
And I have also the button component:
class ExtendButton extends Component {
onExtendBtnClick = () => {
console.log(this)
};
render() {
return (
<button
className="extend-button"
onClick={this.onExtendBtnClick}
>
Extend for 1 week
</button>
);
}
}
export default ExtendButton;
Have your button component take in an onClick prop and set that on its own internal button:
class ExtendButton extends Component {
onExtendBtnClick = () => {
this.props.onClick();
};
render() {
return (
<button
className="extend-button"
onClick={this.onExtendBtnClick}
>
Extend for 1 week
</button>
);
}
}
Then just pass an onClick function to your component:
<ExtendButton onClick={() => {console.log(el)}} />
I am trying to create a website which has many "showcase" pages. On each of them i have a variable number of images (not always the same amount) and im using props to pass images to each page in their respective JSX tags as shown below. But i don't want to have: const {title, image1, image2, image3, etc} = props
Is there a way that i can use a single image prop and in my JSX tag use it as many times for any number of images?
The code below is what i would ike to achieve but of course it doesn't work.
Hockey.js
class Sport extends Component {
render() {
return (
<div>
<Pages title="Hockey" image={hockey1, hockey2, hockey3}/>
<Pages title="Football" image={foot1, foot2}/>
</div>
)
}
}
Pages.js
const Pages = props => {
const { title, image } = props
return ( <div></div>)
}
You can use an array to pass a list of images;
class Sport extends Component {
render() {
return (
<div>
<Pages title="Hockey" images={[hockey1, hockey2, hockey3]} />
<Pages title="Football" images={[foot1, foot2]} />
</div>
)
}
}
Then use in your component:
const Pages = props => {
const {title, images} = props;
return (<div class="pages">
{images.map((image, index) => (
<div class="page" key={index}>
<img src={image} />
</div>
)}
</div>);
}
I need to dispatch an action for deleting a row entry when a custom formatted delete button is clicked
I think what you want is available HERE
you can can props in grid formatter like
render() {
return (
<ReactDataGrid
columns={this._columns}
rowGetter={this.rowGetter}
rowsCount={this._rows.length}
minHeight={500}
rowRenderer={RowRenderer} />);
}
And RowRenderer is a function
const RowRenderer = React.createClass({
propTypes: {
idx: React.PropTypes.string.isRequired
},
setScrollLeft(scrollBy) {
// if you want freeze columns to work, you need to make sure you implement this as apass through
this.refs.row.setScrollLeft(scrollBy);
},
getRowStyle() {
return {
color: this.getRowBackground()
};
},
getRowBackground() {
return this.props.idx % 2 ? 'green' : 'blue';
},
render: function() {
// here we are just changing the style
// but we could replace this with anything we liked, cards, images, etc
// usually though it will just be a matter of wrapping a div, and then calling back through to the grid
return (<div style={this.getRowStyle()}><ReactDataGrid.Row ref="row" {this.props}/></div>);
}
});
I faced a similar issue and solved it in a different way. I had 2 columns for which I wanted to add a selectable image, the functionality added was to delete an entry via redux and download a file.
I had a wrapping top level react component shown below:
class ItemComponent extends Component {
constructor(props) {
super(props);
this.props.getData();
}
additionalColumns = (item) => {
let conditionalImage;
if (record.status === 'SOMETHING') {
conditionalImage = (
<div>
<a href="javascript:void(0)" role="button" onClick={() => this.download(item)}><span className="fa fa-file" /></a>
</div>
);
}
return {
...record,
conditionalImage,
delete: (
<div>
<a href="javascript:void(0)" role="button" onClick={() => this.delete(item)}><span className="fa fa-trash" /></a>
</div>
),
};
};
delete(item) {
//here you would call your redux action
console.log(item);
}
download(item) {
//here you would call your redux action
console.log(item);
}
render() {
return (
<main className="items">
<TableWrapper items={this.props.items} additionalColumns={this.additionalColumns} />
</main>
);
}
}
The TableWrapper component is a functional stateless component that builds a ReactDataGrid:
const TableWrapper = ({ items, additionalColumns }) => {
const itemsWithAddColumns = items.map(additionalColumns);
const rowGetter = rowNumber => itemsWithAddColumns[rowNumber];
return (
<div>
<ReactDataGrid
rowKey="id"
columns={ExternallyDefinedColumns}
rowGetter={rowGetter}
rowsCount={recordsPlus.length}
enableRowSelect="multi"
minHeight={600}
emptyRowsView={ExternallyDefinedEmptyRowsView}
/>
</div>
);
};
So basically what happens here is you take the array that will be passed to ReactDataGrid to populate the rows and extend it with your own custom logic pointing at methods within your top level react component. This means you can then get the result of what item is selected and pass whatever data is needed to your redux layer or update your state if you aren't using redux.
Hey I am trying to create a simple to-do list and I have added the components necessary. However, the state is not being updated in the Title {this.state.data.length} and the TodoList {this.state.data}. A Codepen and the relevant code is below.
https://codepen.io/skasliwal12/pen/BREYXK
const TodoForm = ({addTodo}) => {
let input;
return (
<div>
<input ref={node => {input = node;}} />
<button onClick={(e) => {
e.preventDefault();
addTodo(input.value);
input.value='';
}}> +
</button>
</div>
);
};
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo}</li>
});
return <div> {todoNodes} </div>;
}
const Title = ({todoCount}) => {
return (
<div>
<div>
<h1>To-do App {todoCount} items</h1>
</div>
</div>
);
}
class TestApp extends React.Component {
constructor(props) {
super(props);
this.state = { data : [] }
}
addTodo(val) {
let todo = {text: val}
this.state.data.push(todo);
this.setState = ({data: this.state.data});
console.log('state updated?')
}
render(){
return (
<div>
<Title todoCount={this.state.data.length}/>
<TodoForm addTodo={this.addTodo.bind(this)}/>
<TodoList todos={this.state.data}/>
</div>
);
}
}
ReactDOM.render(<TestApp />, document.getElementById('root'));
Quite simply it is important that you DO NOT MUTATE the state like you are doing here
this.state.data.push(todo);
It is hard to debug and adds side effects that are hard to keep track of. Following your approach you should copy the state to a var, update that var and then pass it as the new field in your state. Which could work but it's also something I do not recommend. A general good approach is to to compute the new state based on the old one
// this.state.data.push(todo); You can remove this line
this.setState(prevState => ({ data: prevState.data.concat(todo) }))
This will fix your issue and avoid mutating the state, which is something you should never do, only update the state using the setState method.
I also updated your TodoList which was not displaying properly, you have to access the text field of the todo in order to show something.
const TodoList = ({todos}) => {
let todoNodes = todos.map(todo => {
return <li>{todo.text}</li>
});
return <div> {todoNodes} </div>;
}
https://codepen.io/anon/pen/MmRVmX?editors=1010
I'm trying to create rows of inputs that updates the state of my application. The app is currently only two components, TimeCalc and ItemsList. State is stored in TimeCalc, and ItemsList handles the conditional render (either show state, or show rows of input.
I have managed to fetch the edited and updated object, but I'm struggling to replace the updated object with the correct object in state. The objects contain the props _id, title, start and end, and preferably I'd like to search for the matching _id, and replace the entire object in the state. But seeing as the _id prop is a sibling prop of the other props, I'm not sure how to do this.
Here is TimeCalc:
import React from 'react';
import ItemsList from '../components/ItemsList';
const items = [
{
_id: '112233',
title: 'M1',
start: 900,
end: 1800
},
{
_id: '223344',
title: 'M2',
start: 1000,
end: 1900
}
];
export default class TimeCalc extends React.Component {
state = {
items
}
handleUpdate = (update) => {
}
render = () => {
return (
<div class="timeCalc flex center">
<ItemsList items={this.state.items} handleUpdate={this.handleUpdate}/>
</div>
)
}
}
And here is ItemsList:
import React from 'react';
export default class ItemsList extends React.Component {
state = {
editing: null
}
toggleEditing = (itemId) => {
this.setState({
editing: itemId
})
}
handleEditItem = () => {
let itemId = this.state.editing;
this.handleItemsUpdate({
_id: itemId,
title: this.refs[`title_${itemId}`].value,
start: this.refs[`start_${itemId}`].value,
end: this.refs[`end_${itemId}`].value,
})
}
handleItemsUpdate = (update) => {
console.log(update);
this.props.handleUpdate(update);
this.setState( { editing: null } );
}
renderItemOrEditField = (item) => {
if(this.state.editing === item._id) {
return <li key={`editing-${item._id} `} class="list-item flex row">
<input
onKeyDown={this.handleEditField}
type="text"
class="form-input"
ref={`title_${item._id}`}
name="title"
defaultValue={item.title}
/>
<input
onKeyDown={this.handleEditField}
type="text"
class="form-input"
ref={`start_${item._id}`}
name="start"
defaultValue={item.start}
/>
<input
onKeyDown={this.handleEditField}
type="text"
class="form-input"
ref={`end_${item._id}`}
name="end"
defaultValue={item.end}
/>
<button onClick={this.handleEditItem} label="Update Item"/>
</li>
} else {
return <li
onClick = {this.toggleEditing.bind(null, item._id)}
key = {item._id}
class = "list-position">
{` ${item.title} & ${item.start} && ${item.end} && ${item._id}`}
</li>
}
}
render = () => {
return (
<ul class="itemsList">
{this.props.items.map((item) => {
return this.renderItemOrEditField(item);
})}
</ul>
)
}
}
I'm trying to recreate MeteorChef's "Click to Edit fields in React", but he's storing the state in a Meteor way.
I'd suggest that you move all of the state to the parent component. Keeping ItemsList stateless separates your concerns more neatly and makes the code much simpler to reason about. In this scenario, all ItemsList needs to be concerned with is presenting data based on the state stored in the parent. In your TimeCalc component, add an "editing" key to the state object, and add a method to handle updates from ItemsList:
state = {
items,
editing: null,
}
handleUpdate = (editing) => {
this.setState({ editing });
}
Then when rendering the child component, pass this handler function, the items array, and the "editing" key as props:
<ItemsList items={this.state.items} editing={this.state.editing} handleUpdate={this.handleUpdate}/>
Now ItemsList can become a stateless functional component, like this:
import React from 'react';
const renderItemOrEditField = (item, editing, handleUpdate) => (
{editing === item._id ? (
<li key={item._id} className="list-item flex row">
<input onKeyDown={handleUpdate(item._id}
// The rest of your content to render if item is being edited
) : (
// content to be rendered if item isn't being edited goes here
)}
);
export default const ItemsList = (props) => (
<ul className="itemsList">
{props.items.map((item) => {
return renderItemOrEditField(item, props.editing, props.handleUpdate);
})}
</ul>
);
This strategy of using "smart" container components that manage state and business logic and "dumb" presentational components can go a long way towards keeping your application more maintainable and less fragile. Hope this helps!