Reuse ReactJS component - javascript

I have built a ReactJS component for rendering emoction. Separate component can be built for each emoction, but I want to use one component but pass separate emoction as required.
This is what works so far:
emoction.js
import { faHeart } from "#fortawesome/free-solid-svg-icons";
import { faHeartBroken } from "#fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import React, { useState } from 'react';
const Emoction = () => {
return (
<FontAwesomeIcon icon={faHeart} />
);
};
export default Emoction;
emoction_hb.js
import { faHeart } from "#fortawesome/free-solid-svg-icons";
import { faHeartBroken } from "#fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import React, { useState } from 'react';
const EmoctionHb = () => {
return (
// <input type="text" />
<FontAwesomeIcon icon={faHeartBroken} />
);
};
export default EmoctionHb;
Now, I am bundling these two components as:
expanded_content.js
import Emoction from "../emoctions/emoctions";
import EmoctionHb from "../emoctions/emoctions_hb";
import styled from "#emotion/styled";
import { faHeartBroken } from "#fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "#fortawesome/react-fontawesome";
import React, { Component } from 'react';
const Merged = styled.div`
display: flex;
flex-direction: row;
justify-content: flex-start;
`;
const expandedContent = () => {
return(
<div>
<Merged>
<Emoction/>
<EmoctionHb/>
</Merged>
</div>
)
};
export default expandedContent;
which when I rendered using App.js
import React from 'react';
import ReactDOM from 'react-dom';
import {BrowserRouter as Router, Switch, Route} from 'react-router-dom';
import expandedContent from './components/merged_component/expanded_content'
class App extends React.Component {
render(){
return(
<Router>
<>
<Route path='/ExpandedContent' exact component={expandedContent}/>
</>
</Router>
)
}
}
export default App;
gives me.
What I am trying to do is that instead of creating a component called emoctions_hb.js I want to reuse emoction.js by passing "faHeartBroken" as the value in it.
If emoction.js is called without any value, I want it to use "faHeartBroken" as default value.
Tried following on to create Parent-Child relationship using https://webomnizz.com/change-parent-component-state-from-child-using-hooks-in-react/ but it did not work out for me.

Just pass the icon as a prop and set the default value to faHeartBroken:
const Emoction = ({ faIcon = faHeartBroken }) => {
return (
<FontAwesomeIcon icon={faIcon} />
);
};

It looks like you're importing useState but you're not implementing it anywhere. You could try implementing state in your expanded_content.js file and pass that down to your child component emoction.js, like this:
const ExpandedContent = () => {
const [heart, setHeart] = useState(true)
return(
<div>
<Emoction heart={heart} setHeart={setHeart}/>
</div>
)
};
export default ExpandedContent;
Notice that you will need to change the name of your component. See the docs here https://reactjs.org/docs/hooks-rules.html.
Then, inside of your Emoction component you will have access to heart which is set to true by default and you can also implement some logic to toggle the state using the function setHeart which is passed down from ExpandedContent:
const Emoction = ({heart, setHeart}) => {
const handleHearts = () => {
setHeart(heart => !heart)
}
return (
heart ? <FontAwesomeIcon icon={faHeart} /> : <FontAwesomeIcon icon={faHeartBroken} />
);
};
export default Emoction;
By using a ternary statement to return your component you can decide to show faHeart or faHeartBroken depending on the current state. All you need to do is add the functionality wherever you need it.

Related

Uncaught TypeError: Cannot read properties of undefined (reading 'params') while using axios and react-router-dom

