How to pass props to .mapped elements with styled-components? - javascript

I've been hitting my head against a wall with this one and I can't quite grasp what's the issue here.
I am pulling some data from API. It's a simple object that contains username, score and isOwner which is basically information if the username equals currently logged user. Now, if isOwner is true i want to style that li position differently.
So lets say that's how my map looks like:
const List = myData.map((el, i) => (
<li key={el.name} isowner={el.isOwner ? 1 : 0}>
{i + 1}. {el.name}
<span>{el.score}</span>
</li>
));
So every li element is generated in a styled ul component.
Now, looking at the styled component list, it looks like:
export const ScoreList = styled.ul`
width: 80%;
display: flex;
flex-direction: column;
li {
border: ${props =>
props.isowner === 1 ? '2px solid yellow' : '1px solid black'};
border-radius: 5px;
}`
for some reason it ignores the value of isowner and displays everything with the black border.
Now looking at chrome dev tools I've noticed something odd.
<li isowner="1">...</li>
<li isowner="0">...</li>
<li isowner="0">...</li>
I can see the 'prop' being put there like this, which I can't notice in any other case when I'm passing props. It looks like the logic is working well but it just doesn't see the element as a prop.
Also, I am using isowner instead of isOwner because otherwise I'm getting a following warning:
React does not recognize the `isOwner` prop on a DOM element.
If you intentionally want it to appear in the DOM as a custom attribute,
spell it as lowercase `isowner` instead.
If you accidentally passed it from a parent component, remove it from the DOM element.
I've tried different methods, instead of isOwner I was comparing el.name with username that I could yoink from redux, results were the same, I got the '1' or 'true' right where I needed it but I just couldnt pass it further.
I would be very very thankful if any of you has an idea about how to deal with this.

this might help you:
export const ScoreLi = styled.li`
border: ${props =>
props.isowner === 1 ? '2px solid yellow' : '1px solid black'};
border-radius: 5px;`
const List = myData.map((el, i) => (
<ScoreLi key={el.name} isowner={el.isOwner ? 1 : 0}>
{i + 1}. {el.name}
<span>{el.score}</span>
</ScoreLi>
));

Related

How to remove or hide a div when checkbox is checked in React?

