Refactoring for Redux/React - javascript

I've been working on a small project for the last of months now involving using functional components and hooks to make an API call and extract some data and do something things with said data. I started with have 1 file that held 3 components in it and was based on classes. I moved everything to functional components and slowly started to build more files. Now, I'm on to my final part of learning React on the side and that comes with Redux. I was wondering if anyone could give me some advice on where to start refactoring my code for Redux and any useful tools out there that may help me along the way. At its core this is a small project but I want to keep the code evolving much like I've evolved through out this.
Below is a component and a .services that handles the API call. This is basically the whole project in a nutshell.
HeroDetails.js
import React, {useState, useEffect} from "react"
import "../App.css"
import {fetchHeroDetail} from './services/HeroDetail.services'
import { useParams } from 'react-router-dom'
function HeroDetail() {
const { id } = useParams()
const [hero, setHero] = useState({})
useEffect(() => {
fetchHeroDetail(id).then(hero => {
setHero(hero);
});
}, [id]);
return(
<div>
<h1>{hero.localized_name} </h1>
<h2>{hero.move_speed}</h2>
<h2>{hero.base_health}</h2>
</div>
)
}
export default HeroDetail
HeroDetail.services.js
import React from 'react'
export const fetchHeroDetail = async (id) => {
const data = await fetch(`https://api.opendota.com/api/heroStats`)
console.log(data)
const heroDetails = await data.json()
console.log(heroDetails)
console.log(heroDetails.find(heroDetail => heroDetail.id === +id))
return heroDetails.find(heroDetail => heroDetail.id === +id)
};

Related

How can I retrieve stuff from an useState declared variable in a component within another component scope

