I am building my first React side project, and I am abit stuck. I can't get the bgColor to work properly.
The Square Component (I am making a grid)
import React from 'react';
import styled from 'styled-components';
const Sqr = styled.div`
border: 1px solid gray;
background-color: ${props => props.bgColor || "red"};
box-sizing: border-box;
`;
// BG COLOR DOESNT WORK
const square = (props) => {
console.log("[square] props", props)
return (
<Sqr>{props.num}</Sqr>
)
};
export default square;
And this is the main component where I am rendering it:
import React from 'react';
import { connect } from 'react-redux';
import styles from './appname.module.css'
import Board from './Board/Board';
import * as actions from '../../store/actions'
import Square from './Square/Square';
import BoardControls from './BoardControls/BoardControls';
class appname extends React.Component {
constructor(props){
super(props);
this.props.initSquares();
}
render() {
return(
<div className={styles.appname}>
<BoardControls />
<Board size={this.props.settings.size}>
{this.props.squares.map(sqr => <Square key={sqr} num={sqr} bgColor={this.props.settings.bgColor} />)}
</Board>
</div>
)
}
}
const mapStateToProps = state => {
return {
settings: state.appname.settings,
squares: state.appname.squares
}
}
const mapDispatchToProps = dispatch => {
return {
initSquares: () => dispatch(actions.initSquares())
}
}
export default connect(mapStateToProps, mapDispatchToProps)(appname);
The background-color is Red in the browser, and if I change that line to read...
const Sqr = styled.div`
border: 1px solid gray;
background-color: ${props => props.bgColor};
box-sizing: border-box;
`;
...the background color property disappears from the class in the browser tools.
The console.log reads [square] props {num: 0, bgColor: "#ccc"}...so it is getting passed.
What am I doing wrong please?
const square = (props) => {
console.log("[square] props", props)
const squareStyle= {
border: '1px solid gray',
background-color: props.bgColor,
box-sizing: 'border-box',
};
return (
<Sqr style={squareStyle}>{props.num}</Sqr>
)
};
Put the Sqr inside the component
const square = (props) => {
const Sqr = styled.div`
border: 1px solid gray;
background-color: ${props => props.bgColor || "red"};
box-sizing: border-box;
`;
return (
<Sqr>{props.num}</Sqr>
)
};
Related
I have a composed page with multiple components. Like the picture, I want to show Component B inside Component A. By default, when clicking on A, hide B and show C in the middle. After clicking again, it toggles to show B and hide C.
When C has been showing, also change the A's border color.
import React from "react";
import styled, { css } from "styled-components";
import {
ComponentA,
ComponentB,
ComponentC,
} from "~/components";
export const NewComponent: React.FC = ({
}) => {
return (
<>
<ComponentA>
<ComponentB>B</ComponentB>
<ComponentC>C</ComponentC>
</ComponentA>
</>
);
};
const ComponentA = styled()`
${({ theme }) => css`
&:active {
border-color: green;
bordor: 1px solid;
}
`}
`;
I tried to use CSS to control the border color, it works with background-color but does not work for border-color. Also, I'm not sure if CSS can control the showing between Component B and component C.
To switch the components you need to use the useState hook with a boolean state. Then add an event onClick on the ComponentA and you can toggle state with the hook.
This way you can handle the border color switching. Create an attribute for ComponentA (you can chose name yourself ) you can pass values as a props, and using Ternary Operator toggle the color.
PS: If you don't use typescript, you can remove this line <{ color: "green" | "white" }> for ComponentA.
import { useState } from "react";
import styled from "styled-components";
export const ComponentA = styled.div<{ color: "green" | "white" }>`
padding: 3rem 7rem;
border: 2px solid ${(props) => props.color};
border-radius: 0.5rem;
`;
const DefaultStyle = styled.div`
max-width: 300px;
padding: 3rem;
border-radius: 0.5rem;
`;
export const ComponentB = styled(DefaultStyle)`
background-color: magenta;
`;
export const ComponentC = styled(DefaultStyle)`
background-color: orange;
`;
export default function App() {
const [isActive, setIsActive] = useState(false);
const toggle = () => setIsActive(!isActive);
return (
<div className="App">
<ComponentA color={isActive ? "green" : "white"} onClick={toggle}>
{isActive ? (
<ComponentC>Component C</ComponentC>
) : (
<ComponentB>Component B</ComponentB>
)}
</ComponentA>
</div>
);
}
import {useState} from "react";
import {
ComponentA,
ComponentB,
ComponentC,
} from "./components";
export const NewComponent: React.FC = ({
}) => {
const [isShowing, setIsShowing] = useState<boolean>(true);
const toggle = () => {
setIsShowing(current => !current);
};
return (
<>
<ComponentA>
<div>
<button onClick={toggle}>Toggle</button>
{isShowing && <ComponentB>B</ComponentB>} // if true
{!isShowing && <ComponentC>C</ComponentC>} // if false
</div>
</ComponentA> </>
)
}
bordor - you wrote it wrong. It's border
When you set border: 1px solid; then border-color: initial
You can set CSS: border: 1px solid green;
I have a React component styled using styled-components, but can it be apply style again? My button is a react component
import Button from "./Button";
Then somewhere else I add margin-top, it don't apply the style.
//margin-top has no effect?
const ButtonStyled = styled(Button)`
margin-top: 100px;
`;
export default function App() {
return <ButtonStyled>My button</ButtonStyled>;
}
Demo here
https://codesandbox.io/s/goofy-montalcini-l47kp?file=/src/App.js:144-150
In order for the styled function to work, the component must accept a className prop. Now, when you define Button:
const Button = ({ children }, props) => {
return <ButtonStyled {...props}>{children}</ButtonStyled>;
};
You try to spread the props to ButtonStyled, which is good, you just mis typed the place of props. Try this:
const Button = ({ children, ...props }) => {
return <ButtonStyled {...props}>{children}</ButtonStyled>;
};
This way, the className prop (which is applied by styled()) will be passed onto StyledButton.
I have seen two options to override the css.
Wrapper the component
Through props
import styled, { css } from "styled-components";
const ButtonStyled = styled.button`
${(props) => {
return css`
margin-top: 10px;
width: 200px;
padding: 16px 0;
border: 1px solid;
color: black;
font-size: 16px;
font-weight: 900;
border-radius: 5px;
${props.newStyled}
`;
}}
`;
const Button = ({ children, ...props }) => {
return <ButtonStyled {...props}>{children}</ButtonStyled>;
};
// OPTION 1: WRAPPER THE COMPONENT
function Question7_option1() {
const DivStyled = styled.div`
button {
margin-top: 100px;
}
`;
return (<DivStyled>
<Button>Button</Button>
</DivStyled>);
}
// OPTION 2: NEW STYLES THROUGH PROPS
function Question7_option2() {
const newStyled = css`
margin-top: 100px;
`;
return (<Button newStyled={newStyled}>Button</Button>);
}
export default Question7_option2;
I hope I've helped you
In the following code, I have two columns. One column has all the tasks. The other column has a group of selected tasks. So, my expectation is that, when a user drags and drops a task from the total tasks column to the selected tasks column, the task should still be there in total tasks column. When the tasks are there in the selected tasks column, there will be a button visible to delete the tasks from selected tasks column. When I click this button, the task gets removed from the selected tasks column. But I am not able to drag it again from the total tasks column. I am getting the following error:
Unable to find draggable with id: task-2
Request you guys to take a look and tell me what's the mistake I am doing
Please find the link for the codesandbox:
https://codesandbox.io/s/unruffled-waterfall-wdyc5?file=/src/task.jsx
Also this is my code:
App.jsx:
import React from 'react';
import '#atlaskit/css-reset';
import styled from 'styled-components';
import { DragDropContext } from 'react-beautiful-dnd';
import initialData from './initial-data';
import Column from './column';
const Container = styled.div`
display: flex;
`;
class App extends React.Component {
state = initialData;
onDragStart = start => {
const homeIndex = this.state.columnOrder.indexOf(start.source.droppableId);
this.setState({
homeIndex,
});
};
onDragEnd = result => {
this.setState({
homeIndex: null,
});
const { destination, source, draggableId } = result;
if (!destination) {
return;
}
if (
destination.droppableId === source.droppableId &&
destination.index === source.index
) {
return;
}
const home = this.state.columns[source.droppableId];
const foreign = this.state.columns[destination.droppableId];
if (home === foreign) {
const newTaskIds = Array.from(home.taskIds);
newTaskIds.splice(source.index, 1);
newTaskIds.splice(destination.index, 0, draggableId);
const newHome = {
...home,
taskIds: newTaskIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newHome.id]: newHome,
},
};
this.setState(newState);
return;
}
const foreignTaskIds = Array.from(foreign.taskIds);
foreignTaskIds.splice(destination.index, 0, draggableId);
const newForeign = {
...foreign,
taskIds: foreignTaskIds,
};
const newState = {
...this.state,
columns: {
...this.state.columns,
[newForeign.id]: newForeign,
},
};
this.setState(newState);
};
deleteHandler = (taskId) => {
console.warn("I am going to delete: " + taskId);
var columnId = 'column-2';
const column = this.state.columns[columnId];
const columnTaskIds = Array.from(column.taskIds);
columnTaskIds.splice(columnTaskIds.indexOf(taskId), 1);
const newcolumn = {
...column,
taskIds: columnTaskIds,
};
var newState = null;
newState = {
...this.state,
columns: {
...this.state.columns,
[newcolumn.id]: newcolumn
}
};
this.setState(newState);
console.log(newState);
}
render() {
return (
<DragDropContext
onDragStart={this.onDragStart}
onDragEnd={this.onDragEnd}
>
<Container>
{this.state.columnOrder.map((columnId, index) => {
const column = this.state.columns[columnId];
const tasks = column.taskIds.map(
taskId => this.state.tasks[taskId],
);
const isDropDisabled = index < this.state.homeIndex;
return (
<Column
key={column.id}
column={column}
tasks={tasks}
isDropDisabled={isDropDisabled}
deleteHandler={this.deleteHandler}
/>
);
})}
</Container>
</DragDropContext>
);
}
}
export default App
column.jsx
import React from 'react';
import styled from 'styled-components';
import { Droppable } from 'react-beautiful-dnd';
import Task from './task';
const Container = styled.div`
margin: 8px;
border: 1px solid lightgrey;
border-radius: 2px;
width: 220px;
display: flex;
flex-direction: column;
`;
const Title = styled.h3`
padding: 8px;
`;
const TaskList = styled.div`
padding: 8px;
transition: background-color 0.2s ease;
background-color: ${props => (props.isDraggingOver ? 'skyblue' : 'white')};
flex-grow: 1;
min-height: 100px;
`;
export default class Column extends React.Component {
isSelectedTasksColumn = this.props.column.id === 'column-2';
render() {
return (
<Container>
<Title>{this.props.column.title}</Title>
<Droppable
droppableId={this.props.column.id}
isDropDisabled={this.props.isDropDisabled}
>
{(provided, snapshot) => (
<TaskList
ref={provided.innerRef}
{...provided.droppableProps}
isDraggingOver={snapshot.isDraggingOver}
>
{this.props.tasks.map((task, index) => (
<Task key={task.id} task={task} index={index} isSelectedTasksColumn={this.isSelectedTasksColumn} deleteHandler={this.props.deleteHandler}/>
))}
{provided.placeholder}
</TaskList>
)}
</Droppable>
</Container>
);
}
}
task.jsx
import React from 'react';
import styled from 'styled-components';
import { Draggable } from 'react-beautiful-dnd';
const Container = styled.div`
border: 1px solid lightgrey;
border-radius: 2px;
padding: 8px;
margin-bottom: 8px;
background-color: ${props =>
props.isDragDisabled
? 'lightgrey'
: props.isDragging
? 'lightgreen'
: 'white'};
`;
const DeleteButton = styled.button`
justify-self: center;
align-self: center;
border: 0;
background-color: black;
color: white;
padding: 7px;
cursor: pointer;
display: ${props => props.isSelectedTasksColumn ? "inline" : "none"};
float: right;
`;
const DisplayValue = styled.p`
align-self: center;
font-size: 20px;
padding: 0;
margin: 0;
display: inline;
`;
export default class Task extends React.Component {
render() {
const isDragDisabled = false;
return (
<Draggable
draggableId={this.props.task.id}
index={this.props.index}
isDragDisabled={isDragDisabled}
>
{(provided, snapshot) => (
<Container
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
isDragging={snapshot.isDragging}
isDragDisabled={isDragDisabled}
>
<DisplayValue>{this.props.task.content}</DisplayValue>
<DeleteButton isSelectedTasksColumn = {this.props.isSelectedTasksColumn}
onClick={() => this.props.deleteHandler(this.props.task.id)}>
✖
</DeleteButton>
</Container>
)}
</Draggable>
);
}
}
initial-data.js:
const initialData = {
tasks: {
'task-1': { id: 'task-1', content: 'Task 1' },
'task-2': { id: 'task-2', content: 'Task 2' },
'task-3': { id: 'task-3', content: 'Task 3' },
'task-4': { id: 'task-4', content: 'Task 4' },
},
columns: {
'column-1': {
id: 'column-1',
title: 'All tasks',
taskIds: ['task-1', 'task-2', 'task-3', 'task-4'],
},
'column-2': {
id: 'column-2',
title: 'Selected tasks',
taskIds: [],
}
},
// Facilitate reordering of the columns
columnOrder: ['column-1', 'column-2'],
};
export default initialData;
Removing React.StrictMode could help
https://reactjs.org/docs/strict-mode.html
I am trying to write a react component where I can load different styled div-s according to passed props and render them. This is my code so far:
Component:
import React, { Component } from "react";
import s, { keyframes } from "styled-components";
import PropTypes from "prop-types";
import { jumpyRotation } from "../animations/jumpyRotation.js";
import { normalRotation } from "../animations/normalRotation.js";
import { baseShape } from "../shapes/base";
const animations = {
jumpyRotation: jumpyRotation,
normalRotation: normalRotation
};
const shapes = {
square: baseShape
};
class Loader extends Component {
render() {
const {
size = "14px",
color = "#000",
fontSize = "14px",
loaderText = "Loading...",
length = "4s",
animation = "jumpyRotation",
shape = "square"
} = this.props;
const styledShape = shapes[shape];
styledShape.attrs.size = size;
styledShape.attrs.color = color;
styledShape.attrs.animation = animation;
styledShape.attrs.length = length;
return (
<LoaderStyles
length={length}
animation={animations[animation]}
fontSize={fontSize}
color={color}
size={size}
>
styledShape
<span className="loader-text">{loaderText}</span>
</LoaderStyles>
);
}
}
Loader.propTypes = {
size: PropTypes.string, // Size in a valid CSS unit
color: PropTypes.string, // A valid CSS color, changes both loader and text
fontSize: PropTypes.string, // Size in a valid CSS unit
loaderText: PropTypes.string, // Text displayed under the loader
length: PropTypes.string, // The length of animation in a valid CSS unit
animation: PropTypes.string // The name of the animation
};
const LoaderStyles = s.div`
font-size: ${props => props.fontSize};
display: flex;
align-items: center;
justify-items: center;
flex-direction: column;
padding: ${props => props.fontSize};
.loader-text {
color: ${props => props.color};
}
`;
export default Loader;
Styled component ../shapes/base:
import s from "styled-components";
export const baseShape = s.div`
margin: ${props => props.size};
height: ${props => props.size};
width: ${props => props.size};
background-color: ${props => props.color};
animation: ${props => props.animation} ${props =>
props.length} linear infinite;
`;
Now according to the styled component docs, using the syntax should work, but I get the following error:
TypeError: Cannot set property 'size' of undefined
Even though the browser debugger shows styledShape as a styled.div
React only recognizes components if they start with capital letters, so the solution was this (omitted irrelevant code from the anwser):
Styled component:
import s from "styled-components";
export const baseShape = s.div`
margin: ${Brops => props.size};
height: ${props => props.size};
width: ${props => props.size};
background-color: ${props => props.color};
animation: ${props => props.animation} ${props =>
props.length} linear infinite;
`;
Component:
import React, { Component } from "react";
import s from "styled-components";
import { BaseShape } from "../shapes/base";
const shapes = {
square: BaseShape
};
class Loader extends Component {
render() {
const {
size,
color,
animation,
shape
} = this.props;
const ShapeDiv = shapes[shape];
return (
<ShapeDiv size={size} color={color} animation={animation} shape={shape}/>
);
}
}
export default Loader;
I'm trying to implement a loader to my react component, when the background image is loading it should display 'loading' and then once it has loaded it should display 'loaded'
I have a setTimeout() on my componentwillMount() to test that the loader functions as expected which it does
I'm struggling to understand how it knows when the image is loaded and to change the loading state
Is it best to put the image into a seperate component with the loader rather than have it on the Hello component?
https://www.webpackbin.com/bins/-KsOEkf9ubvR6iz4bMxG
Update
I have managed to get a simple image loader working using the onload() method attached to the image - https://www.webpackbin.com/bins/-KsNpZPzveUoby1yriFo
Hello.js
import React from 'react'
import Loader from './Loader'
import styled from 'styled-components'
const Card = styled.div`
height: 400px;
width:20%;
background: url('https://www.planwallpaper.com/static/images/Light-Wood-Background-Wallpaper.jpg');
`
export default class Test extends React.Component {
constructor() {
super()
this.state = { loading:true}
}
componentWillMount()
{
setTimeout(() => this.setState({loading: false}), 3000)
console.log("componentDidMount");
}
render() {
return (
<div>
<Card>
<Loader loading={this.state.loading} />
</Card>
</div>
)
}
}
Loader.js
import React, { Component } from 'react'
import styled, { keyframes } from 'styled-components'
import { string } from 'prop-types'
const transition1 = keyframes`
0% { background: #F19939; }
33.33% { background: #F8CA8F; }
66.66% { background: #FBD8AE; }
100% { background: #F19939; }
`
const transition2 = keyframes`
0% { background: #FBD8AE; }
33.33% { background: #F19939; }
66.66% { background: #F8CA8F; }
100% { background: #FBD8AE; }
`
const transition3 = keyframes`
0% { background: #F8CA8F; }
33.33% { background: #FBD8AE; }
66.66% { background: #F19939; }
100% { background: #F8CA8F; }
`
const Box = styled.span`
height: 12px;
width: 12px;
margin: 0 3px 0 3px;
border-radius: 4px;
animation: 0.4s ${transition1 } infinite;
`
const Box2 = styled(Box)`
animation: 0.4s ${transition2 } infinite;
`
const Box3 = styled(Box)`
animation: 0.4s ${transition3 } infinite;
`
const TextWrap = styled.div`
display: flex;
flex: 0 0 100%;
justify-content: center;
color: #fff;
`
const Para = styled.p`
color: #fff
padding: 19px 0 0 0;
`
const ParaLoaded = styled(Para)`
color: #fff;
padding: 22px 0 0 0;
`
export default class Loader extends Component {
render() {
return (
<div >
<div >
<TextWrap>
{
this.props.loading
?
<Para>Loading...</Para>
:
<ParaLoaded>Loaded</ParaLoaded>
}
</TextWrap>
</div>
</div>
)
}
}
You can do it like this: https://www.webpackbin.com/bins/-KsOJpBVfpazfXghJAaF
LoadBackgroundImage.js
const LoadBackgroundImage = (component, imageUrl, seconds, success, failure) => {
let timeoutOccured = false;
const image = new Image();
const timeout = setTimeout(() => {
timeoutOccured = true;
failure();
}, seconds * 1000);
image.onload = () => {
clearTimeout(timeout);
component.style.backgroundImage = `url('${imageUrl}')`;
if (!timeoutOccured) success();
};
image.src = imageUrl;
};
export default LoadBackgroundImage;
Hello.js
import React from 'react'
import Loader from './Loader'
import styled from 'styled-components'
import LoadBackgroundImage from './LoadBackgroundImage'
const Card = styled.div`
height: 400px;
width:20%;
`
export default class Hello extends React.Component {
constructor() {
super()
this.state = { loading:true}
}
componentDidMount() {
LoadBackgroundImage(
this.card,
'https://www.planwallpaper.com/static/images/Light-Wood-Background-Wallpaper.jpg',
5,
() => this.setState({ loading: false }),
() => console.log('The image did not load in 5 seconds')
);
}
render() {
return (
<div>
<Card innerRef={card => this.card = card}>
<Loader loading={this.state.loading} />
</Card>
</div>
)
}
}
In render() you are using innerRef to obtain a reference to the card component and save it in this.card. Then in componentDidMount you are using this reference and LoadBackgroundImage function to load the image and monitor when it's loaded. If the image is loaded in given number of second success callback will be called, otherwise failure callback will be called. The image can still load after 5 seconds, but the success callback will not be called. If you want to be called anyway, you can skip this ckeck: if (!timeoutOccured) in LoadBackgroundImage function.