Link outside a Router Error, while everything set up properly - javascript

Ok, I have no idea why this is not working. Everything is set up properly from what I can see.
I am using "react-router-dom": "^5.0.0"
The code also uses the Tabulator grid library, specifically the React implementation of it. It's not really relevant, just wanted to note it.
The code works 100% without using the sub-component links, so the problem is not there.
The grid generator in Journals creates a table, which has link cells, which lead to the Journal component.
The link component is generated fine, it just doesn't work for reasons I don't know.
CodeSandbox
If you comment out the formatter line in columns in the Journal component, the app works again.
App.js
import React, { Component } from 'react';
import { BrowserRouter as Router, Route } from 'react-router-dom';
import Header from './components/layout/Header';
import Dashboard from './components/pages/Dashboard';
import Journals from './components/pages/Journals';
import Journal from './components/pages/Journal';
class App extends Component {
render() {
return (
<Router>
<div className="App">
<div className="container">
<Header />
<div className="content">
<Route exact path="/" component={Dashboard} />
<Route exact path="/journals" component={Journals} />
<Route path="/journals/:key" component={Journal} /> // <------ ROUTE IS HERE
</div>
</div>
</div>
</Router>
);
}
}
export default App;
Journals.js
import React, { useState, useEffect } from "react";
import { Link } from 'react-router-dom';
import { ReactTabulator } from 'react-tabulator'
import "tabulator-tables/dist/css/tabulator.min.css";
import { reactFormatter } from 'react-tabulator';
function Journals() {
const [journals, setJournals] = useState([]);
useEffect(() => {
fetch("http://localhost:4000/journals")
.then(res => res.json())
.then(data => {
setJournals(data)
})
.catch(err => err);
}, []);
const JournalLink = (props) => {
const cellData = props.cell._cell.row.data;
let key = cellData.key_
let link = `/journals/${key}`
return <Link to={link}>{key}</Link>; // <------ LINK COMPONENT IS HERE
}
const columns = [
{
title: "Number",
field: "key_",
formatter: reactFormatter(<JournalLink />) // <------ LINK COMPONENT USED HERE
},
{ title: "Date", field: "date_" },
];
return (
<div>
<h1>Journals</h1>
<ReactTabulator
data={journals}
columns={columns}
tooltips={true}
layout={"fitData"}
/>
</div >
)
}
export default Journals;
reactFormatter usage example
reactFormatter definition
Journal.js
import React, { useState, useEffect } from "react";
import { ReactTabulator } from 'react-tabulator'
import "tabulator-tables/dist/css/tabulator.min.css";
function Journal(props) {
const [journalItems, setJournalItems] = useState([]);
const initialFormJournalItems = {
id: "",
journalId: "",
companyId: "",
documentKey: "",
documentDate: "",
debitAccount: "",
debit: "",
creditAccount: "",
credit: ""
}
const [formJournalItems, setFormJournalItems] = useState(initialFormJournalItems);
useEffect(() => {
fetch(`http://localhost:4000/journals/${props.match.params.key}`)
.then(res => res.json())
.then(data => {
setJournalItems(data)
})
.catch(err => err);
}, []);
const columns = [
{ title: "Document", field: "documentKey" },
{ title: "Date", field: "documentDate" },
];
return (
<div>
<h1>Journal</h1>
<ReactTabulator
data={journalItems}
columns={columns}
tooltips={true}
layout={"fitData"}
/>
</div >
)
}
export default Journal;

react-tabulator reFormatter is incompatible with react-router library.
https://github.com/ngduc/react-tabulator/blob/0.10.3/lib/Utils.js#L30
From source code,
function reactFormatter(JSX) {
return function customFormatter(cell, formatterParams, onRendered) {
//cell - the cell component
//formatterParams - parameters set for the column
//onRendered - function to call when the formatter has been rendered
onRendered(function () {
var cellEl = cell.getElement();
var CompWithMoreProps = React.cloneElement(JSX, { cell: cell });
react_dom_1.render(CompWithMoreProps, cellEl.querySelector('.formatterCell'));
});
return '<div class="formatterCell"></div>';
};
}
rendering of a formatted element uses the ReactDOM.render function to render the formatted element directly to DOM isolated from parent elements.
A fix to react-tabulator needs to be done to support this use case. One way to go is to have customFormatter return a custom component that provides a way to set its state from outside it. Then onRendered can call this function to set cell.

Related

How to clone data from a passable props on React JS