It's the HomePage component of ReactJS
import React from 'react';
import axios from 'axios';
import { useState, useEffect } from 'react';
import { useNavigate,useParams } from 'react-router-dom';
import { Main } from '../components/Main';
import { Controls } from '../components/Controls';
import { ALL_COUNTRIES } from '../config';
import { List } from '../components/List';
import { Card } from '../components/Card';
import { Details } from './Details';
export const HomePage = () => {
const [countries,setCountries] = useState([]);
const navigate = useNavigate();
useEffect(() => {
axios.get(ALL_COUNTRIES).then(({data})=>setCountries(data))
},[]);
return (
<>
<Controls/>
<List>
{
countries.map((c) => {
const countryInfo = {
img: c.flags.png,
name: c.name,
info: [
{
title:'Population',
description:c.population.toLocaleString(),
},
{
title:'Region',
description:c.region,
},
{
title:'Flag',
description:c.capital,
},
],
};
return (
<Card
key={c.name}
onClick={(e) => {
navigate('/country/${c.name}');
}}
{...countryInfo}
/>
)
})
}
</List>
</>
);
};
It's second components Details
import React from 'react';
import { useParams } from 'react-router-dom';
export const Details = ({match,params}) => {
const { name } = useParams();
return (
<div>
Details {match.params.name}
</div>
);
};
config.js
const BASE_URL = 'https://restcountries.com/v2/';
export const ALL_COUNTRIES=BASE_URL+"all?fields=name,flags,population,capital,region";
export const searchByContry=(name)=>BASE_URL+'name/'+name;
export const filterByCode=(code)=>BASE_URL+'alpha?code'+code.join('');
APP.jsx
import React from 'react';
import ReactDOM from 'react-dom';
import axios from 'axios';
import { Route,Routes,Router,useParams} from 'react-router-dom';
import {useState, useEffect} from 'react';
import './App.css';
import styled from 'styled-components';
import Header from './components/Header';
import { Main } from './components/Main';
import {NotFound} from './pages/NotFound';
import { HomePage } from './pages/HomePage';
import { Details } from './pages/Details';
function App() {
return (
<>
<Header/>
<Main>
<Routes>
<Route path="/" element={<HomePage/>}/>
<Route path="country/:name" element={<Details/>}/>
<Route path="*" element={<NotFound/>}/>
</Routes>
</Main>
</>
);
}
export default App;
HomePage itself looks like this
but when I click on flag/card it sends me on second page as expected but gives me this error
[2]:https://i.stack.imgur.com/39HEw.png
Also, I'm using react-router-domV6 and Axios
and this API https://restcountries.com/v2/all
also both Components are in
APP.js
Details is trying to read params from an undefined object, props.match in this case.
<Route path="country/:name" element={<Details />} /> // <-- no props passed!
...
import React from 'react';
import { useParams } from 'react-router-dom';
export const Details = ({ match, params }) => { // <-- match undefined
const { name } = useParams();
return (
<div>
Details {match.params.name} // <-- Oops, can't read params of undefined
</div>
);
};
Remove the props and access the values returned from the useParams hook.
import React from 'react';
import { useParams } from 'react-router-dom';
export const Details = () => {
const { name } = useParams();
return (
<div>
Details {name}
</div>
);
};
The target path is also malformed. The code navigate('/country/${c.name}') is navigating to the string literal "/country/${c.name}", which is likely not what you meant to do. Fix this to use a string template literal instead to inject the c.name value into the target path.
navigate(`/country/${c.name}`) // note the backticks instead of single quotes
I oftentimes find it useful/helpful to use the generatePath utility function to create path values.
Example:
import { generatePath, useNavigate } from 'react-router-dom';
...
const path = generatePath("/country/:name", { name: c.name });
navigate(path);

React useContext() default value has not changed even I provide a value

Why is the UserContext default value has not changed even I specify a value in <UserContext.Provider>? How can I override the default value of the Context?
this is the App.jsx
import './App.css';
import React, { createContext } from 'react';
import ComponentB from './components/hooks/context/ComponentC';
function App() {
return (
<div className="App">
<ComponentB />
</div>
);
}
export default App;
this is the ComponentB
import React, { createContext, useState } from 'react';
import ComponentC from './ComponentC';
export const UserContext = React.createContext('default');
export const ChannelContext = React.createContext('default');
const provider = UserContext.Provider;
function ComponentB() {
return (
<div>
<provider value='Username'>
<ComponentC />
</provider>
</div>
);
}
export default ComponentB;
this is the ComponentC
import React from 'react';
import ComponentE from './ComponentE';
const ComponentC = () => {
return <ComponentE />;
}
export default ComponentC;
this is the ComponentE
import React, { Component, useContext } from 'react';
import { UserContext, ChannelContext } from './ComponentB';
const ComponentE = () => {
const username = useContext(UserContext);
const channel = useContext(ChannelContext);
return <div>username : {username} channel : {channel}</div>;
}
export default ComponentE;
In your App.jsx file, you say this:
import ComponentB from './components/hooks/context/ComponentC';
^ ^
Down the chain, this leads to this being rendered:
<div className="App">
<div>
username : {username} channel : {channel}
</div>
</div>
As you can see, there's no provider.
Even still, if we fix this one character typo, the issue persists.
This is because you say
const provider = UserContext.Provider;
...
<provider>
...
</provider>
which isn't allowed.
If you do
<UserContext.Provider>
...
</UserContext.Provider>
it works.
https://codesandbox.io/s/wizardly-andras-csxxy?file=/src/App.js
Regarding the first issue, this is why you should do
export const MyComponent = () => <></>;
import { MyComponent } from "./MyComponent";
instead of
const MyComponent = () => <></>;
export MyComponent;
import MyComponent from "./MyComponent";

