undefined argument passed to a fn in custom react hook - reactjs

I'm using a custom hook to open a modal which will perform different actions.
To open and close the modal im using the custom hook useSongUtils methods openModal and closeModal:
export const useSongUtils = () => {
const [isEditing, setIsEditing] = useState(false);
const openModal = ({ cellData }) => {
// outputs undefined
console.log('cell data is', cellData);
setIsEditing(true);
};
const closeModal = () => {
setIsEditing(false);
};
return {
closeModal, isEditing, setIsEditing, openModal,
};
};
And then importing the returned object into my component, where I have a method for a VirtualTable that renders some action links.
The cell data is an id and it is displayed correctly in the optionsRender method (both links work - I get the id). However, the idea is that clicking the Button element, calls the openModal method from usesongUtils and sets isEditing to true. That works.
However I'm also trying to get the cellData arguments in openModal method and it is not working. I'm getting undefined if I try to console.log.
// SongList.js
const optionsRender = ({ cellData }) => (
<div className='songs-list__options'>
<Link to={`/songs/${cellData}/edit`}>
<Icon name='edit' style={{ margin: '0 .2rem .2rem 0' }} />
</Link>
<a rel='noreferrer' target='_blank' href={`localhost/client/song/${cellData}`}>
<Icon name='play' style={{ margin: '0 .2rem' }} />
</a>
{cellData} // I can see the data!!
// im trying to pass cellData to openModal
<Button size='tiny' className='ui button' fluid icon='setting' circular onClick={() => openModal(cellData)} />
</div>
);
const {
closeModal, isEditing, openModal,
} = useSongUtils();

Related

How to revalidate data on react-modal close with SWR?

I am trying to revalidate the data on react-modal close using SWR in a NextJS project.
I am using the SWR hook like so.
const { data, error, isValidating } = useSWR(
process.env.NEXT_PUBLIC_APP_URL + `/api/users`,
fetcher,{
revalidateIfStale: true,
revalidateOnFocus: true,
revalidateOnMount:true,
}
);
useEffect(() => {
if (data) {
setUsers(data.users);
}
}, [data, isValidating, users]);
//handle loading..
//handle error..
return (
<main className="mx-auto max-w-7xl ">
<Header title="All users"/>
<UsersList users={users} />
</main>
);
I am fetching a list of users and displaying them.
const usersList = users.((user) => (
<div className="space-x-5 text-sm" key={user.id}>
{user.name}
<DisableModal id={user.id} isDisabled={user.active}/>
</div>
));
I have a react modal that allows us to disable the users, once I have disabled the users with handle click.
When the modal closes the data is not being refetched.
This is a sample modal from the docs.
When I close the modal, and can see the list of users. They are not refreshed and not using revalidations with use SWR.
export const DisableModal = ({
id,
isDisabled,
}) => {
const [disableModalIsOpen, setDisableModalIsOpen] = useState(false);
function closeDisableModal() {
setDisableModalIsOpen(false);
}
function openPublishModal() {
setDisableModalIsOpen(true);
}
const handleDisableUser = async () => {
//disable logic in rest call.
closeDisableModal();
}
....
}
You can revalidate the data manually using mutate when the onAfterClose callback in the modal gets triggered.
export const DisableModal = () => {
const [showModal, setShowModal] = useState(false);
const { mutate } = useSWRConfig()
return (
<>
<button onClick={() => { setShowModal(true) }}>Trigger Modal</button>
<ReactModal
isOpen={showModal}
onAfterClose={() => {
mutate(process.env.NEXT_PUBLIC_APP_URL + '/api/users')
}}
contentLabel="Minimal Modal Example"
>
<button onClick={() => { setShowModal(false) }}>Close Modal</button>
</ReactModal>
</>
)
}
Calling mutate(process.env.NEXT_PUBLIC_APP_URL + '/api/users') will broadcast a revalidation message to SWR hook with that given key. Meaning the useSWR(process.env.NEXT_PUBLIC_APP_URL + '/api/users', fetcher, { ... }) hook will re-run and return the updated users data.

Avoid re-creating body component on each render of react-modal

I'm using this lib to create a modal
I have 3 components: Table, Modal and List
Table has Modal (a custom React Modal), and the body of Modal will be List.
Now the problem is, List has some functions which change the states of Table, so when I do something that can make Table's state change, Table and Modal will be re-rendered when Modal is re-rendered, it re-creates a new List which leads to lost all stuffs I'm doing with List.
Here is a simple version of my app. link
Now I don't want List to be re-created each time Modal is re-rendered. Is there any way to archive that? (I don't want to create a modal myself or use global state management in this case)
import { useEffect, useMemo, useState } from "react";
import ReactModal from "react-modal";
ReactModal.setAppElement("#root");
const List = ({ onClick }) => {
useEffect(() => {
console.log("List is mounted");
}, []);
return <button onClick={onClick}>Click me!</button>;
};
const Modal = ({ state, body, isOpen }) => {
useEffect(() => {
console.log("Modal is re-rendered");
});
return (
<div
id="react modal wrapper"
style={{
display: `${isOpen ? "block" : "none"}`
}}
>
<ReactModal isOpen={isOpen}>
<div>
state is {state}
<br />
{body}
</div>
</ReactModal>
</div>
);
};
const Table = ({ state, onClick, isOpen }) => {
useEffect(() => {
console.log("Table is re-rendered");
});
const memorizedList = useMemo(() => <List onClick={onClick} />, []);
return (
<div>
state: {state}
<Modal state={state} body={memorizedList} isOpen={isOpen} />
</div>
);
};
const App = () => {
const [state, setState] = useState(1);
const onClick = () => setState((v) => v + 1);
return (
<div>
<button onClick={onClick}>Change state</button>
<Table state={state} onClick={onClick} isOpen={state % 2 === 0} />
</div>
);
};
export default App;

