I'm struggling while creating an element that is passed by the .map function. Basically, I want my webpage to create a div element with some date in it when a button is clicked for that I'm using a .map function but it isn't working out.
const handleSubmit = (e) => {
e.preventDefault();
const data = {title:`${title}`, desc:`${desc}`, date:`${date}`};
data.map(userinfo =>{
return(<div>
<h1>{userinfo.title}</h1>
</div>)
})
console.log(data);
}
In reactJS, if we want to display our data in HTML webpage we usually do that in the render funciton.
We can use userInfo variable in the state object.
The userInfo data is hardcoded for demonstration purposes but you can also populate the userInfo variable either using API or in any other way you like.
Moreover, showUserInfo is another variable (initially false) that would render the data once it is set to true
this.state = {
userInfo: [
{
title: 'one',
desc: '',
date: new Date()
},
{
title: 'two',
desc: '',
date: new Date()
}
],
showUserInfo: false
}
On a click event we can set showUserInfo to true using setState function.
more on setState function via this link ->
https://medium.com/#baphemot/understanding-reactjs-setstate-a4640451865b
handleSubmit = async (event) => {
event.preventDefault();
this.setState(
{
...this.state,
showUserInfo: true
}
)
}
In the render function, if showUserInfo is false then userInfo.map is never going to render unless showUserInfo is set to true which we do using a click listener that is associated with our function handleSubmit.
render(){
return (
<div>
<button onClick={this.handleSubmit}>Click Me</button>
{ this.state.showUserInfo &&
this.state.userInfo.map(item =>(
<div>
<p> {item.date.toString()} </p>
</div>
) ) }
</div>
);
}
Overall the result looks a something like this.
export default class App extends React.Component {
constructor() {
super();
this.state = {
showUserInfo: false,
userInfo: [
{
title: 'one',
desc: '',
date: new Date()
},
{
title: 'two',
desc: '',
date: new Date()
}
],
}
}
handleSubmit = async (event) => {
event.preventDefault();
this.setState(
{
...this.state,
showUserInfo: true
}
)
}
render(){
return (
<div>
<button onClick={this.handleSubmit}>Click Me</button>
{ this.state.showUserInfo &&
this.state.userInfo.map(item =>(
<div>
<p> {item.date.toString()} </p>
</div>
) ) }
</div>
);
}
}
Related
For some reason my React component seems to remember its old state when going to another tab and back again, instead of reloading completely.
Basically when I click on the "Create" tab in my navbar and back to the "Board" tab data is populated twice instead of once, see image below. When going back the Board component this.state has two of each taskIds, as if it the component state still had the data from the initial page load when loading again. I have a React component looking like this:
const columnOrder = ['todo', 'in-progress', 'in-review', 'done']
const EMPTY_COLUMNS = {
'todo': {
id: 'todo',
title: 'TODO',
taskIds: []
},
'in-progress': {
id: 'in-progress',
title: 'In Progress',
taskIds: [],
},
'in-review': {
id: 'in-review',
title: 'In Review',
taskIds: []
},
'done': {
id: 'done',
title: 'Done',
taskIds: []
}
};
export class Board extends Component {
constructor(props) {
super(props);
this.onLoadEpic = this.onLoadEpic.bind(this);
this.state = {
columnOrder: columnOrder,
columns: {
'in-progress': {
id: 'in-progress',
title: 'In Progress',
taskIds: [],
},
// ...more columns similar to above
},
};
// Load state data on mount
componentDidMount() {
loadEpic(arg1, arg2);
}
// Async function loading items from DB and formatting into useful columns
async loadEpic(arg1, arg2) {
axios.get(...)
.then((response) => {
let data = response.data;
let newTasks = {};
let newColumns = EMPTY_COLUMNS;
data.taskItems.forEach(function(item) {
let id = item.id.toString();
newColumns[item.status]["taskIds"].push(id);
newTasks[id] = {
...item,
id: id
}
});
this.setState({
tasks: newTasks,
columns: newColumns
});
})
}
render() {
// Prints ["7"] on initial load and ["7", "7"] after going back and forth
console.log(this.state.columns["in-progress"].taskIds);
return (
// Simplified, but this is the main idea
<Container>
<DragDropContext onDragEnd={this.onDragEnd}>
{
this.state.columnOrder.map((columnId) => {
const column = this.state.columns[columnId]
const tasks = column.taskIds.map(taskId => this.state.tasks[taskId]
return (
<Column key={column.id} column={column} tasks={tasks}/>
)
}
}
</DragDropContext>
</Container>
)
}
}
and an App.js with Routing looking like this:
export default class App extends Component {
static displayName = App.name;
render () {
return (
<Layout>
<Route exact path='/' component={Board} />
<Route exact path='/create' component={Create} />
</Layout>
);
}
}
Okay, so I figured it out: it's the EMPTY_COLUMNS constant that is bugging out. When the component is re-rendered, the same EMPTY_COLUMNS object is referenced - so the constant is being appended to. Instead, I should make a copy of the empty columns:
// Before - same object is being appended to, doesn't work
let newColumns = EMPTY_COLUMNS;
// After - create a deep copy of the constant, does work
let newColumns = JSON.parse(JSON.stringify(EMPTY_COLUMNS));
Here I am getting some problems with AliceCarousel to map my response to display its images in the gallery.
I wanted to display the respective types of images for each gallery.
I am generally following SO example .
Any help or suggestion here to make it possible?
Thanks is advance.
//Js
class KitchenService extends Component {
constructor(props) {
super(props);
this.state = {
currentIndex: 0,
responsive: { 1024: { items: 3 } },
galleryItems: this.galleryItems(),
services : this.props.resume,
...props,
ItemsServices:[]
}
}
static propTypes = {
getService: PropTypes.func.isRequired,
resume: PropTypes.object.isRequired,
auth: PropTypes.object.isRequired,
loading: PropTypes.object.isRequired
}
UNSAFE_componentWillReceiveProps(nextProps) {
if(nextProps.resume !== this.props.resume){
var services = this.props.resume.services;
this.setState({
ItemsServices: services
})
}
}
componentDidMount() {
this.props.getService();
}
slideTo = (i) => this.setState({ currentIndex: i })
onSlideChanged = (e) => this.setState({ currentIndex: e.item })
galleryItems = () => {
return this.state.ItemsServices.map((brand, i) => {
var checkImage = brand.length !== 0 && brand.service_name === "Office";
console.log(checkImage, "checkImage")
return (
<div key={`key-${i}`} className="card-img-top"><img src={brand.service_image_url} /></div>
)
})
};
render() {
const { responsive, currentIndex } = this.state
const items = this.galleryItems();
return(
<div>
<Grid className ="col-12 service-kitchen-gallery-grid" >
<div className="service-gallery-headline">
Kitchen
</div>
<AliceCarousel
dotsDisabled={true}
buttonsDisabled={true}
items={items}
responsive={responsive}
slideToIndex={currentIndex}
onSlideChanged={this.onSlideChanged}
/>
</Grid>
</div>
)
}
}
const mapStateToProps = (state) => ({
resume: state.resume,
});
export default connect(mapStateToProps, {getService }) (KitchenService);
//Error
TypeError: Cannot read property 'ItemsServices' of undefined
service API response
(console.log(services))
[
{
_id: "5f1971da18ba2b04704d65c2",
service_name: "Other",
service_image_url:
"https://res.cloudinary.com/tammycloudinary/image/upload/v1595503076/nou0knjbtkujxwjktang.png",
date: "2020-07-23T11:17:46.928Z",
__v: 0,
},
{
_id: "5f1971b218ba2b04704d65c1",
service_name: "Bedroom",
service_image_url:
"https://res.cloudinary.com/tammycloudinary/image/upload/v1595503036/kfiteeilh4doytio6gs8.png",
date: "2020-07-23T11:17:06.742Z",
__v: 0,
}
];
The issue is not coming from const items = this.galleryItems(); like I originally thought. It is coming from the constructor.
You are attempting to use the state object in order to build the initial state object. This obviously will not work.
constructor(props) {
super(props);
this.state = {
currentIndex: 0,
responsive: { 1024: { items: 3 } },
galleryItems: this.galleryItems(), // <-- Here is the problem
services : this.props.resume,
...props,
ItemsServices:[]
}
}
You attempt to initialize state by calling this.galleryItems. But that function relies on this.state already being declared. Since it has not been created yet (but is in the process of being declared), it is undefined and you get this error.
I don't think gallaryItems really belongs in state at all. It's generally not recommended to store JSX in state anyway. Instead just use the function like you have in the render to compute the JSX needed each render.
Another note: Don't use this.props in the constructor. Instead use the props that are passed in to the constructor.
Y0u can solve this with this solution as well with filter.
render() {
const { services, loading} = this.props.resume;
var checkImage = services.length === 0 ? [] : services.filter((item) => item.service_name === "Kitchen")
return(
<div>
<OwlCarousel className="owl-theme" loop margin={10} nav>
{checkImage.map((item, i) => (
<div className="col-xs-12 item" key={item._id} data-id={item._id} >
<img className="service-gallery-images" src={item.service_image_url} alt=""/>
</div>
))}
</OwlCarousel>
</div>
)
}
I have a class component that Renders a list of elements and I need to focus them when an event occurs.
Here is an example code
class page extends React.Component {
state = {
items: [array of objects]
}
renderList = () => {
return this.state.items.map(i => <button>{i.somekey}</button>)
}
focusElement = (someitem) => {
//Focus some item rendered by renderList()
}
render(){
return(
<div>
{this.renderList()}
<button onClick={() => focusElement(thatElement)}>
</div>
)
}
}
I know that I need to use refs but I tried several ways to do that and I couldn't set those refs properly.
Can someone help me?
you should use the createRefmethod of each button that you would like to focus, also you have to pass this ref to the focusElement method that you have created:
const myList = [
{ id: 0, label: "label0" },
{ id: 1, label: "label1" },
{ id: 2, label: "label2" },
{ id: 3, label: "label3" },
{ id: 4, label: "label4" },
{ id: 5, label: "label5" }
];
export default class App extends React.Component {
state = {
items: myList,
//This is the list of refs that will help you pick any item that ou want to focus
myButtonsRef: myList.map(i => React.createRef(i.label))
};
// Here you create a ref for each button
renderList = () => {
return this.state.items.map(i => (
<button key={i.id} ref={this.state.myButtonsRef[i.id]}>
{i.label}
</button>
));
};
//Here you pass the ref as an argument and just focus it
focusElement = item => {
item.current.focus();
};
render() {
return (
<div>
{this.renderList()}
<button
onClick={() => {
//Here you are able to focus any item that you want based on the ref in the state
this.focusElement(this.state.myButtonsRef[0]);
}}
>
Focus the item 0
</button>
</div>
);
}
}
Here is a sandbox if you want to play with the code
I have a header component where I need to render three buttons, so every three buttons have three props. One is the class name, click handler and text.
So out of three buttons, two buttons act as a toggle button, so based on the click the text should change.
See the below code:
class App extends Component(){
state = {
navigationList: [{
text: 'Signout',
onClickHandler: this.signoutHandler,
customClassName: 'buttonStyle'
}, {
text: this.state.isStudents ? 'Students' : 'Teachers',
onClickHandler: this.viewMode,
customClassName: 'buttonStyle'
}, {
text: this.state.activeWay ? 'Active On' : 'Active Hidden',
onClickHandler: this.activeWay,
customClassName: 'buttonStyle'
}]
}
signoutHandler = () => {
// some functionality
}
viewMode = () => {
this.setState({
isStudents: !this.state.isStudents
})
}
activeWay = () => {
this.setState({
activeWay: !this.state.activeWay
})
}
render(){
return (
<Header navigationList={this.state.navigationList}/>
)
}
}
const Header = ({navigationList}) => {
return (
<>
{navigationList && navigationList.map(({text, onClickHandler, customClassName}) => {
return(
<button
onClick={onClickHandler}
className={customClassName}
>
{text}
</button>
)
})}
</>
)
}
The other way is I can pass all the props one by one and instead of an array I can write three button elements render it, but I am thinking to have an array and render using a map.
So which method is better, the problem that I am facing is if use the array. map render
the approach I need to set the initial value as a variable outside and how can I set the state.
And I am getting the onClick method is undefined, is it because the function is not attached to the state navigation list array.
Update
I declared the functions above the state so it was able to call the function.
So in JS, before the state is declared in the memory the functions should be hoisted isn't.
class App extends React.Component {
constructor(props){
super();
this.state = {
isStudents:false,
activeWay:false,
}
}
createList(){
return [{
text: 'Signout',
onClickHandler: this.signoutHandler.bind(this),
customClassName: 'buttonStyle'
}, {
text: this.state.isStudents ? 'Students' : 'Teachers',
onClickHandler: this.viewMode.bind(this),
customClassName: 'buttonStyle'
}, {
text: this.state.activeWay ? 'Active On' : 'Active Hidden',
onClickHandler: this.activeWay.bind(this),
customClassName: 'buttonStyle'
}];
}
signoutHandler(){
}
viewMode(){
this.setState({
isStudents: !this.state.isStudents
})
}
activeWay(){
this.setState({
activeWay: !this.state.activeWay
})
}
render(){
return (
<div>
<div>ddd</div>
<Header navigationList={this.createList()} />
</div>
)
}
}
const Header = ({navigationList}) => {
console.log(navigationList);
return (
<div>
{navigationList && navigationList.map(({text, onClickHandler, customClassName}) => {
return(
<button
onClick={onClickHandler}
className={customClassName}
>
{text}
</button>
)
})}
</div>
)
}
ReactDOM.render(<App />, document.querySelector("#app"))
https://jsfiddle.net/luk17/en9h1bpr/
Ok I will try to explain, If you see you are using function expressions in your class and as far as hoisting is concerned in JavaScript, functions expressions are not hoisted in JS only function declarations are hoisted, function expressions are treated as variables in JS.
Now for your case you don't have to shift your functions above the state, you can simply use constructor for initializing state as
constructor(props) {
super(props);
this.state = {
isStudents: false,
activeWay: false,
navigationList: [
{
text: "Signout",
onClickHandler: this.signoutHandler,
customClassName: "buttonStyle"
},
{
text: "Teachers",
onClickHandler: this.viewMode,
customClassName: "buttonStyle"
},
{
text: "Active Hidden",
onClickHandler: this.activeWay,
customClassName: "buttonStyle"
}
]
};
}
Now you will have your handlers available as it is
Sandbox with some modification just to show
EDIT:
You can have default text for buttons and change it when clicking,
Sandbox updated
Hope it helps
I have a simple to do app that is working fine, except for the ability to delete items from the list. I have already added the button to each of the list items. I know I want to use the .filter() method to pass the state a new array that doesn't have the deleted to-do but I'm not sure how to do something like this.
Here is the App's main component:
class App extends Component {
constructor(props){
super(props);
this.state = {
todos: [
{ description: 'Walk the cat', isCompleted: true },
{ description: 'Throw the dishes away', isCompleted: false },
{ description: 'Buy new dishes', isCompleted: false }
],
newTodoDescription: ''
};
}
deleteTodo(e) {
this.setState({ })
}
handleChange(e) {
this.setState({ newTodoDescription: e.target.value })
}
handleSubmit(e) {
e.preventDefault();
if (!this.state.newTodoDescription) { return }
const newTodo = { description: this.state.newTodoDescription,
isCompleted: false };
this.setState({ todos: [...this.state.todos, newTodo],
newTodoDescription: '' });
}
toggleComplete(index) {
const todos = this.state.todos.slice();
const todo = todos[index];
todo.isCompleted = todo.isCompleted ? false : true;
this.setState({ todos: todos });
}
render() {
return (
<div className="App">
<ul>
{ this.state.todos.map( (todo, index) =>
<ToDo key={ index } description={ todo.description }
isCompleted={ todo.isCompleted } toggleComplete={ () =>
this.toggleComplete(index) } />
)}
</ul>
<form onSubmit={ (e) => this.handleSubmit(e) }>
<input type="text" value={ this.state.newTodoDescription }
onChange={ (e) => this.handleChange(e) } />
<input type="submit" />
</form>
</div>
);
}
}
And then here is the To-Do's component:
class ToDo extends Component {
render() {
return (
<li>
<input type="checkbox" checked={ this.props.isCompleted }
onChange={ this.props.toggleComplete } />
<button>Destroy!</button>
<span>{ this.props.description }</span>
</li>
);
}
}
Event handlers to the rescue:
You can send onDelete prop to each ToDo:
const Todo = ({ description, id, isCompleted, toggleComplete, onDelete }) =>
<li>
<input
type="checkbox"
checked={isCompleted}
onChange={toggleComplete}
/>
<button onClick={() => onDelete(id)}>Destroy!</button>
<span>{description}</span>
</li>
And from App:
<ToDo
// other props here
onDelete={this.deleteTodo}
/>
As pointed by #Dakota, using index as key while mapping through a list is not a good pattern.
Maybe just change your initialState and set an id to each one of them:
this.state = {
todos: [
{ id: 1, description: 'Walk the cat', isCompleted: true },
{ id: 2, description: 'Throw the dishes away', isCompleted: false },
{ id: 3, description: 'Buy new dishes', isCompleted: false }
],
newTodoDescription: '',
}
This also makes life easier to delete an item from the array:
deleteTodo(id) {
this.setState((prevState) => ({
items: prevState.items.filter(item => item.id !== id),
}))
}
Before you get any further you should never use a list index as the key for your React Elements. Give your ToDo an id and use that as the key. Sometimes you can get away with this but when you are deleting things it will almost always cause issues.
https://medium.com/#robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318
If you don't want to read the article, just know this
Let me explain, a key is the only thing React uses to identify DOM
elements. What happens if you push an item to the list or remove
something in the middle? If the key is same as before React assumes
that the DOM element represents the same component as before. But that
is no longer true.
On another note, add an onClick to your button and pass the function you want it to run as a prop from App.
<button onClick={() => this.props.handleClick(this.props.id)} />
and App.js
...
constructor(props) {
...
this.handleClick = this.handleClick.bind(this);
}
handleClick(id) {
// Do stuff
}
<ToDo
...
handleClick={this.handleClick}
/>