React - implement a component as js api - reactjs

I want to create a stack of toast messages in my app.
I dont want to re-draw the <Toast component over and over again.
My need is to call it as a function and it will render itself
Something like:
Toast.push({time: 5, message: 'Data was submitted', type: 'success'});
Toast.push({time: 2, message: 'Data was not processed yet', type: 'warn'});
This way I can manage the DOM array inside the component and it will be some sort of a singleton component. One instance for the entire app
Is it possible to call and display a component like: Toast.push(...);
Im using function based components with the latest react
Update
This concept is implemented in ant.design https://ant.design/components/message/ - message.warn('Some message').
I want to avoid props and contexts... It needs to be as simple as possible... Toast.push(...) that's it
Thanks

You can create a custom useToast hook, returning an element and a push function.
//App.js
import useToast from "./useToast";
export default function App() {
const { toast, push } = useToast();
return (
<div>
<p>Your stuff here</p>
{toast}
<button onClick={() => push({ message: "hi" })} />
</div>
);
}
//useToast.js
import { useState } from "react";
export default function useToast(initial = []) {
const [list, setList] = useState(initial);
return {
push(data) {
setList([...list, data]);
},
toast: (
<ul>
{list.map(({ message }, i) => <li key={i}>{message}</li>)}
</ul>
),
};
}

Related

loading components twice, probably because of useEffect wrong set-up

