Understanding React refs - why is my reference null here? - reactjs

I have a use-case where a React ref makes sense.
I've tried a few different ways of implementing them, and in this case integrating them with hammerjs.
I'm mostly going off of this question:
adding hammerjs to a react js component properly
My return method in my render is as such:
return (
<div className={"App card-row card-color " + this.props.className} ref={
(el) => this._slider = el
}>
{this.state.bubblemsg ? (
<NotifBubble message={this.state.bubblemsg} merchant={this.props.merchant.merchant}/>
) : (
null
)}
<ScrollMenu
data={this.state.list}
inertiaScrolling={true}
transition={.1}
inertiaScrollingSlowdown={.000001}
/>
</div>
);
Which I would think would attach my div element as a reference.
In my componentDidMount() method, I am then attaching it to hammer:
componentDidMount() {
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', console.log("swipe left"));
this.hammer.on('swiperight', console.log("swipe right"));
}
However, I am getting the error:
TypeError: Cannot read property 'addEventListener' of undefined
And this is directly related to Hammer, and thus the reference I assume.
So what am I doing wrong with my references? I don't totally understand how they're supposed to work and the React tutorial explanation wasn't super clear, so a thorough explanation would be useful.

I think the problem is that in the listening of the hammer you have to pass a function to be called, try inserting an arrow function to log
componentDidMount() {
this.hammer = Hammer(this._slider)
this.hammer.on('swipeleft', () => console.log("swipe left"));
this.hammer.on('swiperight', () => console.log("swipe right"));
}

Related

react.js TypeError: lights.map is not a function

So i am trying to map the data i get from this api, but it gives an error saying "TypeError: lights.map is not a function"
Here is the function which is called to make the call from the API:
function getLight(){
Axios.get('http://{IP}/api/{KEY}/lights').then((res) =>{
console.log(res.data)
setLights(res.data)
})
}
And here is my map:
<button onClick={getLight}>Lights 1</button>
<div>
{lights.map((light) =>{
return(
<li>{light.name}</li>
)
})}
</div>
You should be sure that lights will always be an array so you can use map function.
If you are not sure that lights will be an array you can use
<>
{lights?.map && lights.map((something)=><div>{something}</div>)}
</>
Or, if you do not have access to modern syntax
<>
{lights && lights.map && lights.map((something)=><div>{something}</div>)}
</>
So you will check if map function really exists in lights object(or it's prototype) prior to using it and if not - return undefined and React will not render that line of jsx (you can render some kind of loader animation while you not have an array, for example)
BUT I don't think it's good solution - you really need to check when your variable gets non-array value which cause an error.

React Testing Library - Unable to find the element with data-testid

I am following the docs for react-testing-library to find if the element with data-testid attribute is rendered or not.
The react-testing-library is not able to find the element even though it exists.
TEST
test('Renders step based on the active step', () => {
render(<RenderStep />, { initialState: { implOnboard: initialState } });
});
expect(screen.getByTestId('step-1')).toBeDefined(); // 👉 THROWS ERROR ❌
}
COMPONENT
// redux
const { activeStep } = useSelector((state) => state.implOnboard);
const renderStep = () => {
switch (activeStep) {
case 1:
console.log('Rendering this 🔥'); // 👈 THIS IS GETTING LOGGED TOO!
return (
<div data-testid="step-1">
<StepOne />
</div>
);
case 2:
return (
<div data-testid="step-2">
<StepTwo />
</div>
);
}
};
return <React.Fragment>{renderStep()}</React.Fragment>;
ERROR
TestingLibraryElementError: Unable to find an element by: [data-testid="step-1"]
Please use the queryByTestId instead getByTestId for the case. It will work.
The answer from Elena is wrong and just masks the error. Using queryByTestId will return null if the element is not found, instead of an error when using getByTestId, and the assertion will actually be:
expect(null).toBeDefined();
and this WILL pass. This is not testing anything. toBeDefined() fails only if the element is equal to undefined and it passes for everything else.
If OP wants to actually check if that element is NOT present they should do:
expect(screen.queryByTestId('step-1')).not.toBeInTheDocument();
The original error from OP is probably happening because the expect is outside the test block.
Adding to Elena's response, we need to use queryByTestId because, queryByTestId returns null value if element is not found instead of throwing error unlike getByTestId. Here's the image that explains when to use different methods.
Sometimes you get an error even when using this queryBy, so please check other code before this statement.
Reference - https://www.youtube.com/watch?v=Yghw9FkNGsc&list=PL4cUxeGkcC9gm4_-5UsNmLqMosM-dzuvQ&index=5&ab_channel=TheNetNinja
For those using React Native Web, you will need to use testID which will compile down to data-testid on the element. It needs to be on a react native element not your custom one (pass it through as a prop and put it on a View, etc.).

