Currently, the details of the elements which I want to display are saved at Info.js.
Parent.js is responsible for importing the details needed and then injecting them respectively into each Child.js by .map function as the info stored at Info.js is an array.
I want to dynamically display the relative Child component by the button pressed by users. For example, when the user clicked "First-Tier" button at Parent.js, only the Child.js component with the category of "First-Tier" will be shown. At this moment, my code is not working. I believe the problem is at useEffect but I cannot figure out how to fix this.
I am looking forward to receiving your inspirations. Thanks and please stay safe.
---> Parent.js
import React, { useState, useEffect } from "react";
import Info from "./Info";
import Child from "./Child";
let Category = ["All", "First-Tier", "Second-Tier"];
const Parent = () => {
const [categoryChosen, setCategoryChosen] = useState("All");
let PartsShown = [...Info];
useEffect(() => {
PartsShown = [
...PartsShown.filter((e) => e.category[1] === categoryChosen),
];
}, [categoryChosen, PartsShown]);
return (
<div>
<div>
{Category.map((element) => (
<button
style={{ margin: 10 }}
key={element}
onClick={() => setCategoryChosen(element)}
>
{element}
</button>
))}
</div>
<div>{categoryChosen}</div>
<div>
{PartsShown.map((e) => (
<Child
key={e.name}
name={e.name}
category={e.category[1]}
/>
))}
</div>
</div>
);
};
export default Parent;
---> Child.js
import React from "react";
const Child = ({ name, category }) => (
<div style={{ margin: 10 }}>
<h1>{name}</h1>
<p>{category}</p>
<hr />
</div>
);
export default Child;
--> Info.js
const Info = [
{
name: "A",
description: "Description of A ",
category: ["All", "First-Tier"],
},
{
name: "B",
description: "Description of B",
category: ["All", "Second-Tier"],
}
];
export default Info;
import React, { useState } from "react";
import Info from "./Info";
import Child from "./Child";
const Category = ["All", "First-Tier", "Second-Tier"];
const Parent = () => {
const [partsShown, setPartsShownAndCategory] = useState({
partsArray: [...Info],
category: "All"
});
const changeCategory = category => {
const PartsShown = Info.filter(
element =>
element.category[1] === category || element.category[0] === category
);
setPartsShownAndCategory({
...partsShown,
category: category,
partsArray: PartsShown
});
};
console.log(partsShown);
return (
<div>
<div>
{Category.map(element => (
<button
style={{ margin: 10 }}
key={element}
onClick={() => changeCategory(element)}
>
{element}
</button>
))}
</div>
<div>{partsShown.category}</div>
<div>
{partsShown.partsArray.map(e => (
<Child key={e.name} name={e.name} category={partsShown.category} />
))}
</div>
</div>
);
};
export default Parent;
Related
I am testing a React component using Jest and need to mock data to override the default value of the provider. The issue I am having is that I cannot seem to structure the data properly to pass to the provider in my test.
Here is the test:
// mocked props
const commentId = 'commentId'
const page = '/user'
// mocked data for provider
const mockData = {
commentsById: {
commentId: 'string',
},
}
test('should render CommentBlock correctly with props', () => {
render(
<PostContext.Provider value={mockData}>
<CommentBlock commentId={commentId} page={page} />
</PostContext.Provider>
)
screen.debug()
})
I believe the mockData value in the test has to be changed. The error is thrown when I run the test and line 28 and 29 in the component get an undefined value the data overriding the provider is structured improperly.
Here is the component I am testing:
// import BlockButton from './blockButton';
import { useState, useContext } from 'react'
import { useHistory } from 'react-router-dom'
import dateformat from 'dateformat'
import { observer } from 'mobx-react-lite'
import UnirepContext from '../../context/Unirep'
import PostContext from '../../context/Post'
import { EXPLORER_URL } from '../../config'
import { Page, ButtonType } from '../../constants'
import BlockButton from './blockButton'
import MarkdownIt from 'markdown-it'
const markdown = new MarkdownIt({
breaks: true,
html: false,
linkify: true,
})
type Props = {
commentId: string
page: Page
}
const CommentBlock = ({ commentId, page }: Props) => {
const postContext = useContext(PostContext)
const comment = postContext.commentsById[commentId]
const commentHtml = markdown.render(comment.content)
const unirepConfig = useContext(UnirepContext)
const date = dateformat(new Date(comment.post_time), 'dd/mm/yyyy hh:MM TT')
const history = useHistory()
const [isEpkHovered, setEpkHovered] = useState<boolean>(false)
const gotoPost = () => {
if (page === Page.User) {
history.push(`/post/${comment.post_id}`, { commentId: comment.id })
}
}
return (
<div className="comment-block">
<div className="block-header comment-block-header no-padding">
<div className="info">
<span className="date">{date} |</span>
<span
className="user"
onMouseEnter={() => setEpkHovered(true)}
onMouseLeave={() => setEpkHovered(false)}
>
Post by {comment.epoch_key}{' '}
<img
src={require('../../../public/images/lighting.svg')}
/>
{isEpkHovered ? (
<span className="show-off-rep">
{comment.reputation ===
unirepConfig.commentReputation
? `This person is very modest, showing off only ${unirepConfig.commentReputation} Rep.`
: `This person is showing off ${comment.reputation} Rep.`}
</span>
) : (
<span></span>
)}
</span>
</div>
<a
className="etherscan"
target="_blank"
href={`${EXPLORER_URL}/tx/${comment.id}`}
>
<span>Etherscan</span>
<img
src={require('../../../public/images/etherscan.svg')}
/>
</a>
</div>
<div
className="block-content no-padding-horizontal"
onClick={gotoPost}
>
<div
style={{
maxHeight: page == Page.Home ? '300px' : undefined,
overflow: 'hidden',
}}
dangerouslySetInnerHTML={{
__html: commentHtml,
}}
/>
</div>
<div className="block-buttons no-padding">
<BlockButton
type={ButtonType.Boost}
count={comment.upvote}
data={comment}
/>
<BlockButton
type={ButtonType.Squash}
count={comment.downvote}
data={comment}
/>
<BlockButton type={ButtonType.Share} count={0} data={comment} />
</div>
</div>
)
}
export default observer(CommentBlock)
This was solved by properly mocking the data with this structure:
const postData = {
commentsById: {
commentId: {
id: 'commentId',
content: 'string',
post_time: '00',
reputation: 30,
epoch_key: 'epoch_key test',
},
},
}
This is a beginner react question as I am learning.
I have two files hero.js and sidebar.js. There are two functions I have in hero.js that need to be accessed from buttons in sidebar.js (namely: onSave and onRestore).
How do I pass those functions down. I realize I probably need to do something like `this.onSave' but not sure how as still new to react.
hero.js
import React, { useState, useRef, useCallback } from "react";
import ReactFlow, {removeElements, addEdge,Controls, updateEdge, ReactFlowProvider, useZoomPanHelper,} from "react-flow-renderer";
import Sidebar from './sidebar';
import localforage from 'localforage';
localforage.config({
name: 'react-flow-docs',
storeName: 'flows',
});
...
const { transform } = useZoomPanHelper();
const onSave = useCallback(() => {
if (rfInstance) {
const flow = rfInstance.toObject();
localforage.setItem(flowKey, flow);
}
}, [rfInstance]);
const onRestore = useCallback(() => {
const restoreFlow = async () => {
const flow = await localforage.getItem(flowKey);
if (flow) {
const [x = 0, y = 0] = flow.position;
setElements(flow.elements || []);
transform({ x, y, zoom: flow.zoom || 0 });
}
};
restoreFlow();
}, [setElements, transform]);
return(
<div className="hero_container" >
<ReactFlowProvider>
<div className='rowC'>
<div className='reactflow-wrapper' style={{ height: 500, width: 1200 }} ref={reactFlowWrapper} >
<ReactFlow
elements={elements}
onLoad={onLoad}
onElementsRemove={onElementsRemove}
onEdgeUpdate={onEdgeUpdate}
onConnect={onConnect}
onDragOver={onDragOver}
onDrop={onDrop}
deleteKeyCode={46} /* 'delete'-key */
>
<Controls />
</ReactFlow>
</div>
<div>
<Sidebar />
</div>
</div>
</ReactFlowProvider>
</div>
);
};
sidebar.js
mport React from 'react';
const Sidebar = () => {
const onDragStart = (event, nodeType) => {
event.dataTransfer.setData('application/reactflow', nodeType);
event.dataTransfer.effectAllowed = 'move';
};
return (
<div className='side_controls'>
<div className='add_node_area'>
<h5>Add Nodes</h5>
<div className="description">You can drag these nodes to the pane on the left.</div>
<div className="react-flow__node-input" onDragStart={(event) => onDragStart(event, 'input')} draggable>
Input Node
</div>
<div className="react-flow__node-default" onDragStart={(event) => onDragStart(event, 'default')} draggable>
Default Node
</div>
<div className="react-flow__node-output" onDragStart={(event) => onDragStart(event, 'output')} draggable>
Output Node
</div>
</div>
<div className='save_restore_buttons'>
<button onClick={onSave}>save</button>
<button onClick={onRestore}>restore</button>
</div>
</div>
);
};
export default Sidebar;
You can access these functions inside Sidebar component by passing them as props
<Sidebar onSave={onSave} onRestore={onRestore} />
And in sidebar.js
const Sidebar = (props) => {
const {onSave, onRestore} = props;
Just pass it in as props from hero.js
<Sidebar onSave={onSave} onRestore={onRestore} />
then use it in sidebar.js
const Sidebar = (props) => {
const {onSave, onRestore} = props;
return (
<div className='save_restore_buttons'>
<button onClick={onSave}>save</button>
<button onClick={onRestore}>restore</button>
</div>
)
}
you should pass your functions as props to the Component that you want to use in there, and then in the destination component, you can use those functions with props.[prop-name]
For more information, you can read this doc.
hero.js
<Sidebar onSave={onSave} onRestore={onRestore}/>
Sidebar.js
<div className='save_restore_buttons'>
<button onClick={props.onSave}>save</button>
<button onClick={props.onRestore}>restore</button>
</div>
When I click on bet now the function triggers a console.log from another component. betNow should group all the inputs from stake in one common array but when I click on it it renders the console log from stake and includes all the values that I typed into one array. Everything works but not as I wish. The parent component should display the common array with all the values. I do not understand why it is happening.Could anyone explain me why is reacting like that? Thanks in advance
Parent Component
import React, { useState } from 'react';
import Button from '#material-ui/core/Button';
import FilterMenu from "./selectButton";
import FetchRandomBet from "./fetchRandomBets";
function Betslip() {
const data = [
{
value: 0,
label: "No Filter"
},
{
value: 1,
label: "Less than two"
},
{
value: 2,
label: "More than two"
},
]
const [selectedValue, setSelectedValue] = useState(0);
const [allStakes, setAllStakes] = useState([]);
const handleChange = obj => {
setSelectedValue(obj.value);
}
const betNow = () => {
const stakes = localStorage.getItem("stakes");
const jsnStake = JSON.parse(stakes) || [];
setAllStakes([...allStakes, jsnStake]);
}
return (
<div className="betslip">
<div className="betslip-top">
<h1 className="text">BETSLIP</h1>
<p className="text-two">BET WITH US!</p>
<div>
<FilterMenu
optionsProp={data}
valueProp={selectedValue}
onChangeProp={handleChange}
/>
</div>
</div>
<div>
<FetchRandomBet
valueProp={selectedValue}
/>
</div>
<Button
onClick={betNow}
className="betnow"
variant="contained"
>
Bet Now!
</Button>
</div>
);
}
export default Betslip;
Child Component
import React, { useState, useEffect } from 'react';
function Stake() {
const [stakes, setStakes] = useState([]);
const addStake = (e) => {
e.preventDefault();
const newStake = e.target.stake.value;
setStakes([newStake]);
};
useEffect(() => {
const json = JSON.stringify(stakes);
localStorage.setItem("stakes", json);
}, [stakes]);
console.log(stakes)
return (
<div>
<form onSubmit={addStake}>
<input
style={{
marginLeft: "40px",
width: "50px"
}}
type="text"
name="stake"
required
/>
</form>
</div>
);
}
export default Stake;
You have this console.log in you function that will run every time the component is rendered, since it´s outside of any function:
I have a problem with my react app. I have a blog page where I can create blog posts and display them to the screen. In this part everything works fine. User logs in and can write a post. Each post contains a Read more... link and if the user clicks on that link the app redirects to the actual blog post. There the user can read the whole blog and add some comments. Everything works perfectly except when the user refreshes the page, everything disappears without any error in the console. I use firebase as my back-end and everything is saved there just like it has to be. Each time I click on the particular post I get redirected to that post and everything is ok, but when I refresh the page everything disappears, the post, the comments, even the input field and the submit comment button.
Here is a picture before refresh:
Before
here is a picture after refresh:
After
Also I will include the code for the actual blog and comment section.
The BlogAndCommentPage contains the actual blog post and holds the input field for the comments and the comments that belong to this post.
import React from 'react'
import { projectFirestore } from '../../firebase/config';
import BackToBlogs from './BackToBlogs'
import AddComment from '../commentComponents/AddComment'
class BlogAndCommentPage extends React.Component {
state = { param: '', blog: [] }
componentDidMount = () => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString)
const id = urlParams.get('id')
this.setState({ param: id })
const fetchDataFromFireBase = async () => {
projectFirestore.collection('Blogs').doc(id).get()
.then(doc => {
if(doc.exists) {
let document = [];
document.push(doc.data());
this.setState({ blog: document })
}
})
}
fetchDataFromFireBase()
}
renderContent() {
// Display the blog
const blogs = this.state.blog?.map(value => {
return (
<div key={value.post.blogID}>
<h1>{value.post.title}</h1>
<h6>{`${value.post.name} - ${value.post.date}`}</h6>
<p>{value.post.body}</p>
</div>
)
})
return blogs;
}
render() {
const displayedBlog = this.state.param
return (
<div>
{
displayedBlog ? (
<div>
{this.renderContent()}
<BackToBlogs />
<hr></hr>
<h5 className="mb-2">Add a comment</h5>
<AddComment param={this.state.param} />
</div>
) : ''
}
</div>
)
}
}
export default BlogAndCommentPage
The AddComment component holds the submit button for the comments and the list of the components
import React, { useState, useEffect } from 'react'
import SubmitComment from './SubmitComment'
import CommentHolder from './CommentHolder';
import { useSelector, useDispatch } from 'react-redux';
const AddComment = ({ param }) => {
const [comment, setComment] = useState('');
useEffect(() => {
if (sessionStorage.getItem('user') === null) {
alert('You are not logged in. Click OK to log in.')
window.location = 'http://localhost:3000/'
}
}, [])
const dispatch = useDispatch();
const state = useSelector((state) => state.state);
if (state) {
setTimeout(() => {
setComment('')
dispatch({ type: "SET_FALSE" })
}, 50)
}
return (
<div>
<div>
<div className="row">
<div className="col-sm">
<div className="form-group">
<textarea rows="4" cols="50" placeholder="Comment" className="form-control mb-3" value={comment} onChange={(e) => setComment(e.target.value)} />
</div>
</div>
</div>
</div>
<div className="mb-3">
<SubmitComment comment={comment} param={param} />
</div>
<CommentHolder param={param} />
</div>
)
}
export default AddComment
The CommentHolder renders each comment that belong to that post
import React from 'react';
import { projectFirestore } from '../../firebase/config';
import DeleteComment from './DeleteComment'
class CommentHolder extends React.Component {
state = { docs: [] }
_isMounted = false;
componentDidMount = () => {
const fetchDataFromFireBase = async () => {
const getData = await projectFirestore.collection("Comments")
getData.onSnapshot((querySnapshot) => {
var documents = [];
querySnapshot.forEach((doc) => {
documents.push({ ...doc.data(), id: doc.id });
});
if (this._isMounted) {
this.setState({ docs: documents })
}
});
}
fetchDataFromFireBase()
this._isMounted = true;
}
componentWillUnmount = () => {
this._isMounted = false;
}
renderContent() {
// Delete comments
const deleteComment = async (id) => {
projectFirestore.collection('Comments').doc(String(id)).delete().then(() => {
console.log(`Blog with id: ${id} has been successfully deleted!`)
})
}
// Build comments
let user;
if (sessionStorage.getItem('user') === null) {
user = [];
} else {
user = JSON.parse(sessionStorage.getItem('user'));
const commentArray = this.state.docs?.filter(value => value.blogID === this.props.param)
.sort((a, b) => (a.time > b.time) ? -1 : (b.time > a.time) ? 1 : 0)
.map(comment => {
return (
<div key={comment.id} className="card mb-3" >
<div className="card-body">
<div className="row">
<div className="col-sm">
<h6>{`${comment.name} - ${comment.time}`}</h6>
<p>{comment.comment}</p>
</div>
<div className="col-sm text-right">
{user[0].id === comment.userID ? <DeleteComment commentid={comment.id} onDeleteComment={deleteComment} /> : ''}
</div>
</div>
</div>
</div>
)
});
const updateComments = () => {
const queryString = window.location.search;
const urlParams = new URLSearchParams(queryString)
const id = urlParams.get('id')
const updateComment = projectFirestore.collection('Blogs').doc(id);
return updateComment.update({
'post.comments': commentArray.length
})
}
updateComments()
return commentArray;
}
}
render() {
return (
<div>
{this.renderContent()}
</div>
)
}
}
export default CommentHolder
The DeleteComment deletes the comment
import React from 'react'
const DeleteComment = ({ commentid, onDeleteComment }) => {
return (
<div>
<button onClick={() => onDeleteComment(commentid)} className='btn btn-outline-danger'>X</button>
</div>
)
}
export default DeleteComment
The SubmitComment stores the comment in the Firebase
import React from 'react'
import { projectFirestore } from '../../firebase/config';
import { v4 as uuidv4 } from 'uuid';
import { useDispatch } from 'react-redux';
const SubmitComment = ({ comment, param }) => {
const dispatch = useDispatch();
const onCommentSubmit = () => {
let user;
if (sessionStorage.getItem('user') === null) {
user = [];
} else {
user = JSON.parse(sessionStorage.getItem('user'));
projectFirestore.collection('Comments').doc().set({
id: uuidv4(),
comment,
name: `${user[0].firstName} ${user[0].lastName}`,
userID: user[0].id,
blogID: param,
time: new Date().toLocaleString()
})
dispatch({ type: "SET_TRUE" });
}
}
return (
<div>
<button onClick={() => onCommentSubmit()} className='btn btn-primary'>Add comment</button>
</div>
)
}
export default SubmitComment
In case there is a rout problem here is the code for the routing between the blogs section and the blog + comments section
return (
<Router >
<Route path='/content-page' exact render={(props) => (
<>
<BlogAndCommentPage />
</>
)} />
<Route path='/blogpage' exact render={(props) => (
<>
<div>
<div className="row">
<div className="col-8">
<h1 className='mb-3'>Blog</h1>
</div>
<div className="col-4 mb-3">
<LogoutButton onLogOut={logout} />
<h6 className='float-right mt-4 mr-2'>{displayUser}</h6>
</div>
</div>
{empty ? (<div style={{ color: "red", backgroundColor: "#F39189", borderColor: "red", borderStyle: "solid", borderRadius: "5px", textAlign: 'center' }} className="mb-2">Title and body cannot be blank</div>
) : ("")}
<InputArea getBlogContent={getBlogContent} />
<CreateBlog post={post} onCheckEmptyInputs={checkEmptyTitleAndBody} />
<hr />
<BlogHolder />
</div>
</>
)} />
</Router>
)
If anybody has any clue on why is this happening, please let me know.
Thank you.
As your website is CSR (client side rendering) it doesn't understand the URL in the first execution, you might need to configure a hash router, take a look at:
https://reactrouter.com/web/api/HashRouter
Also, there is a good answer about it here
When I create a custom toolbar in the same file as the Calendar I can use the onView('day') method totally fine. It changes the views. However, when I put the same CalendarToolbar in a different file, and import it in the Calendar file it, doesn't update or change the view. I get the methods as props but it doesn't change anything.
// CustomToolbar
const CalendarToolbar = ({ onView, label, views }) => (
<div>
<h2>
{label}
</h2>
{views.map(view => (
<button
key={view}
type="button"
onClick={() => onView(view)}
>
{view}
</button>
))}
</div>
);
<Calendar
localizer={localizer}
defaultDate={new Date()}
defaultView="day"
events={mockEvents}
style={{ height: '100vh' }}
onSelectEvent={this.handleSelectEvent}
components={{
toolbar: CalendarToolbar,
}}
/>
Just wondered what I'm doing wrong?
I recently wrote my own custom Toolbar component. I copied the original Toolbar, from the repository, and then replaced the render() method with my own, copying what they had done and including my own stuff. My implementation stuff isn't completely important, but if you look at the onClick bits below, it may help you in doing what you want to do:
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import cn from 'classnames';
import ToolbarDateHeader from './ToolbarDateHeader.component';
import { Icon, Button, ButtonGroup, ButtonToolbar } from '../app';
const navigate = {
PREVIOUS: 'PREV',
NEXT: 'NEXT',
TODAY: 'TODAY',
DATE: 'DATE'
};
const propTypes = {
view: PropTypes.string.isRequired,
views: PropTypes.arrayOf(PropTypes.string).isRequired,
label: PropTypes.node.isRequired,
localizer: PropTypes.object,
onNavigate: PropTypes.func.isRequired,
onView: PropTypes.func.isRequired
};
export default class Toolbar extends Component {
static propTypes = propTypes;
render() {
let {
localizer: { messages },
label,
date
} = this.props;
return (
<ButtonToolbar>
<ButtonGroup>
<Button onClick={this.navigate.bind(null, navigate.TODAY)}>
{messages.today}
</Button>
<Button onClick={this.navigate.bind(null, navigate.PREVIOUS)}>
<Icon glyph="caret-left" />
</Button>
<Button onClick={this.navigate.bind(null, navigate.NEXT)}>
<Icon glyph="caret-right" />
</Button>
</ButtonGroup>
<ToolbarDateHeader date={date} onChange={this.toThisDay}>
{label}
</ToolbarDateHeader>
<ButtonGroup className="pull-right">
{this.viewNamesGroup(messages)}
</ButtonGroup>
</ButtonToolbar>
);
}
toThisDay = date => {
this.props.onView('day');
// give it just a tick to 'set' the view, prior to navigating to the proper date
setTimeout(() => {
this.props.onNavigate(navigate.DATE, date);
}, 100);
};
navigate = action => {
this.props.onNavigate(action);
};
view = view => {
this.props.onView(view);
};
viewNamesGroup(messages) {
let viewNames = this.props.views;
const view = this.props.view;
if (viewNames.length > 1) {
return viewNames.map(name => (
<Button
key={name}
className={cn({
active: view === name,
'btn-primary': view === name
})}
onClick={this.view.bind(null, name)}
>
{messages[name]}
</Button>
));
}
}
}
import React, { useState, useEffect } from "react";
import clsx from "clsx";
import moment from "moment";
function RBCToolbar(props) {
const { label, date, view, views, onView, onNavigate } = props;
const [month, setMonth] = useState("January");
const mMonth = moment(date).format("MMMM");
const months = moment.months();
useEffect(() => {
setMonth(mMonth);
}, [mMonth]);
const onChange = (event) => {
const current = event.target.value;
onNavigate("DATE", moment().month(current).toDate());
setMonth(current);
};
const goToView = (view) => {
onView(view);
};
const goToBack = () => {
onNavigate("PREV");
};
const goToNext = () => {
onNavigate("NEXT");
};
const goToToday = () => {
onNavigate("TODAY");
};
return (
<div className="rbc-toolbar">
<div className="rbc-btn-group">
<button onClick={goToToday}>Today</button>
<button onClick={goToBack}>Back</button>
<button onClick={goToNext}>Next</button>
</div>
<div className="rbc-toolbar-label">
{view === "month" ? (
<>
<select className="rbc-dropdown" value={month} onChange={onChange}>
{months?.map((month) => (
<option
value={month}
className="rbc-dropdown-option" //custom class
key={month}
>
{month}
</option>
))}
</select>
<span className="rbc-year"> {moment(date).format("YYYY")}</span>
</>
) : (
label
)}
</div>
<div className="rbc-btn-group">
{views?.map((item) => (
<button
onClick={() => goToView(item)}
key={item}
className={clsx({ "rbc-active": view === item })}
>
{item}
</button>
))}
</div>
</div>
);
}
export default RBCToolbar;