Cannot Get Values from Prop in Twin.macro - javascript

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.

Related

How to use #ngrx/store in component?

My understanding after reading the documentation is that in order to obtain values from an NgRx store, we have to use selectors. So far, I've had luck doing that, only with a small issue regarding typing, which makes me worried if my implementation is incorrect in any way.
So, let's say I want to retrieve a number value from the store, id for example. In my componentName.component.ts I would access this value like this:
id$ = this.store.select(selectId);
where selectId is defined from another file as:
export const selectData = (state: AppState) => state.data;
export const selectId = createSelector(
selectData,
(state: DataState) => state.id,
)
I am able to access id$ in my HTML component easily by doing {{id$ | async}}, but accessing it from the component class itself has proven a bit more difficult. For starters, the variable type is Observable<number> instead of just number, and that makes it hard to use it in cases where it needs to be of number type, such as when comparing it:
ngOnInit(): void {
console.log(this.id$ === 0);
}
The TypeScript error I get from the code above is:
TS2367: This condition will always return 'false' since the types
'number' and 'Observable ' have no overlap.
And console-logging id$ itself confirms that it is indeed of Observable type, so this leads me to believe I am doing something wrong, but I'm unsure exactly what. Any help is appreciated!!!
id$ is an Observable, and you can access its value by subscribing to it from the component class using subscribe function, or from the component template using async pipe.
In Component Class:
ngOnInit(): void {
this.id$.subscribe(id => console.log(id));
}
In Component Template:
<span>{{ id$ | async }}</span>
Check the official docs of Angular about Observable(s):
https://angular.io/guide/observables

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

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>;

How to declare array from API in place of hardcoded array?

I was given an example for some code that has an array hardcoded. Im looking to swap this out for my array that is pulled in from an API using graphql. Below is the code pen to the original example & another for what i've tried with no avail.
I'm pretty new to graphql & js so likely an amateur mistake, any pointers would be much appreciated!
Original code - https://codesandbox.io/s/nice-saha-gwbwv
My pen - https://codesandbox.io/s/quiet-wind-brq8s?file=/src/App.js
I would change your component structure to something like:
import React, { useState } from 'react'
import { graphql } from 'gatsby'
const YourPage = ({data}) => {
console.log('data is', data)
const [filters, setFilters] = useState({
type: "",
category: ""
});
//your calculations
return (
<div>
Your stuff
</div>
)
}
export const query = graphql`
query yourQueryName{
allStrapiHomes {
nodes {
type
category
}
}
}
`
export default YourPage
In your code, upon some critical imports, you are missing a few stuff from Gatsby. If you use a staticQuery, you will need to add a render mode to it. It may look a bit old-fashioned, it's better to use the useStaticQuery hook provided by Gatsby or adding a page query (my approach).
I've added a page query. Your data is under props.data.allStrapiHomes.nodes, destructuring props you omit the first step, so your data will be at data.allStrapiHomes.nodes. Both type and category will be an array if they are set like this in the Strapi back-end.

React Hooks -- Uncaught Invariant Violation: Objects are not valid as a React child

