Props is an empty object, so i cannot props.match.params.id - reactjs

As said in title, my props is an empty object.
This is my component, in which i want to match a proper object in mapStateToProps.
The matching object exists, because when i pass and x.id === 1 , the object is being rendered.
const UserDetails = ({ episode, history }, props) => {
const { id } = useParams();
// const handleDelete = (id) => {
// if (window.confirm("Are you sure wanted to delete the episode ?")) {
// dispatch(deleteEpisode(id));
// }
// };
console.log("hej", props); // it prints empty object
console.log(episode);
return (
<div>
{episode ? (
<div>
<button onClick={() => history.push("/episodes")}>...back</button>
<h1> Tytuł: {episode.title}</h1>
<h3> Data wydania: {episode.release_date}</h3>
<h3> Series: {episode.series} </h3>
<img src={episode.img} alt="episode" />
{/* <div>
{episode.episode.characters.map((id) => {
const properActor = users.find((el) => el.id == id);
return <div>{`${properActor.name} `}</div>;
})}
</div> */}
<button onClick={() => history.push(`/episode/edit/${episode.id}`)}>
Edit
</button>
{/* <button onClick={() => handleDelete(episode.id)}>Delete</button> */}
<div>
<Link to={`/episodes/${episode.id}/addCharacter`}>
<button type="button">Dodaj postać do: {episode.title}</button>
</Link>
</div>
</div>
) : (
<div>Loading...</div>
)}
</div>
);
};
const mapStateToProps = (state, props) => {
return {
episode: state.episodes.episodes
? state.episodes.episodes.find((x) => x.id === props.match.params.id)
: null,
};
};
export default withRouter(connect(mapStateToProps, null)(UserDetails));
for anyone, who woudl like to see the whole project:
https://stackblitz.com/edit/node-fxxjko?file=db.json
hope it works,
to run the database u have to install npm json-server and run
EDIT:
If i do something like this:
const mapStateToProps = (state, props) => {
console.group("mapStateToProps");
console.log(props); // Does props.match.params.id exist? What is it?
console.log(state.episodes.episodes); // Is there an object in this array whose id matches the above?
console.groupEnd();
return {
episode: state.episodes.episodes
? state.episodes.episodes.find(
(x) => x.episodeId === props.match.params.episodeId
)
: null,
};
};
i see the this result:
https://imgur.com/a/ssrJjHV

You are not properly destructuring the rest of your props. Implement ellipsis to get the rest of the props back
It should be:
const UserDetails = ({ episode, history, ...props}) => {
//rest of your code
}
not:
const UserDetails = ({ episode, history }, props) => {
//rest of your code
}

Related

how to delete a component in react?