How to edit a todo in a todo list without hooks and redux

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.

Passing Property To Another Component - React

I need to pass "notecards" (an array) down from "Notecard.js" to "LoadQuestions.js". Console log shows that it is passing, but when I use {notecards} within the "return" it errors as "undefined". Could you please take a look?
Notecard.js (without the imports):
const useStyles = makeStyles((theme) => ({
root: {
maxWidth: 345,
},
media: {
height: 0,
paddingTop: '56.25%', // 16:9
},
}));
export default function Notecard( {notecards} ) {
const classes = useStyles();
const next = () => {
console.log('Next Button Clicked')
};
const previous = () => {
console.log('Back Button Clicked')
};
const hint = () => {
console.log('Hint Button Clicked')
};
console.log({notecards});
return (
<Card className={classes.root}>
<div id="cardBody">
<CardHeader
title="Kate Trivia"
// subheader="Hint: In the 20th century"
/>
<CardContent>
<LoadQuestions notecards={notecards}/>
</CardContent>
</div>
</Card>
);
}
LoadQuestions.js (without imports)
const {useState} = React;
export default function LoadQuestions( {notecards} ) {
const [currentIndex, setCounter] = useState(0);
console.log({notecards});
return (
<div>
<Toggle
props={notecards}
render={({ on, toggle }) => (
<div onClick={toggle}>
{on ?
<h1>{props.notecards} hi</h1> :
<h1>{this.props[currentIndex].backSide}</h1>
}
</div>
)}
/>
<button onClick={() => {
console.log({notecards})
if (currentIndex < (this.props.length-1)) {
setCounter(currentIndex + 1);
} else {
alert('no more cards')
}
}}>Next Card
</button>
<button onClick={() => {
if (currentIndex > 0 ) {
setCounter(currentIndex -1);
} else {
alert('no previous cards')
}
}}>Previous Card
</button>
</div>
);
}
Thanks in advance!
That's all the details I have for you, but stack overflow really wants me to add more before it will submit. Sorry!
You should check if props exists, first time it renders the component it has no props so it shows undefined.
First i must say you destructured notecards out, so no need to use props.
If you want to use props you should change
({notecards}) to (props)
and if not you can directly use notecards since it is destructured
I suggest you two ways
adding question mark to check if exists
<h1>{props?.notecards} hi</h1>//in the case you want to use props
or
add the props in a if statement
<h1>{props.notecards?props.notecards:''} hi</h1> // if notecards is destructured remove the "props."

ReactJs: onClick is preemtively called

I have 2 onClick functions
function VisitGallery(name) {
const history = useHistory();
console.log("visitgallery", name)
history.push("/gallery")
}
function App() {
const accesstoken = "******************"
const [viewport, setviewport] = React.useState({
latitude: ******
longitude: *******
width: "100vw",
height: "100vh",
zoom: 11
})
const [details, setdetails] = React.useState([
])
React.useEffect(() => {
const fetchData = async () => {
const db = firebase.firestore()
const data = await db.collection("data").get()
setdetails(data.docs.map(doc => doc.data()))
}
fetchData();
}, [])
const [selectedpark, useselectedpark] = React.useState(null);
React.useEffect(() => {
const listener = e => {
if (e.key === "Escape") {
useselectedpark(null);
}
};
window.addEventListener("keydown", listener)
return () => {
window.removeEventListener("keydown", listener)
}
}, [])
return (
<div className="App">
<ReactMapGl {...viewport}
mapboxApiAccessToken={accesstoken}
mapStyle="mapbox://**************"
onViewportChange={viewport => {
setviewport(viewport)
}}>
{details.map((details) =>
<Marker key={details.name} latitude={details.lat} longitude={details.long}>
<button class="marker-btn" onClick={(e) => {
e.preventDefault();
useselectedpark(details);
}}>
<img src={icon} alt="icon" className="navbar-brand" />
</button>
</Marker>
)}
{selectedpark ?
(<Popup
latitude={selectedpark.lat}
longitude={selectedpark.long}
onClose={() => {
useselectedpark(null);
}}
>
<div>
<Card style={{ width: '18rem' }}>
<Card.Body>
<Card.Title>{selectedpark.name}</Card.Title>
<Card.Text>
{selectedpark.postalcode}
</Card.Text>
<Button variant="primary" onClick = VisitGallery() >Visit Gallery</Button>
</Card.Body>
</Card>
</div>
</Popup>)
: null}
{
console.log("in render", details)
}
</ReactMapGl>
</div>
);
}
export default App;
The outer onClick is assigned when the marker is first created, and when it is clicked the useselectedpark function is called, details is then assigned to selectedpark.
The inner onClick is assigned to the function VisitGallery(). When the inner onClick is triggered, i want to navigate to another page, hence the history.push().
Ideally, what i want for it to happen is, when the outer onClick is triggered, the cardview shows, and i have an option to visit the next page, which can be triggered by an onClick within the card. However, what is happening right now is both the onClicks are triggered when i click on the thumbnail. How do i fix it such that it is how i want it to be ideally?
ps: do let me know if my explanation is confusing and i will edit it accordingly
Try adding your second onClick into a callback function?
<Button variant="primary" onClick='()=>{ VisitGallery() }' >Visit Gallery</Button>
So that it doesn't automatically invoke the function until the click is triggered.

Resources