React how to convert string to object property - reactjs

In a React project, I want to dynamically access an object property name using a string variable I pass through props. I am able to pass a value as string; however, when I try to use it to access the object property it doesn't work.
Here's an example:
const passedField = 'title'; // this comes from props
const book = { title: 'Sample title', content : 'some content' };
book.passedField; // returns undefined
I access title like this book.title
I want to access it like book.passedField but I get undefined on console.
How can I achieve this?

book.passedField will be returning undefined because there is no passedField in book. Instead use bracket notation as below
const book = { title: 'abc', pages: 350 };
const passedField = 'pages';
console.log(book[passedField])

What you're using is called a dot notation property accessor. What you need to use is a bracket notation property accessor.
book.title and book['title'] are the same, which means you can assign title to a variable, and use the variable name instead like this.
const passedField = 'title';
book[passedField]; // same as book['title']
You might only be familiar with bracket notation with Arrays, like myArray[0], but they work with Objects too! (because Arrays in JavaScript are actually objects)
Solution
const books = [
{
title: 'The Art of War',
contents: 'An ancient Chinese military treatise dating from the Spring and Autumn Period'
},
{
title: 'Winnie the Pooh',
contents: 'Pooh is naive and slow-witted, but he is also friendly, thoughtful, and steadfast.'
}
]
class Book extends React.Component {
constructor(props) {
super(props);
this.findBookByFilter = this.findBookByFilter.bind(this);
}
findBookByFilter() {
let result = null
if (books) {
const {passedFilterField, filterText} = this.props;
result = books.filter(book => {
return book[passedFilterField].toLowerCase().includes(filterText.toLowerCase());
}).map(book => <p>{book.title}</p>)
}
return result;
}
render () {
return this.findBookByFilter();
}
}
class App extends React.Component {
render() {
return (
<Book
passedFilterField='contents'
filterText='thoughtful'
/>
)
}
}
ReactDOM.render(<App />, document.getElementById('root'));
<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>
<div id="root"></div>
Notes
I moved your {book && book.filter} logic to a function called findBookByFilter
I used includes instead of indexOf in the filter
I used destructuring assignment for this.props
I return the matched title, rather than <Link /> for demo purposes.
Documentation
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Property_accessors
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/includes
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment

You can access object properties using book[passedField]

Related

Typescript generic function which gives component properties based on other variables

This one has been puzzling me for a while, trying to create a type-safe email service.
I have an enum of possible template names:
enum TemplateName {
EXAMPLE_TEMPLATE = "EXAMPLE TEMPLATE"
...
}
I have an object of default settings per template:
type EmailConfig<X = React.ComponentType> = {
html: X
subject: string
...
}
type EmailMapping: EmailConfig = {
[key in TemplateName]: EmailConfig
}
const Emails = {
[TemplateName.EXAMPLE_TEMPLATE]: {
html: TestTemplate, // THIS IS A REACT FUNCTIONAL COMPONENT
subject: "This is a test",
...rest
}
...
}
My Templates look like so:
export interface TestTemplateProps {
title?: string
firstName?: string
preview?: string
headline?: string
site?: string
...
}
export const TestTemplate: React.FC<TestTemplateProps> = ({
title = 'Test Email',
site = 'My Website',
preview = 'Important Information from My Site',
firstName = 'there',
headline,
children,
}) => {
return (
...
)
}
I have a generic function I want to be able to pass in an enum value and all the props of the Component that relates to that enum value.
FOR EXAMPLE
sendEmail(TemplateName.EXAMPLE_TEMPLATE, { ... })
Where { ... } is typed to TestTemplateProps interface
My current attempt at sendEmail looks like this:
async sendEmail<X extends keyof EmailMapping>(
template: X,
opts: React.ComponentProps<typeof Emails[X]['html']>
) {
...
}
I've tried just playing around (honestly I am just guessing at what to change at this point) and this is the closest I have come so far.
When I call sendEmail with above code, it forced me to pass one of the Enums, but then in the opts the only "typing" that appears is "children?" and none of the other properties in the TestTemplateProps so I think I'm close!
TIA
Yes. but the first thing that I wanna know is ... are you making a class for data loading and passing data in your interfaces with static function or static constructor for async data loading... if so ... I want to know the
the exact problem you're facing...

