a bit new to react.
I used the create react app https://github.com/facebook/create-react-app
to start a new react project.
the full code is here. https://github.com/bryandellinger/reactswitch/tree/master/src
I am trying to get the background color of a selected element to change and the text to become bold but it appears the class is never added not sure what I am doing wrong.
Switch.js
import React, { PropTypes } from 'react';
import styles from './Switch.css';
const CREDITCARD = 'Creditcard';
const BTC = 'Bitcoin';
const Choice = function (props) {
const cssClasses = [];
if (props.active) {
// <-- check props, not state
cssClasses.push(styles.active);
}
return (
<div
onClick={props.onClick}
className={cssClasses}
>
{props.label} {/* <-- allow any label */}
</div>
);
};
class Switch extends React.Component {
state = {
payMethod: BTC,
};
select = (choice) => {
return (evt) => {
this.setState({
payMethod: choice,
});
};
};
render() {
return (
<div className='switch'>
<Choice
onClick={this.select(CREDITCARD)}
active={this.state.payMethod === CREDITCARD}
label='Pay with Creditcard'
/>
<Choice
onClick={this.select(BTC)}
active={this.state.payMethod === BTC}
label='Pay with Bitcoin'
/>
Paying with: {this.state.payMethod}
</div>
);
}
}
export default Switch;
and Switch.css
.active {
background-color: #4619eb;
font-weight: bold;
}
it appears the active class from switch.css never gets added on the onclick event. not sure what I am missing.
Because of the way webpack is configured in CRA, you need to write your css like this:
:local(.active) {
background-color: #4619eb;
font-weight: bold;
}
CRA only supports importing the whole CSS file directly out of the box. So instead of importing the CSS file as a component, you would do:
import './Switch.css';
CRA docs for adding a stylesheet: https://github.com/facebook/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-stylesheet
Also, the className property should be a string with class names separated with a while space. If you want to set the class name dynamically, check out classnames: https://github.com/JedWatson/classnames.
Related
Problem
When i change the tag value it only changes on the select component but not in the index.astro
I have folder signals where i export signal
export const tagSignal = signal<string>("all");
I use it like this in Select.tsx component, and here evryting changes
import { tagSignal } from "#signal/*";
const setTagValue = (value: string) => {
tagSignal.value = value;
console.log("select", tagSignal.value);
};
export const Select = () => {
const [display, setDisplay] = useState(false);
const [selectedName, setSelectedName] = useState("all"); // this will be change to only signals still under refator
setTagValue(selectedName);
-------
------
but when I import it to index.astro like this I get only "all" value witch is inital value
---
import { Icon } from "astro-icon";
import { Picture } from "astro-imagetools/components";
import Layout from "#layouts/Layout.astro";
import { Select } from "#components/Select";
import Card from "#components/Card.astro";
import { getCollection } from "astro:content";
import { getProjectsByTag } from "#utils/*";
import { tagSignal } from "#signal/*";
const projects = await getCollection("projects");
const filteredProjects = getProjectsByTag(projects, tagSignal.value);
// TODO: add links
console.log("index", tagSignal.value);
---
/// some code here
<section id="projects" class="projects">
<Select client:only="preact" />
<div class="projects-wrapper">
{
filteredProjects.map(({ data: { title, heroImage } }) => (
<Card name={title} bg_path={heroImage} />
))
}
</div>
</section>
---
I see two issues here.
You are depending on dynamic JS variables in an .astro file. It doesn't work the way you are expecting—all the javascript in .astro files, with the exception of the "islands," e.g., your Select.tsx component, is being evaluated when the page is being built. So Astro grabs the initial value of tagSignal, but makes it a static string.
People can get bitten by, e.g., the trivial ©2010—{new Date().getFullYear()} in the footer – it won't magically update on the new year's eve if used in .astro file.
The state (signal's current value) is not shared accross the islands. If you want to share it, you need either a global state solution (I haven't used it in Astro yet), or just create a common parent for the Select and the filtering logic, e.g.:
{/* the signal will be declared (or imported) in `DynamicSection`*/}
<DynamicSection client:only="preact">
<Select />
<div class="projects-wrapper">
{
filteredProjects.map(({ data: { title, heroImage } }) => (
<Card name={title} bg_path={heroImage} />
))
}
</div>
</ DynamicSection>
(The simplest global state solution would be probably using the url with a query string, and derive the state from its value).
Update: I added a codesanbox at the bottom of this with a more detailed explanation of what I need done.
So I don't quite understand how classes work in React, I am new to react and have used useState a little bit, but never have know what to do with a class. I am using this react package that has an example of how to use a controller to make you animation interactive built with flare now called rive. https://github.com/2d-inc/Flare-React#readme
What I want to achieve is to either run a different animation, or the same animation again when I hover over canvas element that is generated. I can create a second animation in flate(rive) that would still output in the same .flr file and I should then be able to reference it in the controller and run it on hover, just stuck on how to even do that part, or even get this controller to work. One thing to note is I can get the animation to run fine without the controller.
In the docs they have this example code
class PenguinController extends FlareComponent.Controller
{
constructor()
{
super();
this._MusicWalk = null;
this._Walk = null;
this._WalkTime = 0;
}
initialize(artboard)
{
this._MusicWalk = artboard.getAnimation("music_walk");
this._Walk = artboard.getAnimation("walk");
}
advance(artboard, elapsed)
{
// advance the walk time
this._WalkTime += elapsed;
const { _MusicWalk: musicWalk, _Walk: walk, _WalkTime: walkTime } = this;
// mix the two animations together by applying one and then the other (note that order matters).
walk.apply(walkTime % walk.duration, artboard, 1.0);
// if you want to slowly disable the head bobbing (musicWalk animation) you could ramp down the
// final argument (the mix argument) to 0.0 over some time. For now we're mixing at full strength.
musicWalk.apply(walkTime % musicWalk.duration, artboard, 1.0);
// keep rendering
return true;
}
}
First of all what is a constructor? what does super mean? then what are they defining in the constructor, is that some state, how do I determine what to define here?
For the initialized I assume I match it to the state above, and get the animation by the name I named it in flare(rive)
The advance part I don't really understand are we setting the animation with this._WalkTime += elapsed; to how long the animation runs? I think I understand the apply section, it is applying a duration to the animation.
Next it has this code
class MyComponent extends React.Component
{
constructor()
{
this.state = { penguinController: new PenguinController() };
}
render()
{
return <FlareComponent controller={this.state.penguinController} /*... more properties here ...*/ />;
}
}
Here is my attempted code currently I get the following error
TypeError: Cannot set property 'state' of undefined
import React from "react"
import PropTypes from "prop-types"
import { useStaticQuery, graphql } from "gatsby"
import FlareComponent from 'flare-react';
import styled from 'styled-components'
import Header from "./header"
import "../sass/index.scss"
const LogoWrapper = styled.div`
width:200px;
height:200px;
`
class AnimationController extends FlareComponent.Controller
{
constructor()
{
super();
this._MusicWalk = null;
}
initialize(artboard)
{
this._MusicWalk = artboard.getAnimation("Wispy Appear");
}
advance(artboard, elapsed)
{
const { _MusicWalk: musicWalk } = this;
musicWalk.apply(musicWalk.duration, artboard, 1.0);
// keep rendering
return true;
}
}
const Layout = ({ children }) => {
const data = useStaticQuery(graphql`
query SiteTitleQuery {
site {
siteMetadata {
title
}
}
}
`)
this.state = { AnimationController: new AnimationController() };
return (
<>
<LogoWrapper>
<FlareComponent width={200} height={200} animationName="Wispy Appear" controller={this.state.AnimationController} file="/wispy-2.flr"/>
</LogoWrapper>
<main className="main-wrapper">{children}</main>
<footer>
© {new Date().getFullYear()}, Built with
{` `}
Gatsby
</footer>
</>
)
}
Layout.propTypes = {
children: PropTypes.node.isRequired,
}
export default Layout
For reference there is a a tutorial on how to make an animation interactive but it's for flutter, but it has some insights into there api https://medium.com/rive/building-a-responsive-house-with-flare-flutter-31af823ba805
Update
Here is my new attempted code after trying to read up on classes in es6, I still need to learn more.
So how do I go about running a second animation on click or hover or any event. The animation runs once now, but I don't know whow to use the controller?
import React from "react"
import Img from 'gatsby-image'
import styled from "styled-components"
import FlareComponent from 'flare-react';
import Layout from "../components/layout"
const LogoWrapper = styled.div`
width:200px;
height:200px;
`
class wispyController extends FlareComponent.Controller
{
constructor()
{
super();
this._Animate = null;
}
initialize(artboard)
{
this._Animate = artboard.getAnimation("Wispy Appear");
}
advance(artboard, elapsed)
{
const { _Animate: animate } = this;
animate.apply(5, artboard, 1.0);
// keep rendering
return true;
}
}
class IndexPage extends React.Component {
constructor()
{
super();
this.state = { wispyController: new wispyController() };
}
render(){
const {data} = this.props;
return(
<Layout>
<LogoWrapper>
<FlareComponent width={200} height={200} animationName="Wispy Appear" controller={this.state.wispyController} file="/Wispy.flr"/>
</LogoWrapper>
{data.allSanityBlogPost.edges.map(({ node: post }) => (
<article key={post.id}>
<h1>{post.name}</h1>
<img src={post.imageGif.asset.url}/>
</article>
))}
</Layout>
)
}
}
export default IndexPage
export const query = graphql`
query IndexQuery {
allSanityBlogPost {
edges {
node {
name
id
imageGif {
asset {
url
}
}
}
}
}
}
`
Ok Hopefully someone can help here, I made a codesandbox so someone can see what I am trying to achieve. There are two animations, on the page you can see the first one which has a controller which should be mixing the two, and then the other two animations on there own. What I want to happen is the first animation run and then the second one to run on hover. HEre is the code sandbox https://codesandbox.io/s/frosty-water-jnj9m
Classes in the context of React can be thought of as classes in most Object Oriented Programming language, they are a blueprint to create an object from. In order to let the language know how and what to do when we create it, it needs a constructor method. This constructor method is calling a special method called super() so that it calls the constructor of the class it is extending from, in this case FlareComponent.Controller
The method advance will be called to add to the captured walk time that the class is keepi track of.
One of the problems is that you are trying to set the state of the component directly instead of using setState https://reactjs.org/docs/react-component.html
I would highly recommend brushing up on React basics before you continue with this, it will really help you get the proper foundation you need.
So I am creating my own site for my resume and have run up against an issue I'm not exactly sure how to solve. I am using React and Redux, the issue comes up that what I want is to display small little projects in a carousel type format using react with proper redux integrated in. Currently my file structure looks like:
src/
actions/
index.js
components/
App.js
NavBar.js
Projects.js
projects/
Project1/
Project2/
containers/
Main.js
reducers/
index.js
projects.js
I am not sure exactly how to accomplish what I want, I have searched for a good solution, but haven't really come across anything yet. I am still relatively new to react. What I don't want is to display one project, scroll down display the next, scroll some more... What I have tried is:
let components = [{TicTacToe}, {}];
let index = 0;
export default class Projects extends React.Component {
constructor(props) {
super(props);
this.state = {
component: components[0]
};
}
renderProject(i)
{
this.setState({component: components[i]});
}
backRotate()
{
index--;
if(index < 0)
{
index = components.length - 1;
}
this.renderProject(index);
}
forwardRotate()
{
index++;
if(index >= components.length)
{
index = 0;
}
this.renderProject(index);
}
render() {
return(
<div>
<button onClick='backRotate'>Back</button>
<div class='carousel-container'>
<this.state.component />
</div>
<button onClick='forwardRotate'>Next</button>
</div>
) }
}
I originally thought this would work, but it does break. I am currently running through App.js in my components folder adding the NavBar component and then adding the Main.js container. The reason for this container was the fact that I need a back and forward button to rotate through each project just like a carousel of images. I have considered adding all components and then just hiding and revealing, but this seems like a needless waste of resources and that there should be a better way to accomplish this.
Is there a way to replace a component on a button click? Completely remove the original component and add the new component in? If so, how do I also accomplish this using redux? Currently my projects reducer is just a place holder.
First of all, it breaks because the componentsarray contains objects, and React can't have that.
A valid React element is either a node (HTMLElement, React Component, text or number) or an array of nodes.
If you just want to keep React components in a collection and be able to select which is displayed, first you have to remove the braces in the components array, like so:
let components = [TicTacToe, AnotherComponent];
And you will also need to fix your backRotateand forwardRotate handlers. They can't be string like in html (note the braces instead of quotes):
<button onClick={this.backRotate}>Back</button>
<button onClick={this.forwardRotate}>Next</button>
...and must be bound to your component instance. A way to bind them is to use the Function.prototype.bind method. It should be done in the constructor:
constructor(props) {
super(props);
this.backRotate = this.backRotate.bind(this);
this.forwardRotate= this.forwardRotate.bind(this);
}
Without binding this would not be a reference to your component instance and calling this within the handlers would definitely fail.
TicTacToe and AnotherComponent must be classes extending React.Component or React.PureComponent, or must be pure functional components of props.
Also, its clearer and may be more supported to declare a capitalized variable containing the component:
render() {
const Comp = this.state.component;
return (
<div>
<button onClick={this.backRotate}>Back</button>
<div class='carousel-container'>
<Comp />
</div>
<button onClick={this.forwardRotate}>Next</button>
</div>
);
}
Finally, you should make index a state of your component (it's changed with the controls of the component) and components a prop (it does not change within the component).
Instead of setting the current component in state you will set the current component index:
import React from 'react':
import PropTypes from 'prop-types';
import { TicTacToe, AnotherComponent } from 'path/to/named/imports/TicTacToeAndAnotherComponent';
export default class Projects extends React.Component {
static propTypes = {
components: PropTypes.any,
}
static defaultProps = {
components: [TicTacToe, AnotherComponent],
}
constructor(props) {
super(props);
this.state = {
index: 0,
};
this.backRotate = this.backRotate.bind(this);
this.forwardRotate= this.forwardRotate.bind(this);
}
backRotate() {
const { components } = this.props;
const { index } = this.state;
if(index - 1 < 0) {
this.setState(() => ({ index: components.length - 1 }));
} else {
this.setState(() => ({ index: index - 1 }));
}
}
forwardRotate() {
const { components } = this.props;
const { index } = this.state;
if(index >= components.length) {
this.setState(() => ({ index: 0 }));
} else {
this.setState(() => ({ index: index + 1 }));
}
}
render() {
const Comp = this.props.components[this.state.index];
return (
<div>
<button onClick={this.backRotate}>Back</button>
<div class='carousel-container'>
<Comp />
</div>
<button onClick={this.forwardRotate}>Next</button>
</div>
);
}
}
Either way Redux won't help you with that.
The purpose of Redux is mainly to provide a single source of data (single source of truth), provide actions and reducers to mutate this data, and notify components registered to listen to store updates. Weither you connect your carousel to the store or not isn't really relevant to resolve your iisue.
I am trying to implement a search bar while using the styled-components library for styling. My issue is that the queried value never changes if I used styled-components. This is my code
import styled from 'styled-components'
import React, from 'react'
const SearchBar = styled.input`
margin-top: 35px;
float: right;
`
class Header extends React.Component {
state = {
query: '',
}
handleNewQuery = () => {
this.setState({
query: this.search.value,
})
console.log(this.search.value);
}
render () {
return (
<SearchBar
placeholder='Search for...'
ref={input => this.search = input}
onChange={this.handleNewQuery}
/>
)
}
}
Which only works if I swap SearchBar with input, otherwise the log prints undefined
The base issue is the the ref that is being created is returning a StyledComponent, not an HTML input element. It simply does not have a value property. The reason it starts working when you removing the styled aspect and simply render an <input />, is then the ref is an actual HTML input element with a value property. Try logging the ref in the change event to see this with first the styled component then a standard input. Either way I'd try approaching it as a Controlled Component using value property and event.target.value instead of attempting to extract the value from a ref.
import React, { Component } from 'react';
import styled from 'styled-components';
import './style.css';
const SearchBar = styled.input`
margin-top: 35px;
float: right;
`;
class Header extends Component {
constructor() {
super();
this.state = {
query: ''
};
}
handleNewQuery = (e) => {
this.setState({
query: e.target.value
})
}
render() {
return (
<div>
<SearchBar
placeholder='Search for...'
onChange={this.handleNewQuery}
value={this.state.query}
/>
</div>
);
}
}
If you absolutely must use a ref with this styled component. You can used the property innerRef which is specific to styled components to access the underlying HTML input element. This would technically give you access the value property. Once again though, the best approach would simply be using a controlled component as described above. The below example is using the newer approach to creating refs, but it would depend on your version of React being used.
<SearchBar
placeholder='Search for...'
onChange={this.handleNewQuery}
value={this.state.query}
innerRef={this.search}
/>
Here is a StackBlitz showing the functionality in action including the innerRef.
Hopefully that helps!
SearchBar should take a value prop instead of using a ref to get the value. Something like this:
<SearchBar value={this.state.search} ... />
I installed the rc-slider React Component on my React app, but I need to output the current values from the slider, how do I do that? This is the current code:
import React from 'react';
import 'rc-slider/assets/index.css';
import 'rc-tooltip/assets/bootstrap.css';
import Slider from 'rc-slider';
const createSliderWithTooltip = Slider.createSliderWithTooltip;
const Range = createSliderWithTooltip(Slider.Range);
export class RangeSlider extends React.Component {
render() {
return (
<div>
<Range min={0} max={10000} defaultValue={[800, 3000]} tipFormatter={value => `${value}€`} />
</div>
)
}
}
Also, how do I change the font-family from the tooltip where values are displayed when grabbing slider's handles?
You could store the slider values in state and use the onChange prop to update the slider values when they change.
The function given to tipFormatter can also return JSX as well as a string, so you can add a custom className and change font-family for that class.
Example
export class RangeSlider extends React.Component {
state = { sliderValues: [800, 3000] };
handleChange = sliderValues => {
this.setState({ sliderValues });
};
render() {
const { sliderValues } = this.state;
return (
<div>
{sliderValues[0]} - {sliderValues[1]}
<Range
min={0}
max={10000}
onChange={this.handleChange}
defaultValue={sliderValues}
tipFormatter={value => <span className="tooltip">{value}€</span>}
/>
</div>
);
}
}
For some reason, that last part of your code was not working for me as I was trying to get the range tooltip to animate correctly and display the correct data. I had to install an older version of rc-slider (8.7.1) in order to fix the tooltip animation that is currently broken at the time of this post. The code fix that got it all working correctly with the older version was:
...
tipFormatter={value => ${value}}
tipProps={{visible: true}}
...