I want to delete it.But it won't be deleted.
If you click the delete text in the modal, it should be deleted, but it doesn't work.What should I do to delete it?
There's an error saying that onRemove is not a function.Please help me.
I want to delete it.But it won't be deleted.
If you click the delete text in the modal, it should be deleted, but it doesn't work.What should I do to delete it?
There's an error saying that onRemove is not a function.Please help me.
export default function Modal({ onRemove, id }) {
return (
<OptionModalWrap>
<ModalWrapper>
<TitleWrap>Edit</TitleWrap>
<TitleWrap>Duplicate</TitleWrap>
<DeleteText onClick={() => onRemove(id)}>Delete</DeleteText>
</ModalWrapper>
</OptionModalWrap>
);
}
export default function GroupList({ title, onRemove }) {
const [showModal, setShowModal] = useState(false);
const optionModal = () => {
setShowModal(prev => !prev);
};
return (
<AdGroups>
<Header>
<Container>
<ActiveWrap>
<ActiveIcon src={Active} />
<SubTitle>Active</SubTitle>
</ActiveWrap>
<Alaram>
<Bell src={bell} />
<Text className="alarmCount">10</Text>
</Alaram>
</Container>
<EtcIconWrap>
<EtcIcon src={plus} onClick={optionModal} />
{showModal && (
<OptionModal showModal={showModal} onRemove={onRemove} />
)}
</EtcIconWrap>
</Header>
<GroupTitle>{title}</GroupTitle>
</AdGroups>
);
}
export default function GroupPage() {
const [Groupdata, setGroupData] = useState([]);
const onRemove = item => {
setGroupData(Groupdata.filter(item => item.id !== item));
};
useEffect(() => {
fetch('./data/AdsGroup/AdsGroupList.json')
.then(res => res.json())
.then(res => setGroupData(res));
}, []);
return (
<GroupPages>
{Groupdata.map(item => {
return (
<GroupList key={item.id} title={item.title} onRemove={onRemove} />
);
})}
</GroupPages>
);
}
You have not passed the id in GroupList and then also to the OptionModal component.
So here is the revised code:
Group Page Component:
Passing the id to GroupList Component
const onRemove = id => {
setGroupData(Groupdata.filter(item => item.id !== id)); // you were item.id !== item which was wrong
};
<GroupList key={item.id} title={item.title} id={item.id} onRemove={onRemove} /> // passing the id
Group List Component:
Added id in the props and passed that to Modal Component. Also calling optionModal function to close the Modal after it deleted
export default function GroupList({ id, title, onRemove }) {
const [showModal, setShowModal] = useState(false);
const optionModal = () => {
setShowModal(prev => !prev);
};
return (
<AdGroups>
<Header>
<Container>
<ActiveWrap>
<ActiveIcon src={Active} />
<SubTitle>Active</SubTitle>
</ActiveWrap>
<Alaram>
<Bell src={bell} />
<Text className="alarmCount">10</Text>
</Alaram>
</Container>
<EtcIconWrap>
<EtcIcon src={plus} onClick={optionModal} />
{showModal && (
<OptionModal id={id} showModal={showModal} onRemove={onRemove;optionModal} />
)}
</EtcIconWrap>
</Header>
<GroupTitle>{title}</GroupTitle>
</AdGroups>
);
}
Modal Component: No change in this component
export default function Modal({ onRemove, id }) {
return (
<OptionModalWrap>
<ModalWrapper>
<TitleWrap>Edit</TitleWrap>
<TitleWrap>Duplicate</TitleWrap>
<DeleteText onClick={() => onRemove(id)}>Delete</DeleteText>
</ModalWrapper>
</OptionModalWrap>
);
}
Didn't your IDE complaint about this piece of code? both of the onRemove & filter functions' props are called item, it shouldn't be.
const onRemove = itemId => {
setGroupData(Groupdata.filter(item => item.id !== itemId));
};

Redux how to access state

I'm having trouble accessing the state in one of my components. I have a component where a user adds a name and a weight and then submits it. They are then redirected to the Home Page. What I want to happen is for the name that was inputed to be displayed on the Home Page. I can see the state updating, but I can't figure out how to access the state and have the name show on the Home Page. Any help would be appreciated.
Here is my Home Page component:
const HomePage = () => {
const classes = useStyles();
const name = useSelector(state => state.move.name);
const displayMovementButtons = () => {
if (name) {
return (
<Button
className={classes.movementButtons}
onClick={() => history.push('/movement/:id')}
>
<div className={classes.movementName} >{name}</div>
</Button>
)
};
return <div className={classes.noMovementsMessage} >Click add button to begin</div>
};
return (
<div className={classes.homePageContent} >
<Header title={"Home Page" }/>
<div>{displayMovementButtons()}</div>
<div className={classes.fabDiv}>
<Fab
className={classes.fab}
onClick={() => history.push(`/add`)}>
<AddIcon />
</Fab>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.move.name,
}
};
const withConnect = connect(
mapStateToProps,
);
export default compose(withConnect)(HomePage);
Here is my reducer, where I think the problem is:
const initialState = []
const addMovementReducer = (state = initialState, action) => {
switch (action.type) {
case ADD_MOVEMENT:
return [ ...state, {name: action.name, weight: action.weight} ]
default:
return state;
}
};
export default addMovementReducer;
Here is a screenshot showing the state (note: I added multiple names and weights, I would eventually like each 'name' to appear on the Home Page):
Your move branch of state is an array. You can't access the name by state.move.name. Instead of this you can get an array of movements from redux store and render them with Array.map() method.
const MovementButtons = ({ movements }) => {
return (
<div>
{
movements.map(({ name, weight }) => {
if (name) {
<Button
className={classes.movementButtons}
onClick={() => history.push('/movement/:id')}
key={name}
>
<div className={classes.movementName}>{name}</div>
</Button>
}
return (
<div className={classes.noMovementsMessage}>Click add button to begin</div>
)
})
}
</div>
);
}
const HomePage = () => {
const classes = useStyles();
const movements = useSelector(state => state.move);
return (
<div className={classes.homePageContent} >
<Header title={"Home Page" }/>
<MovementButtons movements={movements} />
<div className={classes.fabDiv}>
<Fab
className={classes.fab}
onClick={() => history.push(`/add`)}>
<AddIcon />
</Fab>
</div>
</div>
);
};
const mapStateToProps = (state) => {
return {
name: state.move.name,
}
};
const withConnect = connect(
mapStateToProps,
);