ReactJS: How to render a collection of objects

So I'm quite new on web development last couple of days. I come from c++ background and I can't wrap my head through all the principles of reactjs. I have 2 classes. The child class called JobAd should render some information that it got from props.
export default class JobAd extends Component {
constructor(props) {
super(props);
this.state ={
index: props.index,
id: props.jobId,
name: props.name,
description: props.description,
location: props.location,
adress: props.adress,
alreadyApplied: props.alreadyApplied,
open: false,
// toggleJob: props.toggleJob,
};
this.toggleJob = props.toggleJob;
}
render() {
return (
<div className={`${styles.jobAd} d-flex` + "job " + (this.state.open ? 'open': '')} key={this.state.index} onClick={() => this.toggleJob(this.state.index)}>
<div className={`${styles.jobTitle}`}>
{this.state.location} - {this.state.name}
</div>
<div className={`${styles.jobDetails}`}>
<div className={`${styles.jobDescription}`}> {this.state.description}</div>
<div className={`${styles.jobAdress}`}>{this.state.adress}</div>
<ApplyButton jobId= {this.props.id} alreadyApplied = {this.props.alreadyApplied}/>
</div>
</div>
)
}
}
The second class, queries a mongoDB db and creates jobAd objects populating them from the info gotten from db.
class JobExplorer extends React.Component
{
...
result.data.jobs.forEach(job => {
var find = job.employees.find(obj => obj === userId);
if (!(find === undefined)) {
alreadyApplied = true;
}
var toPush = new JobAd ({
index: i,
id:job._id,
description:job.description,
name:job.name,
location:job.locationName,
adress:job.locationAdress,
alreadyApplied:alreadyApplied,
open:false,
toggleJob: this.toggleJob.bind(this)
});
jobList2.push(toPush);
console.log("look");
console.log(jobList2)
});
this.setState({
jobList: jobList2
})
this.setState({
error: null,
jobs: result.data.jobs
});
...
render()
{
console.log("look2");
console.log(this.state.jobList);
return (
<div><Navigation />
{this.state.jobList}
</div>
);
}
But I am faced with the following error which I cannot find a fix for.
Error: Objects are not valid as a React child (found: object with keys {props, context, refs, updater, state, toggleJob}). If you meant to render a collection of children, use an array instead.
How should I instantiate those objects so I could render them using the "architecture" I wrote. Is there a fundamental flaw that I have in my classes?
The below snippet doesn't work because new will return an object (this) not the react component.
So, instead of
var toPush = new JobAd({
index: i,
id: job._id,
...
});
jobList2.push(toPush);
you can do this
var toPush = <JobAd
index={i}
id={job._id}
...
/>;
The above snippet works because <JobAd ... /> is converted to React.createElement(JobAd, ... ). However, you still shouldn't do it like this. since there are a lot of better ways to do this. one of them is:
save just the data in joblist and then render the data list on JobAd component
like below:-
render(){
return this.state.joblist.map((job, i) => (
<JobAd
key={job._id}
index={i}
...
/>
));
}
The key is a really important thing. Read about it: https://reactjs.org/docs/lists-and-keys.html
Things that could be improved:-
Don't copy props in the state as you are doing in JobAd class instead directly render the props.
Don't call setState twice as in JobExplorer. you could set all the keys in
setState at the same time. since that would render the component twice.
Suggestions:-
You should avoid using var as that might cause some issues here.
since, you are just a starter, try using functional component first. they are
quite easier to grasp
You seem to have a misconception about state/props in React and web development. It's very normal; I learned python and Java first and many tutorials seem to assume that people just know this already.
"State" in generally refers to variables containing/referring to values that can change without a page refresh in your application. If you know a value is not going to change, it does not need to be held in state. Storing it in a normal variable is exactly what you should do.
"Props" is just another word for arguments that are passed to React components. There's more to it in reality, but as a beginner, that's all you need to really know for now.
So in your job add, things like name, address, jobs, description shouldn't go in state because they aren't going to change as a result of user interaction or for any other reason, unless the underlying data they are loaded from changes, but then that wouldn't be handled by React but instead by the API that your app gets data from. They should just be rendered, so refer to them like this.props.address in your render method. The value for open, however, need to be in state, because that definitely can change.
As for the error, it looks like you are not calling JobAd correctly. You need to use the syntax <Job Ad/> rather than new JobAd...that won't work in React.
I would recommend doing a tutorial to get the basics down.

Updating the state of an array on React

I am having problems with the response data I get from an API. I use axios to get the JSON object, and the state should be replaced by the list of objects every time the API is called.
Here is the code.
https://i.stack.imgur.com/yHXn0.png
And here is the console
https://i.stack.imgur.com/KnHD2.png
I imagine I am updating the state wrong, but I cant see what it is.
Your error message is pretty clear.
Cannot read property 'push' of undefined
What does that mean? it means you called the method push on a variable that is still undefined
let paineis = empresas.map( (empresa, i) => {
paineis.push(<Painel key={i} nome={empresa.nome} usuarios={empresa.usuarios} />)
// ^-----^
// this is the issue. paineis has not yet been assigned a value!
})
The real issue here is you are using map incorrectly. the return value in a map is the way to push a new value. You want to do this instead
let paineis = empresas.map( (empresa, i) => <Painel key={i} nome={empresa.nome} usuarios={empresa.usuarios} />)
this is an ES6 shorthand for a return, also could be written
let paineis = empresas.map( (empresa, i) => {
return <Painel key={i} nome={empresa.nome} usuarios={empresa.usuarios} />
})

Can't bind anything: "Cannot call method "bind" of undefined"

import React from 'react'
const Buttons = React.createClass({
getInitialState() {
return {clicks: 0}
},
countClicks() {
this.setState({
clicks: ++this.state.clicks
})
console.log(this.state.clicks)
}
render() {
return (
<div>
<div onClick={console.log("HEYYY")}>This should log "HEYYY"</div>
<div onClick=({this.countClicks()}>This should update clicks</div>
)
}
})
This will print "HEYYY" and 1 respectively on the first render but never do anything when actually clicked.
While this gave me some headache I found the solution in an example:
//Instead of doing this
<a onClick={this._changeValue(2)}>Change Value</a>
//Do this
<a onClick={this._changeValue.bind(2)}>Change Value</a>
Ah, perfect, I thought. Except that when I try it I get an error:
TypeError: Cannot call method 'bind' of undefined
I removed the simple console.log because I have no idea what to bind in it and went with countClicks() only, and I did get it somewhat working when I removed the parenthesis like so:
<div onClick=({this.countClicks.bind(this)})>This should update clicks</div>
First of all, why can't I bind something when I have the parenthesis in? Second, I instead got an ugly WARNING:
Warning: bind(): You are binding a component method to the component.
React does this for you automatically in a high-performance way, so
you can safely remove this call.
Is React stupid?!
Or am I..?
My own solution:
render() {
const self = this
<div onClick={function() { self.countClicks() }}>This should update clicks</div>
}
No use of bind, no errors, no warnings.

Resources