I am new to React and I am trying to build a simple todolist app.
Once a task is inserted, a div with an unchecked checkbox appears. Now, I'd like to hide or remove the div when the checkbox is clicked. I think I hooked up everything correctly but I miss, conceptually, what is the best way to do it: should I make a className attribute that changes upon clicking the checkbox? Should I modify the tasks array containing by removing the task that has been checked? Or, is there any other good way I could achieve this?
Thank you so much for your help.
https://scrimba.com/scrim/cof7e4dde89963549cc216a25 Here's the directly editable code
.insert {
margin: 0 auto;
display: block;
padding: 20px;
}
.enter--task {
border-radius: 5px;
}
.tasks {
width: 40%;
min-height: 100%;
color: black;
background-color: white;
margin-top: 20px;
border-radius: 15px;
line-height: 40px;
padding: 10px;
overflow-wrap: break-word;
}
.task--check {
margin: 5px;
}
<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>
import React from "react"
export default function Insert () {
const [task, setTask] = React.useState ("") // settin state for the single task, e.g. the input.
const [taskArray, setTaskArray] = React.useState ([]) // setting state for the array of tasks.
const tasks = taskArray.map((tasks) => {
// {event.target.checked ? "hidden": "tasks"} this on className did not work.
return (
<div className="tasks">
<input
type="checkbox"
className="task--check"
onChange={handleCheck}
/>
{tasks}
</div>
)
}) // This is basically to return a series of paragraphs (soon divs) once enter or add task are clicked.
//How to let the task div disappear upon checking the checkbox?
function handleCheck() {
console.log(event.target.checked ? "checked": "unchecked")
}
function handleChange (event) {
setTask(event.target.value)
} // this is a function to register the value of the input. We use the setTask function to update the value of task to the value present in the input, which in this case it is our target
function handleSubmit (event) {
event.preventDefault()
setTaskArray(task ? [...taskArray,task] : [...taskArray]) // quite hyped! Problem: user can input empty tasks. Solution: ternary operator saying, if task is true (truthy in this case, meaning a string with some content), add it to the taskArray. Otherwise, just return taskArray without appending the falsy task (empty string).
setTask("")
}
// this function handles the event after clicking enter or add task. First, we use event.preventDefault(): this is to avoid that every time that we click enter or add task, the page refreshes and the input is attached to the url (try to remove and see what happens). Then we have to make another array to insert the new task. To do this, we spread the previous array (taskArray) and we add the new element, which is task. After that, we reset the value of task to an empty string - this is to show the placeholder again.
return (
<main className="insert">
<form onSubmit={handleSubmit}>
<input
type="text"
className="enter--task"
placeholder="Enter your task"
onChange={handleChange}
name="task"
value={task} // setting the value of my input equal to my state
/>
<button>Add task</button>
</form>
{tasks}
</main>
)
}
Create a state called checkState
const [checkState, setCheckState] = React.useState(false)
const handleCheck = (event) => {
setCheckState(event.checked);
}
<div className="tasks">
<input
type="checkbox"
checked={checkState}
onChange={handleCheck}
/>
{tasks}
</div>
{checkState && <div>Show div when checkbox checked</div>}
I guess it depends on what you want to achieve, in the classic todo list example, we would remove the task from the state. By hiding it, you would keep the reference in memory. I'm not sure to see a good use case for it.
If you want to remove, you could do it like this. Note that I'm using the index as key which is not recommended as the order of the items could change, instead you should add an id attribute to your tasks, and use it as the key. You will also use it to remove the task.
const tasks = taskArray.map((task, i) => {
return (
<div className="tasks" key={i}>
<input
type="checkbox"
className="task--check"
onChange={() => removeTask(i)}
/>
{task}
</div>
)
})
function removeTask(index) {
setTaskArray(prev => prev.filter((_,i) => i !== index))
}

Implement live markdown rendering using slate.js

