JSX: when are JavaScript expressions evaluated? - javascript

After going through GatsbyJS and React tutorials I got the impression that JavaScript expressions are always evaluated when they are inside {} brackets in JSX. But now I'm looking at a JSX file inside a GatsbyJS starter repo, where it looks like the brackets cause different behavior:
const {
data: {
posts: { edges: posts },
site: {
siteMetadata: { facebook }
}
}
} = props;
(Source)
According to the tutorials, "facebook" should be evaluated as JavaScript and should return undefined, but that's not what's happening. Somehow we end up with a JavaScript object data.site.siteMetadata.facebook, which has some data. What's going on here? Why is "facebook" not evaluated as a JavaScript expression?

The bit of code you copy actually has nothing to do with JSX (see below). It's ES6 object destructuring syntax, like #PrithvirajSahu commented on the question.
Say you have an object like this:
const obj = {
a: 100,
b: {
value: 200,
}
};
You can get the inner values like so:
const { a } = obj;
// same as const a = obj.a
const { b: c } = obj;
// same as const c = obj.b
const { b: { value } } = obj;
// same as const value = obj.b.value
const { b: { value: v } } = obj;
// same as const v = obj.b.value
const { a, { b: { value } } } = obj;
// same as
// const a = obj.a;
// const value = obj.b.value;
So back to your piece of code, it's equivalent to
const posts = props.data.posts.edges;
const facebook = props.data.site.siteMetadata.facebook;
As you have found out, the destructuring syntax is very neat at 1 or maybe 2 levels, but hard to read when there're more. Personally, I only use it at 1 level.
Edit: In the function in the source, only the lines starting with <... is JSX syntax.
const CategoryPage = props => {
// code here is normal js
const { ... } = props;
// JSX start from inside this return function
return (
<React.Fragment>
{ /* code between bracket in this section will be evaluate as 'normal JS' */ }
</React.Fragment>
)
}
Caveat: The code between bracket in JSX has to evaluate to a function. If we write something like this:
<div className="container">
Hello
{"Happy"}
World
</div>
Babel will turn it into the following regular JS:
React.createElement(
"div",
{ className: "container" },
"Hello",
"Happy",
"World"
);
Play with babel here
Whatever we put between bracket will be passed to React.createElement as a child of the div element; therefore only valid React element can be placed here:
Null (render nothing)
A string (will become a DOM text node)
Another React element
An expression/function that evaluates to, or returns any of the above
<div>
{ hasDate && <Date /> }
<div>
or
// somewhere in the code
const showDate = (hasDate) => {
if (!hasDate) return null;
return <Date />
}
// in the render function
return (
<div>
{ showDate(hasDate) }
<div>
)
We also can use bracket to pass value to a element's props:
<div
style={ { color: 'red' } }
onClick={ (event) => {...} }>
{ hasDate && <Date /> }
<div>

Related

How do I create a new JSON object inside a react hook?

