I'm trying to use Solid's react-components to load a user's profile from their webId. I'm running into a problem with useLDflex(). There problem seems to be something to do with React Hooks, but I can't figure it out. My goal is to load the user's profile when the page loads; open to making whatever changes necessary. I'm using MobX for state.
Below is the code and below below is the error in the compiler/web browser. Thank you.
Code (React/JSX/TypeScript):
import React from 'react'; // 16.14.0
import { observer } from 'mobx-react';
import { observable } from 'mobx';
import { useLDflex } from '#solid/react'; // 1.10.0
#observer
export class Profile extends React.Component<{profileId: string}, {}> {
#observable webId = `https://${this.props.profileId}.solidcommunity.net/profile/card#me`;
#observable name = useLDflex(`[${this.webId}`)[0];
render() {
return (
<main role="Profile">
<div className="container">
webId: https://{this.props.profileId}.solidcommunity.net/profile/card#me
Name: {this.name}
</div>
</main>
)
}
}
Error:
src/components/profile/index.tsx
Line 9:24: React Hook "useLDflex" cannot be called at the top level. React Hooks must be called in a React function component or a custom React Hook function react-hooks/rules-of-hooks
Search for the keywords to learn more about each error.
You cannot use React Hooks inside class component, ref here: https://reactjs.org/docs/hooks-faq.html#should-i-use-hooks-classes-or-a-mix-of-both
So you need to rewrite it to functional component with Mobx, or make a higher order component and pass the props into your class component (when your class is too complex to rewrite)
With FC:
import {observer} from "mobx-react";
const Profile = observer(({ profileId }) => {
// ...
const name = useLDflex(`...`);
// ...
})
HOC
const withName = (Component) => ({ profileId }) => {
const name = useLDflex('...');
return <Component name={name} profileId={profileId} />
}
export default withName(Profile);
Related
I am using react router v5, and trying to get URL parameters by props match object into my react component class. However it is not working! What am I doing wrong here?
When I create my component as a JavaScript function it all works fine, but when I try to create my component as a JavaScript class it doesn't work.
Perhaps I am doing something wrong? How do I pass the Match object in to my class component and then use that to set my component's state?
here code:
import React, { Component } from 'react';
import { Link} from "react-router-dom";
export class JobDetail extends Component {
state = {
// jobsData: [],
}
async componentDidMount() {
const props = this.props.match;
console.log("---props data---",props); // it's showing undefined
}
render() {
return (
<>
test message
</>
)
}
}
export default JobDetail
I was trying to pass data within route components. Since the react-router-dom remove props for class components in the latest version (v6), I just imported pubsub.js and tried to pass the data through the Link with an onclick event.
Here is the component waiting for publishing (showing part of codes).
import React, { Component } from 'react'
import { Link } from 'react-router-dom'
import PubSub from 'pubsub-js';
export default class ProductDetails extends Component {
state = {
product: {}
}
componentDidMount() {
PubSub.subscribe('product', (_, product) => {
this.setState({product})
})
}
render(){...}
}
Here is the component passing the data:
import React, { Component } from 'react'
import { Link } from 'react-router-dom';
import PubSub from 'pubsub-js';
export default class ProductHome extends Component {
pubProduct = (product) => {
return () => {
PubSub.publish('product', product)
}
}
...
render(){
const product = {a: 1}
<Link
to='/product/details'
onClick={this.pubProduct(product)}
> Details </Link>
...
}
Both of them are route components. I also tried publishSync but still not working. After click the Link, the state in the ProductDetails component did not change.
If router doesn't support pubsub, how to pass data then? I know using hook apis in react-router-dom v6 could be the best way to handle this kind of problem. But for class components, is there a good way to pass any data within route components in v6?
Thanks!
I'm tryin to show navigation depends on changes of categoryURL from
redux store and changing the state in other components. Redux changes the store and it works fine. But in my
component "this.props.categoryUrl" doesn't reflect on value. Can't
find out why?
import React, {Component} from 'react';
import NavigationItems from './NavigationItems/NavigationItems';
import classes from './Navigation.module.css';
import {connect} from 'react-redux';
const mapStateToProps = state => {
console.log(state)
return {
categoryURL: state.categoryUrl
};
};
class navigation extends Component {
componentDidMount() {
console.log(this.props.categoryUrl);
}
componentDidUpdate(prevProps, prevState, snapshot) {
console.log('NAVIGATION!', this.props.categoryUrl);
}
render() {
let nav = null;
if (this.props.categoryUrl) {
nav = (
<div className={classes.Navigation}>
<NavigationItems/>
</div>
)
}
return (
<>
{nav}
</>
)
}
}
export default connect(mapStateToProps, null)(navigation);
In "normal" React it is needed to use <Navigation/> (Capital letter at the beginning) instead of <navigation/>. Also, If <Navigation/> is being used by another React component then it might be needed to add code that will be executed inside <Navigation/> to refresh the component where you are using <Navigation/> (some kind of callback passed to <Navigation/>). It is this the way or move all the <Navigation/>'s code to the component where you are using <Navigation/>. This is how I solved this kind of problem.
I was looking for steps on the internet to refactor previous applications for a new approach, but I did not find a satisfactory answer...
I know the previous approach for creating applications in ReactJS which uses the Component and render() function for example, but I can see that it is currently different because:
npx create-react-app app-name
is now generating a different template.
For example, previously it was imported:
import React, {Component} from 'react';
and now only:
import React from 'react';
I am asking for tips or simple advice on what I should change so that the old code works without importing the Component.
Has the method of using functions for communication via AJAX (e.g. loadPerson) changed as well?
For example here is some not working example of ./src/PersonDetail.js:
import React from 'react';
import { DetailList } from './DetailList';
import { loadPerson } from './requests';
export class PersonDetail {
constructor(props) {
super(props);
this.state = {person: null};
}
async componentDidMount() {
const {personId} = this.props.match.params;
const person = await loadPerson(personId);
this.setState({person});
}
render() {
const {person} = this.state;
if (!person) {
return null;
}
return (
<div>
<h1 className="title">{person.name}</h1>
<div className="box">{person.description}</div>
<h5 className="title is-5">Details at {person.name}</h5>
<DetailList details={person.details} />
</div>
);
}
}
Thank you in advance.
With the introduction of hooks some core concepts begin to change. Before React 16.8 we used to have a rule of thumb to decide rather a component should be based on class or function:
If the component should hold state then it should be class based. If it doesn't have state (stateless) then it could be a functional component
This used to be true cause there wasn't a way to implement stateful logic inside functional components. Now hooks allow you to implement state in functional components.
The boilerplate generated by create-react-app doesn't import Component from react anymore cause only class based components need to extends from Component and App is now a functional component.
Nothing changed really it's just another way to write your components.
Just import like it used to be:
export class PersonDetail extends React.Component
or give hooks a chance and turn your component into a functional component:
import React, { useState, useEffect } from 'react';
import { DetailList } from './DetailList';
import { loadPerson } from './requests';
const PersonDetail = ({ personID }) => {
const [person, setPerson] = useState(false)
useEffect(() => {
const person = await loadPerson(personId)
setPerson(person)
}, [personID])
return !person ? null : (
<div>
<h1 className="title">{person.name}</h1>
<div className="box">{person.description}</div>
<h5 className="title is-5">Details at {person.name}</h5>
<DetailList details={person.details} />
</div>
)
}
To add to other comments...
You can have class-based components side by side to functional components, there is no inherent need to re-write any old class-based components you have written.
Introducing Hooks
No Breaking Changes
Before we continue, note that Hooks are:
Completely opt-in. You can try Hooks in a few components without rewriting any existing code. But you don’t have to learn or use Hooks right now if you don’t want to.
100% backwards-compatible. Hooks don’t contain any breaking changes.
Available now. Hooks are now available with the release of v16.8.0.
I am assuming the "new approach" you are talking about are React Hooks.
import * as React from "react";
import { DetailList } from "./DetailList";
import { loadPerson } from "./requests";
const PersonDetail = props => {
const [person, setPerson] = React.useState(null);
React.useEffect(() => {
(async () => {
const { personId } = props.match.params;
setPerson(await loadPerson(personId));
})();
}, []);
if (!person) {
return null;
}
return (
<div>
<h1 className="title">{person.name}</h1>
<div className="box">{person.description}</div>
<h5 className="title is-5">Details at {person.name}</h5>
<DetailList details={person.details} />
</div>
);
};
export { PersonDetail };
The change you noticed is that create-react-app now creates functional components. This means your components are no longer classes but just functions.
You can still import Component and export a class that extends Component.
You don't need to write your class as a function but to write the example class as a functional component you can do the following:
import React, { useEffect, useState } from 'react';
import { DetailList } from './DetailList';
import { loadPerson } from './requests';
export default props => {
const { personId } = props.match.params;
const [person, setPerson] = useState(null);
useEffect(() => {
loadPerson(personId).then(person => setPerson(person));
}, [personId]);
if (!person) {
return null;
}
return (
<div>
<h1 className="title">{person.name}</h1>
<div className="box">{person.description}</div>
<h5 className="title is-5">
Details at {person.name}
</h5>
<DetailList details={person.details} />
</div>
);
};
You can read more about react hooks here
With this import,
import React from 'react';
You can extend Component without importing like,
export class PersonDetail extends React.Component{ ...}
Update
Hooks are newly added in React 16.8 and it is not recommended that we should change our exsiting code with Hooks. We can still have our exsiting class-based approach which extends Component or React.Component.
Hooks gives us the capability of maintaining state of the component as well as it gives a space to write React lifecycle methods.
For example, state in class-based component
state = {
stateVariable : "stateValue"
}
In new approach, it is equivalent to
const [stateVariable, setStateVariable] = useState("stateValue")
And for the lifecycle methods we have useEffect.
If you’re familiar with React class lifecycle methods, you can think of useEffect Hook as componentDidMount, componentDidUpdate, and componentWillUnmount combined.
useEffect(() => {
console.log(stateVariable);
});
of-course we need to import useState and useEffect from react package,
import React, {useState, useEffect} from 'react'
Finally, the class-based component
import React from 'react'
class MyComponent extends React.Component{
state={
stateVariable : "stateValue"
}
componentDidMount(){
console.log(this.state.stateVariable)
}
render(){
return(
<div> {this.state.stateVariable} </div>
)
}
}
can be converted to functional component like,
import React, {useState, useEffect} from 'react'
const MyComponent = (props) => {
const [stateVariable, setStateVariable] = useState("stateValue")
useEffect(()=>{
console.log(stateVariable)
})
return(
<div> {stateVariable} </div>
)
}
Note: We don't have access to this in functional component because we don't have class anymore.
If you want to use class components, you have to make them extend React.Component.
There is another way to create components now, which is the functional way. Meaning that a component can be a simple JavaScript function returning an element (usually, some JSX). In this case, you don't need to import React.Component anymore.
Class component:
import React, { Component } from "react"
class PersonDetail extends Component {
render() {
...
}
...
}
Functional component (this is the way React and the community is now pushing for):
import React from "react"
const PersonDetail = () => {
return (<Your JSX code>)
}
I'm having a react component. Let's say Todo
import React, { Component } from 'react';
import injectSheet from 'react-jss';
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export default injectSheet(Todo);
I want to test it with enzyme. And there are two problems with it.
1. Access to the state
(and other component specific features)
When I shallow or mount that composer in the suite I can't get access to its state of course because it's not my component anymore but something new around it.
E.g. this code will give me an error:
it('should have state updated on handleAddTodo', () => {
const todo = shallow(<Todo />);
const length = todo.state('todos').length;
});
It says of course TypeError: Cannot read property 'length' of undefined because the state is not what I expect but this: { theme: {}, dynamicSheet: undefined }
This won't also give me access to props, refs etc.
2. Problems with theme provider
To provide some default colouring to the project like this:
import React, { Component } from 'react';
import Colors from './whatever/Colors';
class App extends Component {
render() {
return (
<ThemeProvider theme={Colors}>
<WhateverInside />
</ThemeProvider>
);
}
}
And of course when running tests it gives me an error [undefined] Please use ThemeProvider to be able to use WithTheme.
So my question is the following. Is there a way to solve this problem in “one single place”. How can I make enzyme agnostic of what is my component wrapped with?
If not, then how do I solve the problem if passing the ThemeProvider features down to the component that I'm testing?
And how can I access the state, ref, props and other things of the wrapped component?
Thank you!
here's what I'd do to test the component,
import React, { Component } from 'react';
import injectSheet from 'react-jss';
const styles = {};
class Todo extends Component {
// methods that incl. state manipulation
render() {
const { classes } = this.props;
return (
<div className={classes.container}>
<WhateverElse />
</div>
);
}
}
export { styles, Todo as TodoCmp }
export default injectSheet(styles)(Todo);
and in the test file, I'd add the following:
import { theme } from 'your-theme-source';
const mockReducer = (prev, curr) => Object.assign({}, prev, { [curr]: curr });
const coerceStyles = styles =>
typeof styles === 'function' ? styles(theme) : styles;
const mockClasses = styles =>
Object.keys(coerceStyles(styles)).reduce(mockReducer, {});
import {TodoCmp, styles} from 'your-js-file';
// then test as you'd normally.
it('should blah blah', () => {
const classes = mockClasses(styles);
const todo = shallow(<Todo classes={classes} />);
const length = todo.state('todos').length;
})
Please read more about it in the nordnet-ui-kit library specifically in the test directory. Here's a quick example
It is not related to JSS specifically. Any HOC wraps your component. Ideally you don't test any internals of a component directly.
Components public api is props, use them to render your component with a specific state and verify the rendered output with shallow renderer.
For some edge cases if first and preferred way is impossible, you can access the inner component directly and access whatever you need directly. You will have to mock the props the HOC would pass otherwise for you.
const StyledComponent = injectSheet(styles)(InnerComponent)
console.log(StyledComponent.InnerComponent)
If your component relies on theming, you have to provide a theme provider, always.