I'm working on the freeCodeCamp drum machine app. In my app with function arrow components, I set state of display with the useState hook in the parent component and pass it as a prop to the child component. In the parent component, I try to render the display state in a div. However, when the method is triggered (on click of the "drum pad" div), the app crashes. In the console I get an error that says "Uncaught Invariant Violation: Objects are not valid as a React child (found: object with keys {display}). If you meant to render a collection of children, use an array instead."
I've been following along a YouTube tutorial for this project but using arrow function components and Hooks instead of regular classes as used in the tutorial--in the tutorial (around 1:55 of this video) the person successfully does what I'm trying to do, so I think the issue is something to do with using Hooks or arrow function components.
// APP COMPONENT (PARENT)
const sounds = [
{ id: 'snare', letter: 'Q', src: 'https://www.myinstants.com/media/sounds/snare.mp3' },
// etc.
];
const App = () => {
const [display, setDisplay] = useState(''); // <----
const handleDisplay = display => { // <----
setDisplay({ display });
}
return (
<div className="App">
<div className="drum-machine">
<div className="display">
<p>{display}</p> // <---- Related to error in console
</div>
<div className="drum-pads">
{sounds.map(sound => (
<DrumPad
id={sound.id}
letter={sound.letter}
src={sound.src}
handleDisplay={handleDisplay} // <----
/>
))}
</div>
</div>
</div>
);
}
// DRUMPAD COMPONENT (CHILD)
const DrumPad = ({ id, letter, src, handleDisplay }) => {
let audio = React.createRef();
const handleClick = () => {
audio.current.play();
audio.current.currentTime = 0;
handleDisplay(id); // <----
}
return (
<div
className="drum-pad"
id={id}
onClick={handleClick}
>
<p className="letter">{letter}</p>
<audio
ref={audio}
id={letter}
src={src}
>
</audio>
</div>
);
}
You're setting the state as an object instead of a string. Remove the curly brackets around it.
const handleDisplay = display => {
setDisplay(display);
}
This was already answered, but since you are following a tutorial, I am assuming you are learning React and wanted to point a couple of things to help you :)
The incorrect use of state was pointed out, but just for clarification (and the reason I think you were using an object): in the "old" way, with Class components, the state used to be an object, and you needed to update it like an object. This example here shows that. With Hooks, you don't need to set the whole State object, only that specific state property. More info here.
Another point is, in your CodePen example at least, you were missing the import for useState. You either need to import it like this import { useState } from React or use it like this React.useState, since this is a separate module, not imported by default when you import React.
The last point is, when creating components using a loop (like your <DrumPad> with the map) you need to provide a "key" attribute. that will help React keep track of things that needs to be updated or rerendered.
O updated your code with those changes in this link, if you wanna see it working:
https://codesandbox.io/s/reverent-browser-zkum2
Good luck and hope you are enjoying React Hooks :)

What actually happens when React component returns?

I have noticed a difference between the data before returning and after a return of a component.
class AComponent extends Component {
render() {
const body = <BComponent crmStatus={...}/>
debugger // log body on the right
// ... render as static html to electron window
return false
}
}
class BComponent extends Component {
render() {
const resultRender = <article className='large'>...</article>
debugger // log resultRender on the left
return resultRender
}
}
My former question was going to be "How to read rendered component's className?", but I have split the questions as answering what is actually happening and why is it like that really started to bug me and might even give me hints to solve my problem.
So the question is:
What is actually happening to the component and why is it like that? I can have really complicated logic in my render() function, but I guess working with the components isn't that easy.
const headerContact = isContactInCRM ? <p>..</p> : <div>..</div>
const headerCallBtnsOrInfo = isSipEnabled && <div>..buttons..</div>
const callTimer = callDuration && <span>{callDuration}</span>
const footerNotes = <footer>..</footer>
const someImportedComponent = <MyComponent />
const resultRender = <section>
{headerContact}
{headerCallBtnsOrInfo}
{callTimer}
{footerNotes}
{someImportedComponent}
</section>
// there is a difference in data between headerContact and someImportedComponent
// when traversing the resultRender's tree in console
Before answering the question, it's worth to look at what is JSX. It just provides syntactic sugar for the React.createElement(component, props, ...children) function.
<div>
<MyComponent/>
</div>
As an example, above JSX snippet will be transformed to following JavaScript code in the compilation process.
React.createElement(
"div",
null,
React.createElement(MyComponent, null)
);
You can try out this using Babel online repl tool. So if we rewrite your example code using normal JavaScript (after compiling JSX), it will be something like this.
class AComponent extends Component {
render() {
const body = React.createElement(BComponent, { crmStatus: '...' });
debugger // log body on the right
// ... render as static html to electron window
return false
}
}
class BComponent extends Component {
render() {
const resultRender = React.createElement('article',{ className: 'large' }, '...' );
debugger // log resultRender on the left
return resultRender
}
}
By looking at above code, we can understand that <BComponent crmStatus={...}/> doesn't create a new object of BComponent class or call render method of BComponent. It just create a ReactElement with BComponent type and crmStatus prop. So what is a ReactElement? ReactElement is a pain JavaScript object with some properties. I recommend you to read this post from official React blog to get an in-depth understanding of React components, elements, and instances.
An element is a plain object describing a component instance or DOM node and its desired properties. It contains only information about
the component type (for example, a Button), its properties (for
example, its color), and any child elements inside it.
Basically, what you have printed in the console is two React elements in different types. The left one is describing DOM node with type 'article' and the right one is describing BComponent type React component instance. So simply you can't expect them to be the same.
Then where does React create an instance of BComponent? Actually, this happens internally in the React code. Usually, we don't have access to these instances or what return by their render methods in our application code.
However, React still provide an escape hatch called 'refs' which you can explicitly access instances of child components. You might be able to use that approach to solve your original problem.
Hope this helps!

Categories