I'm creating a Multi Dropdown component in React.JS, I want to clone a variable (selectedData) from App.js into a component. But when I try to clone data there is always an error "Cannot assign to read only property 'selectedData' of object"
import React from 'react';
import MultiDropdown from './Components/MultiDropdown/MultiDropdown.component';
import { allOptions } from './Utils/DummyData';
import "./App.css";
const App = () => {
var clonedData = [
{ value: 'Normal😐', label: 'Normal😐' },
{ value: 'Angry😡', label: 'Angry😡' },
{ value: 'Love😍', label: 'Love😍' },
]
return(
<div className='app'>
<MultiDropdown
data={allOptions}
placeholder="Select Options"
selectedData={clonedData}
// value={clonedData}
/>
<button onClick={() => console.log("Selected", clonedData)}>Click to See SelectedData</button>
</div>
)
}
export default App;
I wanted to clone variable CloneData, that passed on selectedData, I use this function to clone data
Here's my components code :
export default function MultiDropdown(props: Props): React.Node {
const [data, setData] = React.useState(props.selectedData ? props.selectedData.map(opt => opt.value) : []);
React.useEffect(() => {
props.selectedData = data;
}, [data, props]);
return (
<div>
<Select
ref={props.selectedData}
{...DropDownProps(props, data, SelectOption)}
onChange={selected => setData(selected.map(opt => opt.value))}
/>
{data.map(opt => (<ListContainer key={opt} opt={opt} data={data} set={setData} />))}
</div>
);
}
I'm trying cloning my variable on useEffect
Thankyou guys!
You can't directly change props that come to your component but there is a way:
You can create a useState to store your clonedData pass the state and the function that changes that state.
import React from 'react';
import MultiDropdown from './Components/MultiDropdown/MultiDropdown.component';
import { allOptions } from './Utils/DummyData';
import "./App.css";
const App = () => {
const [clonedData , setClonedData] = React.useState([
{ value: 'Normal😐', label: 'Normal😐' },
{ value: 'Angry😡', label: 'Angry😡' },
{ value: 'Love😍', label: 'Love😍' },
]);
return(
<div className='app'>
<MultiDropdown
data={allOptions}
placeholder="Select Options"
selectedData={clonedData}
changeSelectedData={setClonedData} // pass the setter function.
// value={clonedData}
/>
<button onClick={() => console.log("Selected", clonedData)}>Click to See SelectedData</button>
</div>
)
}
export default App;
Then use this useState hook rather than defining it in the component. Because there is no way to directly pass anything defined in the child component to the parent component

Importing React Autosuggest as Functional Component from Another JSX File

