Dynamically paste value in dot notation in react component - reactjs

I'm trying to use the React-NBA-Logos package(npm link) in a small project and faced a question. According to the documentation here is an example of this package use
import React from 'react';
import * as NBAIcons from 'react-nba-logos';
const Example = () => {
return <NBAIcons.TOR />; // Loads the Toronto Raptors logo
};
export default Example;
The question is it possible to paste a variable with a dynamic string value(with some correct tripcode) here? Something like this
const Example = () => {
const NEW_VAR = 'NYK';
return <NBAIcons.NEW_VAR />;
};

You can get around this by creating an alias for the component.
const Example = () => {
const NEW_VAR = 'NYK';
const Component = NBAIcons[NEW_VAR];
return <Component />;
};

A functional approach would work well here:
function NBAIcon (name, props = {}) {
const Icon = NBAIcons[name];
return <Icon {...props} />;
}
function Example () {
return (
<ul>
<li>{NBAIcon('TOR')}</li>
<li>{NBAIcon('ATL', {size: '64px'})}</li>
</ul>
);
}

Related

Create helper function for a (click handler) function to reuse in multiple React components

For a 'back' button I've created below (onClick) handler function in my React app.
const { length: historyLength, goBack, replace } = useHistory();
const handleBack = () => {
if (historyLength > 2) {
goBack();
} else {
// History length is 2 by default when nothing is pushed to history yet
// https://stackoverflow.com/questions/9564041/why-history-length-is-2-for-the-first-page
replace(HomePage);
}
};
Then I am passing the onClick handler to my child component like: <Button onClick={handleBack}/>
I am using this handleBack function in multiple places in my React app. Is it a good approach make it e.g. a helper function and how exactly?
I also don't see any issue with the code or using it as a utility callback.
Is it a good approach make it e.g. a helper function and how exactly?
Anytime you can make your code more DRY (Don't Repeat Yourself) it's generally a good thing. My personal rule-of-thumb is if I've written the same utility code a third time I'll spend a bit of time to refactor it into a common utility (and unit test!!).
I might suggest creating a custom hook to return the back handler.
Example:
import { useHistory } from 'react-router-dom';
const useBackHandler = () => {
const history = useHistory();
const handleBack = React.useCallback(() => {
const { length: historyLength, goBack, replace } = history;
if (historyLength > 2) {
goBack();
} else {
replace(HomePage);
}
}, []);
return handleBack;
};
export default useBackHandler;
Now you have a single hook to import and use.
import useBackHandler from '../path/to/useBackHandler';
...
const backHandler = useBackHandler();
...
<button type="button" onClick={backHandler}>Back?</button>
If you are needing this function in older class components, then you'll need a way to inject the handleBack as a prop. For this you can create a Higher Order Component.
Example:
import useBackHandler from '../path/to/useBackHandler';
const withBackHandler = Component => props => {
const backHandler = useBackHandler();
return <Component {...props} backHandler={backHandler} />;
};
export default withBackHandler;
To use, import withBackHandler and decorate a React component and access props.backHandler.
import withBackHandler from '../path/to/withBackHandler';
class MyComponent extends React.Component {
...
someFunction = () => {
...
this.props.backHandler();
}
...
}
export default withBackHandler(MyComponent);
#meez
Don't see why this wouldn't work. Just a couple of things: (a) I would add the event argument and e.preventDefault() within the function and (b) would be careful of the function name you are passing on the onClick property of your button: handleBackClick !== handleBack, you'll get an ReferenceError because of an undefined function.
Additionally, I also noticed that this can be achieved with native browser functions. Here's a snippet:
const { length: historyLength, back } = window.history;
const { replace } = window.location;
const handleBack = (e) => {
e.preventDefault();
if (historyLength > 2) {
back();
} else {
replace('homepageUrl');
}
};

Convert react class to react function

Hi so I have two files and i want to convert one of them into a react functional component. The first one is PuzzleGrid and it will call the function Grid.getMatrix()
export const PuzzleGrid = () => {
const newMatrix = GridLib.getMatrix();
}
and the next is Gridlib which is a react class :
export class GridLib {
static getMatrix() {
return Array(6).fill(Array(6).fill(0));
}
}
I want to be able to convert the class into a function, this is what i have, however i am getting an error. does anyone understand why?
export const GridLib = () => {
GridLib.getMatrix = {
Array(6).fill(Array(6).fill(0));
}
}
You can simply return the results of the getMatrix method:
export const GridLib = () => {
return Array(6).fill(Array(6).fill(0));
}
However, this will cause a problem for your PuzzleGrid component, as it depends on GridLib having a getMatrix method. You would have to simply have PuzzleGrid return a GridLib for it to work as such:
export const PuzzleGrid = () => {
return <GridLib />
}
However this pattern is a bit silly and repetetive. I feel like there must be more going on to your question, but you may have reduced it down too much and stripped away some important details. What is the larger context of this issue?
Just use a function properties to declare a "static method"
export const GridLib = () => {
return <View/> ;
}
GridLib.getMatrix = () => {
return Array(6).fill(Array(6).fill(0));
}

react functional component with ag grid cannot call parent function via context

I am using ag-grid-react and ag-grid-community version 22.1.1. My app is written using functional components and hooks. I have a cellrenderer component that is attempting to call a handler within the parent component using the example found here. Is this a bug in ag-grid? I have been working on this application for over a year as I learn React, and this is my last major blocker so any help or a place to go to get that help would be greatly appreciated.
Cell Renderer Component
import React from 'react';
import Button from '../../button/button';
const RowButtonRenderer = props => {
const editClickHandler = (props) => {
let d = props.data;
console.log(d);
props.context.foo({d});
//props.editClicked(props);
}
const deleteClickHandler = (props) => {
props.deleteClicked(props);
}
return (<span>
<Button classname={'rowbuttons'} onClick={() => { editClickHandler(props) }} caption={'Edit'} />
<Button classname={'rowbuttons'} onClick={() => { deleteClickHandler(props) }} caption={'Delete'} />
</span>);
};
export default RowButtonRenderer;
Parent Component
function Checking() {
function foo(props) {
let toggle = displayModal
setNewData(props);
setModalDisplay(!toggle);
}
const context = {componentParent: (props) => foo(props)};
const gridOptions = (params) => {
if (params.node.rowIndex % 2 === 0) {
return { background: "#ACC0C6" };
}
};
const frameworkComponents = {
rowButtonRenderer: RowButtonRenderer,
};
.
.
.
return (
<>
<AgGridReact
getRowStyle={gridOptions}
frameworkComponents={frameworkComponents}
context = {context}
columnDefs={columnDefinitions}
rowData={rowData}
headerHeight="50"
rowClass="gridFont"
></AgGridReact>
</>
);
}
When clicking the edit button on a row, the debugger says that there is a function.
This error is received though:
You are passing the context object in this code section:
const context = {componentParent: (props) => foo(props)};
...
<AgGridReact
context={context}
{...}
></AgGridReact>
And in your cell renderer you call this
props.context.foo({d});
While it should be this
props.context.componentParent({d});
Also you can assign your callback directly since it receives the same parameter and returns the same result (if any)
function foo(props) {
let toggle = displayModal
setNewData(props);
setModalDisplay(!toggle);
}
const context = {componentParent: foo};
You can also use this shorthand syntax from ES6 when assigning object property
function componentParent(props) {
let toggle = displayModal
setNewData(props);
setModalDisplay(!toggle);
}
const context = {componentParent};
Live Demo

How can I create a React component with a variable?

I have a menu. I want the menu items to open different components all with the same style and props.
So I need sth. like this in JSX:
<VARIABLE_HERE style={...} otherProp={'otherProp'}>
where VARIABLE_HERE is a variable i can set (e.g. FileView, SummaryView, ...).
import component1 from '/component1';
import component2 from '/component2';
const availableComponents = {
component1,
component2
};
const MyComponent = (props) => {
const chosenOne = 'component1';
const DisplayedComponent = availableComponents[chosenOne];
return <DisplayedComponent {...props} />;
};
set your choseOne in a condition.
You can create FactoryComponent and inside of the factory component, you can send the VARIABLE_NAME as a prop then you can do what you need. You can check the below example.
FactoryComponent.jsx
const DynamicComponents = {
fileView: FileView,
summaryView: SummaryView
};
const FactoryComponent = (props) => {
const Component = DynamicComponents[props.componentName] || null;
return <Component { ...props }/>
}
Then, you will add the your FactoryComponent to the place that you need. Hypothetically, I am going to use it in Main.jsx.
Main.jsx
const Main = () => {
return <FactoryComponent componentName={VARIABLE_HERE} />
}

UseEffect causes infinite loop with swipeable routes

I am using the https://www.npmjs.com/package/react-swipeable-routes library to set up some swipeable views in my React app.
I have a custom context that contains a dynamic list of views that need to be rendered as children of the swipeable router, and I have added two buttons for a 'next' and 'previous' view for desktop users.
Now I am stuck on how to get the next and previous item from the array of modules.
I thought to fix it with a custom context and custom hook, but when using that I am getting stuck in an infinite loop.
My custom hook:
import { useContext } from 'react';
import { RootContext } from '../context/root-context';
const useShow = () => {
const [state, setState] = useContext(RootContext);
const setModules = (modules) => {
setState((currentState) => ({
...currentState,
modules,
}));
};
const setActiveModule = (currentModule) => {
// here is the magic. we get the currentModule, so we know which module is visible on the screen
// with this info, we can determine what the previous and next modules are
const index = state.modules.findIndex((module) => module.id === currentModule.id);
// if we are on first item, then there is no previous
let previous = index - 1;
if (previous < 0) {
previous = 0;
}
// if we are on last item, then there is no next
let next = index + 1;
if (next > state.modules.length - 1) {
next = state.modules.length - 1;
}
// update the state. this will trigger every component listening to the previous and next values
setState((currentState) => ({
...currentState,
previous: state.modules[previous].id,
next: state.modules[next].id,
}));
};
return {
modules: state.modules,
setActiveModule,
setModules,
previous: state.previous,
next: state.next,
};
};
export default useShow;
My custom context:
import React, { useState } from 'react';
export const RootContext = React.createContext([{}, () => {}]);
export default (props) => {
const [state, setState] = useState({});
return (
<RootContext.Provider value={[state, setState]}>
{props.children}
</RootContext.Provider>
);
};
and here the part where it goes wrong, in my Content.js
import React, { useEffect } from 'react';
import { Route } from 'react-router-dom';
import SwipeableRoutes from 'react-swipeable-routes';
import useShow from '../../hooks/useShow';
import NavButton from '../NavButton';
// for this demo we just have one single module component
// when we have real data, there will be a VoteModule and CommentModule at least
// there are 2 important object given to the props; module and match
// module comes from us, match comes from swipeable views library
const ModuleComponent = ({ module, match }) => {
// we need this function from the custom hook
const { setActiveModule } = useShow();
// if this view is active (match.type === 'full') then we tell the show hook that
useEffect(() => {
if (match.type === 'full') {
setActiveModule(module);
}
},[match]);
return (
<div style={{ height: 300, backgroundColor: module.title }}>{module.title}</div>
);
};
const Content = () => {
const { modules, previousModule, nextModule } = useShow();
// this is a safety measure, to make sure we don't start rendering stuff when there are no modules yet
if (!modules) {
return <div>Loading...</div>;
}
// this determines which component needs to be rendered for each module
// when we have real data we will switch on module.type or something similar
const getComponentForModule = (module) => {
// this is needed to get both the module and match objects inside the component
// the module object is provided by us and the match object comes from swipeable routes
const ModuleComponentWithProps = (props) => (
<ModuleComponent module={module} {...props} />
);
return ModuleComponentWithProps;
};
// this renders all the modules
// because we return early if there are no modules, we can be sure that here the modules array is always existing
const renderModules = () => (
modules.map((module) => (
<Route
path={`/${module.id}`}
key={module.id}
component={getComponentForModule(module)}
defaultParams={module}
/>
))
);
return (
<div className="content">
<div>
<SwipeableRoutes>
{renderModules()}
</SwipeableRoutes>
<NavButton type="previous" to={previousModule} />
<NavButton type="next" to={nextModule} />
</div>
</div>
);
};
export default Content;
For sake of completion, also my NavButton.js :
import React from 'react';
import { NavLink } from 'react-router-dom';
const NavButton = ({ type, to }) => {
const iconClassName = ['fa'];
if (type === 'next') {
iconClassName.push('fa-arrow-right');
} else {
iconClassName.push('fa-arrow-left');
}
return (
<div className="">
<NavLink className="nav-link-button" to={`/${to}`}>
<i className={iconClassName.join(' ')} />
</NavLink>
</div>
);
};
export default NavButton;
In Content.js there is this part:
// if this view is active (match.type === 'full') then we tell the show hook that
useEffect(() => {
if (match.type === 'full') {
setActiveModule(module);
}
},[match]);
which is causing the infinite loop. If I comment out the setActiveModule call, then the infinite loop is gone, but of course then I also won't have the desired outcome.
I am sure I am doing something wrong in either the usage of useEffect and/or the custom hook I have created, but I just can't figure out what it is.
Any help is much appreciated
I think it's the problem with the way you are using the component in the Route.
Try using:
<Route
path={`/${module.id}`}
key={module.id}
component={() => getComponentForModule(module)}
defaultParams={module}
/>
EDIT:
I have a feeling that it's because of your HOC.
Can you try
component={ModuleComponent}
defaultParams={module}
And get the module from the match object.
const ModuleComponent = ({ match }) => {
const {type, module} = match;
const { setActiveModule } = useShow();
useEffect(() => {
if (type === 'full') {
setActiveModule(module);
}
},[module, setActiveModule]);
match is an object and evaluated in the useEffect will always cause the code to be executed. Track match.type instead. Also you need to track the module there. If that's an object, you'll need to wrap it in a deep compare hook: https://github.com/kentcdodds/use-deep-compare-effect
useEffect(() => {
if (match.type === 'full') {
setActiveModule(module);
}
},[match.type, module]);

Resources