React - dynamic properties from strings - javascript

In my test suit, I am trying to generate components with props dynamically, so I will end up with components like so:
<Button primary />
<Button secondary />
Currently, I am a bit stuck:
[
'primary',
'secondary'
].forEach((buttonType) => {
it(`should render the '${buttonType}' button`, () => {
const button = mount(<Button {...buttonType}>Click me</Button>); // incorrect - will not work
// rest of the test omitted
});
});
Any help would be appreciated.

You should replace cardType with buttonType in your function parameter given to forEach.
Then, you should use the following inside the test:
const dynamicProps = { [buttonType]: true };
<Button {...dynamicProps} />
Some elements have been omitted but you get the idea. When you pass a prop without an explicit definition, you actually mean someProp={true}, so in the above case you have to use the primary or whatever as the property of an object, with a value of true.

You cannot map through an array of strings an apply those as boolean props the way you're doing it. Try instead iterating through a map that has each of your button types as keys with true values.
class MyApp extends React.Component {
render() {
let arr = [{primary: true}, {secondary: true}]
return (
<div>
{arr.map(buttonType => <Button {...buttonType} />)}
</div>
);
}
}
const Button = (props) => {
if(props.primary) return <button className="primary">Primary</button>;
if(props.secondary) return <button className="secondary">Secondary</button>;
return null;
}
ReactDOM.render(<MyApp />, document.getElementById("app"));
.primary {
background: orange;
}
.secondary {
background: grey;
}
<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="app"></div>

Related

How to store an input to an array and display it without using ternary operators instead use a function and the function will be onclick to a button

