Just wanted to implement toggling faq type style. I have tried couple different ways but I can't seem to get it right. Basically i want to be able to expand the paragraph when the button is click. The paragraphs should expand one at a time. The code below is able to expand and close the paragraph one at a time as well but if you click on different button and there is an open paragraph, it just closes the first paragraph instead of opening the other paragraph.
Here is a link to sandbox as well: https://codesandbox.io/s/pensive-lichterman-nmjpy?file=/src/App.js:0-886
import { useState } from "react";
import "./styles.css";
import { info } from "./data";
export default function App() {
const [itemId, setItemId] = useState(null);
const [expand, setExpand] = useState(false);
const handleClick = (id) => {
setItemId(id);
setExpand((preState) => !preState);
};
return (
<div className="details">
{info.map(({ title, details, id }, i) => {
return (
<div key={id} className="details-wrapper">
<div>
<h3 className="title">{title}</h3>
<button onClick={() => handleClick(id)}>+</button>
</div>
<p
className="text"
style={{
display: itemId === id && expand ? "block" : "none"
}}
>
{details}
</p>
</div>
);
})}
</div>
);
}
You are only keeping a single id and expand state. So no matter what you do, when the page is re-renedered onClick it sees a single id and only sets the display style for that id.
If you wish to control each element separately then they each need their own expand state saved. There are several ways to do this, but it's probably best to create an "Expandable" component that saves its own state. Something like this:
https://codesandbox.io/s/polished-rain-xnez0?file=/src/App.js
Pretty much, I create a state for each paragraph. That’s what you need to create individual components with their own state.
export default function App() {
return (
<div className="details">
{info.map(({ title, details, id }, i) => {
return <ItemParagragh key={id} title={title} details={details} />;
})}
</div>
);
}
const ItemParagragh = ({ title, details }) => {
const [expand, setExpand] = useState(false);
const handleClick = () => {
setExpand((preState) => !preState);
};
return (
<div className="details-wrapper">
<div>
<h3 className="title">{title}</h3>
<button onClick={() => handleClick()}>+</button>
</div>
<p
className="text"
style={{
display: expand ? "block" : "none"
}}
>
{details}
</p>
</div>
);
};
Related
I have written a project which receives data through an api. Clicking on each button displays corresponding news. For example, when you press the sports button, sports news comes. However, I want the All category to be active when the page is first opened. In other words, those news should have arrived without pressing the all button. How can I do this?
Not - The function inside useffect returns every time it renders and it doesn't work for me. For example, when you refresh the page while reading sports news, all news comes
import React, { useEffect, useState } from "react";
import SpinnerLoad from './components/SpinnerLoad'
import NewsItem from "./components/NewsItem";
import Category from "./components/data/Category"
const App = () => {
const [state, setState] = useState([]);
const [loading, setLoading] = useState(false)
const fetchValue = (category) => {
fetch(`https://inshorts-api.herokuapp.com/news?category=${category}`)
.then(res => res.json())
.then(res => {
setState(res.data)
setLoading(true)
})
.catch((error) => console.log(error))
setLoading(false);
};
const CategoryButton = ({ category }) => (
<button onClick={() => fetchValue(category)} style={{ textTransform: 'capitalize' }}>{category}</button>
);
useEffect(() => {
fetchValue('all')
}, [])
return (
<>
<div className="header-bg">
<h1 className="mb-3">News</h1>
<div className="btns ">
{Category.map((value, index) => {
return <CategoryButton category={value} key={index} />;
})}
</div>
</div>
<div className="news">
<div className="container">
<div className="row">
{
!loading
? <SpinnerLoad/>
:
state.map((data,index) => {
return (
<NewsItem
imageUrl={data.imageUrl}
author={data.author}
title={data.title}
content={data.content}
date={data.date}
key={data.id}
/>
);
})
}
</div>
</div>
</div>
</>
);
};
export default App;
import React from 'react'
import clock from "../components/assets/img/Clock.svg"
import user from "../components/assets/img/User.svg"
const NewsItem = (props) => {
const {imageUrl, title, content, date, author} = props
return (
<div class="col-lg-4 col-md-6 col-12 p-2">
<div className="newsItem">
<img src={imageUrl} alt=''/>
<div className="itemBody">
<p className='title'>{title}</p>
<div className="line"></div>
<p className='content'>{content}</p>
<div className="itemfooter">
<h6><img src={clock} alt='clock'/>{date}</h6>
<h6><img src={user} alt='user'/>{author}</h6>
</div>
</div>
</div>
</div>
)
}
export default NewsItem
In react, if you refresh the app , the state values will reinitialise.
From your question , it seems like you want to store the category value and even after refresh , you want to persist the category value..
For that you can store category value in local or sessionStorage..
const fetchValue = (category) => {
localStorage.setItem("category", category);
// your code
}
// in useEffect , you can check for the category value in the local Storage
useEffect(() => {
// check value in localStorage, if does not exist use "all" as default value
let categoryValue = localStorage.getItem("category") || "all" ;
fetchValue(categoryValue)
},[]);
while building my react app for deployment, I am getting this error
TypeError: Cannot read property '0' of undefined
when I am rending on port3000 I did not see this error but only get it while building the app.
Can anyone assist to resolve this?
import { useState } from "react";
import styles from "./Tabs.module.css"
const Tabs = ({ children}) => {
const [activeTab, setActiveTab] = useState (children [0].props.label);
const handleClick =( e, newActiveTab ) => {
e.preventDefault();
setActiveTab(newActiveTab);
}
return (
<div>
<ul className= {styles.tabs}>
{children.map ((tab) => {
const label = tab.props.label;
return (
<li
className= {label == activeTab ? styles.current : ""}
key= {label}
>
<a href="#" onClick={(e) => handleClick (e, label)}>{label}
</a>
</li>
)
})}
</ul>
{children.map ((tabcontent1) => {
if (tabcontent1.props.label == activeTab)
return (
<div key= {tabcontent1.props.label} className= {styles.content}>{tabcontent1.props.children}
</div>
);
})}
</div>
);
}
export default Tabs ;
In next js, when you don't put export const getServerSideProps = () => {} in your page then that page is automatically subjected to static side rendering. On development mode, you may see a lightening symbol on bottom-right. Anyway you can read the docs on data-fetching on nextjs. However, your issue on this situation can be easily fixed by setting the children through useEffect.
// handle null on your active tab render function
const [activeTab, setActiveTab] = useState(null);
useEffect(() => {
if(children.length)
children[0].props.label
}, [children])
Another Code Sample:
*A simple change in code structure and the way you are trying to do. It's on react but kind of same in next as well *
import React from "react";
const Tabs = ({ tabsData }) => {
const [activeTabIndex, setActiveTabIndex] = React.useState(0);
const switchTabs = (index) => setActiveTabIndex(index);
return (
<div style={{ display: "flex", gap: 20, cursor: "pointer" }}>
{/* Here active tab is given a green color and non actives grey */}
{tabsData.map((x, i) => (
<div
key={i}
style={{ color: activeTabIndex === i ? "green" : "#bbb" }}
onClick={() => switchTabs(i)}
>
{x.label}
</div>
))}
{/* Show Active Tab Content */}
{tabsData[activeTabIndex].content}
</div>
);
};
export default function App() {
// You can place it inside tabs also in this case
// but lets say you have some states on this component
const tabsData = React.useMemo(() => {
return [
// content can be any component or React Element
{ label: "Profile", content: <p>Verify all Input</p> },
{ label: "Settings", content: <p>Settings Input</p> },
{ label: "Info", content: <p>INput info</p> }
];
}, []);
return (
<div className="App">
<Tabs tabsData={tabsData} />
</div>
);
}
and here is also a example sandbox https://codesandbox.io/s/serverless-night-ufqr5?file=/src/App.js:0-1219
I made an example of my question here:
EXAMPLE
I'm mapping an array of objects that have a button that toggles on click, but when clicking on the button every object is changed.
This is the code
export default function App() {
const [toggleButton, setToggleButton] = useState(true);
// SHOW AND HIDE FUNCTION
const handleClick = () => {
setToggleButton(!toggleButton);
};
return (
<div className="App">
<h1>SONGS</h1>
<div className="container">
{/* MAPPING THE ARRAY */}
{songs.map((song) => {
return (
<div className="song-container" key={song.id}>
<h4>{song.name}</h4>
{/* ON CLICK EVENT: SHOW AND HIDE BUTTONS */}
{toggleButton ? (
<button onClick={handleClick}>PLAY</button>
) : (
<button onClick={handleClick}>STOP</button>
)}
</div>
);
})}
</div>
</div>
);
}
I know I should be using spread operator, but I couldn't get it work as I spected.
Help please!
Of course every object will change because you need to keep track of toggled state for each button. Here is one way to do it:
import { useState } from "react";
import "./styles.css";
const songs = [
{
name: "Song A",
id: "s1"
},
{
name: "Song B",
id: "s2"
},
{
name: "Song C",
id: "s3"
}
];
export default function App() {
const [toggled, setToggled] = useState([]);
const handleClick = (id) => {
setToggled(
toggled.indexOf(id) === -1
? [...toggled, id]
: toggled.filter((x) => x !== id)
);
};
return (
<div className="App">
<h1>SONGS</h1>
<div className="container">
{songs.map((song) => {
return (
<div className="song-container" key={song.id}>
<h4>{song.name}</h4>
{toggled.indexOf(song.id) === -1 ? (
<button onClick={() => handleClick(song.id)}>PLAY</button>
) : (
<button onClick={() => handleClick(song.id)}>STOP</button>
)}
</div>
);
})}
</div>
</div>
);
}
There are many ways to do it. Here, if an id is in the array it means that button was toggled.
You can also keep ids of toggled buttons in object for faster lookup.
One way of handling this requirement is to hold local data into states within the Component itself.
I have created a new Button Component and manages the toggling effect there only. I have lifted the state and handleClick method to Button component where it makes more sense.
const Button = () => {
const [toggleButton, setToggleButton] = useState(true);
const click = () => {
setToggleButton((prevValue) => !prevValue);
};
return <button onClick={click}>{toggleButton ? "Play" : "Stop"}</button>;
};
Working Example - Codesandbox Link
I currently have my toggle action in place but the only help I need is that, I would like to close the div as well, like an toggle action. The one that I've currently done is that once I click on another div element the previous one that has been clicked closes, but I'd rather prefer that I have an toggle action on closing and opening on the div element being clicked, without needing to click on another just to close the previous div, I've only grabbed the parts that are needed in the code, just to prevent on copying and pasting the whole file, just to save time on reading.
Code Snippet
const [posts, setPosts] = useState([]);
const [commentState, commentChange] = useState({
activeObject: null
});
const toggleComment = (index) => {
commentChange({...commentState, activeObject: posts[index]})
}
const toggleActiveStyles = (index) => {
if(posts[index] === commentState.activeObject) {
return "dashboard__commentContent toggle";
} else {
return "dashboard__commentContent";
}
}
return error ? (
<span>{error}</span>
) : (
{posts.map((post, i) => (
<button onClick={() => toggleComment(i)} >toggle</button>
<div className={toggleActiveStyles(i)}>
<h1>{post.title}</h1>
</div>
)}
Here is a working codesandbox that you can manipulate to fit to your needs.
Explanation
You would want to keep track of toggled divs and make sure to adjust your class based on that. You can filter out or add to the toggled divs state variable, and do whatever you want while rendering.
Code
import { useState } from "react";
import "./styles.css";
const DATA = ["1", "2", "3", "4"];
export default function App() {
const [closedDivs, setClosedDivs] = useState([]);
const toggleDiv = (i) => {
setClosedDivs((divs) =>
divs.includes(i) ? divs.filter((d) => d !== i) : [...divs, i]
);
};
return (
<div className="App">
{DATA.map((d, i) => (
<div
className={`${closedDivs.includes(i) ? "close" : ""} box`}
onClick={() => toggleDiv(i)}
>
<p> {d} </p>
</div>
))}
</div>
);
}
I have been stuck on this for days reading up on tutorials and articles but can not figure this out. Whenever I click on the pencil icon, I want it to edit the current do to. I have 4 components, the form (searchbar where i add todo), the app.js, the todoList, and a todo.js component. I am keeping all the state in the app and state in the form to keep track of the terms I am entering.
I am thinking I would need to create an editTodo method in the app and pass it down as a prop to the list and then the todoItem. Most tutorials or help online uses hooks or redux but I am learning vanilla React first. I am not asking for the answer directly but rather the steps or thought process to implement editing a todo item in the todolist. I am not sure even if my todo app is correct in the places where I am keeping state. I may get slack for asking.. but I do not know what else to do. Here is my code..
class App extends React.Component {
state = {
todos: []
}
addTodo = (todo) => {
const newToDos = [...this.state.todos, todo];
this.setState({
todos: newToDos
});
};
deleteTodo = (id) => {
const updatedTodos = this.state.todos.filter((todo) => {
return todo.id !== id;
});
this.setState({
todos: updatedTodos
});
}
editTodo = (id, newValue) => {
}
render() {
return (
<div className="container">
<div className="row">
<div className="col">
<Form addTodo={this.addTodo} />
</div>
</div>
<div className="row">
<div className="col">
<ToDoList
todos={this.state.todos}
deleteTodo={this.deleteTodo}
editingTodo={this.state.editingTodo}/>
</div>
</div>
</div>
)
}
}
export default App;
const ToDoList = ({todos, deleteTodo, editingTodo}) => {
const renderedList = todos.map((todo, index) => {
return (
<ul className="list-group" key={todo.id}>
<ToDoItem todo={todo} deleteTodo={deleteTodo} editingTodo={editingTodo}/>
</ul>
)
});
return (
<div>
{renderedList}
</div>
)
}
export default ToDoList;
const ToDoItem = ({todo, deleteTodo}) => {
return (
<div>
<li style={{display: 'flex', justifyContent: 'space-between' }} className="list-group-item m-3">
{todo.text}
<span>
<FontAwesomeIcon
icon={faPencilAlt}
style={{ cursor: 'pointer'}}
/>
<FontAwesomeIcon
icon={faTrash}
style={{ marginLeft: '10px', cursor: 'pointer'}}
onClick={ () => deleteTodo(todo.id)}
/>
</span>
</li>
</div>
);
}
export default ToDoItem;
I don't think the form component is relevant here as I am trying to edit a todo item so will not include it here. If I do need to include it, let me know. It may not look like I have tried to implement this functionality, but either I could not find what I was looking for, understand the code, or just do not know how to implement it.
Update:
I added an isEditing field in the form component to my todo items so that maybe it can help me know if an item is being editing or not. I also redid the editTodo method.
class Form extends React.Component {
state = { term: ''};
handleSubmit = (e) => {
e.preventDefault();
this.props.addTodo({
id: shortid.generate(),
text: this.state.term,
isEditing: false
});
this.setState({
term: ''
});
}
editTodo = (id, newValue) => {
const editedTodos = [...this.state.todos].map((todo) => {
if(todo.id === id) {
todo.isEditing = true;
todo.text = newValue;
}
return todo.text;
});
this.setState({
todos: [...this.state.todos, editedTodos]
});
}
I also passed that method down to the todoList and then to the todoItem like so
const ToDoItem = ({todo, deleteTodo, editTodo}) => {
const renderContent = () => {
if(todo.isEditing) {
return <input type='text' />
} else {
return <span>
<FontAwesomeIcon
icon={faPencilAlt}
style={{ cursor: 'pointer'}}
onClick={ () => editTodo(todo.id, 'new value')}
/>
<FontAwesomeIcon
icon={faTrash}
style={{ marginLeft: '10px', cursor: 'pointer'}}
onClick={ () => deleteTodo(todo.id)}
/>
</span>
}
}
return (
<div>
<li style={{display: 'flex', justifyContent: 'space between'}} className="list-group-item m-3">
{{!todo.isEditing ? todo.text : ''}}
{renderContent()}
</li>
</div>
);
}
So whenever I click on the the edit icon, it successfully shows 'new value' but now also adds an extra todo item which is blank. I figured out how to add the input field so that it shows also. I am accepting the answer Brian provided since it was the most helpful in a lot of ways but have not completed the functionality for editing a todo.
am thinking I would need to create an editTodo method in the app and pass it down as a prop to the list and then the todoItem.
This is exactly what you need to do. And yet:
editTodo method has no logic in it.
ToDoList component receives editingTodo method as a prop instead of defined editTodo.
You are indeed passing the editingTodo futher down to ToDoItem but you are not utilising it there const ToDoItem = ({todo, deleteTodo}) => ...
You don't have an onClick listener on the pencil icon, so nothing can happen.
I don't know how you are planning on doing the editing (modal window with a form, or replacing the text with an input field), either way the bottom line is that you need to trigger your pencil onClick listener with () => editTodo(id, newText).
My recommendation would be - address all 5 points above and for now just hardcode the new value, just to test it out: () => editTodo(id, 'updated value!') and check that everything works. You can worry about getting the real value in there as your next step.