How can I edit a todo in my react app using hooks?

I'm trying to figure out how to edit a todo item in my react app using hooks, but I can't seem to figure out how to write the code.
Most of the solutions I've seen online are using class components and it's not written with the same logic as my app.
Here is my current code
function TodoList() {
const [todos, setTodos] = useState([]);
const addTodo = todo => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(newTodos);
};
const removeTodo = id => {
const removedArr = [...todos].filter(todoId => todoId.id !== id);
setTodos(removedArr);
};
const completeTodo = id => {
let updatedTodos = todos.map(todo => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
const editTodo = e => {
setTodos(e.target.value);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{todos.map(todo => (
<div>
<div
key={todo.id}
className={todo.isComplete ? 'complete' : ''}
key={todo.id}
onClick={() => completeTodo(todo.id)}
>
{todo.text}
</div>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
</div>
))}
</>
);
}
Here is the code from the other component
function TodoForm(props) {
const [input, setInput] = useState('');
const handleChange = e => {
setInput(e.target.value);
};
const handleSubmit = e => {
e.preventDefault();
props.onSubmit({
id: Math.floor(Math.random() * 10000),
text: input,
complete: false
});
setInput('');
};
return (
<form onSubmit={handleSubmit}>
<input
placeholder='todo...'
value={input}
onChange={handleChange}
name='text'
/>
<button onClick={handleSubmit}>add todo</button>
</form>
);
}
So right now everything works where I can add todos and delete todos + cross out todos. Only thing missing is being able to edit them.
I saw some suggestions about updating the text value with an input form, but I'm not too sure how I'd implement that in my editTodo function.
Similar to your removeTodo handler, you want to pass the todo.id to completeTodo.
<div className={todo.isComplete ? "complete" : ""} key={todo.id} onClick={() => completeTodo(todo.id)}>
Then you would update a bool value in the todo object.
const completeTodo = (id) => {
let updatedTodos = todos.map(todo => {
if(todo.id === id){
todo.isComplete = true
}
return todo
})
setTodos(updatedTodos)
};
Edit: add styling strikethrough
You'll then conditionally add a css style based on isComplete boolean
CSS
.complete {
text-decoration: line-through;
}
To be able to click on the Remove button, place it outside the todo div in your map function.
{todos.map((todo, isComplete) => (
<>
<div
key={todo.id}
onClick={completeTodo}
className={isComplete ? 'complete' : ''}
>
{todo.text}
</div>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
</>
))}
As discussion with you in another question here it is:
TodoList.js
import React, { useState } from "react";
import TodoForm from "./TodoForm";
import Todo from "./Todo";
function TodoList({ onClick }) {
const [todos, setTodos] = useState([]);
//Track is edit clicked or not
const [editId, setEdit] = useState(false);
//Save input value in input box
const [inputValue, setInputValue] = useState("");
const handleEditChange = (id, text) => {
setEdit(id);
setInputValue(text);
};
const addTodo = (todo) => {
if (!todo.text || /^\s*$/.test(todo.text)) {
return;
}
const newTodos = [todo, ...todos];
setTodos(newTodos);
console.log(newTodos);
};
const removeTodo = (id) => {
const removedArr = [...todos].filter((todoId) => todoId.id !== id);
setTodos(removedArr);
};
const completeTodo = (id) => {
let updatedTodos = todos.map((todo) => {
if (todo.id === id) {
todo.isComplete = !todo.isComplete;
}
return todo;
});
setTodos(updatedTodos);
};
const editTodo = (id, text) => {
let editTodos = todos.map((todo) => {
if (todo.id === id) {
todo.text = text;
}
return todo;
});
setTodos(editTodos);
setEdit(false);
};
return (
<>
<TodoForm onSubmit={addTodo} />
{/* I want to move this code below into a new component called Todo.js */}
<Todo
todos={todos}
completeTodo={completeTodo}
removeTodo={removeTodo}
editTodo={editTodo}
handleEditChange={handleEditChange}
editId={editId}
inputValue={inputValue}
setInputValue={setInputValue}
/>
</>
);
}
export default TodoList;
Todo.js
// I want to move this code into this component
import React, { useState } from "react";
import { FaWindowClose, FaRegEdit } from "react-icons/fa";
const Todo = ({
todos,
completeTodo,
removeTodo,
editTodo,
editId,
handleEditChange,
inputValue,
setInputValue
}) => {
return todos.map((todo) => (
<div className="todo-row">
{editId === todo.id ? (
<input
type="text"
value={inputValue}
onChange={(e) => setInputValue(e.target.value)}
/>
) : (
<div
key={todo.id}
className={todo.isComplete ? "complete" : ""}
onClick={() => completeTodo(todo.id)}
>
{todo.text}
</div>
)}
{editId === todo.id ? (
<button onClick={() => editTodo(todo.id, inputValue)}>Edit todo</button>
) : (
<>
<FaWindowClose onClick={() => removeTodo(todo.id)} />
<FaRegEdit onClick={() => handleEditChange(todo.id, todo.text)} />
</>
)}
</div>
));
};
export default Todo;
Make sure you read and understand code first. Logic is pretty simple what you do in completeTodo. You just need to update text part. Tricky part is to open in input. So logic is like track if user click on id set that id. And check if id is there open input with that id value other wise normal one.
Here is demo of this POC: https://codesandbox.io/s/nostalgic-silence-idm21?file=/src/Todo.js:0-1059

How to render a component on Click on list item to show its detail?

I have a Dashboard component that is fetching all city data from an API and store it in the cities state.
Now I want that when a city name is clicked a new page opens having all the props of the clicked city.
function Dashboard() {
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
// Here I want to show a detail page of the clicked item //
// <DetailsPage city={e} />
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you like to show the details under the selected city you can keep it in your component state and render it conditionally:
function Dashboard() {
const [selectedCity, setSelectedCity] = useState(null);
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
setSelectedCity(e)
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
{selectedCity === city ? <DetailsPage city={city} /> : null}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you want to only show the selected city content (with probably a back button):
function Dashboard() {
const [selectedCity, setSelectedCity] = useState(null);
const [cities, setcities] = useState(null);
useEffect(() => {
axios.get('http://localhost:2000/city/')
.then(res => {
console.log(res);
setcities(res.data);
});
}, []);
const handleClick = (e) => {
setSelectedCity(e)
}
if (selectedCity) {
return <DetailsPage city={e} onBack={() => setSelectedCity(null)} />
}
return (
<div >
<div>Dashboard</div>
<ul className="list-group list-group-flush">
{cities !== null ?
cities.map(city => {
return (
<li className="list-group-item" key={city._id} onClick={() => handleClick(city)}>
{city.City}
</li>
);
}) :
null
}
</ul>
{console.log(cities)}
</div>
);
}
If you want a separate page with a different URL, it will be more complex than this.
You need to use a router like react-router.
const handleClick = (e) => {
history.push("/city", { id: e.id });
}
You have to read the data on both pages. So you may need to put your cities and the selected city values in a React Context so that you can use it on the details page. Alternatively, you can fetch the data on the parent component and move these states to it, so that you can pass the values to both pages.
If you fetch data on the Dashboard page, you should also handle the scenario in which a user refreshes the details page or enters the URL directly. You may need a different API to fetch a city by its ID. Alternatively, you can simply redirect to the Dashboard page if you are on the details page and you don't have the required data.

How to show/hide an item of array.map()

I want to show/hide a part of JSX depending on isCommentShown state property. But as this part is inside a map loop isCommentShown acts for all mapped items not only the current one. So when I toggleComment every comment inside a loop is shown/hidden. I imagine this can be solved by moving everything into a separate component because every component has its own state. But I wonder if I can can solve this without that.
const SearchResults = () => {
const [isCommentShown, setIsCommentShown] = useState(false);
const toggleComment = () => {
setIsCommentShown(!isCommentShown);
};
return (
<>
{props.search_results.map(obj =>
<div key={obj.id}>
{ obj.comment ? <img onClick={toggleComment}/> : null }
<div>{obj.text}</div>
{ isCommentShown ? <p>{obj.comment}</p> : null }
</div>
)}
</>
);
};
You could use the useState hook to create an object that will keep all the search result ids as keys and a boolean value indicating if the comment should be shown or not.
Example
const { useState, Fragment } = React;
const SearchResults = props => {
const [shownComments, setShownComments] = useState({});
const toggleComment = id => {
setShownComments(prevShownComments => ({
...prevShownComments,
[id]: !prevShownComments[id]
}));
};
return (
<Fragment>
{props.search_results.map(obj => (
<div key={obj.id}>
{obj.comment ? (
<button onClick={() => toggleComment(obj.id)}>Toggle</button>
) : null}
<div>{obj.text}</div>
{shownComments[obj.id] ? <p>{obj.comment}</p> : null}
</div>
))}
</Fragment>
);
};
ReactDOM.render(
<SearchResults
search_results={[
{ id: 0, text: "Foo bar", comment: "This is rad" },
{ id: 1, text: "Baz qux", comment: "This is nice" }
]}
/>,
document.getElementById("root")
);
<script src="https://unpkg.com/react#16/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js"></script>
<div id="root"></div>
Instead of storing true or false, you must store the comment id to show provided you only want to show one comment at a time. Its important to uniquely identify the item to be expanded
const SearchResults = () => {
const [commentShown, setCommentShown] = useState({});
const toggleComment = (id) => {
setCommentShown(prev => Boolean(!prev[id]) ? {...prev, [id]: true} : {...prev, [id]: false});
};
return (
<>
{props.search_results.map(obj =>
<div key={obj.id}>
{ obj.comment ? <img onClick={() => toggleComment(obj.id)}/> : null }
<div>{obj.text}</div>
{ commentShown[id] ? <p>{obj.comment}</p> : null }
</div>
)}
</>
);
};
If at all you need to open multiple comments simultaneously you can maintain a map of open ids
const SearchResults = () => {
const [commentShown, setCommentShown] = useState('');
const toggleComment = (id) => {
setCommentShown(prev => prev.commentShown !== id? id: '');
};
return (
<>
{props.search_results.map(obj =>
<div key={obj.id}>
{ obj.comment ? <img onClick={() => toggleComment(obj.id)}/> : null }
<div>{obj.text}</div>
{ commentShown === obj.id ? <p>{obj.comment}</p> : null }
</div>
)}
</>
);
};
Use the id to target the toggle on the comment you want.
More precisely, use the state to store the show/hide values, and pass the id to the onclick event to precise which comment to toggle. This should do the job:
class SearchResults extends React.Component {
constructor(props) {
super(props);
this.state = {};
for (let result of props.search_results) {
this.state[`${result.id}IsShown`] = true;
}
}
toggleComment(id) {
let key = `${result.id}IsShown`;
this.setState({[key]: !this.state[key]});
}
render() {
return (
<>
{this.props.search_results.map(result =>
<div key={result.id}>
{
result.comment
? <img onClick={() => toggleComment(result.id)}/>
: null
}
<div>{result.text}</div>
{ isCommentShown ? <p>{obj.comment}</p> : null }
</div>
)}
</>
);
}
}

Resources