I have two issues first how do I add/update the JSON items within a hook?
The other being that React won't let me use the name stored from a previous JSON file.
I am open to other solutions, basically, as my input field are dynamically generated from a JSON file I'm unsure of the best way to store or access the data that's input into them I think storing them in a react hook as JSON and then passing them though as props to another component is probably best.
What I want to happen is onChange I would like the quantity value to be stored as a JSON object in a Hook here's my code:
React:
import React, { useState, useEffect } from 'react';
import Data from '../shoppingData/Ingredients';
import Quantities from '../shoppingData/Quantities';
const ShoppingPageOne = (props) => {
//element displays
const [pageone_show, setPageone_show] = useState('pageOne');
//where I want to store the JSON data
const [Quantities, setQuantities] = useState({});
useEffect(() => {
//sets info text using Json
if (props.showOne) {
setPageone_show('pageOne');
} else {
setPageone_show('pageOne hide');
}
}, [props.showOne]);
return (
<div className={'Shopping_Content ' + pageone_show}>
//generates input fields from JSON data
{Data.map((Ingredients) => {
const handleChange = (event) => {
// this is where I'd like the Hook to be updated to contain instances of the ingredients name and quantity of each
setQuantities(
(Ingredients.Name: { ['quantities']: event.target.value })
);
console.log(Quantities);
};
return (
<div className="Shopping_input" key={Ingredients.Name}>
<p>
{Ingredients.Name} £{Ingredients.Price}
</p>
<input
onChange={handleChange.bind(this)}
min="0"
type="number"
></input>
</div>
);
})}
<div className="Shopping_Buttons">
<p onClick={props.next_ClickHandler}>Buy Now!</p>
</div>
</div>
);
};
export default ShoppingPageOne;
JSON file:
//Json data for the shopping ingredients
export default [
{
Name: 'Bread',
Price: "1.10",
},
{
Name: 'Milk',
Price: "0.50",
},
{
Name: 'Cheese',
Price: "0.90",
},
{
Name: 'Soup',
Price: "0.60",
},
{
Name: 'Butter',
Price: "1.20",
}
]
Assuming your Quantities object is meant to look like:
{
<Ingredient Name>: { quantities: <value> }
}
you need to change your handleChange to look like this
const handleChange = (event) => {
setQuantities({
...Quantities,
[Ingredients.Name]: {
...(Quantities[Ingredients.Name] ?? {}),
quantities: event.target.value
}
});
};
Explanation
When updating state in React, it is important to replace objects rather than mutating existing ones, as this is what tells React to rerender components. This is commonly done using the spread operator, and with array functions such as map and filter. For example:
const myObject = { test: 1 };
myObject.test = 2; // Mutates existing object, wrong!
const myNewObject = { ...myObject, test: 2 }; // Creates new object, good!
Note the spread operator doesn't operate below the first level, what I mean by that is, objects within the object will be copied by reference, for example:
const myObject = { test : { nested: 1 } };
const myObject2 = { ...myObject };
myObject2.test.nested = 2;
console.log(myObject.test.nested); // outputs 2
Also in my answer, I have used the nullish coalescing operator (??), this will return it's right operand if the left operand is null or undefined, for example:
null ?? 'hello'; // resolves to "hello"
undefined ?? 'world'; // resolves to "world"
"foo" ?? "bar"; // resolves to "foo"
In my answer I used it to fallback to an empty object if Quantities[Ingredients.Name] is undefined.
Finally, I used square brackets when using a variable as an object key as this causes the expression to be evaluated before being used as a key:
const myKey = 'hello';
const myObject = {
[myKey]: 'world';
};
console.log(myObject); // { hello: 'world' }

How to access child key-values on objects and clonate that object

I'm tryin to make a list from an object on React. Before I continue, I'll share my code:
const genres = {
Rock: {
album1: '',
album2: '',
},
Jazz: {
album1: '',
album2: '',
},
Pop: {
album1: '',
album2: '',
}
};
let myFunc = genress => {
let newObject = {};
Object.keys(genress).map(gen => {
newObject[gen] = 'a';
let newChild = newObject[gen];
let oldChild = genress[gen];
Object.keys(oldChild).map(gen2 => {
newChild[gen2] = 'b';
let newGrandChild = newChild[gen2];
console.log(newGrandChild);
})
});
return newObject;
}
myFunc(genres);
I wanna render that object on a list.
<ul>
<li>Rock
<ul>
<li>album1</li>
<li>album2</li>
</ul>
</li>
</ul>
...And so on
Before placing it on React I'm trying it on a normal function. I'm making a new object just to make sure I'm accesing the right values. The problem is, when I return the new object at the end of the function it returns the genres but not the albums, only the 'a' I set in the first Object.key. The console.log on the second Object.key logs undefined, and can't figure out why this is happening.
My idea is to have access to every level on the object so I can set them to variables and return them on the render's Component. I'll make more levels: Genres -> Bands -> Albums -> songs.
Thanks so much in advance :)
From what I can understand is that you are iterating over the object incorrectly.
The reason why 'a' is the only thing showing up is that you are hard coding that every time you run the loop and setting that current key that that value.
So essentially your code does not work because you set the value of the current key to be 'a' which is a string so there are no keys on 'a' so the second loop does not produce anything.
newObject[gen] = 'a'; // you are setting obj[Rock]='a'
let newChild = newObject[gen]; // this is just 'a'
let oldChild = genress[gen]; // this is just 'a'
Object.keys(newObject[gen]) // undefined
What I think you are trying to do is iterate over the object and then render the contents of that object in a list.
Let me know if the below answers your question.
You can see the code working here:
https://codesandbox.io/s/elastic-dhawan-01sdc?fontsize=14&hidenavigation=1&theme=dark
Here is the code sample.
import React from "react";
import "./styles.css";
const genres = {
Rock: {
album1: "Hello",
album2: "Rock 2"
},
Jazz: {
album1: "",
album2: ""
},
Pop: {
album1: "",
album2: ""
}
};
const createListFromObject = (key) => {
return (
<div>
<h1>{key}</h1>
<ul>
{Object.entries(genres[key]).map(([k, v], idx) => (
<li key={`${key}-${k}-${v}-${idx}`}>{`Key: ${k} Value ${v}`}</li>
))}
</ul>
</div>
);
};
export default function App() {
return (
<div className="App">{Object.keys(genres).map(createListFromObject)}</div>
);
}
<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>

