Invalid hook call with useCallback in react - reactjs

I have a problem, I tried to transform in a class this function from this example :
https://codesandbox.io/s/5vn3lvz2n4
But, I got an error. Here is the code :
import React, { Component, useCallback } from "react";
import Gallery from "react-photo-gallery";
import Carousel, { Modal, ModalGateway } from "react-images";
class Gallerie extends Component {
constructor(props){
super(props)
this.state = {
currentImage: 0,
setCurrentImage: 0,
viewerIsOpen: false,
setViewerIsOpen: false,
}
}
render(){
const openLightbox = useCallback((event, { photo, index }) => {
this.state.setCurrentImage(index);
this.state.setViewerIsOpen(true);
}, []);
const closeLightbox = () => {
this.state.setCurrentImage(0);
this.state.setViewerIsOpen(false);
};
return (
<div>
<Gallery photos={this.props.photos} onClick={() => openLightbox} />
<ModalGateway>
{this.state.viewerIsOpen ? (
<Modal onClose={() =>closeLightbox}>
<Carousel
currentIndex={this.state.currentImage}
views={this.props.photos.map(x => ({
...x,
srcset: x.srcSet,
caption: x.title
}))}
/>
</Modal>
) : null}
</ModalGateway>
</div>
);
}
}
export default Gallerie;
Here is the problem and what I got :
I don't exaclty know what the useCallBack does. If I just copy / paste the example, it works. The problem is that the variable "photos" is used as props cause it will be different for each user. So I need it into other components. If I use a function like the example, I can't use props...
Thanks for your help.

You're using a hook inside a class based component. First convert it to a functional component
const Gallerie = props =>{
const [state, setState] = useState({
currentImage: 0,
setCurrentImage: 0,
viewerIsOpen: false,
setViewerIsOpen: false,
})
const openLightbox = useCallback((event, { photo, index }) => {
setState({...state, setCurrentImage: index, setViewerIsOpen: true});
}, []);
const closeLightbox = () => {
setState({...state, setCurrentImage: 0,setViewerIsOpen: false })
};
return (
<div>
<Gallery photos={props.photos} onClick={() => openLightbox} />
<ModalGateway>
{state.viewerIsOpen ? (
<Modal onClose={() =>closeLightbox}>
<Carousel
currentIndex={state.currentImage}
views={props.photos.map(x => ({
...x,
srcset: x.srcSet,
caption: x.title
}))}
/>
</Modal>
) : null}
</ModalGateway>
</div>
);
}

Related

Why am I receiving this error "Uncaught TypeError: state.map is not a function" it only happens when I set my setState in my handleClick function

It's saying state.map is not a function but it works just fine before i call the handleclick function.I am stuck here and would like to know why its giving me the state.map is not a function error. Added bonus too would
be me asking how does one setState when you have objects inside objects.
For example if id like to return with setStatea {...item, value:false} value is inside and object thats inside and object. How would I specifically target the value I need inside and array of objects. Thank you.
import React from "react";
import Quiz from "./components/Quiz";
import Menu from "./components/Menu";
import { v4 as uuidv4 } from 'uuid'
function App() {
const [state,setState] = React.useState([])
const [start,setStart] = React.useState(false)
React.useEffect(()=> {
if(start === true){
console.log(state)
fetch("https://opentdb.com/api.php?amount=5&category=27&difficulty=easy&type=multiple")
.then(res => res.json())
.then(data => setState(data.results.map(item =>
({selectedQuestion: "",
buttons: item.incorrect_answers.concat(item.correct_answer).map(item =>
({use: item ,value: false, id: uuidv4()})),
questions: item.question
}))))
}} , [start])
function startGame () {
setStart(prevState => !prevState)
}
const myButtons = state.map(function(item) {
return (<Quiz
key={uuidv4()}
buttons={item.buttons}
value ={item.buttons.map(item => item.value)}
question = {item.questions}
handleClick ={(event) =>handleClick(event.target.value)}
/> ) })
console.log(state.map(item => item))
function handleClick (event) {
if (event === "false"){
//this is what is giving me issues//
setState(item => (
{
...item, selectedQuestion: "howdy"
}))
//////////////////////////////////////////////////////////////
}
else {
console.log("hi")
}
}
return (<div>
{start === false ? <Menu
startGame ={startGame}
/> : <div className="parent--quiz">
<div className="quiz">
{myButtons}
</div>
</div>
}
</div>
)
}
export default App;
Quiz component is here
function Quiz (props) {
const styles ={
backgroundColor: props.value === true ? "red" : "#D6DBF5"
}
return (
<div>
<div className="cards" style={{ borderTop: "2px solid #fff ", marginLeft: 20, marginRight: 20 }}>
<h1 className="quiz--h1">{props.question}</h1>
<div className="quiz--buttons-div" >
{props.buttons.map((item) => (
<button className="quiz--buttons" style={styles} value={item.value} onClick={props.handleClick}>{item.use}</button>
))}
</div>
</div>
</div>
)
}
export default Quiz
code sections are fine

