Does material-ui useStyles really requires the entire props object? - javascript

According to the DOCS, if we want our component to support style overrides with classes we should pass the entire props object to the useStyles hook:
function Nested(props) {
const classes = useStyles(props);
const { someProp1, someProp2, ...rest } = props;
return (...);
}
This is forcing us to destructure the props inside the component's body instead of inside the component's signature.
After some short playing around, i found out that all useStyles really needs to support the classes feature is to pass it an object with the classes prop:
function Nested({ someProp1, someProp2, classes ...rest }) {
const localClasses = useStyles({ classes });
return (...);
}
While this seems to work just fine, i'm wondering if i am missing something. I'm not sure if the DOCs are too defensive or trying to keep it short and simple, maybe they wanted to save us from renaming our local classes variable and it might as well just say: "Pass an object that holds the classes prop..." or something like that. But maybe there is more to it and i'm missing something crucial.

It looks like the useStyles() function that is returned from makeStyles just takes an optional object (which could be props). I didn't dig into it too much, but at first glance I don't see any expectations that you're actually passing any specific object/properties.
Check out the links here to the source code for more info
Here are the types
import { ClassNameMap, Styles, WithStylesOptions } from '#material-ui/styles/withStyles';
import { DefaultTheme } from '../defaultTheme';
export default function makeStyles<
Theme = DefaultTheme,
Props extends object = {},
ClassKey extends string = string,
>(
styles: Styles<Theme, Props, ClassKey>,
options?: Omit<WithStylesOptions<Theme>, 'withTheme'>,
): keyof Props extends never
? // `makeStyles` where the passed `styles` do not depend on props
(props?: any) => ClassNameMap<ClassKey>
: // `makeStyles` where the passed `styles` do depend on props
(props: Props) => ClassNameMap<ClassKey>;

Related

Cannot Get Values from Prop in Twin.macro

You can see an example of what I am trying to do here: https://codesandbox.io/s/vibrant-leaf-qj8vz
Note: this particular example is using Twin.macro with Styled Components. On my local computer I tried the same thing with the same results using Twin.macro with emotion/next.js.
Here is a sample component illustrating what I am trying to do:
import React from 'react'
import tw from 'twin.macro'
const Acme = ({ children, type }) => <div css={[tw`${type}`]}>{children}</div>
export default Acme
Here is how I would use that component: <Acme type="text-2xl">Text Goes Here</Acme>
My expectation is that I will be able to style this instance of the <Acme /> component via the tailwind css classes that I pass into the type prop. Instead, I get the following error message:
/src/components/Acme.js: twin.macro: Property value expected type of string but got null Learn more: https://www.npmjs.com/package/twin.macro
When trying to figure this out, I noticed something interesting that may be relevant. Here is a variation of the code that does work:
const Acme = ({ children, type }) => {
const typeClass = 'text-2xl'
const typeObj = {
class: 'text-2xl',
}
return <div css={[tw`${typeClass}`]}>{children}</div>
}
export default Acme
Note that I have created a variable typeClass and set it to the same tailwind css class. Note, in particular, the following line of code:
css={[tw`${typeClass}`]}
I have replace the prop type with the variable typeClass. This works. But now, instead of using the variable typeClass let's use the object typeObj that I have created as follows:
const Acme = ({ children, type }) => {
const typeClass = 'text-2xl'
const typeObj = {
class: 'text-2xl',
}
return <div css={[tw`${typeObj.class}`]}>{children}</div>
}
export default Acme
This does not work and produces the same error:
/src/components/Acme.js: twin.macro: Property value expected type of string but got null Learn more: https://www.npmjs.com/package/twin.macro
This is so even though typeClass === typeObj.class evaluates to true.
I don't know if this is helpful, but perhaps it can help indicate a solution. If I can get the type prop to behave like the typeClass variable then hopefully this would work.
Either way, any idea why this is not working and how to fix it?
Thanks.
I found the answer (meaning that someone else answered it on a different site). Here is is. I have to rewrite both the Component and the usage of the component as follows:
// Acme.js
const Acme = ({ children, type }) => <div css={[type]}>{children}</div>
---
// App.js
import tw from "twin.macro"
<Acme type={tw`text-2xl`}>Text Goes Here</Acme>
I have tried this out and it works.

