Sharing helper functions globally across the whole React app - javascript

How would one go about making global functions in React that you only have to import into one file that shares it across every page?
Right now I can have to import helper.tsx into each file where I want to use them in.
For example I have a helper.tsx file that exports functions as such:
interface cookieProps {
name: string;
value: string;
exdays: number;
}
export const getCookie = ({ name }: cookieProps) => {
var i,
x,
y,
ARRcookies = document.cookie.split(";");
for (i = 0; i < ARRcookies.length; i++) {
x = ARRcookies[i].substr(0, ARRcookies[i].indexOf("="));
y = ARRcookies[i].substr(ARRcookies[i].indexOf("=") + 1);
x = x.replace(/^\s+|\s+$/g, "");
if (x == name) {
return unescape(y);
}
}
return "";
};
Now were I do import it into my App.tsx file like this:
import React, { useEffect } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Home from "./pages/Home";
import { createTheme, ThemeProvider } from "#mui/material/styles";
import Navbar from "./components/Navbar";
import Footer from "./components/Footer";
import Cards from "./pages/Cards";
import { getCookie } from "./controllers/helpers";
const theme = createTheme({
palette: {
primary: {
main: "#fff",
},
secondary: {
main: "#f8f8f8",
},
})
export default function App() {
return (
<ThemeProvider theme={theme}>
<BrowserRouter>
<Navbar />
<Routes>
<Route path="/home" element={<Home />} />
<Route path="/cards" element={<Cards />} />
<Route path="*" element={<Home />} />
</Routes>
<Footer />
</BrowserRouter>
</ThemeProvider>
);
}
I cannot call it from Home or Cards etc. When declared cookie imports are not initialised or used inside App.tsx I cannot access them. I want to be able to call all of those helper functions from every page possible and not have to import them again.
Should I make the imports into vars and call them as helper.getCookie?

Bind that function into window, like window.getCookie = getCookie, or add to a helper object like window.helper = {getCookie() {...}}
p/s: I am using this method, not sure if that is a right way to do or if it affects whole website performance, but no problems so far

Related

How can you wrap a story with a react-router layout route?

I have a very simple and plain ComponentX that renders some styled HTML, no data fetching or even routing needed. It has a single, simple story. ComponentX is meant to be used in a dark-themed website, so it assumes that it will inherit color: white; and other such styles. This is crucial to rendering ComponentX correctly. I won't bore you with the code for ComponentX.
Those contextual styles, such as background-color: black; and color: white;, are applied to the <body> by the GlobalStyles component. GlobalStyles uses the css-in-js library Emotion to apply styles to the document.
import { Global } from '#emotion/react';
export const GlobalStyles = () => (
<>
<Global styles={{ body: { backgroundColor: 'black' } }} />
<Outlet />
</>
);
As you can see, this component does not accept children, but rather is meant to be used as a layout route, so it renders an <Outlet />. I expect the application to render a Route tree like the below, using a layout route indicated by the (1)
<Router>
<Routes>
<Route element={<GlobalStyles/>} > <== (1)
<Route path="login">
<Route index element={<Login />} />
<Route path="multifactor" element={<Mfa />} />
</Route>
Not pictured: the <Login> and <Mfa> pages call ComponentX.
And this works!
The problem is with the Stories. If I render a plain story with ComponentX, it will be hard to see because it expects all of those styles on <body> to be present. The obvious solution is to create a decorator that wraps each story with this <Route element={<GlobalStyles/>} >. How can this be accomplished? Here's my working-but-not-as-intended component-x.stories.tsx:
import React from 'react';
import ComponentX from './ComponentX';
export default {
component: ComponentX,
title: 'Component X',
};
const Template = args => <ComponentX {...args} />;
export const Default = Template.bind({});
Default.args = {};
Default.decorators = [
(story) => <div style={{ padding: '3rem' }}>{story()}</div>
];
(I realize that I can make <GlobalStyles> a simple wrapper component around the entire <Router>, but I want to use this pattern to create stories for other components that assume other, intermediate layout routes.)
What I've usually done is to create custom decorator components to handle wrapping the stories that need specific "contexts" provided to them.
Example usage:
Create story decorator functions
import React from 'react';
import { Story } from '#storybook/react';
import { ThemeProvider } from '#mui/material/styles';
import CssBaseline from '#mui/material/CssBaseline';
import { MemoryRouter as Router, Routes, Route } from 'react-router-dom';
import theme from '../src/constants/theme';
import { AppLayout } from '../src/components/layout';
// Provides global theme and resets/normalizes browser CSS
export const ThemeDecorator = (Story: Story) => (
<ThemeProvider theme={theme}>
<CssBaseline />
<Story />
</ThemeProvider>
);
// Render a story into a routing context inside a UI layout
export const AppScreen = (Story: Story) => (
<Router>
<Routes>
<Route element={<AppLayout />}>
<Route path="/*" element={<Story />} />
</Route>
</Routes>
</Router>
);
.storybook/preview.js
import { INITIAL_VIEWPORTS } from '#storybook/addon-viewport';
import { ThemeDecorator } from './decorators';
export const parameters = {
actions: { argTypesRegex: '^on[A-Z].*' },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/,
},
},
options: {
storySort: {
includeName: true,
method: 'alphabetical',
order: ['Example', 'Theme', 'Components', 'Pages', '*'],
},
},
viewport: {
viewports: {
...INITIAL_VIEWPORTS,
},
},
};
export const decorators = [ThemeDecorator]; // <-- provide theme/CSS always
Any story that needs the app layout and routing context:
import React from 'react';
import { ComponentStory, ComponentMeta } from '#storybook/react';
import { AppScreen, MarginPageLayout } from '../../.storybook/decorators';
import BaseComponentX from './ComponentX';
export default {
title: 'Components/Component X',
component: BaseComponentX,
decorators: [AppScreen], // <-- apply additional decorators
parameters: {
layout: 'fullscreen',
},
} as ComponentMeta<typeof BaseComponentX>;
const BaseComponentXTemplate: ComponentStory<typeof BaseComponentX> = () => (
<BaseComponentX />
);
export const ComponentX = BaseComponentXTemplate.bind({});
In my example you could conceivably place all your providers and that Global component (w/ props) in what I've implemented as ThemeDecorator and set as a default decorator for all stories.