Can not pass on property from one Component to another

I trie to pass an Object from my Card Component to my FloatingActionButtons Component.
But i don't work i got the error:
TypeError: Cannot read property 'name' of undefined
If i pass just one attribute from the Object like name,abteilung,bereich,... then it works.
So, how can i pass the whole Object to the Component ?
Card.js
import FloatingButton from "./FloatingActionButtons";
export default function Card() {
const plan = new Plan("Max Mustermann", "woamen.jpg", "ITH/A", "IT", 13);
return (
<FloatingButton azubi={plan}></FloatingButton>
);
FloatingActionButtons.js
export default function FloatingActionButtons({ azubi }) {
<Typography gutterBottom>{azubi.name}</Typography>
}
Plan.js
export default class Plan {
constructor(name, image, abteilung, bereich, azubis) {
this.name = name;
this.image = image;
this.abteilung = abteilung;
this.bereich = bereich;
this.azubis = azubis;
}
}
I've made a Codesandbox attempting to recreate your issue and it appears to be fine. Please compare to your own and check for any syntax issues: for example, your FloatingActionButtons.js snippet doesn't have a return keyword however the error you're getting doesn't match up with that issue.
Based on the snippet the code must work fine. Make sure you're not missing any value while updating the components. Here is the working snippet based on your code:
class Plan {
constructor(name, image, abteilung, bereich, azubis) {
this.name = name;
this.image = image;
this.abteilung = abteilung;
this.bereich = bereich;
this.azubis = azubis;
}
}
function Card() {
const plan = new Plan("Max Mustermann", "woamen.jpg", "ITH/A", "IT", 13);
return (<FloatingButton azubi={plan} />);
}
function FloatingButton({
azubi
}) {
return (<p> {azubi.name}</p>);
}
ReactDOM.render(<Card />, document.getElementById('root'));
<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>
<div id="root"></div>

Correct modification of state object arrays in ReactJS

I have an array of points located at this.state.points. I have an updatePoint() function I use to update individual points in this array. I thought that using .slice() would leave my state untouched until I call setState but when I console.log this.state.points after I set the value for points[i], I see it is already updated. How do I prevent my state from getting updated before I call setState?
updatePoint(i, point) {
console.log('updatePoint '+i)
let points = this.state.points.slice()
points[i] = point
this.setState({points: points})
}
This version also does not work:
updatePoint(i, point) {
console.log('updatePoint '+i)
let points = this.state.points.slice()
points.forEach((j) => {
points[j] = Object.assign({}, this.state.points[j])
})
points[i] = point
this.setState({points: points})
}
Array.slice does not perform a deep copy, so if your this.state.points is an array of objects, you'll need to copy those object items too.
You can copy an object using Object.assign.
You can use the spread operator (...) that came with ES6:
updatePoint(i, point) {
console.log('updatePoint '+i)
let points = [...this.state.points]
console.log("points", points);
points[i] = point;
console.log("state",this.state.points);
this.setState({points: points})
console.log(this.state.points);
}
You can test it here (and also try it checking the console.log on your own environment):
class Hello extends React.Component {
constructor(props){
super(props);
this.updatePoint = this.updatePoint.bind(this);
this.state = {
points: [{name: "uno"},
{name: "dos"},
{name: "tres"}]
};
console.log(this.state.points);
}
// i = 1, name: ocho
updatePoint = (i, point) => {
console.log('updatePoint '+ i)
console.log("spread", [...this.state.points]);
const newPoints = [...this.state.points];
console.log("points", newPoints);
newPoints[i] = point;
console.log("state",this.state.points);
this.setState({
points: newPoints
});
}
render() {
const nowState = this.state.points.map(item => <p key={item.name}>{item.name}</p>);
return (
<div>Hello
<button
onClick={() => this.updatePoint(1, {name: "ocho"})}
>Click
</button>
<p>Current state
{nowState}</p>
</div>);
}
}
ReactDOM.render(
<Hello name="World" />,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>
More info on the spread operator:
From https://learn.microsoft.com/en-us/scripting/javascript/reference/spread-operator-decrement-dot-dot-dot-javascript :
Allows parts of an array literal to be initialized from an iterable expression (such as another array literal), or allows an expression to be expanded to multiple arguments (in function calls).
https://redux.js.org/docs/recipes/UsingObjectSpreadOperator.html
An alternative approach is to use the object spread syntax proposed for the next versions of JavaScript which lets you use the spread (...) operator to copy enumerable properties from one object to another in a more succinct way.

Replace Tags in Email Template

I'm trying to figure out what's the best way to replace tags within a component. So, given the following text...
*|FIRSTNAME|*,
*|LINK|*
You can sign in with your email address (*|EMAIL|*)
Your password is:
*|PASSWORD|*
Thanks!
*|SIGNATURE|*
and this:
export default class Template extends Component {
componentWillMount () {
this.setState({
mergeTags: {
'*|FIRSTNAME|*': 'John',
'*|LINK|*': 'www.test.com',
'*|EMAIL|*': 'email#email.com',
'*|PASSWORD|*': 'supersecurepassword',
'*|SIGNATURE|*': 'Bob Law's Law Blog'
}
})
}
I want to display, in a live preview window that updates as the user types, this:
John,
www.test.com
You can sign in with your email address (email#email.com)
Your password is:
supersecurepassword
Thanks!
Bob Law's Law Blog
Don't know your exact setup, but this, or something like it, should work.
JSFiddle demo here: https://jsfiddle.net/jonahe/zrrfyxh7/
class Parent extends React.Component {
constructor(props) {
super(props);
this.state = {
'*|FIRSTNAME|*': 'John',
'*|LINK|*': 'www.test.com',
'*|EMAIL|*': 'email#email.com',
'*|PASSWORD|*': 'supersecurepassword',
'*|SIGNATURE|*': "Bob Law's Law Blog"
};
}
// use componentDidMount, or else the ref thing below won't work.
componentDidMount() {
let innerText = this.templateElement.innerHTML;
// loop through all the properties / "tags"
for(let tagKey in this.state) {
// replace the occurence of the tag key string, with the tag value in this.state
innerText = innerText.replace(tagKey, this.state[tagKey])
}
this.templateElement.innerHTML = innerText;
}
render() {
// get a reference to the element that contains the raw template
return <p ref={ template => this.templateElement = template }>
*|FIRSTNAME|*,
*|LINK|*
You can sign in with your email address (*|EMAIL|*)
Your password is:
*|PASSWORD|*
Thanks!
*|SIGNATURE|*
</p>;
}
}
Hope that helps. Tell me if anything is unclear.
Edit: Added a more React-like example, where the template isn't pure text, but HTML. JSFiddle: https://jsfiddle.net/jonahe/539g38kc/

Getting the warning from chrome dev tools that each child should have a unique key prop

I'm learning Reactjs and tried to copy the example from this facebook git, children to make the warning disappear, but I'm still getting it:
var MyComponent = React.createClass({
getInitialState: function () {
return {idx: 0};
},
render: function() {
var results = this.props.results;
return (
<ol>
{results.map(function(result) {
return <li key={result.id}>{result.book}</li>;
})}
</ol>
);
}
});
var result = [
{title: "book", content: "booky"},
{title: "pen", content: "penny"},
];
document.addEventListener("DOMContentLoaded", function () {
ReactDOM.render(<MyComponent results={results} />, document.getElementById('widgets'));
});
A few things going on here that need to be rectified:
The variable you're declaring and assigning the array of results to is named result but you are passing a variable named results as the results prop to MyComponent. Rename this variable to results.
The two object properties you're attempting to access on the result object inside the map function are id and book, neither of which are defined on the object itself (title and content are). Change {result.book} to {result.title} in this case.
Finally, in order to supply a unique ID to each element returned from the map, set the second parameter of your map (e.g. results.map(function(result, i) {... and use the array index as your unique key.
In summary:
results.map(function(result, i) {
return <li key={i}>{result.title}</li>;
})

Resources