React context to change components - javascript

I'm building a dashboard that displays multiple graphs on the main page.
A graph has additional filter functions (change min max). The graph is in one component with the control elements and the display for the min/max is in another component.
I want to change the Min/Max display when the graph has changed (i.e. after switching the control element)
Currently my components look like this:
graph.js
var controllElements = (<button onClick={() => this.minmax('min)>..
<button onClick={() => this.minmax('max)>)
return(
<div>
{graph}
{controllElements}
</div>)
minmaxview.js
return(
<div>{value}
</div>)
I do not use redux in this project and it is not possible to use it in this project.
So I thought I would use the react context api but have no experience with it.
In the end, it should look like this.
If button min is clicked (graph.js) => Change value in minmaxview.js
My question is now. Can I use react context for this? If possible, are there any good examples? Or does anyone have another solution?
If necessary through sessions (Probably not possible due to an infinite loop in the compUpdate when setting state)
Thank you in advance.

You would do something like below to utilize React Context API's Provider/Consumer concept. Clearly it will need to be tailored within your app on however it is structured but based on minimal example that you provided, I tried my best to come closely to do Provider/Consumer psuedo react-code.
//graph.js
export const GraphContext = React.createContext(0); // asuming default for min/max is 0
var controllElements = (<button onClick={() => this.minmax('min)>..
<button onClick={() => this.minmax('max)>)
return(
<div>
{graph}
<GraphContext.Provider value={this.state.graphValuesObject}>
{/* Assume minmax function will update graphValuesObject that can be passed around */}
{controllElements}
</GraphContext.Provider>
</div>)
//minmaxview.js
import GraphContext from './graph.js';
return (
<GraphContext.Consumer>
{graphValuesObject => <div>{value}</div> }
</GraphContext.Consumer>
)
More info here: https://reactjs.org/docs/context.html

Related

i18next <Trans> avoid retranslation of nested components

I am using i18next with React for my project. For most of the translations I use the t() function with normal text. However, for some cases I would prefer to use the <Trans /> component.
I prepared some example below:
function Example() {
const { t } = useTranslation();
const reusableComponent = <p>{t('TranslateMeOnlyOnce')}</p>;
return (
<>
{reusableComponent}
<Trans i18nKey={'OnlyOnceWrapper'}>
The reusable component says {reusableComponent}
</Trans>
</>
)
}
Which expect would my ranslation file to look something like this:
...
TranslateMeOnlyOnce: 'Translate me only once',
OnlyOnceWrapper: 'The reusable component says <1>Translate me only once</1>',
...
However, as you can see I would be required to translate the text of the nested component twice. I would really like to just translate the The reusable component says {reusableComponent} in the second translation string and not all of the first translation string for another time.
For those wondering why I would want to do this, lets say const reusableComponent is a Create Button with some function to create a new entry placed at the top of the Screen. When however, there is no entry displayed in my list of entries I want to display a message saying something like: 'Unfortunately there is no entry yet. Click on [CreateButtonIsDisplayedHere] to create a new entry'.
Let's say I experience that users find a translation 'New' instead of 'Create' more useful or the other way round, I would want to change only the translation of the button and not every other place containing this button.
I also found myself a solution to this issue already, however, in my experience this is super ugly to maintain as I need to pass the content as string and not as React child Element which pretty much take all advantages of using the <Trans /> Component and I could better use the t() function using sprintf.
function UglySolution() {
const { t } = useTranslation();
const reusableComponent = <p>{t('Translate me only Once')}</p>;
return (
<>
{reusableComponent}
<Trans
i18nKey={'OnlyOnceWrapper'}
components={[reusableComponent]}
variables={{ v: t('Translate me only Once') }}
defaults='The reusable component says <1>{{v}}</1>'
/>
</>
)
}
Which would expect my translation file to look something like this:
...
TranslateMeOnlyOnce: 'Translate me only once',
OnlyOnceWrapper: 'The reusable component says <1>{{v}}</1>',
...
So my question is: Can I make my translation file to look something like the second example I created without using the ugly <Trans /> component from the second example and more something like the component in the first example I created?
Resusing translations
There is a way using the <Trans /> component as in the first example with reusing previous translations. Using $t(KeyToAnotherTranslation) inside the translation string.
This does not fully solve the issue as it's also not too handy to maintain but would be a lot prettier than the second example shown.
function Example() {
const { t } = useTranslation();
const reusableComponent = <p>{t('TranslateMeOnlyOnce')}</p>;
return (
<>
{reusableComponent}
<Trans i18nKey={'OnlyOnceWrapper'}>
The reusable component says {reusableComponent}
</Trans>
</>
)
}
(Taken 1:1 from the code from the question)
With a translation file looking something like this:
...
TranslateMeOnlyOnce: 'Translate me only once',
OnlyOnceWrapper: 'The reusable component says <1>$t(TranslateMeOnlyOnce)</1>',
...
More information about variable usage in i18next.

React re-rendering components issue

I started my first real project in React, I'm developing a portfolio site and I have a strange issue when I use the category filter to switch the categories.
The issue is: The site shows all the projects, if you click in Artwort or switch between the buttons you will see that not all the projects are showing the transition animation, it seems that the projects in the current category are not rendering again. Another weird thing is in the react developer tools the profiler shows how all the components are rendering when y change the category.
I think this behavior will have logical explanation, but I couldn't find it because I'm using useEffect dependency with the currentCat state.
you can see the error here: https://toiatemp-manuverrastro.vercel.app/
Here is the components:
https://github.com/manuverrastro/toia/blob/main/src/components/Filter.js
https://github.com/manuverrastro/toia/blob/main/src/components/Work.js
https://github.com/manuverrastro/toia/blob/main/src/components/WorkList.js
https://github.com/manuverrastro/toia/blob/main/src/components/WorkListContainer.js
Does anyone have some idea of what is happening?
It is because the key property in your WorkList.js file. Although the categories are different, work.id is not getting changed while you switch between the tabs. Since you have given work.id as the key parameter React tries to render the same previous element without re-rendering it. Because of that you don't see any animation in those Work components.
You can change your key prop which is given to the Work component, by concatenating the current selected category. So each time you switch between the tabs, key prop will differ. It will result in re-rendering the Work components. I have changed your code as my suggestion.
WorkList.js
import Work from "./Work";
const WorkList = ({ work, currentCat }) => {
return (
<>
{currentCat
? work
.filter((work) => work.category == currentCat)
.map((work) => {
return (
<Work
key={`${currentCat}-${work.id}`}
id={work.id}
slug={work.slug}
thumbnail={work.thumbnail}
image={work.image}
title={work.title}
category={work.category}
/>
);
})
: work.map((work) => {
return (
<Work
key={`all-${work.id}`}
id={work.id}
slug={work.slug}
thumbnail={work.thumbnail}
image={work.image}
title={work.title}
category={work.category}
/>
);
})}
</>
);
};
export default WorkList;

React: passing mapped data from button onclick to display on another component

I'm having some difficulty passing data that I have mapped from an API, it displays a bunch of Event cards based on how many events there are in the API array.
This my Event Cards component;
export default function EventCard() {
const classes = useStyles();
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
const result = await axios("http://localhost:23455/Event");
setData(result.data);
};
fetchData();
}, []);
const handleClick = (value) => () => {
console.log(value);
};
return (
<div>
<Row>
{" "}
{data.map((item) => (
<Card
className={classes.root}
style={{ marginRight: "25px", marginBottom: "25px" }}
>
<CardHeader
avatar={
<Avatar aria-label="recipe" className={classes.avatar}>
{item.icon}
</Avatar>
}
action={
<IconButton aria-label="settings">
<MoreVertIcon />
</IconButton>
}
title={item.name}
subheader="September 14, 2016"
/>
<CardMedia
className={classes.media}
image={LordsLogo}
title="Paella dish"
/>
<CardContent>
<Typography variant="body2" color="textSecondary" component="p">
<p key={item.id}> {item.description}</p>
<p key={item.id}> {item.startDate}</p>
<p key={item.id}> {item.endDate}</p>
</Typography>
</CardContent>
<CardActions disableSpacing>
<IconButton aria-label="add to favorites">
<Button variant="outlined">Steward</Button>
</IconButton>
<IconButton aria-label="share">
<Button variant="outlined" onClick={handleClick({ item })}>
Tickets
</Button>
</IconButton>
</CardActions>
</Card>
))}
</Row>
</div>
);
}
I have an onclick function which logs what data is being added into "value" onclick, if I click a card, the console log the value of that specific card:
What I'm trying to do now is use this information in another component called ServiceForm. I want to be able to click the button, be linked to ServiceForm, and use the variables within "Item" in the ServiceForm component, Ideally as shown below;
<Form.Group as={Col} controlId="formGridForeName">
<Form.Label>**{item.description}**</Form.Label>
<Form.Control
name="firstName"
placeholder="Enter name"
onChange={this.handleChange}
/>
</Form.Group>
EventCard if a functionalComponent and ServiceForm is a class based component, how could I pass the information from the first to the latter? Thanks
Edit: To show component hierarchy
ServiceForm is rendered in ServiceApplication, as shown:
import * as React from "react";
import { ServiceForm } from "../components/ServiceForm";
class ServiceApplication extends React.Component {
render() {
return (
<React.Fragment>
<h1>Service Application</h1>
<h6>Users can use this page to apply for tickets.</h6>
<ServiceForm />
</React.Fragment>
);
}
}
export default ServiceApplication;
EventCard component is rendered in EventsPage, as shown below;
import EventCard from "../components/EventCards/EventCard";
class EventsPage extends React.Component {
render() {
return (
<React.Fragment>
<h1>Events</h1>
<h6>
Welcome to the Events, here you can apply for seats or tickets at the
events shown below.
</h6>
<Row>
<EventCard />
</Row>
</React.Fragment>
);
}
}
export default EventsPage;
The idea is to pass on the ID when Clicking the 'Tickets' button on the EventCard (the ID is being pulled from an API and mapped).
It's a question of how you want to store state in your app really.
Take a simple example where a component which is a child of another. In that case, the parent can store the state locally e.g. useState() and pass it down as props to the child.
const SomeParent = () => {
const [isTrue, setIsTrue] = React.useState(true)
return (
<Child isTrue={isTrue} />
)
}
There may be cases where you want to share state across multiple components, or across your whole app. In that case, you have a load of different options.
1. Lifting state up to the highest point you need.
It would be easiest to move your state which is shared between multiple components up to the highest point that the two components share.
e.g.
Section component <- store state here
Parent one
child one
child two
Parent two
child one
Here you can again either useState() or alternatively store the state in a useReducer() and pass dispatch down.
The data flow would then look something like this:
initialise state in the section component e.g. const [someState, setSomeState]
pass the setSomeState callback down to the Parent component and in turn child component as a prop e.g.
In the child component set state onClick using the callback e.g. onClick = {() => action(item)}
pass the state value down to your second Parent component and in turn child component e.g.
You have access to the state in your form
This can be an object or multiple state values. As complexity increases, I tend to reach for useReducer().
2. Implementing useContext OR Redux in your application to create an application state accessible from any component (This is a much bigger explanation and there are lots of resources on each...
NOTE: I'm not sure why you're using two sets of brackets here:
const handleClick = (value) => () => {
console.log(value);
};
and when you're calling handleClick you need to switch it for an arrow function if you're passing a value:
onClick={handleClick({ item })
...
onClick={() => handleClick(item)}
Try this:
const handleClick = (value) => {
console.log(value);
};
<Button variant="outlined" onClick={() => handleClick({ item })} />
basically, you want to trigger a class component function from a functional component by passing item prop, if it was a parent child components you can pass a function as a prop and trigger it whenever you want from your parent or child component.
but in your case both components are not connected, what you can do without using redux is to create a public local storage for ex let's call it localStorage.js :
var PublicData = (function() {
var data = null;
var getData = function() {
return data;
};
var setData = function(props) {
data = props;
};
var clearData = function() {
data = null;
};
return {
GET: getData,
SET: setData,
CLEAR: clearData
}
})();
export default PublicData;
then you import it and use like the following :
import PublicData from './PublicData.js';
PublicData.SET(apiData); // you set your data once you fetch it
const data = PublicData.GET(); // get data
PublicData.CLEAR(); // clear your data
Note : this is not an actual localStorage, but it work the same, it will help you to share variables between your components.
Parent
/ \
Service Event
I'm guessing from your question that a simplification of your app tree looks like the above.
The problem you are having is the following:
Parent
/ \
Service Event
Data
In a top down architecture Service does not know about Event and any data over there in the that branch.
So you have 2 options (likely more, but these 2 get you a long way):
Hoist the data
Parent
Data
/ \
Service Event
Store the data somewhere else and provide a mechanism for access
Data
Parent
/ \
Service Event
Option 1 can be achieved by passing props (the data) and functions (passed using props) to manipulate the data (or, indeed, populate it in the first place i.e. fetch it)
The flow would look something like:
Parent is stateful. Parent passes the current state of the data to children that care about it (in your case that looks like Service and Event). Parent passes a function to Event, which can be attached to a click handler within the Event subtree of elements.
Due to passing a function down, when that click handler is invoked it can set the state of the Parent, from which the normal top-down rendering flow will handle setting updates and passing the data changes down the tree, whereby they will (usually) invoke a re-render.
I think Recoil uses this method, and it is becoming increasing popular once more as we look to split applications up and eschew global state management.
Option 2 is where data management libraries like Redux, Cerebral, MobX et al live. If you’re familiar with the publisher/subscriber pattern then you'll know how they work.
In essence, these libraries hold shared state for your application, such as the data you have here. They then (typically) provide a pattern for you to manage changes to the state (via a published event) and ensure that components that subscribe to that data receive the new state and update.
The 'discussion' here differs from option 1:
The click handler publishes an event, which, in essence, asks for some manipulation to the data. Manipulation occurs (or not), and subscribers are updated. In your case, the click would tell the centralised store/s (i.e. anything listening) to fetch the data, when it is done and the data is changed (from empty to filled) it lets all concerned elements (or, quite possibly, the entire application tree and leverage top-down rendering) know about it and they can do what they need to do.
Which option you pick is entirely up to you, they both have pros and cons.
Good luck thinking about your data, how you store it, and how you use it!
I'm glad you asked this question because this is a problem that has been faced and solved a lot of the times. Other contributors have written nice comprehensive answers so I'll keep it short.
1. Move shared stated to a common ancestor.
This one's pretty straight forward but gets very hairy very quickly.
2. Use Context API
You can use react's context API to move code responsible for fetching data to your context and use that data anywhere in your app without a lot of hassle. This approach is getting quite popular nowadays. Take a look at this and here's a sweet library to help you get started.
3. Use a state management library (preferred)
You can choose to use any state management library you like to solve this problem. There are quite a few options, and redux is the most popular one and for a very good reason. It enables you in implementing separation for concern between your data and your UI, but is a bit verbose.
Mobx is also popular and has a good developer experience but I personally don't like that it enforces a certain style to your code.
If you're new to react and state management then these libraries could be really daunting. I'd suggest you start with zustand. It's super simple and gets the job done. ;)
You can simply use Custom Event https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent. It is not the best practice but it work.In EventCard, when user click you dispatch a Custom event with payload is item, then ServiceForm will listener that event and store item as state. Working demo here
https://codesandbox.io/s/distracted-aryabhata-ec6gc
EventCard
const handleClick = () => {
const item = {
description: "This is description"
};
const event = new CustomEvent("customEvent", {
detail: item
});
window.dispatchEvent(event);
};
Service Form
const [item, setItem] = useState(null);
...
useEffect(() => {
const handler = (e) => {
setItem(e.detail);
};
window.addEventListener("customEvent", handler);
return () => window.removeEventListener("customEvent", handler);
}, []);

How to test function in a react SFC which using hooks?

https://reactjs.org/docs/hooks-faq.html#how-to-test-components-that-use-hooks
the document show that we should use React DOM to test component.But in many cases, our logical components render another ui component.And maybe the function in logical component will be passed to the ui component as a prop, just like
function Foo(){
const [count, setCount] = useState(0);
useEffect(() => {
document.title = `You clicked ${count} times`;
});
function onChange(value) {
setCount(value);
}
return <Bar value={count} onChange={onChange} />
}
function Bar(props){
const { value, onChange } = props;
return (
<div>
<p>You clicked {value} times</p>
<button onClick={() => onChange(count + 1)}>
Click me
</button>
</div>
);
}
In this case, how can I test the onChange() and other functions which can't be obtained in DOM?This is the simplest case. If we use some component library like material-design and ant-design, we usually don't know the DOM structure.
As the docs suggest:
If your testing solution doesn’t rely on React internals, testing
components with Hooks shouldn’t be different from how you normally
test components.
The goal is not to test the onChange function at all, whether the hooks work or not is already tested by the React team.
Your current change to render a component does not change the test at all, rendering Foo still renders a button and p however deep the component chain goes.
On using a framework like Antd or Material Design, it might be difficult to know the complete DOM structure. But instead, or better you can query by things that the user sees on your page.
For eg. using the React testing library recommended in the docs:
const button = getByText(container, /Click Me/i);
This ties in directly with what user is seeing on the page and leads to much better tests.

Best practice for mapping an array in React app for performance optimization

I have a simple dictionary app I'm making that returns search results. A search is queried on every onChange of the search field using a lokiJS in-memory database for the dictionary, so the results come very quick.
It is important for me to optimize the rendering of the results, so that the 50 or so filtered search results keep flowing as the user types and the queries happen on each key stroke.
Here's what I've been doing so far: (this works but is it the best/fastest way?)
Each (sync) query of the database returns an array of objects, which I then map with something like this:
queryDB(query) {
const results = queryLokiJsDB(query);
const resultsMapped = results.map((mpd) =>
<dl key={mpd["$loki"]} onClick={() => this.clickFunction(mpd.p, mpd.f)}>
<dt>{mpd.p} - {mpd.f}</dt> <dd>{mpd.e} <em>{mpd.c}</em></dd>
</dl>);
this.setState({ results: (
<div>
{resultsMapped}
</div>
)});
}
Then, once I have the results mapped like that, I add the mapped components to the state, and then the screen gets rendered with the new results.
render() {
return (
<div>
<SearchBarStuff />
<div className="results-container">
{this.state.results}
</div>
</div>
);
}
I made this when I was just learning React and I understand that people consider it really bad practice to store components in the state.
I was wondering what the best practice in terms of performance optimization would be (and code cleanness). Should I store the results array in state then render then map them into components in the render function? Would that cause any performance hits?
Another answer said that "A function in the render method will be created each render which is a slight performance hit."
And why is it so bad to store components in state? Is it just for code clarity? Keeping the state small? I appreciate your help and patience.
I'm not quite sure but I am thinking the same problem as #larz explained in comments. Also, as you mentioned the state will be clearer. So here what would I do if I were you.
First, set your state just with the results:
queryDB(query) {
const results = queryLokiJsDB(query);
this.setState({ results });
}
Then, map through the results but instead of creating your JSX immediately I would use a separate component. Pass it mpd (the element) and onClick function with its reference.
render() {
return (
<div>
<SearchBarStuff />
<div className="results-container">
{this.state.results.map( mpd => (
<Item key={mpd["$loki"]} mpd={mpd} onClick={this.clickFunction} />
) )}
</div>
</div>
);
}
Use the Item like that:
const Item = ( props ) => {
const { mpd, onClick } = props;
const handleClick = () => onClick( mpd.p, mpd.f );
return (
<dl onClick={handleClick}>
<dt>{mpd.p} - {mpd.f}</dt> <dd>{mpd.e} <em>{mpd.c}</em></dd>
</dl>
);
}
In this way, you are not using an arrow function in your onClick handler, so this function will not be recreated in every render since we use the reference.

Categories