I'm trying to write a test to check if the component Header is rendering the company logo (context.company?.logoUrl, via context) and if it receives the companyId, via props.
import { Container, Image } from "react-bootstrap";
type Props = {
context: AppContextProps;
companyId: string | undefined;
};
const Header = ({ context, companyId }: Props) => {
if (!context.company) {
return <label>Loading...</label>;
}
return (
<Container className="header">
<a href={`/${companyId}`}>
<Image
src={context.company?.logoUrl}
className="header-logo"
/>
</a>
</Container>
);
};
export default WithContext(Header);
The component Header is wrapped by a high order component, WithContext.
const WithContext = Component => {
return props => (
<AppContext.Consumer>
{({state}) => <Component context={ state } {...props} />}
</AppContext.Consumer>
);
}
export default WithContext;
And here it is the AppContext structure.
export const AppContext = React.createContext();
class AppContextProvider extends Component {
state = {
company: null,
};
getCompanyData() {
try {
const response = await companyService.getPublicProfile();
this.setState({ company: response });
} catch (error) {
console.log({ error });
}
}
componentDidMount() {
this.setState({
company: this.getCompanyData,
});
}
render() {
return (
<AppContext.Provider value={{ state: this.state }}>
{this.props.children}
</AppContext.Provider>
);
}
}
export default AppContextProvider;
This code was written 4 years ago, that's the reason why we are using class components and context this way (I know it is not the best, but we need to keep it that way). 😊
Here it is the base of the test (Jest one).
it("should load company logo URL", async () => {
const propsMock = {
companyId: "abcd9876",
};
const contextMock = {
company: {
logoUrl: "https://picsum.photos/200/300",
},
};
render(
<AppContext.Consumer>
{({ state }) => <Header context={state} {...propsMock} />}
</AppContext.Consumer>
);
// Don't mind this expect :P
expect(1 + 2).toBe(3);
});
Every time I run the test, I get an error in Context.Consumer.
TypeError: Cannot destructure property 'state' of 'undefined' as it is
undefined.
render(
28 | <AppContext.Consumer>
> 29 | {({ state }) => <Header context={state} {...propsMock} />}
| ^
30 | </AppContext.Consumer>
31 | );
Basically I'm struggling to mock a Consumer and pass the context (state, which is supposed to be contextMock) and props (propsMock) so I can pass them to the Header component.
Do you guys have any idea how to make this test work properly?
Thank you!
Related
I am very beginner in react and i got stacked with a warning, I can not resolve them even i read a lot about it in the internet.
The warning is:
The App.tsx relevant code parts:
const [selectedMoment, setSelectedMoment] = useState<IMoment | null>(null);
const [editMode, setEditMode] = useState(false);
const handleOpenCreateForm = () => {
setSelectedMoment(null);
setEditMode(true);
}
return (
<Fragment>
<NavBar openCreateForm={handleOpenCreateForm} />
</Fragment>);
The menu is in the NavBar.tsx:
interface IProps {
openCreateForm: () => void;
}
export const NavBar: React.FC<IProps> = ({ openCreateForm }) => {
return (
<Menu fixed='top' inverted>
<Container>
<Menu.Item>
<Button positive content="Moment upload" onClick={openCreateForm} />
</Menu.Item>
</Container>
</Menu>
)
}
They are semantic-ui-react elements.
Anybody idea why do i get this warning?
This method is considered legacy, the alternative API is getDerivedStateFromProps.
Here’s a sample of what the old method would look like:
class List extends React.Component {
componentWillReceiveProps(nextProps) {
if (nextProps.selected !== this.props.selected) {
this.setState({ selected: nextProps.selected });
this.selectNew();
}
}
// ...
}
The new method works a bit differently:
class List extends React.Component {
static getDerivedStateFromProps(props, state) {
if (props.selected !== state.selected) {
return {
selected: props.selected,
};
}
// Return null if the state hasn't changed
return null;
}
// ...
}
Basically I have an issue with rendering information got from firebase to the screen.
When I'm trying to call the function which gets the information from the database inside componentDidMount(), the function is not even executed, but when I call it inside the render() function, which I know it's now the right thing to do it works, it goes into an infinite loop and it keeps accessing the database over and over again, but it renders the correct information to the screen. So the function itself is not the issue, I guess, since it is able to retrieve the information from the database.
Also a console.log() inside the componentDidMount() seems to work so componentDidMount() does fire.
So how should I go forward with this issue? I've been struggling with this for several hours now. I can't seem to find the issue.
This is my code:
export default class Cars extends Component {
constructor(){
super();
this.state = {
cars: []
}
}
componentDidMount(){
this.loadCarsFromDB();
}
loadCarsFromDB = () => (
<FirebaseContext.Consumer>
{firebase => {
firebase.accessFirebase("cars").get()
.then(snapshot => {
let cars = [];
snapshot.docs.forEach(doc => {
cars.push(doc.data());
})
return cars;
})
.then(cars => {
this.setState({cars: cars});
})
.catch(err => {
console.log(err);
});
}
}
</FirebaseContext.Consumer>
)
renderCars = () => {
return this.state.cars.map(car => <Car
brandName={car.brandName}
model={car.model}
color={car.color}
price={car.price} />)
}
render() {
return (
<div className="car-item">
{this.renderCars()}
</div>
);
}
}
Firebase class except the credentials
export default class Firebase {
constructor() {
app.initializeApp(config);
}
accessFirebase = () => {
let db = app.firestore();
return db.collection("cars");
}
}
This is the Car function
const car = (props) => (
<div className="Car">
<span>{props.brandName ? props.brandName : "Nu exista"}</span>
<span>{props.model ? props.model : "Nu exista"}</span>
<span>{props.color ? props.color : "Nu exista"}</span>
<span>{props.price ? props.price : "Nu exista"}</span>
</div>
)
export default car;
And this is the index.js file. I don't know, maybe it has something to do with the use of contexts. I basically create only one firebase instance which should allow me to query the database from anywhere in the code by using only this very instance.
ReactDOM.render(
<FirebaseContext.Provider value={new Firebase()}>
<App />
</FirebaseContext.Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
App.jsx file
class App extends Component {
render(){
return(
<div>
<Cars/>
</div>
)
}
}
export default App;
You are not supposed to use the FirebaseContext.Consumer component from loadCarsFromDB. So I would lift up FirebaseContext.Consumer around Cars and pass down the firebase property as a prop.
class App extends Component {
render(){
return(
<div>
<FirebaseContext.Consumer>
{firebase => (
<Cars firebase={firebase}/>
)
}
<FirebaseContext.Consumer />
</div>
)
}
}
loadCarsFromDB = () => (
this.props.firebase.accessFirebase("cars").get()
.then(snapshot => {
let cars = [];
snapshot.docs.forEach(doc => {
cars.push(doc.data());
})
return cars;
})
.then(cars => {
this.setState({cars: cars});
})
.catch(err => {
console.log(err);
});
)
I have this component that I split for easy management. Before splitting everything worked as expected, after splitting I am getting an error when I click on an icon which calls the createReactionsIcon. Error says
TypeError: updateReaction is not a function
onClick
./components/home/components/SingleUpload.jsx:26
23 |
24 | return icons.map(({ key, text, type }) => (
25 | <IconText
> 26 | onClick={() => updateReaction(item.id, key)}
| ^ 27 | key={key}
28 | type={type}
29 | text={text}
How can I access this correctly from my Home component where updateReaction is returning updateReaction from the redux store.
SubComponent
import PropTypes from 'prop-types';
import React from 'react';
import { Avatar, Card, Icon, List } from 'antd';
import { LIST_TEXTS, STYLES } from '../constants';
const { AVATAR, CARD_CONTAINER, CARD_LIST, ICON, USER_LIST } = STYLES;
const { INNER, MORE, UPLOAD, VERTICAL } = LIST_TEXTS;
const IconText = ({ type, text, onClick }) => (
<span>
<Icon type={type} style={ICON} onClick={onClick} />
{text}
</span>
);
function createReactionsIcon(item, updateReaction) {
const { like, dislike, maybe } = item.reactions;
const icons = [
{ key: 'like', text: `${like.count}`, type: 'heart' },
{ key: 'dislike', text: `${dislike.count}`, type: 'dislike' },
{ key: 'maybe', text: `${maybe.count}`, type: 'meh' },
];
return icons.map(({ key, text, type }) => (
<IconText
onClick={() => updateReaction(item.id, key)}
key={key}
type={type}
text={text}
/>
));
}
export default class SingleUpload extends React.Component {
render() {
const { values } = this.props;
return (
<div style={CARD_CONTAINER}>
<List
itemLayout={VERTICAL}
dataSource={values}
renderItem={item => {
const { avatar, description, id, uploader: { image, name } } = item;
return (
<List.Item style={USER_LIST}>
<Card
actions={createReactionsIcon(item, this.updateReaction)}
cover={<img alt={UPLOAD} src={image} />}
extra={<Icon type={MORE} />}
hoverable
key={id}
title={(
<a href="/">
<Avatar src={avatar} style={AVATAR} />
{name}
</a>
)}
type={INNER}
style={CARD_LIST}
>
{description}
</Card>
</List.Item>
);
}}
/>
</div>
);
}
}
Home.js
import PropTypes from 'prop-types';
import React from 'react';
import { connect } from 'react-redux';
import { bindActionCreators } from 'redux';
import SingleUpload from './SingleUpload';
import ComparisonUpload from './ComparisonUpload';
import { STYLES } from '../constants';
import * as actions from '../actions';
import { getUploads } from '../selectors';
const { CARD_CONTAINER } = STYLES;
class Home extends React.Component {
componentDidMount() {
const { actions: { requestUploadList } } = this.props;
requestUploadList();
}
updateReaction = (id, reaction) => {
const { actions: { updateReaction } } = this.props;
const payload = { id, reaction };
updateReaction(payload);
}
render() {
const { uploads } = this.props;
return (
<div style={CARD_CONTAINER}>
<SingleUpload values={[...uploads.values()]} />
<ComparisonUpload values={[...uploads.values()]} />
</div>
);
}
}
Home.propTypes = {
actions: PropTypes.objectOf(PropTypes.object),
uploads: PropTypes.instanceOf(Map),
};
const mapStateToProps = state => ({
uploads: getUploads(state),
});
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(actions, dispatch),
});
export default connect(mapStateToProps, mapDispatchToProps)(Home);
Pass your function to component as props,
<SingleUpload values={[...uploads.values()]} updateReaction = {this.updateReaction}/>
Now you can use this in your child component,
<IconText onClick={() => this.props.updateReaction(item.id, key)}
You can pass the updateReaction from your parent to child as a callback
<SingleUpload values={[...uploads.values()]} hanldeReaction={this.updateReaction} />
And you can access it in the child using props.hanldeReaction
<Card actions={createReactionsIcon(item, this.props.hanldeReaction)}
You have to pass down the updateReaction() event-handler you defined in Home as a prop to SingleUpload. Then you can access that prop from anywhere inside your component.
Which means we can cleanup the actions prop inside the Card since we only need to pass the item now.
<Card actions={createReactionsIcon(item)}
As well as createReactionsIcon, now we just call that prop directly inside the function
function createReactionsIcon(item) {
const { like, dislike, maybe } = item.reactions;
const icons = [
{ key: 'like', text: `${like.count}`, type: 'heart' },
{ key: 'dislike', text: `${dislike.count}`, type: 'dislike' },
{ key: 'maybe', text: `${maybe.count}`, type: 'meh' },
];
return icons.map(({ key, text, type }) => (
<IconText
onClick={() => this.props.updateReaction(item.id, key)}
key={key}
type={type}
text={text}
/>
));
}
Less redundant code overall which sounds like what you are trying to achieve.
I'm having troubles updating the header class so it updates it's className whenever displaySection() is called. I know that the parent state changes, because the console log done in displaySection() registers the this.state.headerVisible changes but nothing in my children component changes, i don't know what I'm missing, I've been trying different solutions for some hours and I just can't figure it out what i'm doing wrong, the header headerVisible value stays as TRUE instead of changing when the state changes.
I don't get any error code in the console, it's just that the prop headerVisible from the children Header doesn't get updated on it's parent state changes.
Thank you!
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected }, () => {
this.sectionRef.current.changeSection(this.state.section)
})
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<Layout>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</Layout>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { this.props.selectSection("projects")}}>
{" "}
Projects
</span>
</header>
)
There seemed to be a couple of issues with your example code:
Missing closing div in Header
Using this.props instead of props in onclick in span in Header
The below minimal example seems to work. I had to remove your call to this.sectionRef.current.changeSection(this.state.section) as I didn't know what sectionRef was supposed to be because it's not in your example.
class IndexPage extends React.Component {
constructor(props) {
super(props)
this.state = {
section: "",
headerVisible: true,
}
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
this.setState({ section: sectionSelected })
setTimeout(() => {
this.setState({
headerVisible: !this.state.headerVisible,
})
}, 325)
setTimeout(()=>{
console.log('this.state', this.state)
},500)
}
render() {
return (
<div>
<Header selectSection={this.displaySection} headerVisible={this.state.headerVisible} />
</div>
)
}
}
const Header = props => (
<header className={props.headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => { props.selectSection("projects")}}>
{" "}
Projects
</span>
</div>
</header>
)
ReactDOM.render(
<IndexPage />,
document.getElementsByTagName('body')[0]
);
.visible {
opacity: 1
}
.invisible {
opacity: 0
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
There is a markup error in your code in Header component - div tag is not closed.
Also, I suppose, you remove some code to make example easy, and there is artifact of this.sectionRef.current.changeSection(this.state.section) cause this.sectionRef is not defined.
As #Felix Kling said, when you change the state of the component depending on the previous state use function prevState => ({key: !prevState.key})
Any way here is a working example of what you trying to achieve:
// #flow
import * as React from "react";
import Header from "./Header";
type
Properties = {};
type
State = {
section: string,
headerVisible: boolean,
};
class IndexPage extends React.Component<Properties, State> {
static defaultProps = {};
state = {};
constructor(props) {
super(props);
this.state = {
section: "",
headerVisible: true,
};
this.displaySection = this.displaySection.bind(this)
}
displaySection(sectionSelected) {
setTimeout(
() => this.setState(
prevState => ({
section: sectionSelected,
headerVisible: !prevState.headerVisible
}),
() => console.log("Debug log: \n", this.state)
),
325
);
}
render(): React.Node {
const {section, headerVisible} = this.state;
return (
<React.Fragment>
<Header selectSection={this.displaySection} headerVisible={headerVisible} />
<br/>
<div>{`IndexPage state: headerVisible - ${headerVisible} / section - ${section}`}</div>
</React.Fragment>
)
}
}
export default IndexPage;
and Header component
// #flow
import * as React from "react";
type Properties = {
headerVisible: boolean,
selectSection: (section: string) => void
};
const ComponentName = ({headerVisible, selectSection}: Properties): React.Node => {
const headerRef = React.useRef(null);
return (
<React.Fragment>
<header ref={headerRef} className={headerVisible ? 'visible' : 'invisible'}>
<div className="navbar-item column is-size-7-mobile is-size-5-tablet is-uppercase has-text-weight-semibold">
<span onClick={() => selectSection("projects")}>Projects</span>
</div>
</header>
<br/>
<div>Header class name: {headerRef.current && headerRef.current.className}</div>
</React.Fragment>
);
};
export default ComponentName;
I have a simple post React project where I have a list of posts Posts.js and a full post template FullPost.js. In Posts.js, I pass the slug and id so that I can render the url.
For example, 'food' has a list of posts. I click on a post and it passes the 'slug: carrots' and 'id: 2' to 'food/carrots'. This 'id' is passed so it can find the correct pathname to my carrot post in my database i.e. 'mydatabase.com/food/posts/2/'
This works well, however when I refresh the page the 'slug' and 'id' that got passed to the FullPost component earlier disappears. Additionally, I would like to be able to access the URL directly, for example, if I type in 'www.mywebsite.com/food/carrots' it would load my carrot post. Currently my FullPost does not load when I refresh or when I go to the post's URL directly. Is there a way to load the post from these other entry points?
Below is my code:
My database:
posts:
0:
slug: "beans"
id: 1
1:
slug: "milk"
id: 2
2:
slug: "carrots"
id: 3
Posts.js: A list of posts.
import React, { Component } from 'react';
import axios from '../../../axiosPosts';
import Aux from '../../../hoc/Aux/Aux';
import classes from './Posts.css';
import Post from '../../../components/Post/Post';
class Posts extends Component {
state = {
posts: []
}
componentDidMount () {
this.getData(this.props.pathname, this.props.filter);
}
getData(pathname, filter) {
axios.get(pathname + '.json')
.then(response => {
const post = response.data.filter(({category}) => category === filter);
const updatedPosts = post.map(post => {
return {
...post
}
});
this.setState({
posts: updatedPosts
});
})
.catch(error => {
console.log(error);
});
}
postSelectedHandler = ( slug, id ) => {
const URL = `${this.props.match.url}/${slug}`;
this.props.history.push({pathname: URL, id: id });
}
render () {
let posts = <p style={{textAlign: 'center'}}>Whoops! Something went wrong.</p>;
if(!this.state.error) {
posts = this.state.posts.map(post => {
return (
<Post
key={post.id}
title={post.slug}
clicked={() => this.postSelectedHandler( post.slug, post.id )} />
);
});
};
return (
<Aux>
<div className={classes.PostList}>{posts}</div>
</Aux>
)
}
}
export default Posts;
FullPost.js (Actual Post)
import React, { Component } from 'react';
import axios from '../../../axiosPosts';
import classes from './FullPost.css';
class FullPost extends Component {
state = {
loadedPost: null,
}
componentDidMount () {
const { location: {id} } = this.props;
this.loadData(id);
}
loadData(id) {
if ( id ) {
if ( !this.state.loadedPost || (this.state.loadedPost && this.state.loadedPost.id !== +id) ) {
axios.get( '/food/posts/' + (id - 1) + '.json' )
.then( response => {
this.setState( { loadedPost: response.data, locationId: id } );
});
}
}
}
render () {
let post = <p style={{ textAlign: 'center' }}>Please select a Post!</p>;
const { location: {id} } = this.props;
if ( id ) {
post = <p style={{ textAlign: 'center' }}>Loading...!</p>;
}
if ( this.state.loadedPost ) {
post = (
<div className={classes.FullPost}>
<h1 className={classes.Title}>{this.state.loadedPost.title}</h1>
</div>
);
}
return post;
}
}
export default FullPost;
App.js: Main js
import React, { Component } from 'react';
import { Route, Switch } from 'react-router-dom';
import classes from './App.css';
import Layout from './hoc/Layout/Layout';
import Posts from './pages/Posts/Posts';
import FullPost from './pages/FullPost/FullPost';
class App extends Component {
constructor(props) {
super(props);
console.log('[App.js] Inside Constructor', props);
}
render() {
return (
<div className={classes.App}>
<Layout>
<Switch>
<Route path="/food/" exact component={Posts} />
<Route path="/food/:slug" exact component={FullPost} />
<Route render={() => <h1>Whoops! What you're looking for isn't here anymore.</h1>} />
</Switch>
</Layout>
</div>
);
}
}
export default App;
You have to use something like React Router.
In a nutshell your issue is that there is no URL matching logic in your code (everything would work the same even if you didn't change the URL at all).