How can I correctly use the React context API in a class based component? [duplicate]

In this example, I have this react class:
class MyDiv extends React.component
constructor(){
this.state={sampleState:'hello world'}
}
render(){
return <div>{this.state.sampleState}
}
}
The question is if I can add React hooks to this. I understand that React-Hooks is alternative to React Class style. But if I wish to slowly migrate into React hooks, can I add useful hooks into Classes?
High order components are how we have been doing this type of thing until hooks came along. You can write a simple high order component wrapper for your hook.
function withMyHook(Component) {
return function WrappedComponent(props) {
const myHookValue = useMyHook();
return <Component {...props} myHookValue={myHookValue} />;
}
}
While this isn't truly using a hook directly from a class component, this will at least allow you to use the logic of your hook from a class component, without refactoring.
class MyComponent extends React.Component {
render(){
const myHookValue = this.props.myHookValue;
return <div>{myHookValue}</div>;
}
}
export default withMyHook(MyComponent);
Class components don't support hooks -
According to the Hooks-FAQ:
You can’t use Hooks inside of a class component, but you can definitely mix classes and function components with Hooks in a single tree. Whether a component is a class or a function that uses Hooks is an implementation detail of that component. In the longer term, we expect Hooks to be the primary way people write React components.
As other answers already explain, hooks API was designed to provide function components with functionality that currently is available only in class components. Hooks aren't supposed to used in class components.
Class components can be written to make easier a migration to function components.
With a single state:
class MyDiv extends Component {
state = {sampleState: 'hello world'};
render(){
const { state } = this;
const setState = state => this.setState(state);
return <div onClick={() => setState({sampleState: 1})}>{state.sampleState}</div>;
}
}
is converted to
const MyDiv = () => {
const [state, setState] = useState({sampleState: 'hello world'});
return <div onClick={() => setState({sampleState: 1})}>{state.sampleState}</div>;
}
Notice that useState state setter doesn't merge state properties automatically, this should be covered with setState(prevState => ({ ...prevState, foo: 1 }));
With multiple states:
class MyDiv extends Component {
state = {sampleState: 'hello world'};
render(){
const { sampleState } = this.state;
const setSampleState = sampleState => this.setState({ sampleState });
return <div onClick={() => setSampleState(1)}>{sampleState}</div>;
}
}
is converted to
const MyDiv = () => {
const [sampleState, setSampleState] = useState('hello world');
return <div onClick={() => setSampleState(1)}>{sampleState}</div>;
}
Complementing Joel Cox's good answer
Render Props also enable the usage of Hooks inside class components, if more flexibility is needed:
class MyDiv extends React.Component {
render() {
return (
<HookWrapper
// pass state/props from inside of MyDiv to Hook
someProp={42}
// process Hook return value
render={hookValue => <div>Hello World! {hookValue}</div>}
/>
);
}
}
function HookWrapper({ someProp, render }) {
const hookValue = useCustomHook(someProp);
return render(hookValue);
}
For side effect Hooks without return value:
function HookWrapper({ someProp }) {
useCustomHook(someProp);
return null;
}
// ... usage
<HookWrapper someProp={42} />
Source: React Training
you can achieve this by generic High order components
HOC
import React from 'react';
const withHook = (Component, useHook, hookName = 'hookvalue') => {
return function WrappedComponent(props) {
const hookValue = useHook();
return <Component {...props} {...{[hookName]: hookValue}} />;
};
};
export default withHook;
Usage
class MyComponent extends React.Component {
render(){
const myUseHookValue = this.props.myUseHookValue;
return <div>{myUseHookValue}</div>;
}
}
export default withHook(MyComponent, useHook, 'myUseHookValue');
Hooks are not meant to be used for classes but rather functions. If you wish to use hooks, you can start by writing new code as functional components with hooks
According to React FAQs
You can’t use Hooks inside of a class component, but you can
definitely mix classes and function components with Hooks in a single
tree. Whether a component is a class or a function that uses Hooks is
an implementation detail of that component. In the longer term, we
expect Hooks to be the primary way people write React components.
const MyDiv = () => {
const [sampleState, setState] = useState('hello world');
render(){
return <div>{sampleState}</div>
}
}
You can use the react-universal-hooks library. It lets you use the "useXXX" functions within the render function of class-components.
It's worked great for me so far. The only issue is that since it doesn't use the official hooks, the values don't show react-devtools.
To get around this, I created an equivalent by wrapping the hooks, and having them store their data (using object-mutation to prevent re-renders) on component.state.hookValues. (you can access the component by auto-wrapping the component render functions, to run set currentCompBeingRendered = this)
For more info on this issue (and details on the workaround), see here: https://github.com/salvoravida/react-universal-hooks/issues/7
Stateful components or containers or class-based components ever support the functions of React Hooks, so we don't need to React Hooks in Stateful components just in stateless components.
Some additional informations
What are React Hooks?
So what are hooks? Well hooks are a new way or offer us a new way of writing our components.
Thus far, of course we have functional and class-based components, right? Functional components receive props and you return some JSX code that should be rendered to the screen.
They are great for presentation, so for rendering the UI part, not so much about the business logic and they are typically focused on one or a few purposes per component.
Class-based components on the other hand also will receive props but they also have this internal state. Therefore class-based components are the components which actually hold the majority of our business logic, so with business logic, I mean things like we make an HTTP request and we need to handle the response and to change the internal state of the app or maybe even without HTTP. A user fills out the form and we want to show this somewhere on the screen, we need state for this, we need class-based components for this and therefore we also typically use class based components to orchestrate our other components and pass our state down as props to functional components for example.
Now one problem we have with this separation, with all the benefits it adds but one problem we have is that converting from one component form to the other is annoying. It's not really difficult but it is annoying.
If you ever found yourself in a situation where you needed to convert a functional component into a class-based one, it's a lot of typing and a lot of typing of always the same things, so it's annoying.
A bigger problem in quotation marks is that lifecycle hooks can be hard to use right.
Obviously, it's not hard to add componentDidMount and execute some code in there but knowing which lifecycle hook to use, when and how to use it correctly, that can be challenging especially in more complex applications and anyways, wouldn't it be nice if we had one way of creating components and that super component could then handle both state and side effects like HTTP requests and also render the user interface?
Well, this is exactly what hooks are all about. Hooks give us a new way of creating functional components and that is important.
React Hooks let you use react features and lifecycle without writing a class.
It's like the equivalent version of the class component with much smaller and readable form factor. You should migrate to React hooks because it's fun to write it.
But you can't write react hooks inside a class component, as it's introduced for functional component.
This can be easily converted to :
class MyDiv extends React.component
constructor(){
this.state={sampleState:'hello world'}
}
render(){
return <div>{this.state.sampleState}
}
}
const MyDiv = () => {
const [sampleState, setSampleState] = useState('hello world');
return <div>{sampleState}</div>
}
It won't be possible with your existing class components. You'll have to convert your class component into a functional component and then do something on the lines of -
function MyDiv() {
const [sampleState, setSampleState] = useState('hello world');
return (
<div>{sampleState}</div>
)
}
For me React.createRef() was helpful.
ex.:
constructor(props) {
super(props);
this.myRef = React.createRef();
}
...
<FunctionComponent ref={this.myRef} />
Origin post here.
I've made a library for this. React Hookable Component.
Usage is very simple. Replace extends Component or extends PureComponent with extends HookableComponent or extends HookablePureComponent. You can then use hooks in the render() method.
import { HookableComponent } from 'react-hookable-component';
// πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡
class ComponentThatUsesHook extends HookableComponent<Props, State> {
render() {
// πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡πŸ‘‡
const value = useSomeHook();
return <span>The value is {value}</span>;
}
}
if you didn't need to change your class component then create another functional component and do hook stuff and import it to class component
Doesn't work anymore in modern React Versions. Took me forever, but finally resulted going back to go ol' callbacks. Only thing that worked for me, all other's threw the know React Hook Call (outside functional component) error.
Non-React or React Context:
class WhateverClass {
private xyzHook: (XyzHookContextI) | undefined
public setHookAccessor (xyzHook: XyzHookContextI): void {
this.xyzHook = xyzHook
}
executeHook (): void {
const hookResult = this.xyzHook?.specificHookFunction()
...
}
}
export const Whatever = new WhateverClass() // singleton
Your hook (or your wrapper for an external Hook)
export interface XyzHookContextI {
specificHookFunction: () => Promise<string>
}
const XyzHookContext = createContext<XyzHookContextI>(undefined as any)
export function useXyzHook (): XyzHookContextI {
return useContext(XyzHookContextI)
}
export function XyzHook (props: PropsWithChildren<{}>): JSX.Element | null {
async function specificHookFunction (): Promise<void> {
...
}
const context: XyzHookContextI = {
specificHookFunction
}
// and here comes the magic in wiring that hook up with the non function component context via callback
Whatever.setHookAccessor(context)
return (
< XyzHookContext.Provider value={context}>
{props.children}
</XyzHookContext.Provider>
)
}
Voila, now you can use ANY react code (via hook) from any other context (class components, vanilla-js, …)!
(…hope I didn't make to many name change mistakes :P)
Yes, but not directly.
Try react-iifc, more details in its readme.
https://github.com/EnixCoda/react-iifc
Try with-component-hooks:
https://github.com/bplok20010/with-component-hooks
import withComponentHooks from 'with-component-hooks';
class MyComponent extends React.Component {
render(){
const props = this.props;
const [counter, set] = React.useState(0);
//TODO...
}
}
export default withComponentHooks(MyComponent)
2.Try react-iifc: https://github.com/EnixCoda/react-iifc

HOC in react.js docs

In React docs in HOC section, there is an example
const CommentListWithSubscription = withSubscription(
CommentList,
(DataSource) => DataSource.getComments()
);
Could you please explain how a function scope works?
They put the second param as an arrow function, in this arrow function we have DataSource param and return a result of DataSource.getComments()
realization of HOC withSubscription
function withSubscription(WrappedComponent, selectData) {
return class extends React.Component {
constructor(props) {
super(props);
this.handleChange = this.handleChange.bind(this);
this.state = {
data: selectData(DataSource, props)
};
}
...
Here they use selectData as that function (DataSource) => DataSource.getComments()
and fire that function again with DataSource param
This moment a bit confused, is it the same DataSource that we put in arrow function above or different? and in general how does it work?
DataSource inside the HOC (withSubscription) is an existing variable in that scope or global scope, likely obtained via static import or a context.
DataSource in the parent (the code that calls the HOC) is just a place-holding parameter. Basically the parent is telling the HOC: "I don't know what data source you're using, just retrieve the comments from it (DataSource.getComments()) and use it as your state data".
If the parent wants another HOC instance to use different data (like blog post in the example), it just changes the instruction to DataSource.getBlogPost() for that HOC, possibly using some extra parameters passed via the HOC's props, like in the example. This pattern makes HOCs as flexible as it should be.
HOC concept
Any hoc is a normal function which returns a react Component , but with modified props
considering example in react docs :
const CommentListWithSubscription = withSubscription(
CommentList,
(DataSource) => DataSource.getComments()
);
I agree this is definitely not a easiest example to prefer.
consider a simple HOC withData
const withData = (component, endpoint) => {
function WrappedComponent(props) {
const [data, setData] = useState(null);
useEffect(() => {
fetch(endpoint).then((res) => setData(res));
}, []);
return (
<component data={data} {...props} />
);
}
return WrappedComponent;
};
const ComponentWithData = withData(MyCustomComponent, api_endpoint)
function MyCustomComponent (props){
const { data } = props
// use this data to do whatever
}
when a prop is injected into <component/> like data={[1,2,3]}, that prop can be accesible through props of resulting component like props.data
withSubscription takes 2 arguments: WrappedComponent and selectData.
Notice that in the constructor of withSubscription they set the initial state to equal the result of invoking selectData. selectData is called with 2 arguments: DataSource is the first argument and props is the second.
In this case DataSource is probably some module they imported previously but just didn't show you...
When they wrap a CommentListWithSubscription with "withSubscription", the DataSource there is the data source that is passed in the constructor to initialize the state. They should've named it dataSource instead, that's the correct naming convention.
Hope that helped :)
If don't understand HOC from their docs, just keep searching in other sources. There are hundreds if not thousands of sources that explain that concept. In programming you sometimes need to go through several sources for a concept or and idea to sink in properly. Good luck!

How to pass arguments to a class extension component

I am trying to extend a component as such:
class LoginPage extends React.Component {
...
}
export default withKeycloak(LoginPage);
However the example does it like this:
const LoginPage = ({ keycloak, keycloakInitialized }) => {
I'm not sure if this is me not understanding something in ES6 or React itself, but I feel there must be a way to pass arguments to a component like that - correct?
I've been reading the documentation but it's not quite clear.
In class components you have the props inside this, so you can access them anywhere inside the class by using this.props like so.
const {keycloak, keycloakInitialized} = this.props;
// or simply
console.log(this.props.keycloak);
and you should take a look at this to understand destructuring.
so this is destructoring from the props object. To achieve the same result inside of a class component write this inside your render function.
render() {
const {keycloak, keycloakInitialized} = this.props
return(<div></div>)
}

How to define propTypes for a component wrapped with withRouter?

I'm wondering what the best practices are for defining propTypes on a component that will be wrapped with a 3rd-party HOC, in this case, withRouter() from React-Router.
It is my understanding that the point of propTypes is so you (and other developers) know what props a component should expect, and React will give warnings if this is violated.
Therefore, since the props about location are already passed by withRouter() with no human intervention, is it necessary to worry about them here?
Here is the component I'm working with:
const Menu = ({ userId, ...routerProps}) => {
const { pathname } = routerProps.location
return (
// Something using userID
// Something using pathname
)
}
Menu.propTypes = {
userId: PropTypes.number.isRequired,
// routerProps: PropTypes.object.isRequired,
// ^ this is undefined, bc withRouter passes it in later?
}
export default withRouter(Menu)
//.... in parent:
<Menu userId={id} />
What would be the convention in this case?
It is my understanding that the point of propTypes is so you (and other developers) know what props a component should expect, and React will give warnings if this is violated.
This is correct.
What would be the convention in this case?
I don't think you will find a definitive answer to this. Some will argue that if you define one propType you should define all expected prop types. Others will say, as you did, that it wont be provided by the parent component (excluding the HOC) so why bother. There's another category of people that will tell you not to worry with propTypes at all...
Personally, I fall into either the first or last category:
If the component is for consumption by others, such as a common ui component (e.g. TextField, Button, etc.) or the interface for a library, then propTypes are a helpful, and you should define them all.
If the component is only used for a specific purpose, in a single app, then it's usually fine to not worry about them at all as you'll spend more time maintaining them than debugging when the wrong props are passed (especially if you're writing small, easy to consume functional components).
The argument for including the routerProps would be to protect you against changes to the props provided by withRouter should they ever change in the future.
So assuming you want to include the propTypes for withRouter then we need to breakdown what they should actually be:
const Menu = ({ userId, ...routerProps}) => {
const { pathname } = routerProps.location
return (
// Something using userID
// Something using pathname
)
}
Looking at the above snippet, you may think the propTypes should be
Menu.propTypes = {
userId: PropTypes.number.isRequired,
routerProps: PropTypes.object.isRequired
}
But you'd be mistaken... Those first 2 lines pack in a lot of props transformation. In fact, it should be
Menu.propTypes = {
userId: PropTypes.number.isRequired,
location: PropTypes.shape({
pathname: PropTypes.string.isRequired
}).isRequired
}
Why? The snippet is equivalent to:
const Menu = (props) => {
const userId = props.userId
const routerProps = Object.assign({}, props, { userId: undefined }
const pathname = routerProps.location.pathname
return (
// Something using userID
// Something using pathname
)
}
As you can see, routerProps doesn't actually exist in the props at all.
...routerProps is a rest parameter so it gets all the other values of the props, in this case, location (and maybe other things you don't care about).
Hope that helps.

Categories