I'm writing a markdown text editor using slate.js. I'm trying to implement the following live-rendering effect (from Typora):
As you can see,
When I'm typing, the text is turning to bold automatically.
When I hit the space key, the four asterisks disappeared, only the text itself is visible.
When I focus the cursor back to the text, the asterisks shows up again (so I can modify them).
I've already implemented the first item thanks to the example of MarkdownPreview, here is the code of it (take from the slate repository):
import Prism from 'prismjs'
import React, { useCallback, useMemo } from 'react'
import { Slate, Editable, withReact } from 'slate-react'
import { Text, createEditor, Descendant } from 'slate'
import { withHistory } from 'slate-history'
import { css } from '#emotion/css'
// eslint-disable-next-line
;Prism.languages.markdown=Prism.languages.extend("markup",{}),Prism.languages.insertBefore("markdown","prolog",{blockquote:{pattern:/^>(?:[\t ]*>)*/m,alias:"punctuation"},code:[{pattern:/^(?: {4}|\t).+/m,alias:"keyword"},{pattern:/``.+?``|`[^`\n]+`/,alias:"keyword"}],title:[{pattern:/\w+.*(?:\r?\n|\r)(?:==+|--+)/,alias:"important",inside:{punctuation:/==+$|--+$/}},{pattern:/(^\s*)#+.+/m,lookbehind:!0,alias:"important",inside:{punctuation:/^#+|#+$/}}],hr:{pattern:/(^\s*)([*-])([\t ]*\2){2,}(?=\s*$)/m,lookbehind:!0,alias:"punctuation"},list:{pattern:/(^\s*)(?:[*+-]|\d+\.)(?=[\t ].)/m,lookbehind:!0,alias:"punctuation"},"url-reference":{pattern:/!?\[[^\]]+\]:[\t ]+(?:\S+|<(?:\\.|[^>\\])+>)(?:[\t ]+(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\)))?/,inside:{variable:{pattern:/^(!?\[)[^\]]+/,lookbehind:!0},string:/(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|\((?:\\.|[^)\\])*\))$/,punctuation:/^[\[\]!:]|[<>]/},alias:"url"},bold:{pattern:/(^|[^\\])(\*\*|__)(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^\*\*|^__|\*\*$|__$/}},italic:{pattern:/(^|[^\\])([*_])(?:(?:\r?\n|\r)(?!\r?\n|\r)|.)+?\2/,lookbehind:!0,inside:{punctuation:/^[*_]|[*_]$/}},url:{pattern:/!?\[[^\]]+\](?:\([^\s)]+(?:[\t ]+"(?:\\.|[^"\\])*")?\)| ?\[[^\]\n]*\])/,inside:{variable:{pattern:/(!?\[)[^\]]+(?=\]$)/,lookbehind:!0},string:{pattern:/"(?:\\.|[^"\\])*"(?=\)$)/}}}}),Prism.languages.markdown.bold.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.italic.inside.url=Prism.util.clone(Prism.languages.markdown.url),Prism.languages.markdown.bold.inside.italic=Prism.util.clone(Prism.languages.markdown.italic),Prism.languages.markdown.italic.inside.bold=Prism.util.clone(Prism.languages.markdown.bold); // prettier-ignore
const MarkdownPreviewExample = () => {
const renderLeaf = useCallback(props => <Leaf {...props} />, [])
const editor = useMemo(() => withHistory(withReact(createEditor())), [])
const decorate = useCallback(([node, path]) => {
const ranges = []
if (!Text.isText(node)) {
return ranges
}
const getLength = token => {
if (typeof token === 'string') {
return token.length
} else if (typeof token.content === 'string') {
return token.content.length
} else {
return token.content.reduce((l, t) => l + getLength(t), 0)
}
}
const tokens = Prism.tokenize(node.text, Prism.languages.markdown)
let start = 0
for (const token of tokens) {
const length = getLength(token)
const end = start + length
if (typeof token !== 'string') {
ranges.push({
[token.type]: true,
anchor: { path, offset: start },
focus: { path, offset: end },
})
}
start = end
}
return ranges
}, [])
return (
<Slate editor={editor} value={initialValue}>
<Editable
decorate={decorate}
renderLeaf={renderLeaf}
placeholder="Write some markdown..."
/>
</Slate>
)
}
const Leaf = ({ attributes, children, leaf }) => {
return (
<span
{...attributes}
className={css`
font-weight: ${leaf.bold && 'bold'};
font-style: ${leaf.italic && 'italic'};
text-decoration: ${leaf.underlined && 'underline'};
${leaf.title &&
css`
display: inline-block;
font-weight: bold;
font-size: 20px;
margin: 20px 0 10px 0;
`}
${leaf.list &&
css`
padding-left: 10px;
font-size: 20px;
line-height: 10px;
`}
${leaf.hr &&
css`
display: block;
text-align: center;
border-bottom: 2px solid #ddd;
`}
${leaf.blockquote &&
css`
display: inline-block;
border-left: 2px solid #ddd;
padding-left: 10px;
color: #aaa;
font-style: italic;
`}
${leaf.code &&
css`
font-family: monospace;
background-color: #eee;
padding: 3px;
`}
`}
>
{children}
</span>
)
}
const initialValue: Descendant[] = [
{
type: 'paragraph',
children: [
{
text:
'Slate is flexible enough to add **decorations** that can format text based on its content. For example, this editor has **Markdown** preview decorations on it, to make it _dead_ simple to make an editor with built-in Markdown previewing.',
},
],
},
{
type: 'paragraph',
children: [{ text: '## Try it out!' }],
},
{
type: 'paragraph',
children: [{ text: 'Try it out for yourself!' }],
},
]
export default MarkdownPreviewExample
My question is, how can I implement the second and third items? I've been thinking about it for a long time, but didn't find any good way to achieve them.
The good news:
The original text is saved unharmed (somewhere in your application)
Slate.js is rendering a new element for each style. thanks to the live demonstration link you've posted it is clear that the DOM is saving the state of your markdown (which suggest that we can add manipulations on-top of slate if required)
The bad news:
Implementing an editor is a headache. Using an existing one is not always as intuitive and easy as we expected. That's why I love the Monaco editor project so much (a free editable editor that gives me the experience of VS code for free? yes please!)
What we can do?
the easy way:
Use Monaco editor in our project. From my experience - it is better in terms of coding and formatting technical texts (and maybe this is the engine behind VS code)
The not-so-easy way:
We can make a simpler implementation - a panel with the raw text and another panel for preview (by the way - this is exactly what happens when you edit markdown files in VS code anyways). That way we can always render the view to reflect our most recent changes. For starters - you can use this project to do it and add tweaks to it so suit your demands
The hard way:
Using slate.js commands (read this doc for the general concept + useful examples). This approach will let slate handle the load but requires you to deep-dive into that project as well. Note that you can register custom commands and queries to suit your need without breaking your work pipeline (read this)
The insane way:
Try to override slate by using custom events on-top of rendered elements. This can be tricky but achievable if you love to play with projects internals and inject values on the fly. Not very recommended though
My Recommendation
Create a custom command that will apply the markdown style you want (for instance: bold)
Use slate handler for tracking the space key hit and use your command
function onKeyDown(event, editor, next) {
if (event.key == 'Enter') {
// TODO: add markdown style
editor.applyMarkdownBold() // applyMarkdownBold is a made-up name for your custom command
} else {
return next()
}
}
I know - this example will apply bold style to all text but - if you combine it with a range selection you will get the style you need for the relevant area in you editor (again - docs for the rescue)
From this point - applying a specific selection via key press or adding the markdown characters by clicking it (using an event + query + command) is only a matter of time

How to pass custom data-attribute to styled component?

I have this Styled component, where I'm trying to pass DATA-attribute which is coming from props to it. (This is the solution we have on Stack Overflow)
export const InlineEditReadViewErrorContainer = styled.div.attrs(props => ({
'data-cy': props.dataCy
}))`
border: 2px solid #de350b;
border-radius: 3px;
`;
This is how I use this styled component in code
<InlineEditReadViewErrorContainer dataCy='blabla'>
{readView}
</InlineEditReadViewErrorContainer>
But this is doesn't change anything
I think that we must use correctly the attributes that are added to a component in React and more if they are needed only to style a component.
We should use as many native properties as possible but without compromising the private data that would be exposed in the HTML that the client displays in broser, therefore:
If you are going to use data-attributes:
Remember how to name these attributes:
The attribute name should not contain any uppercase letters, and must be at least one character long after the prefix "data-"
Note: I would just use the simplest possible, with booleans to give a set of properties as the first answer described, for example:
component.js
<Error data-active={true}>
{readView}
</Error>
component.styles.js
export const Error = styled.div`
&[data-active="true"] {
border: 2px solid #de350b;
border-radius: 3px;
}
`;
If you want to use custom props without them being displayed in the DOM as the second comment has described, using transient props:
For sample:
component.js
<Error $active={true}>
{readView}
</Error>
component.styles.js
export const Error = styled.div`
${({$active}) => $active ? css`
border: 2px solid #de350b;
border-radius: 3px;
`: null}
`;
The prop should already be "passed" in the sense that it will show up on the component for the purposes of using it in Cypress. If you want to use it internally you could also use transient props such as this
const Comp = styled.div`
color: ${props =>
props.$draggable || 'black'};
`;
render(
<Comp $draggable="red" draggable="true">
Drag me!
</Comp>
);
It was much easier. You can pass the data attribute directly where you use the styled component and everything will be fine.
<InlineEditReadViewErrorContainer data-cy='dateInput'>
{textValue}
</InlineEditReadViewErrorContainer>
Maybe it's related to your bundler, since you should be able to pass a data attribute to a styled-component directly. However, if you're extending an existing component, be aware that you need to pass it through props. Those two situations will attach the data attribute to the final HTML:
function Text(props) {
return (
<p data-foo={props['data-foo']} className={props.className}>
{props.children}
</p>
);
}
const StyledText = styled(Text)`
color: blueviolet;
`;
const StyledHeading = styled.h1`
color: gray;
`;
export default function App() {
return (
<div>
<StyledHeading data-bar="foo">Hello StackBlitz!</StyledHeading>
<StyledText data-foo="bar">
Start editing to see some magic happen :)
</StyledText>
</div>
);
}

binding textContent and overriding dom with svelte

I have a div with contenteditable=true and bind:textContent={value} so it behaves pretty much like a textarea.
The only issue I have with it is that I want to override the content of the div by processing the value, but seems like it is not possible.
To test I wrote this
<div contenteditable="true" bind:textContent={value}>testVal</div>
where value is an exported property of the component.
I kind of expected value to be set to testVal, but instead the div contains the value property.
I sort of understand why this is happening and that what I am doing is sort of an edge case, but is it at all possible to change this behaviour to kind of get a one way binding to value?
and I have tried my "normal" way of creating a one way binding (with some hacks to demonstrate issues):
<div contenteditable="true" on:input={e => value = e.target.textContent}>
{#each (value || "").split("") as part}
{part}
{/each}
</div>
this looks fine, but whenever I change type in the div my input gets multiplied, i.e. if I type e the div gets updated with ee. If I add another e I get eeee
I think the way to go is to use your "normal" way of creating a one way binding. Otherwise, using multiple ways of binding on the same element will conflict.
I used a combination of on:input like you described and, inside of the div, {#html html}
The following example formats each other word in bold as you type (there's some glitch when starting with an empty field):
<script>
import {tick} from "svelte";
let html = '<p>Write some text!</p>';
// for the implementation of the two functions below, see
// https://stackoverflow.com/a/13950376/4262276
let saveSelection = (containerEl) => { /**/ };
let restoreSelection = (containerEl, savedSel) => { /**/ };
let editor;
function handleInput(e){
const savedSelection = saveSelection(editor);
html = e.target.textContent
.split(" ")
.map((t, i) => i % 2 === 0
? `<span style="font-weight:bold">${t}</span>`
: t
)
.join(" ");
tick().then(() => {
restoreSelection(editor, savedSelection);
})
}
</script>
<div
bind:this={editor}
contenteditable="true"
on:input={handleInput}
>{#html html}</div>
<style>
[contenteditable] {
padding: 0.5em;
border: 1px solid #eee;
border-radius: 4px;
}
</style>

How do I render conditional styles (React + Sass)

I have these classes in my scss file:
.errorNotice {
display: none;
font-size: 12px;
color: #D85B5F;
background: white;
padding: 5px;
.error & {
display: inline-block;
}
}
And in my render function,
{this.state && this.state.error ?
(
<p className={styles.errorNotice + (this.state.error ? styles.error : '')}>{this.state.error}</p>
) :
null
}
This isn't giving me the styles I want though. It's giving me a weird concatenation:
campaigns-components-WaitlistForm-styles_errorNotice-1X-Ty4campaigns-components-WaitlistForm-styles_error-JNXx2E
My goal is to display the errorNotice with the correct display attribute.
Thanks
Use string literals to combine strings. In classname you just need the name of the classes.
So with the ` you can start a string literal and inside of ${} you can run javascript code and return something:
<p className={`errorNotice ${this.state.error ? "error" : ''}`}>{this.state.error}</p>
Maybe it's also an option for you to have a look at styled-components(https://www.styled-components.com). IMHO for React projects this is really neat to use and you don't have to create special css-files anymore or take care of classnames.

Categories

Resources