React-alert library does not seem to be re-rendering when state changes

So I'm trying to do error handling using a library called react-alert.
I'm using Axios as my REST handler. I created an action and a reducer, which are both working fine; saw my state update in my DevTools extension when I make a post request.
Here's my functional component called Alert.js
import React, { Fragment, useState } from "react";
import { useAlert } from "react-alert";
import { useSelector } from "react-redux";
import { useEffect } from "react";
export default function Alert(props) {
const alert = useAlert();
const error = useSelector((state) => state.errors.errors);
const [errorMSG, setERRORS] = useState();
useEffect(() => {
if (error !== errorMSG) {
setERRORS(error);
alert.show(errorMSG);
} else {
alert.show("Welcome!");
}
}, [errorMSG]);
return <Fragment />;
then I called it in my main App.js
//Main Imports
import React, { Component, Fragment } from "react";
import ReactDOM from "react-dom";
// React Alert
import { Provider as AlertProvider } from "react-alert";
import AlertTemplate from "react-alert-template-basic";
import Alert from "./layout/Alert";
const alertOptions = {
timeout: 3000,
position: "top center",
};
//Components import
import Header from "./layout/Header";
import Dashboard from "./products/Dashboard";
//Redux import
import { Provider } from "react-redux";
import store from "../store";
class App extends Component {
render() {
return (
<Provider store={store}>
<AlertProvider template={AlertTemplate} {...alertOptions}>
<Alert />
<Fragment>
<Header />
<div className="container-fluid">
<Dashboard />
</div>
</Fragment>
</AlertProvider>
</Provider>
);
}
}
ReactDOM.render(<App />, document.getElementById("app"));
I do see the Welcome! when the app mounts, but when I have an error I see nothing. Could anyone help me find a solution to this; would be really appreciated.
You see "Welcome" because when your component mounts first time, it calls the useEffect hook. But the useEffect hook isn't getting triggered afterwards because of the wrong dependency item provided for the useEffect. You need to add error instead of errorMSG, because error is coming from your store and you want to show the message when there is a new error emitted:
useEffect(() => {
if (error !== errorMSG) {
setERRORS(error);
alert.show(errorMSG);
} else {
alert.show("Welcome!");
}
}, [error]);

How can I maintain my store's state while using react-router and redux?

I am building an app prototype that essentially simulates ecommerce. I have components that each have different items that can be added to a cart(below I just show an example of how one would basically work). These components are accessed via different routes using the react-router. There is a header component that displays the number of items currently in the cart. The header gets the number of items in the cart from the state in the redux store. However, if I navigate to a new route, the store goes back to the default state. I need the the store to keep its state when a new route is navigated to. For example, if I go to the ShoppingPage, add an item to the cart, and then go back to the Home page, I need the cart to still have an item in it.
actions.js
export const actionTypes = Object.freeze({
UPDATE_CART: Symbol('UPDATE_CART'),
});
export const updateCart = (payload) => {
return {
type: actionTypes.UPDATE_CART,
payload,
};
};
export default actionTypes;
reducer.js
import actions from './actions';
export const INITIAL_STATE = {
cart: [],
};
export default (state = INITIAL_STATE, action) => {
switch (action.type) {
case actions.UPDATE_CART: {
return {
...state,
cart: action.payload,
};
}
default: {
return state;
}
};
};
index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';
import reducer, { INITIAL_STATE } from './reducer';
const store = createStore(reducer, INITIAL_STATE);
console.log(store.getState());
ReactDOM.render(
<Provider store ={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
, document.getElementById('root'));
serviceWorker.unregister();
ShoppingPage.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { updateCart } from './actions';
class ShoppingPage extends Component {
addToCart = () => {
const cart = [...this.props.cart];
cart.push('new item');
this.props.modifyCart(cart);
render() {
return(
<div>
<button onClick={addToCart}>
Add To Cart
</button>
</div>
)
}
}
const mapDispatchToProps = dispatch => ({
modifyCart: payload => dispatch(updateCart(payload)),
});
const mapStateToProps = state => ({
cart: state.cart,
});
export default connect(
mapStateToProps,
mapDispatchToProps,
)(ShoppingPage);
Home.js
import React, { Component } from 'react';
import { ListGroup, ListGroupItem } from 'reactstrap';
class Home extends Component {
render() {
return(
<div>
<ListGroup>
<ListGroupItem><a href='/ShoppingPage'>ShoppingPage</a></ListGroupItem>
</div>
)
}
}
export default Home;
Header.js
import React, { Component } from 'react';
import { Navbar, NavbarBrand } from 'reactstrap';
import { connect } from 'react-redux';
class Header extends Component {
render() {
return(
<Navbar sticky='top' className='nav'>
<NavbarBrand href='/'>Online Shopping</NavbarBrand>
<span>{'Items in Cart: '}{this.props.cart.length}</span>
</Navbar>
)
}
}
const mapStateToProps = state => ({
cart: state.cart,
});
export default connect(
mapStateToProps
)(Header);
Routes.js
import React from 'react';
import { Switch, Route } from 'react-router-dom';
import Home from './Home';
import ShoppingPage from './ShoppingPage';
const Routes = () => (
<Switch>
<Route exact path='/' component={Home} />
<Route exact path='/ShoppingPage' component={ShoppingPage} />
</Switch>
);
export default Routes;
App.js
import React from 'react';
import Routes from './Routes';
import Header from './Header';
function App() {
return (
<div>
<Header />
<Routes />
</div>
);
}
export default App;
What's likely happening is that during navigation the web app "reloads" again (which is wiping the redux state). In order to navigate with react router you want to look at <Link>.
For example,
Home.js
<a href='/ShoppingPage'>ShoppingPage</a>
should be changed to:
<Link to="/ShoppingPage">ShoppingPage</Link>

callback render loses router context for child react-router & redux

I've been developing an idea but am getting stuck on something unusual (my brain hurts on react-router).
I am trying to dynamically render a list of items using .map from a returned object (of multiple similar objects) and appending them to the render(){return(<div />)}.
I just dont know another way than call a function then .map the result for this callback.
I think that the way I'm doing this means the rendered items lose context. The react-router <Link /> will function as expected in the normal flow (placed inside the render(){return(<div />)} ) but not when the item is created from outside of the render. I have posted the error below the code.
I have read Many different ways of getting around this using context and location/history and withRouter. Frankly I'm lost.
I would appreciate if someone could look at my example below and guide me in the right direction.
A few notes:
- main focus appears to be in mystuff
- i have many unnecessary imports i know
- stripped down for clarity, i would get lost otherwise
index
import _ from 'lodash';
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import { store, history } from './store';
import Main from './Main';
import { routyr } from './Menu';
// remaining paths in Menu.js (routyr) for menu visibility
const router = (
<Provider store={store}>
<Router history={history}>
<Route path="/" component={Main}>
{routyr}
</Route>
</Router>
</Provider>
)
render (router, document.getElementById('app'));
Main
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import * as actionCreators from './actionCreators';
import App from './app';
function mapStateToProps(state){
return{
info: state.info,
myProfile: state.myProfile
}
}
function mapDispatchToProps(dispatch){
return { actions: bindActionCreators(actionCreators, dispatch) }
}
const Main = connect(mapStateToProps, mapDispatchToProps)(App);
export default Main;
routyr
import React from 'react';
import { Link } from 'react-router';
import { Router, Route, IndexRoute, browserHistory } from 'react-router';
import { Provider } from 'react-redux';
import { store, history } from './store';
//pages
import App from './app';
import Landing from './Landing';
import Me from './mystuff';
import ViewStuff from './viewStuff';
//Routes for index.js
export const routyr = (
<span>
<IndexRoute component={Landing} />
<Route path="/myStuff" component={Me} />
<Route path="/viewStuff" component={ViewStuff} />
</span>
)
//Menu types
//loaded by app.js
export const menuLoggedIn = (
<div className="MainMenu">
<Link to='/' className="buttonA green">Home</Link>
<Link to='myStuff' className="buttonA green">My Stuff</Link>
</div>
);
export const menuLoggedOut = (
<div className="MainMenu">
<Link to='/login' className="buttonA green">Login</Link>
</div>
);
app
import React from 'react';
import _ from 'lodash';
import { Link } from 'react-router';
import auth from './auth';
import Landing from './Landing';
import Header from './Header';
import { menuLoggedIn, menuLoggedOut } from './Menu';
export default class App extends React.Component {
constructor(){
super();
this.state={
auth: auth.loggedIn(),
menu: null
};
}
componentWillMount(){
if (this.state.auth==true) {
this.setState({
menu: menuLoggedIn
})
}else{
this.setState({
menu: menuLoggedOut
});
}
}
render(){
return (
<div>
<Header />
{this.state.menu}<br />
<div id="view">
{React.cloneElement(this.props.children, this.props)}
</div>
</div>
);
}
};
mystuff
import React, { PropTypes } from 'react';
import { render } from 'react-dom';
import { Link } from 'react-router';
import { withRouter } from 'react-router';
import { Provider } from 'react-redux';
import * from './whacks';
export default class Me extends React.Component{
constructor(){
super();
}
componentDidMount() {
function listThem(oio){
oio.map(function(ducks){
render(
<div className="ListItem">
<Link to="/viewStuff"> _BROKEN_ View Stuff</Link>
<div className="listLabel">{ducks.type}</div>
<h3>{ducks.description.title}</h3>
{ducks.description.long}
</div>, document.getElementById('fishes').appendChild(document.createElement('div'))
);
});
}
var some = new Whacks();
some.thing(more, (close, open) => {
if(close){
console.log(close));
} else {
doIt(open);
}
});
}
render(){
return(
<div>
<Link to="viewStuff"> _WORKING_ View Stuff</Link>
<div id="fishes">
</div>
</div>
)
}
}
store
import { createStore, compose } from 'redux';
import { syncHistoryWithStore } from 'react-router-redux';
import { browserHistory } from 'react-router';
import { combineReducers } from 'redux';
import { routerReducer } from 'react-router-redux';
/*-------ROOT REDUCER---------*/
/*-------DEFAULT STATES---------*/
/*-------CREATE STORE---------*/
/*-------INTEGRATE HISTORY---------*/
import me from './reducers/obj';
import myProfile from './reducers/myProfile';
const rootReducer = combineReducers(
{
routing: routerReducer,
me,
myProfile
}
);
//TEMP remove harcoded var
const uuidSet = "fa78d964";
export const defaultState = {
uuid: uuidSet,
};
export const store = createStore(rootReducer, defaultState, window.__REDUX_DEVTOOLS_EXTENSION__ && window.__REDUX_DEVTOOLS_EXTENSION__());
export const history = syncHistoryWithStore(browserHistory, store);
actionCreators
export function me (obj){
return {
type: "ADD_OBJECTLIST",
obj
}
}
export function myProfile (dump){
return {
type: "MY_DATA",
dump
}
}
from package.json
"react-redux": "^5.0.2",
"react-router": "^3.0.2",
"react-router-redux": "^4.0.7",
"redux": "^3.6.0",
error
Uncaught Error: s rendered outside of a router context cannot navigate.
#UG,
I have tried the following in mystuff:
constructor(){
super();
this.state={
oio: {}
};
}
and
some.thing(more, (close, open) => {
if(close){
console.log(close));
} else {
this.setState({
oio: open
});
}
});
and
render(){
let flat = this.state.oio;
flat.map(function(ducks){
return (
<div className="ListItem">
<Link to="/viewStuff">View Stuff</Link>
<div className="listLabel">{ducks.type}</div>
<h3>{ducks.description.title}</h3>
{ducks.description.long}
</div>
)
})
}
and receive
Uncaught TypeError: flat.map is not a function
at Me.render
I am not sure if I get your issue completely. But I think you want to use Link inside render() method of myStuff
You can change that to following :
render(){
return(
<div>
<Link to="viewStuff"> _WORKING_ View Stuff</Link>
<div id="fishes">
{
oio.map(function(ducks){
return (
<div className="ListItem">
<Link to="/viewStuff"> _BROKEN_ View Stuff</Link>
<div className="listLabel">{ducks.type}</div>
<h3>{ducks.description.title}</h3>
{ducks.description.long}
</div>
);
}
</div>
</div>
)
}
As per the comment from James,
You should use react state to maintain oio object.
constructor() {
super();
//init
this.setState({oio : {}});
}
and update the state in async call, when state updates, component can be rerendered.
Huge thanks to UG_ for smacking me in the ear with state.
I have pulled in a component and created each components props from the callback objects.
My Working solution is as follows in mystuff:
constructor(props){
super(props);
this.state={
oio: []
}
}
componentDidMount() {
let listThem = (stuff) => {
let ioi = [];
stuff.forEach(function(dood, index, array) {
let lame = <MyItem plop={dood} key={index} />;
ioi.push(lame);
});
return (
this.setState({
oio: ioi
})
);
}
var some = new Whacks();
some.thing(more, (close, open) => {
if(close){
console.log(close));
} else {
listThem(open);
}
});
}
render(){
return(
<div>
{this.state.oio}
</div>
)
}
Which renders a new copy of the MyItem component with props from each returned object. So now my returned items contain context!

Categories

Resources