I want to get the investments variable within the scope of another component...
I have written the code down-bellow which is ok, but how can I get this const investment since I already exported the function InvestmentsList? How can that be done without resorting to useSelector() from react-redux?
import React, { useEffect, useState } from 'react';
import {
Card,CardBody,CardHeader,CardTitle,Col,Row,Table} from 'reactstrap';
import moment from 'moment';
const InvestmentsList = () => {
const [investment, setInvestment] = useState([]);
async function fetchInvestments() {
const response = await fetch('http://localhost:3000/products');
const investments = await response.json();
// waits until the request completes...
setInvestment(investments);
}
useEffect(() => {
fetchInvestments();
}, []);
return (
<>{/* ...Returns something */}</>)
export default InvestmentsList;
You can't access data of another component.
If two components are not to far from each other, you can also store the data in a parent component and provide "investment" as props to both components.
Also, Redux, React Context API or RecoilJS will allow you to store data of variable "investment" somewhere outside the component.

Failing to use document object inside of react component. "document is not defined" error

I've got some browser sniffing code that I'm using inside of a react component
import React, { useState } from 'react'
const SomeComponent = ({props}) => {
const isIE = document.documentMode;
return (
<div>
Some browser sniffing code here.
</div>
)
}
I'm getting this error on build, though,
"document is not defined"
It also fails when I try it like this
import React, { useState } from 'react'
const isIE = document.documentMode;
const SomeComponent = ({props}) => {
console.log(isIe)
return (
<div>
Some browser sniffing code here.
</div>
)
}
I'm running from a create-react-library component in a next server.
What's the best way to access the document object inside of react?
I've seen some browser sniffing react code here but it didn't work cause I got the errors on build Browser Detection in ReactJS
When you are using Next.js, you should be aware that some of your code will be run in the server-side where window, document and other browser specific APIs will be not available.
Since useEffect hook only runs in the client-side, you can use it together with useState to achieve your goal:
import React, { useState, useEffect } from "react";
const SomeComponent = ({ props }) => {
const [isIE, setIsIE] = useState(false);
useEffect(() => {
setIsIE(document.documentMode);
}, []);
if (!isIE) return null;
return <div>Some browser sniffing code here.</div>;
};
Next.js is a SSR framework, your code will run both on server-side and client side. Window would be not defined when running on server-side. If you want to get property from window, you could try these way.
Get the window when you need
if (typeof window !== 'undefined') {
console.log(window.document)
}
In some specific situation, you need to run your component only on client side. In that case, you could using dynamic.
components/ClientSideComponent
const ClientSideComponent = props => {
// You could get window directly in this component
console.log(window.document)
return (
<div>
run your code only on client side
</div>
)
}
pages/index
import dynamic from 'next/dynamic';
const ClientSideComponent = dynamic(() => import('../components/ClientSideComponent'), {
ssr: false,
});
const HomePage = props => {
return <ClientSideComponent />
}

Tests React component with jest and enzyme

i have components presented below. I am totally new in unit testing. Can anyone give any one give me advice how and what should I test in this component? I was trying to shallow render it, to check is text in h2 is present but i still getting errors.
import React, { useEffect } from 'react';
import { Form, Field } from 'react-final-form';
import { useHistory, Link } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';
import { loginUser, clearErrorMessage } from '../../redux/auth/authActions';
import Input from '../Input/Input';
import ROUTES from '../../routes/routes';
import './LoginForm.scss';
const LoginForm = () => {
const dispatch = useDispatch();
const history = useHistory();
const { loading, isLogged, errorMessage } = useSelector(state => state.auth);
useEffect(() => {
if (isLogged) {
history.push('/');
}
return () => {
dispatch(clearErrorMessage());
};
}, [dispatch, history, isLogged]);
const handleSubmitLoginForm = values => {
if (!loading) {
dispatch(loginUser(values));
}
};
const validate = ({ password }) => {
const errors = {};
if (!password) {
errors.password = 'Enter password!';
}
return errors;
};
return (
<article className="login-form-wrapper">
<h2>SIGN IN</h2>
<Form onSubmit={handleSubmitLoginForm} validate={validate}>
{({ handleSubmit }) => (
<form onSubmit={handleSubmit} autoComplete="off" className="login-form">
<div className="login-form__field">
<Field name="email" component={Input} type="email" label="E-mail" />
</div>
<div className="login-form__buttons">
<button type="submit" className={loading ? 'button-disabled' : ''}>
Sign in
</button>
</div>
</form>
)}
</Form>
</article>
);
};
export default LoginForm;
I am open for any advices :)
First of all, I am not recommending using shallow in your tests and here is a great article why.
I also recommend to check out react-testing-library instead of Enzyme as it is much nicer to use.
Now, to answer your question. You are using here hooks for redux and react-router, so you need to provide the necessary data to your componenent in test so that it can use those hooks. Let me show you an example test (that checks text in h2 element):
import React from 'react';
import { mount } from 'enzyme';
import {Provider} from 'react-redux';
import {MemoryRouter, Route} from 'react-router';
import LoginForm from './LoginForm';
describe('Login Form', () => {
it('should have SIGN IN header', () => {
const store = createStore();
const component = mount(
<Provider store={store}>
<MemoryRouter initialEntries={['/login']}>
<Route path="/:botId" component={LoginForm} />
</MemoryRouter>
</Provider>
)
expect(component.find('h2').text()).toEqual('SIGN IN');
});
});
Some explanation to this example.
I am using mount instead of shallow as I prefer to render as much as possible in my test, so that I can check if everything works together as it should.
You can see that I am not rendering my component directly, but rather it is wrapped with other components (Provider from react-redux and MemoryRouter from react-router). Why? Because I need to provide context to my Component. In this case it's redux and router context so that the data used inside exists and can be found (for example useSelector(state => state.auth) must have some state provided so that it can access auth property). If you remove any of them you would get some error saying that this context is missing - go ahead and check for yourself :).
See here for some details around testing with router context
As for testing with redux in my example there is a createStore function that I didn't define as there are a few approaches to this. One involves creating a real store that you use in your production application. This is the one that I prefer and colleague of mine wrote great article around this topic here. Other is to create some kind of mock store, like in this article. Again, I prefer the first approach, but whichever is better for you.
Answering your other question on what should you test in this example. There are multiple possibilities. It all depends mostly on you business case, but examples that I would test here includes:
Typing something into an input, clicking a button and observing that login is successful (by redirecting to new path - / in this case)
not typing a password and clicking a button - error should be shown
Checking if button class changes when it's loading
Do not dispatch login action twice, when already loading
And so on...
That's really just a tip of an iceberg on what could be written around testing, but I hope it helps and gives you a nice start to dig deeper into the topic.

React Context API with multiple values performance

I'm using the React Context API to store many global state values (around 10 and probably more will be needed) and many components are using them. Unfortunately whenever any of the values change, all components using the useContext hook have to rerender. My current solution is to use useMemo for the return value of the components and useCallback for any complex functions and inside custom hooks I have. This addresses most of my performance concerns, but having to use the useMemo and useCallback all the time is quite annoying and missing one is quite easy. Is there a more professional way to do it?
Here's an example based on my code:
GlobalStateContext.js
import React, { useState } from 'react'
const GlobalStateContext = React.createContext({ })
export const GlobalStateProvider = ({ children }) => {
const [config, setConfig] = useState({
projectInfo: ''
})
const [projectFile, setProjectFile] = useState('./test.cpp')
const [executionState, setExecutionState] = useState("NoProject")
return (
<GlobalStateContext.Provider
value={{
executionState,
config,
projectFile,
setExecutionState,
setConfig,
setProjectFile,
}}
>
{children}
</GlobalStateContext.Provider>
)
}
export default GlobalStateContext
Example.jsx
import React, { useContext } from 'react'
import GlobalStateContext from '../utils/GlobalStateContext.js'
export default Example = () => {
const {
executionState,
setExecutionState,
} = useContext(GlobalStateContext)
return useMemo(
() => (
<div>
The current execution state is: {executionState}
<br />
<button onClick={() => setExecutionState('Running')}>Running</button>
<button onClick={() => setExecutionState('Stopped')}>Stopped</button>
<button onClick={() => setExecutionState('Crashed')}>Crashed</button>
</div>
),
[
executionState,
setExecutionState,
]
)
}
Currently, this problem is unavoidable with context. There is an open RFC for context selectors to solve this, but in the meantime, some workarounds are useContextSelector and Redux, both of which prevent a subscribing component from rendering if the data it's reading did not change.

