Where's the right place to put code that interacts with the DOM in a gatsby site? I want to toggle the visibility of some components by adding/removing a class when another element is clicked.
The gatsby-browser.js file seems like it should contain this code but the API doesn't seem to call any of the functions after the DOM has loaded.
Similarly, using Helmet calls it too soon. Using window.onload never seems to trigger at all regardless of where it's included.
window.onload = function () {
// add event listener to the toggle control
}
Is there an event I can use to run my code when the DOM is ready?
Do you really need to wait for the DOM to be ready? When working in react you need to change the way you think about these things. For example you could add an on click that changes state and then reflect the state change in your classname prop.
Code Example:
import React, { useState } from "react"
const MyApp = () => {
const [visible, setVisible] = useState(true) // true is the initial state
return (
<div>
<div className={visible ? "visible-class" : "hidden-class"}>
My content
</div>
<button onClick={() => setVisible(!visible)}>Click me!</button>
</div>
)
}
export default MyApp
Or you could take it a step further and not even render that content to the DOM until you want to.
Example:
import React, { useState } from "react"
const MyApp = () => {
const [visible, setVisible] = useState(true) // true is the inital state
return (
<div>
<button onClick={() => setVisible(!visible)}>Click me!</button>
{visible && <div>My content here</div>}
</div>
)
}
export default MyApp
You can use the React cyclelife with componentDidMount().
This need to update your component like that :
import React from 'react'
class YourComponent extends React.Component {
componentDidMount() {
// Your Javascript function here
}
render() {
return(
<div className="YourComponentHere"></div>
)
}
}
export default YourComponent
Hope that help you!
If your component is a functional component, try using React Hook useEffect, which will guarantee the execution after the component is rendered.
import React, { useEffect } from 'react'
const MyComponent = () => {
useEffect(() => {
console.log("Document loaded");
});
return (
<main>
<text>Pretty much the component's body code around here</text>
</main>
)
}
export default MyComponent
Related
I am trying to trigger a start function in a different componentB when I click the start button in componentA
Note: Both components are neither parent to child components
Component A
import React from "react"
function ComponentA(props) {
return (
<div>
<button>Start</button>
</div>
)
}
export default ComponentA;
Component B
import React from "react";
function ComponentB(props) {
const [isStarted, setStarted] = React.useState(false);
const start = () => setStarted(true);
return <div>{isStarted ? "Starting..." : "Not Starting.."}</div>;
}
export default ComponentB;
One way you could do it is by creating a callback prop on ComponentA, changing the state of the parent component of ComponentA and passing it to ComponentB via a prop and capture that prop change with a useEffect.
Example:
Parent
function Parent(){
const [started, setStarted] = useState(false)
return(
<div>
<ComponentA onClick={() => setStarted(true)}/>
<ComponentB started={started}/>
</div>
)
}
ComponentA
function ComponentA({onClick}){
return(
<div>
<button onClick={() => onClick()}/>
</div>
)
}
ComponentB
function ComponentB({started}) {
const [isStarted, setStarted] = React.useState(started);
useEffect(() => {
setStarted(started)
}, [started])
return <div>{isStarted ? "Starting..." : "Not Starting.."}</div>;
}
Another way would be using useContext:
https://reactjs.org/docs/hooks-reference.html#usecontext
https://reactjs.org/docs/context.html
Honestly, I am a bit lazy to also include an example which is in my opinion worse. Here is an example that uses useContext that might be useful.
https://stackoverflow.com/a/54738889/7491597
I've been studying react and developing an app, but i got a problem using context. In one component I create the context and provide its value, but when I try to use the current value of context in another component, I have the default value. Code:
Component One:
export const OwnerInformationContext = React.createContext({})
function NameChoose() {
...
const [ownerInformation,setOwnerInformation] = useState({})
function onpressSubmitButton(e : FormEvent) {
e.preventDefault();
...
setOwnerInformation({name:'name',roomId:'id',owner:'true'})
}
return(
<div className="page-container">
<OwnerInformationContext.Provider value={ownerInformation} />
...
<form onSubmit={onpressSubmitButton}>
...
</form>
...
);
}
export default NameChoose;
So when i try to use by:
import { OwnerInformationContext } from '../NameChoose/index'
function ComponentTwo(){
const consumeOwnerContext = useContext(OwnerInformationContext)
useEffect(() => {
console.log(consumeOwnerContext)
}, [])
return <h1>test</h1>
}
I got the default value provide in component one, that's {}.
It looks like your context provider is not actually wrapping any components, as it has a self-closing tag:
<OwnerInformationContext.Provider value={ownerInformation} />
It should be:
<OwnerInformationContext.Provider value={ownerInformation}>
{/* Your child components here will have access to the context */}
</OwnerInformationContext.Provider>
You are using useEffect as ComponentDidMount meaning only at start(mount) the value will be console log.
you should give consumeOwnerContext as a dependency to useEffect like this
useEffect(()=>{
console.log(consumeOwnerContext);
},[consumeOwnerContext]);
And rename consumeOwnerContext to consumeOwnerValue, because you are getting the value out of the context using useContext.
After that when you will click on submit button you should have ComponentTwo console log it.
import React, { useState, useEffect, useContext } from "react";
export const OwnerInformationContext = React.createContext({});
function ComponentTwo() {
const consumeOwnerContext = useContext(OwnerInformationContext);
useEffect(() => {
// You are using consumeOwnerContext inside useEffect, in that case add
// it as dependency if you want to see the updated consumeOwnerContext value
console.log(consumeOwnerContext);
}, [consumeOwnerContext]);
return <div>test</div>;
};
function NameChoose() {
const [ownerInformation, setOwnerInformation] = useState({});
function onpressSubmitButton(e) {
e.preventDefault();
setOwnerInformation({ name: "name",roomId: "id",owner: "true",});
}
return (
// The 'OwnerInformationContext.Provider' has to wrap the component
// that will use its context value. In your case, ComponentTwo
// has to be a child of NameChoose.
<OwnerInformationContext.Provider value={ownerInformation}>
<div className="page-container">
<form onSubmit={onpressSubmitButton}>
<button type="submit">Submit</button>
</form>
</div>
<ComponentTwo />
</OwnerInformationContext.Provider>
);
}
export default NameChoose;
I know this is horrible convention, but I'm trying to quickly conditionally render screens in my React Native app with global variables (so no redux):
App.js:
if (global.clickStatus !== 'clicked') {
return <Screen1 />;
}
return <Screen2 />;
The app begins on Screen1, where there is a button that makes global.clickStatus = 'clicked'. When this is clicked, I want Screen2 to render. The problem is, the global.clickStatus doesn't seem to update on my App.js (even though global.clickStatus is changed, it still renders Screen1.
How can I get it to update?
I believe in <App /> component because it is a function component you can introduce a state if your button is clicked. Then with clicked state you can manipulate which component to show.
Similarly like the following - obviously this is a simplified example:
const App = () => {
const [clicked, setClicked] = useState(false);
return <>
<div onClick={() => setClicked(true)}>Click me</div>
{ clicked ? <Screen2 /> : <Screen1 /> }
</>
}
Suggested read is Using the State Hook.
The app begins on Screen1, where there is a button that makes global.clickStatus = 'clicked'
When you click the button, you did not set any state for App.js component => no re-render action is made.
I just assume the button is in Screen 1. Try code below:
import React from "react";
import "./styles.css";
export default function App() {
// Create a state
const [renderIndex, setRenderIndex] = useState(new Date().getTime())
if (global.clickStatus !== 'clicked') {
// Assume you have a button in Screen1
// Pass a callback function from this component to Screen1
// When button in Screen1 is clicked, call this callback function to update renderIndex => App component will re-render
return <Screen1 callBack={() => setRenderIndex(new Date().getTime())}/>;
}
return <Screen2 />;
}
Working with an array of mapped items, I am attempting to toggle class in a child component, but state change in the parent component is not passed down to the child component.
I've tried a couple different approaches (using {this.personSelectedHandler} vs. {() => {this.personSelectedHandler()} in the clicked attribute, but neither toggled class successfully. The only class toggling I'm able to do affects ALL array items rendered on the page, so there's clearly something wrong with my binding.
People.js
import React, { Component } from 'react';
import Strapi from 'strapi-sdk-javascript/build/main';
import Person from '../../components/Person/Person';
import classes from './People.module.scss';
const strapi = new Strapi('http://localhost:1337');
class People extends Component {
state = {
associates: [],
show: false
};
async componentDidMount() {
try {
const associates = await strapi.getEntries('associates');
this.setState({ associates });
}
catch (err) {
console.log(err);
}
}
personSelectedHandler = () => {
const currentState = this.state.show;
this.setState({
show: !currentState
});
};
render() {
return (
<div className={classes.People}>
{this.state.associates.map(associate => (
<Person
name={associate.name}
key={associate.id}
clicked={() => this.personSelectedHandler()} />
))}
</div>
);
}
}
export default People;
Person.js
import React from 'react';
import classes from './Person.module.scss';
const baseUrl = 'http://localhost:1337';
const person = (props) => {
let attachedClasses = [classes.Person];
if (props.show) attachedClasses = [classes.Person, classes.Active];
return (
<div className={attachedClasses.join(' ')} onClick={props.clicked}>
<img src={baseUrl + props.photo.url} alt={props.photo.name} />
<p>{props.name}</p>
</div>
);
};
export default person;
(Using React 16.5.0)
First of all, in your People.js component, change your person component to:
<Person
name={associate.name}
key={associate.id}
clicked={this.personSelectedHandler}
show={this.state.show}}/>
You were not passing the prop show and also referring to a method inside the parent class is done this way. What #Shawn suggested, because of which all classes were toggled is happening because of Event bubbling.
In your child component Person.js, if you change your onClick to :
onClick={() => props.clicked()}
The parenthesis after props.clicked executes the function there. So, in your personSelectedHandler function, you either have to use event.preventDefault() in which case, you also have to pass event like this:
onClick={(event) => props.clicked}
and that should solve all your problems.
Here's a minimal sandbox for this solution:
CodeSandBox.io
I'm trying to test a simple component that take some props (it have no state, or redux connection) with Enzyme, it works for the plain elements like <div /> and so on, but when i try to test if the element rendered by the child component exists, it fails.
I'm trying to use mount but it spit me a lot of errors, i'm new in this so, here is my code:
import React, { Component } from 'react';
import WordCloud from 'react-d3-cloud';
class PredictWordCloud extends Component {
render() {
const fontSizeMapper = word => Math.log2(word.value) * 3.3;
const { size, data, show } = this.props;
if (!show)
return <h3 className='text-muted text-center'>No data</h3>
return (
<section id='predict-word-cloud'>
<div className='text-center'>
<WordCloud
data={data}
fontSizeMapper={fontSizeMapper}
width={size}
height={300} />
</div>
</section>
)
}
}
export default PredictWordCloud;
It's just a wrapper for <WordCloud />, and it just recieves 3 props directly from his parent: <PredictWordCloud data={wordcloud} size={cloudSize} show={wordcloud ? true : false} />, anything else.
The tests is very very simple for now:
import React from 'react';
import { shallow } from 'enzyme';
import PredictWordCloud from '../../components/PredictWordCloud.component';
import cloudData from '../../helpers/cloudData.json';
describe('<PredictWordCloud />', () => {
let wrapper;
beforeEach(() => {
wrapper = shallow(<PredictWordCloud data={cloudData} size={600} show={true} />)
});
it('Render without problems', () => {
const selector = wrapper.find('#predict-word-cloud');
expect(selector.exists()).toBeTruthy();
});
});
For now it pass but if we change the selector to: const selector = wrapper.find('#predict-word-cloud svg'); where the svg tag is the return of <Wordcloud /> component, the tests fails because the assertion returns false.
I tried to use mount instead of shallow, exactly the same test, but i get a big error fomr react-d3-cloud:
PredictWordCloud Render without problems TypeError: Cannot read property 'getImageData' of null.
This is specially weird because it just happens in the test environment, the UI and all behaviors works perfectly in the browser.
You can find your component directly by Component name.
Then you can use find inside your sub-component as well.
e.g
it('Render without problems', () => {
const selector = wrapper.find('WordCloud').first();
expect(selector.find('svg')).to.have.length(1);
});
or
You can compare generated html structure as well via
it('Render without problems', () => {
const selector = wrapper.find('WordCloud').first();
expect(selector.html()).to.equal('<svg> Just an example </svg>');
});