I am not able to change state and pass props

I have the stake component that is rendered 4 times in the parent class component. I am trying to pass valueNewStake as prop to its parent component and group all the inputs in one common array (see allStakes). For a reason I am not able to change the state and also the dom does not render the button next to the component. Can anyone explain me why it is happening as I am new in react. Thanks
import React, { Component } from 'react';
import Stake from './stake';
class FetchRandomBet extends Component {
constructor(props) {
super(props);
this.state = {
loading: true,
bet: null,
value: this.props.value,
allStakes: ['']
};
}
async componentDidMount() {
const url = "http://localhost:4000/";
const response = await fetch(url);
const data = await response.json();
this.setState({
loading: false,
bet: data.bets,
});
}
render() {
const { valueProp: value } = this.props;
const { bet, loading } = this.state;
if (loading) {
return <div>loading..</div>;
}
if (!bet) {
return <div>did not get data</div>;
}
return (
< div >
{
loading || !bet ? (
<div>loading..</div>
) : value === 0 ? (
<div className="bet-list">
<ol>
<p>NAME</p>
{
bet.map(post => (
<li key={post.id}>
{post.name}
</li>
))
}
</ol>
<ul>
<p>ODDS</p>
{
bet.map(post => (
<li key={post.id}>
{post.odds[4].oddsDecimal}
<div className="stake-margin">
<Stake
allStakes={this.props.valueNewStake}
onChange={() => { this.setState({ allStakes: [...this.props.valueNewStake] }) }}
>
<button>ok</button>
</Stake>
</div>
</li>
))
}
</ul>
</div>
import React, { useState } from 'react';
import CurrencyInput from 'react-currency-input-field';
function Stake() {
const [newStake, setStake] = useState(['']);
const changeStake = (e) => {
setStake(e.target.value)
}
return (
<>
<CurrencyInput
onChange={changeStake}
valueNewStake={newStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
{newStake}
</>
);
}
export default Stake;
You're not passing your props to your Stake component
function Stake({ allStakes, onChange }) {
// do something with your props here
const [newStake, setStake] = useState(['']);
const changeStake = (e) => {
onChange()
setStake(e.target.value)
}
return (
<>
<CurrencyInput
onChange={changeStake}
valueNewStake={newStake}
style={{
marginLeft: "40px",
width: "50px"
}}
placeholder="Stake"
decimalScale={2}
prefix="£"
/>
{newStake}
</>
);
}

Comment reply system with React Quill and Firebase Firestore

I'm making a comment system with React Quill as my editor and Firebase Firestore. Each comment post gets stored in firestore. Each stored comment has a reply button, and when clicked, the editor should be populated with the comment content I want to reply to. Basically I need to populate my editor with the content stored in firestore database. Here's a screenshot as to watch I want to achieve:
Comment reply
Here's some code from the comment editor component
class NewComment extends Component {
constructor(props) {
super(props);
this.state = {
comment: {
commentID: "",
content: "",
createDate: new Date(),
featureImage: "",
isPublish: "True",
createUserID: "",
},
};
}
...
onChangeCommentContent = (value) => {
this.setState({
comment: {
...this.state.comment,
content: value,
},
});
};
...
render() {
return (
<Container>
<Row>
<Col xl={9} lg={8} md={8} sn={12}>
<h2 className={classes.SectionTitle}>Comment</h2>
<FormGroup>
<ReactQuill
ref={(el) => (this.quill = el)}
value={this.state.comment.content}
onChange={(e) => this.onChangeCommentContent(e)}
theme="snow"
modules={this.modules}
formats={this.formats}
placeholder={"Enter your comment"}
/>
</FormGroup>
</Col>...
The reply button is in a different component where I render the stored comments. Tell me if you need the full code from the components.
Here is a simple example on how to pass on information between two components via the parent component using function components:
// Index.js
const MyComponent = () => {
const [replyValue, setReplyValue] = useState("");
const onClick = (value) => {
setReplyValue(value);
};
return (
<>
<Comment value="This is a reply" onClick={onClick} />
<Comment value="This is another reply" onClick={onClick} />
<CreateReply quoteValue={replyValue} />
</>
);
};
// Comment.js
export const Comment = ({ value, onClick }) => {
return (
<div className="comment" onClick={() => onClick(value)}>
{value}
</div>
);
};
// CreateReply.js
export const CreateReply = ({ quoteValue = "" }) => {
const [value, setValue] = useState("");
useEffect(() => {
setValue(quoteValue);
}, [quoteValue]);
const onValueUpdated = (newValue) => {
if (newValue !== value) {
setValue(newValue);
}
};
return (
<>
<ReactQuill value={value} onChange={onValueUpdated} />
</>
);
};
Here is the same example using class components:
// Index.js
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
replyValue: ""
};
}
onClick = (value) => {
this.setState({
replyValue: value
});
};
render() {
return (
<>
<Comment value="This is a reply" onClick={this.onClick} />
<Comment value="This is another reply" onClick={this.onClick} />
<CreateReply quoteValue={this.state.replyValue} />
</>
);
}
}
// Comment.js
export class Comment extends React.Component {
render() {
return (
<div
className="comment"
onClick={() => this.props.onClick(this.props.value)}
>
{this.props.value}
</div>
);
}
}
// CreateReply.js
export class CreateReply extends React.Component {
constructor(props) {
super(props);
this.onValueUpdated = this.onValueUpdated.bind(this);
this.state = {
value: props.quoteValue
};
}
componentDidUpdate(prevProps) {
if (this.props.quoteValue !== prevProps.quoteValue) {
this.setState({
value: this.props.quoteValue
});
}
}
onValueUpdated = (newValue) => {
if (newValue !== this.state.value) {
this.setState({
value: newValue
});
}
};
render() {
return (
<>
<ReactQuill value={this.state.value} onChange={this.onValueUpdated} />
</>
);
}
}

How to unit test a component that has a ref prop

How do you unit test a component that has a ref prop ? i'm getting this error
● Should render › should render
TypeError: Cannot add property current, object is not extensible
I looked at another question Unit testing React component ref, but there is no solution for that question.
this is the test
CommentList.test.tsx
import "#testing-library/jest-dom";
import React from "react";
import { CommentListComponent as CommentList } from "./CommentList";
import { render, getByText, queryByText, getAllByTestId } from "#testing-library/react";
const props = {
user: {},
postId: null,
userId: null,
currentUser: {},
ref: {},
comments: [
{
author: { username: "barnowl", gravatar: "https://api.adorable.io/avatars/400/bf1eed82fbe37add91cb4192e4d14de6.png", bio: null },
comment_body: "fsfsfsfsfs",
createdAt: "2020-05-27T14:32:01.682Z",
gifUrl: "",
id: 520,
postId: 28,
updatedAt: "2020-05-27T14:32:01.682Z",
userId: 9,
},
{
author: { username: "barnowl", gravatar: "https://api.adorable.io/avatars/400/bf1eed82fbe37add91cb4192e4d14de6.png", bio: null },
comment_body: "fsfsfsfsfs",
createdAt: "2020-05-27T14:32:01.682Z",
gifUrl: "",
id: 519,
postId: 27,
updatedAt: "2020-05-27T14:32:01.682Z",
userId: 10,
},
],
deleteComment: jest.fn(),
};
describe("Should render <CommentList/>", () => {
it("should render <CommentList/>", () => {
const commentList = render(<CommentList {...props} />);
expect(commentList).toBeTruthy();
});
});
This is the component.
CommentList.tsx
import React, { Fragment, useState, Ref } from "react";
import Grid from "#material-ui/core/Grid";
import OurSecondaryButton from "../../../common/OurSecondaryButton";
import CommentListContainer from "../commentListContainer/commentListContainer";
function CommentList(props: any, ref: Ref<HTMLDivElement>) {
const [showMore, setShowMore] = useState<Number>(2);
const [openModal, setOpenModal] = useState(false);
const [showLessFlag, setShowLessFlag] = useState<Boolean>(false);
const the_comments = props.comments.length;
const inc = showMore as any;
const min = Math.min(2, the_comments - inc);
const showComments = (e) => {
e.preventDefault();
if (inc + 2 && inc <= the_comments) {
setShowMore(inc + 2);
setShowLessFlag(true);
} else {
setShowMore(the_comments);
}
};
const handleClickOpen = () => {
setOpenModal(true);
};
const handleCloseModal = () => {
setOpenModal(false);
};
const showLessComments = (e) => {
e.preventDefault();
setShowMore(2);
setShowLessFlag(false);
};
const isBold = (comment) => {
return comment.userId === props.userId ? 800 : 400;
};
// show comments by recent, and have the latest comment at the bottom, with the previous one just before it.
const filterComments = props.comments
.slice(0)
.sort((a, b) => {
const date1 = new Date(a.createdAt) as any;
const date2 = new Date(b.createdAt) as any;
return date2 - date1;
})
.slice(0, inc)
.reverse();
const showMoreComments = () => {
return filterComments.map((comment, i) => (
<div key={i}>
<CommentListContainer ref={ref} comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
));
};
console.log(ref);
return (
<Grid>
<Fragment>
<div data-testid="comment-list-div" style={{ margin: "30px 0px" }}>
{props.comments.length > 2 ? (
<Fragment>
{min !== -1 && min !== -2 ? (
<Fragment>
{min !== 0 ? (
<OurSecondaryButton onClick={(e) => showComments(e)} component="span" color="secondary">
View {min !== -1 && min !== -2 ? min : 0} More Comments
</OurSecondaryButton>
) : (
<OurSecondaryButton onClick={(e) => showLessComments(e)} component="span" color="secondary">
Show Less Comments
</OurSecondaryButton>
)}
</Fragment>
) : (
<OurSecondaryButton onClick={(e) => showLessComments(e)} component="span" color="secondary">
Show Less Comments
</OurSecondaryButton>
)}
</Fragment>
) : null}
</div>
</Fragment>
{showLessFlag === true ? (
// will show most recent comments below
showMoreComments()
) : (
<Fragment>
{/* filter based on first comment */}
{filterComments.map((comment, i) => (
<div key={i}>
<CommentListContainer ref={ref} comment={comment} openModal={openModal} handleCloseModal={handleCloseModal} isBold={isBold} handleClickOpen={handleClickOpen} {...props} />
</div>
))}
</Fragment>
)}
</Grid>
);
}
export default React.forwardRef(CommentList) as React.RefForwardingComponent<HTMLDivElement, any>;
I fixed it, i had to import
import CommentList from "./CommentList";
instead of
import { CommentListComponent as CommentList } from "./CommentList";
and do this to the props
ref: {
current: undefined,
},
and comment/remove this line of code from commentList
// // prevents un-necesary re renders
// // export default React.memo(CommentList);
// // will be useful for unit testing.
// export { CommentList as CommentListComponent };

How do I trigger a function several components higher in React?

Just learning React, and I would like to add an onClick on the font awesome icon, and run the markTaskAsCompleted function. I'm having trouble because it's several components lower in the hierarchy. How would you ideally go about this? Bear in mind that I also have to pass the ID of the task in the function.
class TasksBase extends React.Component {
constructor(props) {
super(props);
this.state = {
loading: false,
tasks: [],
};
}
componentDidMount() {
this.onListenForTasks();
}
onListenForTasks() {
this.setState({ loading: true });
this.unsubscribe = this.props.firebase
.tasks()
.orderBy('created', 'desc')
.onSnapshot(snapshot => {
if (snapshot.size) {
let tasks = [];
snapshot.forEach(doc =>
tasks.push({ ...doc.data(), uid: doc.id }),
);
this.setState({
tasks: tasks,
loading: false
});
} else {
this.setState({ tasks: null, loading: false });
}
});
}
markTaskAsCompleted(){
console.log("Completed");
}
componentWillUnmount() {
this.unsubscribe();
}
render() {
const { tasks, loading } = this.state;
return (
<div>
{loading && <div>Loading ...</div>}
{tasks ? (
<TaskList tasks={tasks} />
):(
<div>There are no tasks ...</div>
)}
</div>
);
}
}
const Tasks = withFirebase(TasksBase);
const TaskList = ({ tasks }) => (
<ul className="tasks">
{tasks.map( task => (
<Task key={task.uid} task={task} />
))}
</ul>
);
const Task = ({ task }) => (
(!task.completed && !task.obsolete && !task.waitingForDependencies) &&
<li className="task">
<strong>{task.userId}</strong> {task.name}
<div className="icons">
<FontAwesomeIcon icon="check-circle"/>
<FontAwesomeIcon icon="times-circle" />
</div>
</li>
);
const condition = authUser => !!authUser;
export default compose(
withEmailVerification,
withAuthorization(condition),
)(Tasks);
Bind your class function in the constructor:
this.markTaskAsCompleted = this.markTaskAsCompleted.bind(this);
Pass the function into the child component with props:
<TaskList tasks={tasks} handleMarkCompleted={this.markTaskAsCompleted} />
Pass the function again to child component, this is prop drilling and is not the latest greatest approach but it works:
const TaskList = ({ tasks, handleMarkCompleted }) => (
<ul className="tasks">
{tasks.map( task => (
<Task key={task.uid} task={task} handleMarkCompleted={handleMarkCompleted} />
))}
</ul>
);
Trigger the function with onClick:
inside <Task>...
<FontAwesomeIcon icon="check-circle" onClick={() => handleMarkCompleted(task.uid)} />
If passing data into the function (ex. task.uid) make it a param in the function definition as well so you can use it:
markTaskAsCompleted(id){
console.log("Completed", id);
}
you will need to pass it down the tree as
markTaskAsCompleted={this.props.markTaskAsCompleted}
and make sure that the function is bound to the parent in the constructor.
u can use refs or document.getElementById to get the ID.

Resources