React: store uploaded array into global variable?

I'm currently working on using React to upload a CSV file and convert the data to an array so I can access phone numbers. I've actually got it almost completely functional, with just one problem: I can't figure out how to store the array properly in a variable (dataDump) on the global level. It stores it inside another array.
Here's a picture of my console so you can see what I mean.
I'm able to access the contents of dataDump if I use dataDump[0] (as seen in the function for handleClick), but that won't work for a global variable. I need to be able to send the array's values to other components/files, so I don't think having to call it like that will work. Chances are I'm over-complicating this in my head and the answer is incredibly simple, but I've spent the past 2-3 weeks learning React, Twilio, Mongodb etc. from scratch so my brain's not cooperating.
I'll appreciate any help! Thanks! Code below. (Note this is a component that's imported to the App page.)
import React from "react";
import CSVReader from "react-csv-reader";
var dataDump = [];
console.log(dataDump);
const papaparseOptions = {
header: true,
dynamicTyping: true,
skipEmptyLines: true,
transformHeader: header => header.toLowerCase().replace(/\W/g, "_"),
complete: function(results) {
dataDump.push(results.data);
console.log(dataDump);
var rows = results.data;
let numbers = rows.map(a => a.phone_number); //make the results ONLY the phone numbers
// console.log(numbers);
document.getElementById("data2").innerHTML=numbers; //display the phone numbers
}
};
class Import extends React.Component {
constructor(props) {
super(props);
this.state = {data:[]};
this.handleClick = this.handleClick.bind(this);
}
handleForce = data => {
// console.log(data.length);
console.log(data);
this.setState({data: data});
};
handleClick = () => {
console.log("success");
console.log(this.state.data);
console.log("Next is Numbies:");
let numbies = dataDump[0].map(a => a.phone_number);
console.log(numbies);
document.getElementById("data").innerHTML=numbies;
}
render() {
return (
<div className="container">
<CSVReader
className="csv-input"
label="Select CSV file to import"
onFileLoaded={this.handleForce}
parserOptions={papaparseOptions}
/>
<div>
</div>
<button onClick={this.handleClick.bind(this)}>
Test
</button>
<div id="data" />
<div id="data2" />
<div id="data3">
</div>
</div>
);
}
}
export default Import;
// export default DataController;
Under the hood React-Redux is using context and hooks these days, so don't bother implementing a Redux stack until you've outgrown the simpler, React API, or at least you've fixed your issue. Folks joke that Redux is like shooting a fly with a bazooka. More info on React-Redux internals here and here's the documentation for React's Context.
Some psuedo-code to get you on the right path:
// context.js
import { createContext } from 'react';
export const Store = createContext();
// app.js
import React from 'react';
import { Store } from './context';
import Import from './import'; // I wouldn't change the casing on or reuse a reserved keyword personally, maybe calling this something like 'CsvImporter' would be an improvement
function App() {
const [dataDump, setDataDump] = React.useState([]);
return (
<Store.Provider value={{ dataDump, setDataDump }}>
<Import dataDump={dataDump} setDataDump={setDataDump} />
</Store.Provider>
);
}
Now your import component has two new props, dataDump and setDataDump. You can call setDataDump just like any other call to setting state. Nice!
So you need the dataDump in a new component? That's easy peasy, lemon squeezy, and all without global variables or tossing module scoping to the side:
// foobar.js
import React from 'react';
import { Store } from './context';
export function Foobar() {
// you probably want to do more than force render an array as a string, but this is just a proof of concept
return (
<Store.Consumer>
{({ dataDump, setDataDump }) => (
<p>
`${dataDump}`
</p>
)}
</Store.Consumer>
);
}
Just make sure that Foobar or other components are rendered as children of the Provider in app.js and now you have a 'global' context for passing around dataDumps.

Categories

Resources