import './App.css';
import { useState } from 'react';
function App() {
const [array, setArray] = useState([])
const clicked = () => {
if (array.length) {
return(
array.map(value => <p>{value}</p>)
);
} else {
return (
null
);
}
}
return (
<div className="App">
<input onChange={(event) => setArray([...array, event.target.value])} />
<button onClick={clicked}>Click</button>
</div>
);
}
export default App;
Im expecting that there is a function that will be used in the onclick method in a button. when we click the button the (event.target.value) will be display in a paragraph WITHOUT USING A TERNARY OPERATORS. I also expect that ternary operators WIL BE CHANGE TO AN IF STATEMENT IN THE FUNCTION.
I think this is what you're trying to do - but you need two states.
The flow goes like this:
When you type in the input box those letters need to be held in state. Every time you change what's in the box the state gets updated. This state is a string.
When the button is clicked whatever is stored in the input box state gets pushed to the output state. This state is an array (because you want to hold a series of words etc).
In the JSX you can have a function call (I've called it getOutput that takes whatever is in the output state, maps over it, and produces a list of words in the output box.
Whenever state changes the component is rendered. This means that the value attribute on the input will always hold the current value in the input state, and the output box will always hold the mapped contents of the output state.
const { useState } = React;
function Example() {
// Two states - one for the input string,
// and one for the output (array)
const [ input, setInput ] = useState('');
const [ output, setOutput ] = useState([]);
// Amends the input state with the new value
function handleChange(e) {
setInput(e.target.value);
}
// When the button is clicked add the
// current input state to the output (array),
// reset the input state, and focus on it again
function handleClick() {
setOutput(prev => [ ...prev, input]);
setInput('');
}
// Creates a series of paragraphs using
// the output state
function getOutput(output) {
if (!output.length) return 'No data';
return output.map(el => <p>{el}</p>);
}
return (
<main>
<input
value={input}
onChange={handleChange}
/>
<button onClick={handleClick}>
Click
</button>
<section className="output">
{getOutput(output)}
</section>
</main>
);
}
ReactDOM.render(
<Example />,
document.getElementById('react')
);
button { margin-left: 1em; }
.output { margin-top: 1em; border: 1px solid darkgray; padding: 0.5em;}
.output p { line-height: 0.8em; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.production.min.js"></script>
<div id="react"></div>

Calling Stateless Functional Components

I'm currently learning React and i am working through 'The Road to React' by Robin Wieruch.
I've just refactored some code from a stateful to a functional stateless component like so:
function Search(props) {
const { value, onChange, children } = props;
return (
<form>
{children} <input
type="text"
value={value}
onChange={onChange}
/>
</form>
);
}
Gets Refactored to:
const Search = ({ value, onChange, children }) => {
<form>
{children} <input
type="text"
value={value}
onChange={onChange}
/>
</form>
}
However nothing is rendering anymore. Are functional stateless components called the same was as stateful ones?
This is how I'm calling the Search component in the App class:
render() {
const { searchTerm, list } = this.state;
return (
<div className="App">
<Search
value = { searchTerm }
onChange = { this.onSearchChange }
>
Search
</Search>
<Table
list = { list }
pattern = { searchTerm }
onDismiss = { this.onDismiss }
/>
</div>
)
I'm not receiving an error at all, so i'm not getting much that's pointing me in the right direction, i'm hoping i'm just missing something silly.
Thanks in advance!
In both cases, it's a stateless function only as there's no state and it's not an class component either.
1st case is working correctly because it's returning the element with the return keyword.
2nd refactored case is also correct but you are not returning anything you need to return the element for it to be rendered.
return example
const func = () => {
... // any more calculations or code
return ( // you are returning the element here
<div>
...
</div>
)
}
If there's no calculation or any additional code and you have to return only an element you can directly return it by using (...) instead of {...} as follows
const func = () => ( // you are directly returning element
<div>
...
</div>
)
PS: for more info you can check into arrow functions

passing className from the function in React

I have an array of items and I want to change the background of some of the items.
Checking via the bool you are gonna see that part in switch.
className={classes.Items} this is working but how can I pass className here?
const createClassName = (data) => {
const className = "Items";
switch (data.isRead){
case false:
return className + "Unread";
case true:
return className ;
}
};
{props.data.map((item) => (
<MenuItem
// className={classes.Items}
//className={createClassNameForNotificationNeededRow(item)}
key={item.key}
>
Looks like you use module style files to load classes and then you want to to conditionally apply them, in this case your data is a boolean, either true or false, and remember that classes you read from the imported style modules will be changed by some plugins before they make their way to browser to make it a unique className like items_knc5d5sx so keep in mind to use classes.someClassName instead using a string, anyway, here is a small suggestion for your use case:
const createClassName = (classes, data) => {
return `${classes.items} ${!data.isRead ? classes.Unread : ""}`
};
Then use this function createClassName when you render to create the classes for each item dynamically.
{props.data.map((item) => (
<MenuItem
className={createClassName(classes, item)}
key={item.key}
>
}
As stated in the comments, no need for that switch, I've changed it to an inline if-statement.
To get the desired behaviour, call the createClassName function on each map() iteration.
Example snippet:
class Example extends React.Component {
// Function to create item className
createClassName = (data) => {
return (data.isRead) ? 'ItemsUnread' : 'Items';
};
// Debug test data
testData = [ { id: 1 }, { id: 2 }, { id: 3, isRead: true } ];
// Render
render() {
return (
<div>
{this.testData.map((data) => {
const className = this.createClassName(data);
const content = `${data.id}: ${className}`;
return <div className={className}>{content}</div>
})}
</div>
);
}
}
ReactDOM.render(<Example />, document.body);
.Items { color: green; }
.ItemsUnread { color: red; }
<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>

how to pass arguments up via props in functions with React?

In a class, I would write: this.prop.bind(this,arg)
like this:
<button onClick={this.props.delete.bind(this, id)} id='deletebtn'>
X
</button>
but how would I do the same thing in a function?
I need to use a class or there is a way to do it without it?
ps: did hooks killed classes? if not when are classes really necessary?
You want to create a function that returns a function, like this:
function deleteItem(id) {
return function() {
... <using `id`>
}
}
or with arrow functions:
const deleteItem = (id) => {
return () => {
... <using `id`>
}
}
or even shorter:
const deleteItem = (id) => () => {
... <using `id`>
}
and then you do
<button onClick={deleteItem(id)} id='deletebtn'>
where deleteItem(id) is now a function.
To answer your question about hooks, they did not "kill classes" but provide a arguably better alternative only for React components. Class components are still supported. It is hard to imagine a situation that cannot be handled by the new functional components with hooks, though.
You can pass the removeItem handler down to the child component -
const { useState } = React
const initialItems =
[ "muffins", "cake", "pies" ]
const MyApp = ({ init = initialItems }) => {
const [items, setItems] = useState(init)
const removeItem = (pos = 0) => event =>
setItems([...items.slice(0, pos), ...items.slice(pos + 1)])
const addItem = event =>
event.key === "Enter"
? setItems([...items, event.target.value ])
: null
return <div>
{items.map((name, i) =>
<Item name={name} onDelete={removeItem(i)} />
)}
<div className="item">
<input onKeyDown={addItem} placeholder="Type here and press [ENTER]..." />
</div>
</div>
}
const Item = ({ name = "", onDelete }) =>
<div className="item">
{name}
<button onClick={onDelete}>🗑️</button>
</div>
ReactDOM.render(<MyApp />, document.body)
body {
font-family: monospace;
}
.item {
background-color: ghostwhite;
padding: 0.5rem;
}
input {
display: block;
box-sizing: border-box;
width: 100%;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.1/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.1/umd/react-dom.production.min.js"></script>
Okay, I found a way,
Like #JulienD Said it, I just need it to create a function that returns a function
this is how I did it.
the child sends the argument to the parent
<button onClick={() => {props.delPost(id)}}>delete</button>
the parent sends the argument up one more level
<Post delPost={(id)=>{props.delPost(id)}} key={post.id} postItem={post} />)
finally the root parent make use of the argument
const deletePost = (id) => {
console.log(id);
}
<Feed delPost={deletePost} posts={posts} />

Remove element from array with onClick and react

I'm very new to JS and React and coding in general. I'm trying to remove an item from an array using onClick, like so:
const { Component } = React;
class Board extends Component{
constructor(props) {
super(props);
this.state = {
comments: [
'I like Rosie',
'I like you',
'I like bacon',
'lalalala'
],
}
}
removeComment = (e) =>{
var filteredArray = this.state.comments.filter(item => item !== e.target.value)
this.setState({comments: filteredArray});
console.log(e.target.value)
}
eachComment = (text, i) => {
return ( <p id={i} key={i} > comment: {text} {i}</p> )
}
render(){
console.log('render');
return (
<div className="gif-list" onClick={this.removeComment}>
{this.state.comments.map(this.eachComment)}
</div>
);
}
}
ReactDOM.render(
<Board/>,
document.querySelector('#mount'));
<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="mount"><div>
Not much is happening though. My console.log(e.target.value) comes back as undefined. What am I doing wrong?
I have tried looking at other answers but implementing (to me) abstract solutions to other similar problems is not working for me.
Thanks in advance, and apologies for such a simple question.
The div you have the event handler on does not have a value attribute. But even if it did I don't think it will do what you want.
You'll need to change your code a bit in order to get it working like you want:
class Board extends React.Component{
constructor(props) {
super(props);
this.state = {
comments: [
'I like Rosie',
'I like you',
'I like bacon',
'lalalala'
],
}
}
removeComment = (e) =>{
// Check the index in the filter to see if it should be kept or removed
var filteredArray = this.state.comments.filter((item, i) => i != e.target.id)
this.setState({comments: filteredArray});
console.log(e.target.id)
}
eachComment = (text, i) => {
// Move the event handler to be on each item
return ( <p id={i} key={i} onClick={this.removeComment}> comment: {text} {i}</p> )
}
render(){
return (
<div className="gif-list">
{this.state.comments.map(this.eachComment)}
</div>
);
}
}
ReactDOM.render(<Board />, 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" />
You'll notice the important changes are:
Give the remove handler to each mapped element
Use an attribute from the event target that actually exists and can be used to determine which element should be removed.
For this one you need to specifically pass in the param when you bind the onClick function
return (
<div className="gif-list" onClick={(e)=>this.removeComment(e)}>
{this.state.comments.map(this.eachComment)}
</div>
);

Categories