I have built a ToDo React App (https://codesandbox.io/s/distracted-easley-zjdrkv) that does the following:
User write down an item in the input bar
User hit "enter"
Item is saved into the list below (local storage, will update later)
There is some logic to parse the text and identify tags (basically if the text goes "#tom:buy milk" --> tag=tom, text=buy milk)
The problem I am facing are:
useEffect runs twice at load, and I don't understand why
After the first item gets saved, if I try saving a second item, the app crashes. Not sure why, but I feel it has to do with the point above...and maybe the event listener "onKeyDown"
App
import { useState, useEffect } from 'react'
import './assets/style.css';
import data from '../data/data.json'
import InputBar from "./components/InputBar/InputBar"
import NavBar from "./components/NavBar/NavBar"
import TabItem from "./components/Tab/TabItem"
function App() {
const [dataLoaded, setDataLoaded] = useState(
() => JSON.parse(localStorage.getItem("toDos")) || data
)
useEffect(() => {
localStorage.setItem("toDos", JSON.stringify(dataLoaded))
console.log('update')
}, [dataLoaded])
function deleteItem(id){
console.log(id)
setDataLoaded(oldData=>{
return {
...oldData,
"items":oldData.items.filter(el => el.id !== id)
}
})
}
return (
<div className='container'>
<NavBar/>
<InputBar
setNewList = {setDataLoaded}
/>
{
//Items
dataLoaded.items.map(el=>{
console.log(el)
return <TabItem item={el} key={el.id} delete={deleteItem}/>
})
}
</div>
)
}
export default App
InputBar
import { useState, useEffect } from 'react'
import { nanoid } from 'nanoid'
import '../../assets/style.css';
export default function InputBar(props){
const timeElapsed = Date.now();
const today = new Date(timeElapsed);
function processInput(s) {
let m = s.match(/^(#.+?:)?(.+)/)
if (m) {
return {
tags: m[1] ? m[1].slice(1, -1).split('#') : ['default'],
text: m[2],
created: today.toDateString(),
id:nanoid()
}
}
}
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(e.target.value), ...oldData.items]
}
}
)
e.target.value=""
}
}
return(
<div className="main-input-div">
<input type="text" onKeyDown={(e) => handleKeyDown(e)}/>
</div>
)
}
Tab
import { useState } from 'react'
import "./tab-item.css"
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import { faTrash } from "#fortawesome/free-solid-svg-icons";
export default function TabItem(props) {
return (
<div className="tab-item">
<div className="tab-item-text">{props.item.text}</div>
<div className="tab-item-actions">
<FontAwesomeIcon icon={faTrash} onClick={()=>props.delete(props.item.id)}/>
</div>
<div className="tab-item-details">
<div className="tab-item-details-tags">
{
props.item.tags.map(el=><div className="tab-item-details-tags-tag">{el}</div>)
}
</div>
</div>
<div className="tab-item-date">{props.item.created}</div>
</div>
)
}
The above answer is almoost correct. I am adding more info to the same concepts.
useEffect running twice:
This is most common ask in recent times. It's because the effect runs twice only in development mode & this behavior is introduced in React 18.0 & above.
The objective is to let the developer see & warn of any bugs that may appear due to a lack of cleanup code when a component unmounts. React is basically trying to show you the complete component mounting-unmounting cycle. Note that this behavior is not applicable in the production environment.
Please check https://beta-reactjs-org-git-effects-fbopensource.vercel.app/learn/synchronizing-with-effects#step-3-add-cleanup-if-needed for a detailed explanation.
App crashes on second time: It's probably because you are trying to update the input value from event.target.value if you want to have control over the input value, your input should be a controlled component meaning, your react code should handle the onChange of input and store it in a state and pass that state as value to the input element & in your onKeyDown handler, reset the value state. That should fix the crash.
export default function InputBar(props){
const [inputVal, setInputVal] = useState("");
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(e.target.value), ...oldData.items]
}
}
)
setInputVal("")
}
}
return(
<div className="main-input-div">
<input
type="text"
value={inputVal}
onChange={(e) => {setInputVal(e.target.value)}}
onKeyDown={(e) => handleKeyDown(e)}
/>
</div>
)
}
Hope this helps. Cheers!
Your app is using strict mode, which in a development mode renders components twice to help detect bugs (https://reactjs.org/docs/strict-mode.html#detecting-unexpected-side-effects).
root.render(
<StrictMode>
<App />
</StrictMode>
);
As for the crash, I think it's happening due to props.setNewList being an asynchronous call and the resetting of e.target.value - something like this seemed to fix it for me:
function handleKeyDown(e) {
console.log(e.target.value)
console.log(document.querySelector(".main-input-div input").value)
if(e.keyCode==13){
const inputVal = e.target.value;
props.setNewList(oldData =>{
return {
...oldData,
"items" : [processInput(inputVal), ...oldData.items]
}
}
)
e.target.value=""
}
}
I will add, that using document.querySelector to get values isn't typical usage of react, and you might want to look into linking the input's value to a react useState hook.
https://reactjs.org/docs/forms.html#controlled-components

I am not getting data of my useState through .map function in React Js

React Js
I am trying to display data through .map which is in react state object, but I am not getting data on browser with any error, yes I now that I am not write setState in this code because i want only display my data on my browser. if this problem will be solved then I will done my setState, I Think setState is not necessary for only showing data which is in State Object.
import React from 'react';
import TinderCard from 'react-tinder-card';
function TinderCards(){
const [people, setPeople] = React.useState([
{
name: 'steve jobs',
url: 'https://cdn.britannica.com/04/171104-050-AEFE3141/Steve-Jobs-iPhone-2010.jpg'
},
{
name: 'mark zukerberg',
url: 'https://cdn.britannica.com/54/187354-050-BE0530AF/Mark-Zuckerberg.jpg'
}
]);
return (
<div>
{
people.map(person =>{
<h1>{person.name}</h1>
})
}
</div>
)
// const people = [] same thing
// BAD
// const people = [];
// people.push('sonny', 'qazi')
// GOOD (push to an array in REACT)
// setPeople([...people, 'mathan', 'lal'])
}
export default TinderCards;
You don’t return anything from map.
Change your return section to this:
return (
<div>
{people.map(person => (
<h1>{person.name}</h1>
))}
</div>
)
or if you like to keep the curly brackets syntax:
return (
<div>
{people.map(person => {
return <h1>{person.name}</h1>
})}
</div>
)

Need to re-render component after state is set to true

In my react app, I have a page that allows the user to "add" exercises to a personal list. The option to add an exercise is included in one "page" and the ability to see added exercises is view in a other. Side by side showing the two pages, notice 'Your Exercises' is empty. What I am trying to do is display these selected exercises. The exercises themselves are loaded via mapping function, from an array of objects. each object has a "selected" field and are set as "false" by default. My "add" button in the exercises page changes the state value of each component to "true", so now what I want to do is get these "true" values and render them on the page where it should show your exercises.
//Render your exercises
import React from "react";
import ExListItem from "./ExListItem";
// import selectedExercises from "../../../selectedExercises";
import exercises from "../../../exercises";
const selectedExerciseList = exercises
.filter(function (item) {
return item.selected === true;
})
.map(function ({name, reps}) {
return {name, reps};
});
// MAP HERE
function createSelectedExercises(exercises) {
return <ExListItem name={exercises.name} reps={exercises.reps} />;
}
function ExerciseList() {
return (
<ul data-tag="channelList" id="exercise-list" class="list-group mb-3">
{selectedExerciseList.map(createSelectedExercises)}
</ul>
);
}
export default ExerciseList;
Shouldnt this map the exercises?
You may need to use React state to accomplish this! You can do so with the following:
import React, { useState, useEffect } from 'react'
const Component = () => {
const [exercises, setExercises] = useState([])
useEffect(() => {
setExercises(exerciseFilterFunction(exercises)) // this will cause the re-render
}, [])
return (
<div>{exercises.map(e => <div>{e.name}</div>)}</div>
)
}

Best way to access react-table instance from child to parent component [need your opinions]

Currently I'm doing this:
import * as React from "react";
import { useTable } from "react-table";
const Table = ({ onInit }) => {
const table = useTable({ data: [...], columns: [...] });
React.useEffect(() => {
onInit(table);
}, []);
return <table>...</table>
};
const TableParent = () => {
const [table, setTable] = React.useState();
return <div>
<Table onInit={table => setTable(table)} />
<div>
{/* Used table instance here */}
</div>
</div>
};
but not sure if this is fine or it is a bad pattern.
I'm a React beginner.
Please, I want to hear your opinions.
Thank you!
I do it like this, and it works well
Let's assume:
Parent component is App and
Child component is Table
From Parent component: You can pass a callback to table component
In table component: You call the same callback passing instance as a parameter using hooks.useInstance plugin point
I made it easy for you, Check the console log:
https://codesandbox.io/s/get-react-table-instance-in-parent-component-rx66s?file=/src/App.js

ReactJS hooks useContext issue

I'm kind of to ReactJS and I'm trying to use useContext with hooks but I'm having some trouble. I've been reading through several articles but I could not understand it.
I understand its purpose, but I can't figure out how to make it work properly. If I'm correct, the purpose is to be able to avoid passing props down to every children and be able to access values from a common provider at any depth of the component tree. This includes functions and state values. Please correct me if I'm wrong.
I've been testing with the following files. This is the ManagerContext.js file:
import { createContext } from 'react';
const fn = (t) => {
console.log(t);
}
const ctx = createContext({
title: 'This is a title',
editing: false,
fn: fn,
})
let ManagerContext = ctx;
export default ManagerContext;
Then I have the LessonManager.js file which is used in my main application:
import React from 'react';
import LessonMenu from './LessonMenu.js';
export default function LessonManager() {
return (
<LessonMenu />
)
}
And finally the LessonMenu.js:
import React from 'react';
import 'rsuite/dist/styles/rsuite.min.css';
import ManagerContext from './ManagerContext.js';
export default function LessonMenu() {
const value = React.useContext(ManagerContext);
return (
<div>
<span>{value.title}</span>
<button
onClick={()=>value.fn('ciao')}
>click</button>
<button
onClick={()=>value.title = 'new title'}
>click</button>
</div>
)
}
In the LessonMenu.js file the onClick={()=>value.fn('ciao')} works but the onClick={()=>value.title = 'new title'} doesn't re render the component.
I know something is wrong, but can someone make it a bit clearer for me?
In order for rerendering to occur, some component somewhere must call setState. Your code doesn't do that, so no rendering happens.
The setup you've done for the ManagerContext creates a default value, but that's only going to get used if you don't render any ManagerContext.Provider in your component tree. That's what you're doing now, but it's almost certainly not what you want to. You'll want to have some component near the top of your tree render a ManagerContext.Provider. This component can will be where the state lives, and among the data it sends down will be a function or functions which set state, thus triggering rerendering:
export default function LessonManager() {
const [title, setTitle] = useState('SomeOtherTitle');
const [editing, setEditing] = useState(false);
const value = useMemo(() => {
return {
title,
setTitle,
editing,
setEditing,
log: (t) => console.log(t)
}
}, [title, editing]);
return (
<ManagerContext.Provider value={value} >
<LessonMenu />
</ManagerContext.Provider/>
)
}
// used like:
export default function LessonMenu() {
const value = React.useContext(ManagerContext);
return (
<div>
<span>{value.title}</span>
<button onClick={() => value.log('ciao')}>
click
</button>
<button onClick={() => value.setTitle('new title')}>
click
</button>
</div>
)
}

Resources