Reliable way to tell if a function is a functional React component or just a plain function

Given a functional React component:
const AComponent = (props) {
// ...
return <div>{...}</div>;
};
And a plain JS function which may or may not return a React element, e.g.:
function aFunction() {
// Returns React element.
return <div></div>
}
// OR:
function aFunction() {
// Doesn't return a React element, doesn't have to do with React at all.
return 123;
}
How do I implement the following function:
function isTrulyFunctionalComponent(fn) {
// ???
}
console.log(isTrulyFunctionalComponent(AComponent)); // true
console.log(isTrulyFunctionalComponent(aFunction)); // false
so that it works even when the code is transpiled and minified?
I thought about checking fn.name within isTrulyFunctionalComponent, e.g.:
function isTrulyFunctionalComponent(fn) {
// Won't work when minifying
return typeof fn === "function" && fn.name[0] === fn.name[0].toUpperCase();
}
but when I minify the code, fn.name is empty and the code above breaks...
Thanks for the attention!
EDIT: I am trying to achieve the following:
function MyComponent({
componentForDataItem = AComponent,
data = []
} = {}) {
const componentIsReactComponent = isTrulyFunctionalComponent(componentForDataItem);
return data.map(dataItem => {
// componentForDataItem can either be a functional component
// or a function returning a React component to use for the given dataItem.
const Component = componentIsReactComponent
? componentForDataItem
: componentForDataItem(dataItem);
return (
<Component {...} />
});
}
<MyComponent data={[...]} /> // Uses the default AComponent for all items of data
<MyComponent data={[...]} componentForDataItem={dataItem => {
// Custom logic for a specific dataItem, e.g.:
return if dataItem.value === "check" ? AnotherComponent : AComponent;
}} />

`object` supplied to `ReactiveComponent`, expected `function`

I'm getting this error when trying to use ReactiveSearch for a search bar. This is how I'm initialising it:
render() {
const { tenantConfig, size, componentId } = this.props;
return (
<ReactiveComponent
componentId={componentId}
defaultQuery={this.defaultQuery}
>
<SearchDropdownDashboard
size={size}
handleSearchDashboard={this.handleSearchDashboard}
fetching={this.state.fetching}
tenantConfig={tenantConfig}
/>
</ReactiveComponent>
);
}
And this is the function that is being passed in:
defaultQuery = () => {
const { dashboardText } = this.state;
const { mustNotObj } = this.props;
let obj;
obj = {
query: {
bool: {
must_not: mustNotObj,
must: multiMatchSearch(dashboardText)
}
},
from: 0,
size: 20
};
return obj;
};
Any suggestions as to what I'm doing wrong here? The function seems to be passed correctly to the component.
If you are using v3, then it is due to the recent changes introduced in API. You will need to use render prop or React render Pattern as done in the below example.
You can check the docs here: https://opensource.appbase.io/reactive-manual/advanced/reactivecomponent.html#usage-with-defaultquery.
I have created the example of Usage of ReactiveComponent on both the versions:
v3 : https://codesandbox.io/s/serene-ritchie-rjo3m
v2 : https://codesandbox.io/s/tender-ramanujan-f3g31
Hope this helps!

How to write clean, readable ReactJS Components with conditional renders?

The result is not showing in my window. I have created a global object for testing:
const obj = {
onam: [
{
name: "name3",
image: "https://resize.hswstatic.com/w_828/gif/kelp-biofuel.jpg"
},
{
name: "name2",
image: "https://resize.hswstatic.com/w_828/gif/kelp-biofuel.jpg"
}
],
christmas: [
{
name: "name1",
image: "https://resize.hswstatic.com/w_828/gif/kelp-biofuel.jpg"
},
{
name: "name0",
image: "https://resize.hswstatic.com/w_828/gif/kelp-biofuel.jpg"
}
]
}
Below is the function I am calling inside render.
const grid = (props) => {
if (props == "All") {
const keys = Object.keys(obj);
// looping through keys
keys.forEach((key, index) => {
// looping through array in keys
for(let j = 0; j < Object.keys(obj).length; j++) {
return (
<div>
<a><img src={obj[key][j].image}>{obj[key][j].name}</img></a>
</div>
)
}
});
}
}
I know there is some error in the above function but I cant sort it out. I am calling {grid("All")} inside render. My aim is to display a div containing a div with all images with a tag. I would like to learn a clean way of conditionally rendering my components.
There are several concerns here Ashwin.
Your component name should begin with uppercase.
Use an array instead of object obj. Its easier to work with arrays and loop through them than it is to loop objects.
Don't wrap the entire component within an if statement. Its bad practice.
props == "All" is just wrong. props is an object not a string. This is the main reason nothing is rendering. That if statement will always return false.
<img> doesn't take children so don't pass {obj[key][j].name} into the img tag. Move that bit after the closing img tag into the <a> tag.
I'm rewriting your component with some comments to help you learn, but there are several other ways to achieve the same result, perhaps with a better approach. This works for me and I find it's more readable and easier to understand.
import React from "react"
/* An array of objects is always easier to work than objects with
multiple arrays as values. You might end up duplicating a few keys like
"festival" in this example, but it will make your life easier.
*/
const arr = [
{
festival: "oman",
name: "name3",
image: "https://resize.hswstatic.com/w_828/gif/kelp-biofuel.jpg",
},
{
festival: "oman",
name: "name2",
image: "https://resize.hswstatic.com/w_828/gif/kelp-biofuel.jpg",
},
{
festival: "christmas",
name: "name1",
image: "https://resize.hswstatic.com/w_828/gif/kelp-biofuel.jpg",
},
{
festival: "christmas",
name: "name0",
image: "https://resize.hswstatic.com/w_828/gif/kelp-biofuel.jpg",
},
]
/*
Always make sure your components are uppercase
De-structure your props with { propName1, propName2 }
because that way you or anybody else looking at your
component immediately can recognize what props are being
passed.
*/
const Grid = ({ whatToRender }) => {
/*
Create a custom render function inside your component if
you need to render conditionally. Its easier to work with
debug and maintain.
*/
const renderGrid = () => {
if (whatToRender === "all") { // use === over == because that will check for type equality as well.
return arr.map((item) => ( // () is equivalent to return () when looping.
/* Always pass a unique key if you are creating lists or lopping */
<div key={item.image}>
<a>
<img src={item.image} alt={item.name} />
{item.name}
</a>
</div>
))
}
if (whatToRender === "onam") {
return arr.map((item) => {
if (item.festival === "onam") {
return (
<div key={item.image}>
<a>
<img src={item.image} alt={item.name} />
{item.name}
</a>
</div>
)
}
})
}
if (whatToRender === "christmas") {
return arr.map((item) => {
if (item.festival === "christmas") {
return (
<div key={item.image}>
<a> {/* Image tags don't take children they are self-closing */}
<img src={item.image} alt={item.name} />
{item.name}
</a>
</div>
)
}
})
} // Return an empty div if none of the cases pass. So, you return valid JSX
return <div></div>
}
return renderGrid() // Finally return your custom render function
}
export default Grid
EDIT
On visiting this question again, I realized that there was a better way to write it. A much shorter version. It uses the same arr defined in the above code sample.
const Grid = ({ whatToRender }) => {
const renderGrid = () => {
return arr.map((item) => {
if (item.festival === whatToRender || whatToRender === "all") {
return (
<div key={item.image}>
<a>
<img src={item.image} alt={item.name} />
{item.name}
</a>
</div>
)
}
})
}
return renderGrid()
}
export default Grid
Which one to use? The second version not only because it's much shorter, but also because its reusable. If in the future you add another festival to arr say Easter, you just need changes in the array and the prop value you are passing. The component will require no changes.

Categories

Resources