I'm currently making a simple web frontend with react using react-autosuggest to search a specified user from a list. I want to try and use the Autosuggest to give suggestion when the user's type in the query in the search field; the suggestion will be based on username of github profiles taken from github user API.
What I want to do is to separate the AutoSuggest.jsx and then import it into Main.jsx then render the Main.jsx in App.js, however it keeps giving me 'TypeError: _ref2 is undefined' and always refer to my onChange function of AutoSuggest.jsx as the problem.
Below is my App.js code:
import './App.css';
import 'bootstrap/dist/css/bootstrap.min.css';
import Header from './views/header/Header';
import Main from './views/main/Main';
import Footer from './views/footer/Footer';
const App = () => {
return (
<>
<Header/>
<Main/> <- the autosuggest is imported in here
<Footer/>
</>
);
}
export default App;
Below is my Main.jsx code:
import React, { useState } from 'react';
import Container from 'react-bootstrap/Container';
import Row from 'react-bootstrap/Row';
import axios from 'axios';
import { useEffect } from 'react';
import AutoSuggest from '../../components/AutoSuggest';
const Main = () => {
const [userList, setUserList] = useState([]);
useEffect(() => {
axios.get('https://api.github.com/users?per_page=100')
.then((res) => setUserList(res.data))
.catch((err) => console.log(err));
}, [])
return (
<Container>
<br/>
<Row>
<AutoSuggest userList={userList} placeHolderText={'wow'} />
</Row>
</Container>
);
}
export default Main;
Below is my AutoSuggest.jsx code:
import React, { useState } from "react";
import Autosuggest from 'react-autosuggest';
function escapeRegexCharacters(str) {
return str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
function getSuggestions(value, userList) {
const escapedValue = escapeRegexCharacters(value.trim());
if (escapedValue === '') {
return [];
}
const regex = new RegExp('^' + escapedValue, 'i');
return userList.filter(user => regex.test(user.login));
}
function getSuggestionValue(suggestion) {
return suggestion.name;
}
function renderSuggestion(suggestion) {
return (
<span>{suggestion.name}</span>
);
}
const AutoSuggest = ({userList, placeHolderText}) => {
const [value, setValue] = useState('');
const [suggestions, setSuggestions] = useState([]);
const onChange = (event, { newValue, method }) => { <- error from console always refer here, I'm not quite sure how to handle it..
setValue(newValue);
};
const onSuggestionsFetchRequested = ({ value }) => {
setValue(getSuggestions(value, userList))
};
const onSuggestionsClearRequested = () => {
setSuggestions([]);
};
const inputProps = {
placeholder: placeHolderText,
value,
onChange: () => onChange()
};
return (
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={() => onSuggestionsFetchRequested()}
onSuggestionsClearRequested={() => onSuggestionsClearRequested()}
getSuggestionValue={() => getSuggestionValue()}
renderSuggestion={() => renderSuggestion()}
inputProps={inputProps} />
);
}
export default AutoSuggest;
The error on browser (Firefox) console:
I have no idea what does the error mean or how it happened and therefore unable to do any workaround.. I also want to ask if what I do here is already considered a good practice or not and maybe some inputs on what I can improve as well to make my code cleaner and web faster. Any input is highly appreciated, thank you in advance!
you have to write it like this... do not use the arrow function in inputProps
onChange: onChange

React load script on specfic div

I need to load a script on specific component in my React app.
Below is my script and i need this to load on bottom-most div in my component
<div id="rexxxx"></div>
<script>
new carouselInlineWidget("xx", {
/*Your REVIEWS.io account ID:*/
store: "xxxxxxxxxxxx",
sku: "",
lang: "en",
carousel_type: "xxxxx",
styles_carousel: "CarouselWidget--xxxxxx",
/*Widget settings:*/
options: {
general: {
/*What reviews should the widget display? Available options: company, product, third_party. You can choose one type or multiple separated by comma.*/
enable_auto_scroll: 10000,
},
header: {
},
reviews: {
},
popups: {},
},
styles: {
},
});
</script>
I have my React component
import React from 'react'
import edenredLogo from '../../images/edenred-logo.webp'
import { useHistory } from 'react-router-dom'
import { useSelector } from 'react-redux'
import './landing.less'
const Landing = () => {
const history = useHistory()
return (
<>
<div className="script-here"/>
</>
)
}
export default Landing
You can use the custom hook:
import { useEffect } from 'react';
const useScript = (url, position, async = true) => {
useEffect(() => {
const placement = document.querySelector(position);
const script = document.createElement('script');
script.src = url;
script.async = typeof async === 'undefined' ? true : async;
placement.appendChild(script);
return () => {
placement.removeChild(script);
};
}, [url]);
};
export default useScript;
Usage:
useScript(url, ".script-here");
Or just use dangerouslySetInnerHTML
<div className="script-here" dangerouslySetInnerHTML={{__html: your_script}} />

Memorizing values upon an async call in react

I'm trying to memorize some values in a react component because it's re rendering even when data hasn't changed (and wont change). Using useEffect + useState the data displays correctly, but the functions are triggered each time the component is re rendered. Currently am trying to implement the useMemo hook but the async call/promise is not resolving in the render process, so it doesn't even loads the data. I'll try to give the most information out of this:
This is my AppRouter component, i create the contract and pass it as value to a provider that will be used in some other components:
import { useWeb3React } from "#web3-react/core";
import React, { useEffect, useState } from "react";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import { AdminScreen } from "../components/admin/AdminScreen";
import { ContractsContext } from "../components/ContractsContext";
import { Navbar } from "../components/ui/Navbar";
import { getLibrary } from "../helpers/web3Getters";
import { useCreateContract, useGetLibrary } from "../hooks.js/contractsHooks";
import { createContract } from "../web3-utils/contractCreation";
import { MarketRoutes } from "./MarketRoutes";
import { PrivateRoute } from "./PrivateRoute";
export const AppRouter = () => {
const context = useWeb3React();
//console.log("[1] context in app router: ", context);
const { contract: kolorTokenContract, loading: loadingContract } =
useCreateContract(
context,
"0x9414f981a5B5ef2bE455f2427E2166c35e8989fB",
"abis/KolorToken.json"
);
return (
//<p>{loadingLibrary ? "library ready" : "loading library"}</p>
<ContractsContext.Provider value={[kolorTokenContract]}>
<BrowserRouter>
<Navbar />
{/* Set private route for Admining nfts & tokens */}
<Routes>
<Route
path="/admin"
element={
<PrivateRoute>
<AdminScreen />
</PrivateRoute>
}
/>
<Route path="/*" element={<MarketRoutes />} />
</Routes>
</BrowserRouter>
</ContractsContext.Provider>
);
};
The contract is then obtained from my custom context in the admin route (which is what im testing now) and then passed to one of its children:
import React, { memo, useContext, useMemo } from "react";
import { getERC20Info } from "../../helpers/tokenGetters";
import { useGetERC20Info } from "../../hooks.js/contractsHooks";
import { ContractsContext } from "../ContractsContext";
export const TokenInfo = memo((tokenContract) => {
//const { _address: ERC20Address } = tokenContract;
const { address, owner, vault, supply } = useGetERC20Info(tokenContract);
//const result = useMemo(() => getERC20Info(tokenContract), [tokenContract]);
//console.log("contract from tokeninfo:", tokenContract);
//console.log("result: ", result);
return (
<div className="row align-items-center">
<div className="col-8 col-md-6 col-sm-4 ">Minting Form</div>
<div className="col-4 col-md-3 col-sm-2 animate__animated animate__fadeInRightBig">
<h2>Kolor Token Info</h2>
<p>
Address: <b>{address}</b>
</p>
<p>
Owner: <b>{owner}</b>
</p>
<p>
Vault: <b>{vault}</b>
</p>
<p>
Current supply: <b>{supply}</b>
</p>
</div>
<hr />
</div>
);
});
Actually i'm using a custom hook with useState and useEffect to fetch the data, but it re renders the TokenInfo component even when the tokenContract hasn't changed at all. This is my custom hook:
export const useGetERC20Info = (contract) => {
//console.log("contract from usegeterc20info effect: ", contract);
const [state, setState] = useState({
address: "loading...",
owner: "loading...",
vault: "loading...",
supply: "loading",
});
useEffect(() => {
getERC20Info(contract).then(({ address, owner, vault, supply }) => {
setState({
address,
owner,
vault,
supply,
});
return () => {
setState({});
};
});
}, [contract]);
return state;
};
My getERC20Info function, tries to fetch data from the blockchain, nothing wrong with that, its working fine:
export const getERC20Info = async (contract) => {
console.log("getting erc20 info...");
console.log("contract from geterc20info: ", contract);
const { _address: address } = contract;
const owner = await getERC20Owner(contract);
const vault = await getERC20Vault(contract);
const supply = await getERC20Supply(contract);
//console.log("supply: ", supply);
return {
address,
owner,
vault,
supply,
};
};
Thanks in advance for any help!

getting issue while working with nested routes in react

I am getting issues while rendering contact data.
Here the case is when I click the continue button in my app it triggers the checkoutContinuedHandler() function that results in a change of URL but the ContactData component is not rendered and my CheckoutSummary component also vanishes as I am rendering it on the same page.
I Checked twice that export is done and there is no spelling mistakes.
I tried different solutions from the stack and discussed them with my mate still the issue is on...
import React, { Component } from "react";
import { Route } from "react-router-dom";
import CheckoutSummary from "../../components/Order/CheckoutSummary/CheckoutSummary";
import ContactData from "./ContactData/ContactData";
class Checkout extends Component {
state = {
ingredients: {
salad: 1,
meat: 1,
cheese: 1,
bacon: 1,
},
};
componentDidMount() {
const query = new URLSearchParams(this.props.location.search);
const ingredients = {};
for (let param of query.entries()) {
// ['salad','1']
ingredients[param[0]] = +param[1];
}
this.setState({ ingredients: ingredients });
}
checkoutCancelledHandler = () => {
this.props.history.goBack();
};
checkoutContinuedHandler = () => {
this.props.history.replace("/checkout/contact-data");
console.log(this);
};
render() {
return (
<div>
<CheckoutSummary
ingredients={this.state.ingredients}
checkoutCancelled={this.checkoutCancelledHandler}
checkoutContinued={this.checkoutContinuedHandler}
/>
<Route
path={this.props.match.path + "/contact-data"}
component={ContactData}
/>
</div>
);
}
}
export default Checkout;

Categories