How to get the value of variable to change the css of body/root element in reactJS?

I am passing a variable name theme with the help of useContext to every component in the react. I want to change the bgColor of body or root element as my theme changes as per the following cases. Simply if
theme === light : bgColor => white
theme === dark : bgColor => #2d2d2d
theme === intense-dark : bgColor => black
How can access the CSS properties of root/body element in any of the React Component or vice-versa ( how can I access the value of variable theme in the `App.css )? Any of both ways is fine for me.
I am changing the theme as follows in one of my React component?
import { useContext } from "react";
import Notes from "./Notes";
import themeContext from "../context/themes/themeContext";
const Home = (props) => {
const contextForThemes = useContext(themeContext);
const { theme } = contextForThemes;
let noteStyle = {
backgroundColor: "white",
color: "black",
};
if (theme.light) {
noteStyle = { backgroundColor: "white", color: "black" };
} else if (theme.dark) {
noteStyle = {
backgroundColor: "#2d2d2d",
color: "whitesmoke",
};
} else if (theme.anotherDark) {
noteStyle = {
backgroundColor: "black",
color: "#84ff00",
};
}
return (
<div style={noteStyle}>
<Notes showAlert={props.showAlert} />
</div>
);
};
export default Home;
although I solved the question in the following way:
I made a div container wrapping the App.js and defined the context in the index.js as follows:
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import ThemeState from './context/themes/ThemeState';
ReactDOM.render(
<React.StrictMode>
<ThemeState>
<App />
</ThemeState>
</React.StrictMode>,
document.getElementById('root')
);
So you don't need to supply value as App.js represents the whole body of your page.
For reference, I am also sharing App.js
import "./App.css";
import { useState,useContext } from "react";
import Home from "./components/Home";
import About from "./components/About";
import Navbar from "./components/Navbar";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import NoteState from "./context/notes/NoteState";
import themeContext from "./context/themes/themeContext";
function App() {
const contextForThemes = useContext(themeContext);
const { theme } = contextForThemes;
let bgColor = 'white';
if(theme.light) {
bgColor ='white';
}
else if(theme.dark) {
bgColor = '#2d2d2d';
}
else if(theme.anotherDark) {
bgColor = 'black';
}
return (
<>
<NoteState>
<div style={{backgroundColor:bgColor, paddingBottom:'150px'}}>
<Router>
<Navbar showAlert={showAlert}/>
<div className="container">
<Switch>
<Route exact path="/">
<Home showAlert={showAlert} />
</Route>
<Route exact path="/about">
<About />
</Route>
</Switch>
</div>
</Router>
</div>
</NoteState>
</>
);
}
export default App;
Create a class in CSS file which contains the changed state css, after that create a state that regulates the event in which you want you css to change. Let's say on click triggers css change, in that case provide a state to check the click, it may be a toggle state as well, and once the event is triggered, show the specific css class in the parent tag using .

React navigation - cannot read property 'push' of undefined

I have a create profile page and an auth page (where one enters a code sent by text). I'd like to navigate to the auth page, when a profile is created.
I have a component for the create profile page and one for the auth page.
Based on the example here.
Relevant code:
// CreateProfileComponent.js
import React from 'react';
..
import { withRouter } from "react-router";
class CreateProfile extends React.Component {
constructor(props) {
super(props);
}
handleCreateProfile(e) {
e.preventDefault();
if (condition) {
createProfile(...);
this.props.history.push('/auth');
} else {
// re-render
}
}
render() {
return (
// data-testid="create-profile" - workaround (https://kula.blog/posts/test_on_submit_in_react_testing_library/) for
// https://github.com/jsdom/jsdom/issues/1937
<form onSubmit={this.handleCreateProfile.bind(this)} data-testid="create-profile">
...
</form>
);
}
}
export default withRouter(CreateProfile);
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import CreateProfile from './CreateProfileComponent';
import { TokenEntry } from './TokenEntryComponent';
ReactDOM.render(
<Router>
<ProtectedRoute exact path="/" component={ActivityList} />
<Route path="/login" component={CreateProfile.WrappedComponent} />
<Route path="/auth" component={TokenEntry} />
</Router>,
document.getElementById('root')
);
//createprofilecomponent.test.js
import React from 'react';
import {
LocationProvider,
createMemorySource,
createHistory
} from '#reach/router';
import { render, screen, fireEvent } from '#testing-library/react';
import CreateProfile from '../CreateProfileComponent';
const source = createMemorySource('/login');
const history = createHistory(source);
let displayName = null;
let phoneNumber = null;
let legalAgreement = null;
global.window = { location: { pathname: null } };
function Wrapper({children}) {
return <LocationProvider history={history}>{children}</LocationProvider>;
}
beforeEach(() => {
render(
<CreateProfile.WrappedComponent location={'/'} />,
{ wrapper: Wrapper }
);
});
it("navigates to /auth when good data entered", () => {
// setup
fireEvent.submit(screen.getByTestId('create-profile'));
expect(global.window.location.pathname).toEqual('/auth');
});
I'm getting
TypeError: Cannot read property 'push' of undefined
during tests and in Chrome.
What am I missing?
Use the react-router-dom
import { withRouter } from "react-router-dom";
Changed
export default withRouter(CreateProfile);
To
export const CreateProfileWithRouter = withRouter(CreateProfile);
Changed
<Route path="/login" component={CreateProfileWithRouter.WrappedComponent} />
To
<Route path="/login" component={CreateProfileWithRouter} />
Pull request created to include an example in withRouter's documentation.
Example gist

Unable to console.log props using Link

I am trying to make a single web application. Basically, I am trying to use the ReactRouter to display what is passed as a Route Parameter. However, I am unable to do that. To check if somethings wrong, I decided to console.log out this.props.match, still nothing shows up. Could someone explain what the problem is? And a possible get around?
My code is-
import React from 'react';
export default class Post extends React.Component {
state = {
id: null
}
componentDidMount(props) {
console.log(this.props.match);
}
render = () => {
return (<div>Hello WOrld</div>)
}
}
The App.js file:
import React, { Fragment, Component } from 'react';
import Navbar from './components/Navbar';
import Home from './components/Home';
import Contact from './components/Contact';
import About from './components/About'
import Post from './components/Post';
import { BrowserRouter, Route } from 'react-router-dom';
class App extends Component {
render = () => {
return (
<BrowserRouter>
<div className="App">
<Navbar />
<Route exact path="/" component={Home} />
<Route path="/contact" component={Contact} />
<Route path="/about" component={About} />
<Route path="/:post-id" component = {Post} />
</div>
</BrowserRouter>
);
}
}
export default App;
I just ran your code on my end, it looks like the problem is using /:post-id. I changed that to /:pid and it worked. I got the below object when I console log this.props.match
{
"path":"/:pid",
"url":"/1",
"isExact":true,
"params":
{
"pid":"1"
}
}
I hope this helps.
You have to load the component with router
try this
import { withRouter } from 'react-router-dom';
class Post extends React.Component {
state = {
id: null
}
componentDidMount(props) {
console.log(this.props.match);
}
render = () => {
return (<div>Hello WOrld</div>)
}
}
export default withRouter(Post);

ReactJS and nested routes with react-router v4

I am making a website with ReactJS and I am using react-router v4 to navigate through website pages.
I am trying to use nested routes to achieve the same result as shown in the diagram I uploaded.
To be more clear, I want to have some kind of layout component where the header and footer stay the same, and only the content in the middle is changed with every click to the next or previous button. The 'home_page' and the 'create_record' page have the same layout, and the 3 step pages share another layout from the home_page. It's like the Airbnb style layout and navigation.
Any suggestion on how can I achieve this?
Here is my solution. This might not so effective but this is working on me.
So my strategy is get the window url and check the step.
In here I create 8 files.
Header
Footer
Home.js
NewRecord.js
Steps.js
StepOne.js
StepTwo.js
StepThree.js
I will describe only the App.js and the Steps.js. Others is up to you.
App.js
import React from 'react'
import ReactDOM from 'react-dom'
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom'
import Home from './Home'
import NewRecord from './NewRecord'
import Steps from './Steps'
const App = () => {
return (
<Router>
<Switch>
<Route exact path="/" component={Home}/>
<Route exact path="/new_record" component={NewRecord}/>
<Route path="/new_record/step_1" component={Steps}/>
<Route path="/new_record/step_2" component={Steps}/>
<Route path="/new_record/step_3" component={Steps}/>
</Switch>
</Router>
)
}
export default App
Steps.js
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import Header from './Header';
import Footer from './Footer';
import StepOne from './StepOne';
import StepTwo from './StepTwo';
import StepThree from './StepThree';
export default class Steps extends Component {
constructor (props) {
super(props)
this.state = {
url : window.location.href,
currentStep: 0
}
}
checkCurrentStep = () => {
// check the url using javascript search() method
if (this.state.url.search('step_1') != -1) {
this.state.currentStep = 1;
} else if (this.state.url.search('step_2') != -1) {
this.state.currentStep = 2;
} else {
this.state.currentStep = 3;
}
}
componentWillMount () {
this.checkCurrentStep()
}
render() {
return (
<div>
<Header />
{ (this.state.currentStep == 1) ? <StepOne /> :
(this.state.currentStep == 2) ? <StepTwo /> :
<StepThree />
}
<Footer />
</div>
);
}
}

Categories

Resources